2022-03-08 06:23:05 +00:00
|
|
|
-- name: Football (soccer)
|
2022-10-01 05:51:00 +00:00
|
|
|
-- description: Play football in castle grounds.
|
2022-03-13 00:05:19 +00:00
|
|
|
-- incompatible: gamemode
|
2022-03-08 06:23:05 +00:00
|
|
|
|
|
|
|
---------------
|
|
|
|
-- constants --
|
|
|
|
---------------
|
|
|
|
|
|
|
|
ballGravity = 1.5
|
|
|
|
ballRadius = 50
|
|
|
|
ballFriction = 0.988
|
|
|
|
ballRestitution = 0.65
|
|
|
|
ballWaterDrag = 0.9
|
|
|
|
ballParallelInertia = 0.3
|
|
|
|
ballPerpendicularInertia = 1
|
|
|
|
|
2022-03-09 05:34:14 +00:00
|
|
|
ballActionValues = {
|
|
|
|
[ACT_WATER_PUNCH] = { xz = 20, y = 40, directionless = false },
|
|
|
|
[ACT_MOVE_PUNCHING] = { xz = 35, y = 0, directionless = false },
|
|
|
|
[ACT_PUNCHING] = { xz = 35, y = 0, directionless = false },
|
|
|
|
[ACT_GROUND_POUND] = { xz = 40, y = 40, directionless = true },
|
|
|
|
[ACT_GROUND_POUND_LAND] = { xz = 40, y = 40, directionless = true },
|
|
|
|
[ACT_JUMP_KICK] = { xz = 10, y = 32, directionless = false },
|
|
|
|
[ACT_SLIDE_KICK_SLIDE] = { xz = -7, y = 25, directionless = false },
|
|
|
|
[ACT_SLIDE_KICK] = { xz = -7, y = 25, directionless = false },
|
|
|
|
[ACT_LONG_JUMP] = { xz = 0, y = 0, directionless = false },
|
|
|
|
[ACT_CROUCH_SLIDE] = { xz = 0, y = 0, directionless = false },
|
|
|
|
}
|
|
|
|
|
2022-03-08 06:23:05 +00:00
|
|
|
---------------
|
|
|
|
-- globals --
|
|
|
|
---------------
|
|
|
|
|
|
|
|
gBallTouchedLocal = false
|
|
|
|
gCachedBalls = {}
|
|
|
|
|
|
|
|
-----------
|
|
|
|
-- utils --
|
|
|
|
-----------
|
|
|
|
|
2022-03-09 05:34:14 +00:00
|
|
|
function clamp(x, a, b)
|
|
|
|
if x < a then return a end
|
|
|
|
if x > b then return b end
|
|
|
|
return x
|
|
|
|
end
|
|
|
|
|
|
|
|
function vec3f_degrees_between(a, b)
|
|
|
|
local ansAgain = math.acos(vec3f_dot(a, b) / (vec3f_length(a) * vec3f_length(b)))
|
|
|
|
return math.deg(ansAgain)
|
|
|
|
end
|
|
|
|
|
2022-03-08 06:23:05 +00:00
|
|
|
function my_global_index()
|
|
|
|
return gNetworkPlayers[gMarioStates[0].playerIndex].globalIndex
|
|
|
|
end
|
|
|
|
|
|
|
|
function my_location()
|
|
|
|
local np = gNetworkPlayers[gMarioStates[0].playerIndex]
|
|
|
|
return tostring(np.currCourseNum) .. '-' .. tostring(np.currLevelNum) .. '-' .. tostring(np.currAreaIndex) .. '-' .. tostring(np.currActNum)
|
|
|
|
end
|
|
|
|
|
|
|
|
function active_player(m)
|
|
|
|
local np = gNetworkPlayers[m.playerIndex]
|
|
|
|
if m.playerIndex == 0 then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
if not np.connected then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
return is_player_active(m)
|
|
|
|
end
|
|
|
|
|
|
|
|
function get_cached_ball(obj)
|
|
|
|
local key = my_location() .. '-' .. tostring(obj.oSyncID)
|
|
|
|
if gCachedBalls[key] == nil then
|
|
|
|
local cb = {}
|
|
|
|
cb.oGlobalOwner = obj.oGlobalOwner
|
|
|
|
cb.oHitTime = obj.oHitTime
|
|
|
|
cb.oNetworkTime = obj.oNetworkTime
|
|
|
|
cb.oPosX = obj.oPosX
|
|
|
|
cb.oPosY = obj.oPosY
|
|
|
|
cb.oPosZ = obj.oPosZ
|
|
|
|
cb.oVelX = obj.oVelX
|
|
|
|
cb.oVelY = obj.oVelY
|
|
|
|
cb.oVelZ = obj.oVelZ
|
|
|
|
gCachedBalls[key] = cb
|
|
|
|
end
|
|
|
|
return gCachedBalls[key]
|
|
|
|
end
|
|
|
|
|
|
|
|
function should_reject_packet(obj)
|
|
|
|
local cb = get_cached_ball(obj)
|
|
|
|
if obj.oHitTime < cb.oHitTime then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
if obj.oHitTime == cb.oHitTime and obj.oGlobalOwner > cb.oGlobalOwner then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
if obj.oHitTime == cb.oHitTime and obj.oGlobalOwner == cb.oGlobalOwner and obj.oNetworkTime < cb.oNetworkTime then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
function find_ball()
|
|
|
|
local obj = obj_get_first(OBJ_LIST_DEFAULT)
|
|
|
|
while obj ~= nil do
|
|
|
|
if get_id_from_behavior(obj.behavior) == id_bhvBall then
|
|
|
|
return obj
|
|
|
|
end
|
|
|
|
obj = obj_get_next(obj)
|
|
|
|
end
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
function spawn_or_move_ball(x, y, z)
|
|
|
|
-- search for ball
|
|
|
|
local obj = find_ball()
|
|
|
|
if obj ~= nil then
|
|
|
|
-- move ball
|
|
|
|
obj.oPosX = x
|
|
|
|
obj.oPosY = y
|
|
|
|
obj.oPosZ = z
|
|
|
|
obj.oVelX = 0
|
|
|
|
obj.oVelY = 0
|
|
|
|
obj.oVelZ = 0
|
|
|
|
|
|
|
|
obj.oGlobalOwner = my_global_index()
|
2022-03-10 03:08:41 +00:00
|
|
|
obj.oHitTime = get_network_area_timer()
|
|
|
|
obj.oNetworkTime = get_network_area_timer()
|
2022-03-08 06:23:05 +00:00
|
|
|
network_send_object(obj, false)
|
|
|
|
|
|
|
|
return obj
|
|
|
|
end
|
|
|
|
|
|
|
|
-- don't spawn unless server
|
|
|
|
if my_global_index() ~= 0 then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
-- spawn ball
|
|
|
|
return spawn_sync_object(
|
|
|
|
id_bhvBall,
|
|
|
|
E_MODEL_SPINY_BALL,
|
|
|
|
x, y, z,
|
|
|
|
|
|
|
|
function(obj)
|
|
|
|
obj.oGlobalOwner = my_global_index()
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
--------------
|
|
|
|
-- behavior --
|
|
|
|
--------------
|
|
|
|
|
|
|
|
-- define ball's custom fields
|
|
|
|
define_custom_obj_fields({
|
|
|
|
oNetworkTime = 'u32',
|
|
|
|
oHitTime = 'u32',
|
|
|
|
oGlobalOwner = 'u32',
|
|
|
|
oFrozen = 'u32',
|
|
|
|
})
|
|
|
|
|
2022-03-13 05:28:57 +00:00
|
|
|
--- @param obj Object
|
2022-03-10 02:59:55 +00:00
|
|
|
function bhv_ball_particle_trail(obj)
|
|
|
|
local spi = obj_get_temp_spawn_particles_info(E_MODEL_SPARKLES)
|
|
|
|
if spi == nil then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
spi.behParam = 2
|
|
|
|
spi.count = 1
|
|
|
|
spi.offsetY = -1 * ballRadius
|
|
|
|
spi.forwardVelBase = 8
|
|
|
|
spi.forwardVelRange = 0
|
|
|
|
spi.velYBase = 6
|
|
|
|
spi.velYRange = 0
|
|
|
|
spi.gravity = 0
|
|
|
|
spi.dragStrength = 5
|
|
|
|
spi.sizeBase = 10
|
|
|
|
spi.sizeRange = 30
|
|
|
|
|
|
|
|
cur_obj_spawn_particles(spi)
|
|
|
|
end
|
|
|
|
|
2022-03-13 05:28:57 +00:00
|
|
|
--- @param obj Object
|
2022-03-10 02:59:55 +00:00
|
|
|
function bhv_ball_particle_bounce(obj)
|
|
|
|
local spi = obj_get_temp_spawn_particles_info(E_MODEL_MIST)
|
|
|
|
if spi == nil then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
spi.behParam = 3
|
|
|
|
spi.count = 5
|
|
|
|
spi.offsetY = -1 * ballRadius
|
|
|
|
spi.forwardVelBase = 6
|
|
|
|
spi.forwardVelRange = -6
|
|
|
|
spi.velYBase = 6
|
|
|
|
spi.velYRange = -6
|
|
|
|
spi.gravity = 0
|
|
|
|
spi.dragStrength = 5
|
|
|
|
spi.sizeBase = 8
|
|
|
|
spi.sizeRange = 13
|
|
|
|
|
|
|
|
cur_obj_spawn_particles(spi)
|
|
|
|
end
|
|
|
|
|
2022-03-13 05:28:57 +00:00
|
|
|
--- @param obj Object
|
2022-03-08 06:23:05 +00:00
|
|
|
function bhv_ball_init(obj)
|
|
|
|
-- flags and such
|
|
|
|
obj.oFlags = OBJ_FLAG_UPDATE_GFX_POS_AND_ANGLE
|
|
|
|
obj.oGraphYOffset = 0
|
|
|
|
cur_obj_scale(1.4)
|
|
|
|
|
|
|
|
-- physics
|
|
|
|
obj.oWallHitboxRadius = 40.00
|
|
|
|
obj.oGravity = 2.50
|
|
|
|
obj.oBounciness = -0.75
|
|
|
|
obj.oDragStrength = 0.00
|
|
|
|
obj.oFriction = 0.99
|
|
|
|
obj.oBuoyancy = -2.00
|
|
|
|
|
|
|
|
-- hitbox
|
|
|
|
obj.hitboxRadius = 100
|
|
|
|
obj.hitboxHeight = 100
|
|
|
|
|
|
|
|
-- custom values
|
|
|
|
obj.oNetworkTime = 0
|
|
|
|
obj.oHitTime = 0
|
|
|
|
obj.oFrozen = 0
|
|
|
|
|
|
|
|
-- cache
|
|
|
|
local cb = get_cached_ball(obj)
|
|
|
|
|
|
|
|
network_init_object(obj, false, {
|
|
|
|
'oPosX',
|
|
|
|
'oPosY',
|
|
|
|
'oPosZ',
|
|
|
|
'oVelX',
|
|
|
|
'oVelY',
|
|
|
|
'oVelZ',
|
|
|
|
'oGlobalOwner',
|
|
|
|
'oNetworkTime',
|
|
|
|
'oHitTime',
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
2022-03-13 05:28:57 +00:00
|
|
|
--- @param obj Object
|
2022-03-08 06:23:05 +00:00
|
|
|
function bhv_ball_player_collision(obj)
|
2022-03-09 05:34:14 +00:00
|
|
|
local alterPos = { x = 0, y = 0, z = 0}
|
|
|
|
|
2022-03-08 06:23:05 +00:00
|
|
|
local m = nearest_mario_state_to_object(obj)
|
2022-03-09 05:34:14 +00:00
|
|
|
if m == nil then return alterPos end
|
2022-03-08 06:23:05 +00:00
|
|
|
local player = m.marioObj
|
2022-03-09 05:34:14 +00:00
|
|
|
if player == nil then return alterPos end
|
|
|
|
|
2022-03-08 06:23:05 +00:00
|
|
|
local playerRadius = 37
|
2022-03-09 05:34:14 +00:00
|
|
|
local playerHeight = 160
|
|
|
|
|
|
|
|
local objPoint = { x = obj.oPosX, y = obj.oPosY, z = obj.oPosZ }
|
2022-03-08 06:23:05 +00:00
|
|
|
local v = { x = obj.oVelX, y = obj.oVelY, z = obj.oVelZ }
|
|
|
|
|
|
|
|
-- figure out player-to-ball radius
|
2022-03-09 05:34:14 +00:00
|
|
|
local alterBallFlags = (ACT_FLAG_ATTACKING | ACT_FLAG_BUTT_OR_STOMACH_SLIDE | ACT_FLAG_DIVING)
|
|
|
|
local playerBallRadius = ballRadius
|
|
|
|
if ballActionValues[m.action] ~= nil or (m.action & alterBallFlags) ~= 0 then
|
2022-03-08 06:23:05 +00:00
|
|
|
playerBallRadius = playerBallRadius + 50
|
|
|
|
end
|
|
|
|
|
2022-03-09 05:34:14 +00:00
|
|
|
------------------------------------------------
|
|
|
|
-- calculate position and determine collision --
|
|
|
|
------------------------------------------------
|
2022-03-08 06:23:05 +00:00
|
|
|
|
2022-03-09 05:34:14 +00:00
|
|
|
-- calculate cylinder values
|
|
|
|
local cylY1 = player.oPosY + playerRadius
|
|
|
|
local cylY2 = player.oPosY + playerHeight - playerRadius
|
|
|
|
local cylPoint = { x = player.oPosX, y = clamp(obj.oPosY, cylY1, cylY2), z = player.oPosZ }
|
|
|
|
local cylDist = vec3f_dist(cylPoint, objPoint)
|
|
|
|
|
|
|
|
-- check for collision
|
|
|
|
if cylDist > (playerBallRadius + playerRadius) then
|
|
|
|
return alterPos
|
|
|
|
end
|
|
|
|
|
|
|
|
gBallTouchedLocal = (m.playerIndex == 0)
|
|
|
|
|
|
|
|
local vDifference = { x = objPoint.x - cylPoint.x, y = objPoint.y - cylPoint.y, z = objPoint.z - cylPoint.z }
|
|
|
|
local differenceDir = { x = vDifference.x, y = vDifference.y, z = vDifference.z }
|
2022-03-10 02:59:55 +00:00
|
|
|
if vec3f_length(differenceDir) ~= 0 then
|
|
|
|
vec3f_normalize(differenceDir)
|
|
|
|
end
|
2022-03-09 05:34:14 +00:00
|
|
|
|
|
|
|
alterPos.x = (cylPoint.x + differenceDir.x * (playerBallRadius + playerRadius + 1)) - objPoint.x
|
|
|
|
alterPos.y = (cylPoint.y + differenceDir.y * (playerBallRadius + playerRadius + 1)) - objPoint.y
|
|
|
|
alterPos.z = (cylPoint.z + differenceDir.z * (playerBallRadius + playerRadius + 1)) - objPoint.z
|
|
|
|
|
|
|
|
-----------------------------------------
|
|
|
|
-- figure out player's attack velocity --
|
|
|
|
-----------------------------------------
|
|
|
|
|
|
|
|
local vPlayer = { x = player.oVelX, y = player.oVelY, z = player.oVelZ }
|
|
|
|
local playerTheta = (m.faceAngle.y / 0x8000) * math.pi
|
|
|
|
|
|
|
|
-- have attacks alter velocity further
|
|
|
|
local alterXz = 0
|
|
|
|
local alterY = 0
|
|
|
|
local alterDirectionless = false
|
|
|
|
|
|
|
|
if ballActionValues[m.action] ~= nil then
|
|
|
|
alterXz = ballActionValues[m.action].xz
|
|
|
|
alterY = ballActionValues[m.action].y
|
|
|
|
alterDirectionless = ballActionValues[m.action].directionless
|
|
|
|
elseif ((m.action & (ACT_FLAG_BUTT_OR_STOMACH_SLIDE | ACT_FLAG_DIVING)) ~= 0) or (m.action == ACT_SLIDE_KICK_SLIDE) or (m.action == ACT_SLIDE_KICK) then
|
|
|
|
-- dive or slide sends it upward, and slows xz
|
|
|
|
alterXz = -7
|
|
|
|
alterY = 25
|
|
|
|
elseif (m.action & ACT_FLAG_ATTACKING) ~= 0 then
|
|
|
|
-- other attacks should just do something reasonable
|
|
|
|
alterXz = 10
|
|
|
|
alterY = 10
|
|
|
|
end
|
|
|
|
|
|
|
|
-- adjust angle
|
|
|
|
local theta = playerTheta
|
|
|
|
if alterDirectionless and differenceDir.z ~= 0 then
|
|
|
|
theta = math.atan2(differenceDir.x, differenceDir.z)
|
|
|
|
end
|
|
|
|
|
|
|
|
vPlayer.x = vPlayer.x + math.sin(theta) * alterXz
|
|
|
|
vPlayer.z = vPlayer.z + math.cos(theta) * alterXz
|
|
|
|
if vPlayer.y < alterY then vPlayer.y = vPlayer.y + alterY end
|
2022-03-08 06:23:05 +00:00
|
|
|
|
2022-03-09 05:34:14 +00:00
|
|
|
local vPlayerMag = vec3f_length(vPlayer)
|
|
|
|
|
|
|
|
-------------------------------------------------
|
|
|
|
-- figure out which velocity interaction to do --
|
|
|
|
-------------------------------------------------
|
|
|
|
|
|
|
|
local v = { x = obj.oVelX, y = obj.oVelY, z = obj.oVelZ }
|
|
|
|
|
|
|
|
local doReflection = (vPlayerMag == 0)
|
|
|
|
|
|
|
|
-- make sure ball is offset in the vPlayer direction
|
|
|
|
if not doReflection then
|
|
|
|
local objCylDir = { x = cylPoint.x - objPoint.x, y = cylPoint.y - objPoint.y, z = cylPoint.z - objPoint.z }
|
|
|
|
local objCylDirXZ = { x = objCylDir.x, y = 0, z = objCylDir.z }
|
|
|
|
local objCylDirMag = vec3f_length(objCylDir)
|
|
|
|
|
|
|
|
local vPlayerXZ = { x = vPlayer.x, y = 0, z = vPlayer.z }
|
|
|
|
local vPlayerXZMag = vec3f_length(vPlayerXZ)
|
|
|
|
|
|
|
|
if objCylDirMag > 0 and vPlayerXZMag > 0 then
|
|
|
|
doReflection = (vec3f_degrees_between(vPlayer, objCylDir)) <= 120
|
|
|
|
and (vec3f_degrees_between(vPlayerXZ, objCylDirXZ)) <= 120
|
2022-03-08 06:23:05 +00:00
|
|
|
end
|
2022-03-09 05:34:14 +00:00
|
|
|
end
|
|
|
|
|
2022-03-10 02:59:55 +00:00
|
|
|
-- make sure player has a velocity
|
|
|
|
if not doReflection and vPlayerMag == 0 then
|
|
|
|
doReflection = true
|
|
|
|
end
|
|
|
|
|
2022-03-09 05:34:14 +00:00
|
|
|
--------------------------------------
|
|
|
|
-- calculate velocity (interaction) --
|
|
|
|
--------------------------------------
|
2022-03-08 06:23:05 +00:00
|
|
|
|
2022-03-09 05:34:14 +00:00
|
|
|
if not doReflection then
|
|
|
|
local vPlayerDir = { x = vPlayer.x, y = vPlayer.y, z = vPlayer.z }
|
|
|
|
vec3f_normalize(vPlayerDir)
|
|
|
|
|
|
|
|
-- split velocity into parallel/perpendicular to normal
|
|
|
|
local perpendicular = vec3f_project(v, vPlayerDir)
|
2022-03-08 06:23:05 +00:00
|
|
|
local parallel = { x = v.x - perpendicular.x, y = v.y - perpendicular.y, z = v.z - perpendicular.z }
|
2022-03-09 05:34:14 +00:00
|
|
|
|
|
|
|
-- apply friction
|
|
|
|
vec3f_mul(parallel, 0.5)
|
|
|
|
|
|
|
|
local parallelMag = vec3f_length(parallel)
|
|
|
|
local perpendicularMag = vec3f_length(perpendicular)
|
|
|
|
|
|
|
|
if perpendicularMag == 0 or perpendicularMag < vPlayerMag then
|
|
|
|
vec3f_copy(perpendicular, vPlayer)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- reflect velocity along normal
|
2022-03-08 06:23:05 +00:00
|
|
|
local reflect = {
|
2022-03-09 05:34:14 +00:00
|
|
|
x = parallel.x + perpendicular.x,
|
|
|
|
y = parallel.y + perpendicular.y,
|
|
|
|
z = parallel.z + perpendicular.z
|
2022-03-08 06:23:05 +00:00
|
|
|
}
|
|
|
|
|
2022-03-09 05:34:14 +00:00
|
|
|
-- set new velocity
|
2022-03-08 06:23:05 +00:00
|
|
|
obj.oVelX = reflect.x
|
2022-03-09 05:34:14 +00:00
|
|
|
obj.oVelY = reflect.y
|
2022-03-08 06:23:05 +00:00
|
|
|
obj.oVelZ = reflect.z
|
2022-03-09 05:34:14 +00:00
|
|
|
end
|
2022-03-08 06:23:05 +00:00
|
|
|
|
2022-03-09 05:34:14 +00:00
|
|
|
-------------------------------------
|
|
|
|
-- calculate velocity (reflection) --
|
|
|
|
-------------------------------------
|
2022-03-08 06:23:05 +00:00
|
|
|
|
2022-03-09 05:34:14 +00:00
|
|
|
if doReflection then
|
|
|
|
-- split velocity into parallel/perpendicular to normal
|
|
|
|
local perpendicular = vec3f_project(v, differenceDir)
|
|
|
|
local parallel = { x = v.x - perpendicular.x, y = v.y - perpendicular.y, z = v.z - perpendicular.z }
|
2022-03-08 06:23:05 +00:00
|
|
|
|
2022-03-09 05:34:14 +00:00
|
|
|
-- apply friction and restitution
|
|
|
|
vec3f_mul(parallel, ballFriction)
|
|
|
|
vec3f_mul(perpendicular, ballRestitution)
|
|
|
|
|
|
|
|
-- play sounds
|
|
|
|
local parallelLength = vec3f_length(parallel)
|
|
|
|
local perpendicularLength = vec3f_length(perpendicular)
|
2022-03-08 06:23:05 +00:00
|
|
|
|
2022-03-09 05:34:14 +00:00
|
|
|
if perpendicularLength > 5 then
|
|
|
|
cur_obj_play_sound_2(SOUND_GENERAL_BOX_LANDING_2)
|
|
|
|
elseif parallelLength > 3 then
|
|
|
|
cur_obj_play_sound_2(SOUND_ENV_SLIDING)
|
2022-03-08 06:23:05 +00:00
|
|
|
end
|
2022-03-09 05:34:14 +00:00
|
|
|
|
|
|
|
local pushOutMag = 10
|
|
|
|
|
|
|
|
-- reflect velocity along normal
|
|
|
|
local reflect = {
|
|
|
|
x = parallel.x - perpendicular.x,
|
|
|
|
y = parallel.y - perpendicular.y,
|
|
|
|
z = parallel.z - perpendicular.z
|
|
|
|
}
|
|
|
|
|
|
|
|
-- set new velocity
|
|
|
|
obj.oVelX = reflect.x + differenceDir.x * pushOutMag
|
|
|
|
obj.oVelY = reflect.y + differenceDir.y * pushOutMag
|
|
|
|
obj.oVelZ = reflect.z + differenceDir.z * pushOutMag
|
2022-03-08 06:23:05 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
return alterPos
|
|
|
|
end
|
|
|
|
|
2022-03-13 05:28:57 +00:00
|
|
|
--- @param obj Object
|
|
|
|
--- @param offset Vec3f
|
2022-03-08 06:23:05 +00:00
|
|
|
function bhv_ball_resolve(obj, offset)
|
|
|
|
local a = { x = obj.oPosX + 0, y = obj.oPosY + 0, z = obj.oPosZ + 0 }
|
|
|
|
local dir = { x = offset.x * -1.0, y = offset.y * -1.0, z = offset.z * -1.0}
|
|
|
|
|
|
|
|
info = collision_find_surface_on_ray(
|
|
|
|
a.x, a.y, a.z,
|
|
|
|
dir.x, dir.y, dir.z)
|
|
|
|
|
|
|
|
obj.oPosX = info.hitPos.x + offset.x
|
|
|
|
obj.oPosY = info.hitPos.y + offset.y
|
|
|
|
obj.oPosZ = info.hitPos.z + offset.z
|
|
|
|
|
|
|
|
if info.surface == nil then return nil end
|
|
|
|
return { x = info.surface.normal.x, y = info.surface.normal.y, z = info.surface.normal.z }
|
|
|
|
end
|
|
|
|
|
2022-03-13 05:28:57 +00:00
|
|
|
--- @param obj Object
|
2022-03-08 06:23:05 +00:00
|
|
|
function bhv_ball_loop(obj)
|
|
|
|
if obj.oFrozen ~= 0 then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
gBallTouchedLocal = false
|
|
|
|
local cb = get_cached_ball(obj)
|
|
|
|
|
|
|
|
-- detect when a packet was received
|
|
|
|
if obj.oNetworkTime ~= cb.oNetworkTime then
|
|
|
|
if should_reject_packet(obj) then
|
|
|
|
-- reject packet
|
|
|
|
obj.oGlobalOwner = cb.oGlobalOwner
|
|
|
|
obj.oHitTime = cb.oHitTime
|
|
|
|
obj.oNetworkTime = cb.oNetworkTime
|
|
|
|
obj.oPosX = cb.oPosX
|
|
|
|
obj.oPosY = cb.oPosY
|
|
|
|
obj.oPosZ = cb.oPosZ
|
|
|
|
obj.oVelX = cb.oVelX
|
|
|
|
obj.oVelY = cb.oVelY
|
|
|
|
obj.oVelZ = cb.oVelZ
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local orig = { x = obj.oPosX, y = obj.oPosY, z = obj.oPosZ }
|
|
|
|
|
|
|
|
obj.oVelY = obj.oVelY - ballGravity
|
|
|
|
if obj.oVelX == 0 and obj.oVelY == 0 and obj.oVelZ == 0 then
|
|
|
|
obj.oVelY = obj.oVelY + 0.01
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
-- detect player collisions
|
2022-03-09 05:34:14 +00:00
|
|
|
local alterPos = bhv_ball_player_collision(obj)
|
2022-03-08 06:23:05 +00:00
|
|
|
|
|
|
|
-- alter end-point based on player collisions
|
|
|
|
local a = { x = obj.oPosX, y = obj.oPosY, z = obj.oPosZ }
|
|
|
|
local v = { x = obj.oVelX, y = obj.oVelY, z = obj.oVelZ }
|
|
|
|
local b = { x = v.x, y = v.y, z = v.z }
|
2022-03-09 05:34:14 +00:00
|
|
|
vec3f_sum(b, b, alterPos)
|
2022-03-08 06:23:05 +00:00
|
|
|
|
|
|
|
-- regular movement
|
|
|
|
local info = collision_find_surface_on_ray(
|
|
|
|
a.x, a.y, a.z,
|
|
|
|
b.x, b.y, b.z)
|
|
|
|
|
|
|
|
obj.oPosX = info.hitPos.x
|
|
|
|
obj.oPosY = info.hitPos.y
|
|
|
|
obj.oPosZ = info.hitPos.z
|
|
|
|
|
|
|
|
-- detect normal along movement vector
|
|
|
|
local vMag = vec3f_length(v)
|
2022-03-10 02:59:55 +00:00
|
|
|
if vMag > 0 then
|
|
|
|
local vNorm = { x = v.x / vMag, y = v.y / vMag, z = v.z / vMag }
|
|
|
|
b = { x = v.x + vNorm.x * (vMag + ballRadius), y = v.y + vNorm.y * (vMag + ballRadius), z = v.z + vNorm.z * (vMag + ballRadius) }
|
|
|
|
end
|
2022-03-08 06:23:05 +00:00
|
|
|
|
|
|
|
info = collision_find_surface_on_ray(
|
|
|
|
a.x, a.y, a.z,
|
|
|
|
b.x, b.y, b.z)
|
|
|
|
|
|
|
|
-- figure out the standard normal
|
|
|
|
local colNormals = {}
|
|
|
|
if info.surface ~= nil then
|
|
|
|
table.insert(colNormals, { x = info.surface.normal.x, y = info.surface.normal.y, z = info.surface.normal.z })
|
2022-03-11 02:23:25 +00:00
|
|
|
if vMag > 5 then
|
|
|
|
bhv_ball_particle_bounce(obj)
|
|
|
|
end
|
2022-03-08 06:23:05 +00:00
|
|
|
else
|
|
|
|
table.insert(colNormals, nil)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- resolve collisions around ball
|
|
|
|
table.insert(colNormals, bhv_ball_resolve(obj, { x = ballRadius, y = 0, z = 0 }))
|
|
|
|
table.insert(colNormals, bhv_ball_resolve(obj, { x = -ballRadius, y = 0, z = 0 }))
|
|
|
|
table.insert(colNormals, bhv_ball_resolve(obj, { x = 0, y = 0, z = ballRadius }))
|
|
|
|
table.insert(colNormals, bhv_ball_resolve(obj, { x = 0, y = 0, z = -ballRadius }))
|
|
|
|
table.insert(colNormals, bhv_ball_resolve(obj, { x = 0, y = ballRadius, z = 0 }))
|
|
|
|
table.insert(colNormals, bhv_ball_resolve(obj, { x = 0, y = -ballRadius, z = 0 }))
|
|
|
|
|
|
|
|
-- figure out collision normal
|
|
|
|
local collisionN = { x = 0, y = 0, z = 0 }
|
|
|
|
local collisionCount = 0
|
|
|
|
for _, colN in ipairs(colNormals) do
|
|
|
|
if colN ~= nil then
|
|
|
|
vec3f_sum(collisionN, collisionN, colN)
|
|
|
|
collisionCount = collisionCount + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- reflect collisions
|
|
|
|
if collisionCount > 0 then
|
|
|
|
-- calculate total normal
|
|
|
|
vec3f_mul(collisionN, 1.0 / collisionCount)
|
|
|
|
vec3f_normalize(collisionN)
|
|
|
|
|
|
|
|
-- split velocity into parallel/perpendicular to normal
|
|
|
|
local perpendicular = vec3f_project(v, collisionN)
|
|
|
|
local parallel = { x = v.x - perpendicular.x, y = v.y - perpendicular.y, z = v.z - perpendicular.z }
|
|
|
|
|
|
|
|
-- apply friction and restitution
|
|
|
|
vec3f_mul(parallel, ballFriction)
|
|
|
|
vec3f_mul(perpendicular, ballRestitution)
|
|
|
|
|
|
|
|
-- stop ball in parallel axis
|
|
|
|
local parallelLength = vec3f_length(parallel)
|
|
|
|
if parallelLength < ballParallelInertia then
|
|
|
|
vec3f_mul(parallel, 0)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- stop ball in perpendicular axis
|
|
|
|
local perpendicularLength = vec3f_length(perpendicular)
|
|
|
|
if perpendicularLength < ballPerpendicularInertia then
|
|
|
|
vec3f_mul(perpendicular, 0)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- play sounds
|
|
|
|
if perpendicularLength > 5 then
|
|
|
|
cur_obj_play_sound_2(SOUND_GENERAL_BOX_LANDING_2)
|
|
|
|
elseif parallelLength > 3 then
|
|
|
|
cur_obj_play_sound_2(SOUND_ENV_SLIDING)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- reflect velocity along normal
|
|
|
|
local reflect = {
|
|
|
|
x = parallel.x - perpendicular.x,
|
|
|
|
y = parallel.y - perpendicular.y,
|
|
|
|
z = parallel.z - perpendicular.z
|
|
|
|
}
|
|
|
|
|
|
|
|
-- set new velocity
|
|
|
|
obj.oVelX = reflect.x
|
|
|
|
obj.oVelY = reflect.y
|
|
|
|
obj.oVelZ = reflect.z
|
|
|
|
end
|
|
|
|
|
|
|
|
-- float in water
|
|
|
|
local waterLevel = find_water_level(obj.oPosX, obj.oPosZ)
|
|
|
|
if obj.oPosY < waterLevel then
|
|
|
|
obj.oVelX = obj.oVelX * ballWaterDrag
|
|
|
|
obj.oVelY = obj.oVelY * ballWaterDrag + 2
|
|
|
|
obj.oVelZ = obj.oVelZ * ballWaterDrag
|
|
|
|
end
|
|
|
|
|
|
|
|
-- sanity check floor
|
|
|
|
local floor = find_floor_height(obj.oPosX, obj.oPosY, obj.oPosZ)
|
|
|
|
if obj.oPosY <= floor or floor <= -10000 then
|
|
|
|
obj.oPosX = orig.x
|
|
|
|
obj.oPosY = orig.y
|
|
|
|
obj.oPosZ = orig.z
|
|
|
|
obj.oVelX = -obj.oVelX
|
|
|
|
obj.oVelY = -obj.oVelY
|
|
|
|
obj.oVelZ = -obj.oVelZ
|
|
|
|
end
|
|
|
|
|
|
|
|
-- update rotation
|
|
|
|
if obj.oVelX ~= 0 or obj.oVelZ ~= 0 then
|
|
|
|
local moveAngle = atan2s(obj.oVelZ * 100, obj.oVelX * 100)
|
|
|
|
local xzMag = math.sqrt(obj.oVelX * obj.oVelX + obj.oVelZ * obj.oVelZ)
|
|
|
|
obj.oFaceAngleYaw = moveAngle
|
|
|
|
obj.oFaceAnglePitch = obj.oFaceAnglePitch + xzMag * 100
|
|
|
|
end
|
|
|
|
|
|
|
|
-- send out object if we touched it
|
2022-03-10 03:08:41 +00:00
|
|
|
local updateRateSend = (obj.oGlobalOwner == my_global_index() and (get_network_area_timer() - obj.oNetworkTime) > 5)
|
2022-03-08 06:23:05 +00:00
|
|
|
if gBallTouchedLocal or updateRateSend then
|
|
|
|
if gBallTouchedLocal then
|
|
|
|
obj.oGlobalOwner = my_global_index()
|
2022-03-10 03:08:41 +00:00
|
|
|
obj.oHitTime = get_network_area_timer()
|
2022-03-08 06:23:05 +00:00
|
|
|
end
|
2022-03-10 03:08:41 +00:00
|
|
|
obj.oNetworkTime = get_network_area_timer()
|
2022-03-08 06:23:05 +00:00
|
|
|
network_send_object(obj, false)
|
|
|
|
end
|
|
|
|
|
2022-03-10 02:59:55 +00:00
|
|
|
-- spawn a particle trail
|
|
|
|
if vMag > 50 then
|
|
|
|
bhv_ball_particle_trail(obj)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- hack: make sure we never set velocity to nan
|
|
|
|
if obj.oVelX ~= obj.oVelX then obj.oVelX = 0 end
|
|
|
|
if obj.oVelY ~= obj.oVelY then obj.oVelY = 0 end
|
|
|
|
if obj.oVelZ ~= obj.oVelZ then obj.oVelZ = 0 end
|
|
|
|
|
2022-03-08 06:23:05 +00:00
|
|
|
-- hack: save pos/vel to detect packets
|
|
|
|
cb.oGlobalOwner = obj.oGlobalOwner
|
|
|
|
cb.oHitTime = obj.oHitTime
|
|
|
|
cb.oNetworkTime = obj.oNetworkTime
|
|
|
|
cb.oPosX = obj.oPosX
|
|
|
|
cb.oPosY = obj.oPosY
|
|
|
|
cb.oPosZ = obj.oPosZ
|
|
|
|
cb.oVelX = obj.oVelX
|
|
|
|
cb.oVelY = obj.oVelY
|
|
|
|
cb.oVelZ = obj.oVelZ
|
|
|
|
end
|
|
|
|
|
|
|
|
id_bhvBall = hook_behavior(nil, OBJ_LIST_DEFAULT, true, bhv_ball_init, bhv_ball_loop)
|
|
|
|
|
|
|
|
----------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
--------------
|
|
|
|
-- gamemode --
|
|
|
|
--------------
|
|
|
|
|
|
|
|
GAME_STATE_WAIT = 0
|
|
|
|
GAME_STATE_ACTIVE = 1
|
|
|
|
GAME_STATE_SCORE = 2
|
|
|
|
GAME_STATE_OOB = 3
|
|
|
|
GAME_STATE_OVER = 4
|
|
|
|
|
|
|
|
sSoccerBall = nil
|
|
|
|
sBallSpawnPos = { x = (4573 + -4634) / 2, y = -50, z = (-2077 + -212) / 2 }
|
|
|
|
sBallHidePos = { x = sBallSpawnPos.x, y = -5000, z = sBallSpawnPos.z }
|
|
|
|
|
|
|
|
sGameModeInitialized = false
|
|
|
|
sWaitTimeout = 30 * 5
|
|
|
|
sOobTimeout = 30 * 5
|
|
|
|
sScoreTimeout = 30 * 5
|
|
|
|
sOverTimeout = 30 * 15
|
|
|
|
sStateTimer = sWaitTimeout
|
|
|
|
sMaxScore = 5
|
|
|
|
|
|
|
|
gGlobalSyncTable.gameState = GAME_STATE_WAIT
|
|
|
|
gGlobalSyncTable.displayText = ' '
|
|
|
|
gGlobalSyncTable.displayFont = FONT_HUD
|
|
|
|
gGlobalSyncTable.displayColor = 0xFFFFFF
|
2022-03-11 02:23:25 +00:00
|
|
|
gGlobalSyncTable.scoreRed = 0
|
|
|
|
gGlobalSyncTable.scoreBlue = 0
|
2022-03-08 06:23:05 +00:00
|
|
|
|
|
|
|
function gamemode_initialize()
|
2022-03-11 02:33:52 +00:00
|
|
|
-- hide the SM64 HUD
|
|
|
|
hud_hide()
|
|
|
|
|
2022-03-08 06:23:05 +00:00
|
|
|
sGameModeInitialized = true
|
|
|
|
end
|
|
|
|
|
|
|
|
function gamemode_shuffle()
|
|
|
|
local t = {}
|
|
|
|
local count = 0
|
|
|
|
-- create table of players
|
|
|
|
for i = 0, (MAX_PLAYERS-1) do
|
|
|
|
local m = gMarioStates[i]
|
|
|
|
local s = gPlayerSyncTable[i]
|
|
|
|
if active_player(m) then
|
|
|
|
table.insert(t, s)
|
|
|
|
count = count + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- shuffle
|
|
|
|
for i = #t, 2, -1 do
|
|
|
|
local j = math.random(i)
|
|
|
|
t[i], t[j] = t[j], t[i]
|
|
|
|
end
|
|
|
|
|
|
|
|
-- assign teams
|
|
|
|
local team1Count = 0
|
|
|
|
local team2Count = 0
|
|
|
|
local oddS = nil
|
|
|
|
for i, s in ipairs(t) do
|
|
|
|
if (i - 1) < count / 2 then
|
|
|
|
s.team = 1
|
|
|
|
team1Count = team1Count + 1
|
|
|
|
oddS = s
|
|
|
|
else
|
|
|
|
s.team = 2
|
|
|
|
team2Count = team2Count + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- shuffle odd player
|
|
|
|
if team1Count > team2Count then
|
|
|
|
oddS.team = math.random(1, 2)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function gamemode_wait()
|
|
|
|
sSoccerBall = spawn_or_move_ball(sBallSpawnPos.x, sBallSpawnPos.y, sBallSpawnPos.z)
|
|
|
|
|
|
|
|
-- server only
|
|
|
|
if my_global_index() == 0 then
|
|
|
|
-- claim the ball
|
|
|
|
if sSoccerBall.oGlobalOwner ~= my_global_index() then
|
|
|
|
sSoccerBall.oGlobalOwner = my_global_index()
|
2022-03-10 03:08:41 +00:00
|
|
|
sSoccerBall.oHitTime = get_network_area_timer()
|
|
|
|
sSoccerBall.oNetworkTime = get_network_area_timer()
|
2022-03-08 06:23:05 +00:00
|
|
|
network_send_object(sSoccerBall, false)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- clear sparkles
|
|
|
|
for i=0,(MAX_PLAYERS-1) do
|
|
|
|
local sm = gPlayerSyncTable[i]
|
|
|
|
sm.sparkle = false
|
|
|
|
end
|
|
|
|
|
|
|
|
-- decrement timer
|
|
|
|
sStateTimer = sStateTimer - 1
|
|
|
|
|
|
|
|
-- update the visible timer
|
|
|
|
if math.floor(sStateTimer / 30) ~= math.floor((sStateTimer + 1) / 30) then
|
|
|
|
gGlobalSyncTable.displayFont = FONT_HUD
|
|
|
|
gGlobalSyncTable.displayText = tostring(1 + math.floor(sStateTimer / 30))
|
|
|
|
gGlobalSyncTable.displayColor = 0xFFFFFF
|
|
|
|
end
|
|
|
|
|
|
|
|
-- start the round
|
|
|
|
if sStateTimer <= 0 then
|
|
|
|
gGlobalSyncTable.gameState = GAME_STATE_ACTIVE
|
|
|
|
gGlobalSyncTable.displayFont = FONT_HUD
|
|
|
|
gGlobalSyncTable.displayText = ' '
|
|
|
|
gGlobalSyncTable.displayColor = 0xFFFFFF
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function gamemode_active()
|
|
|
|
-- server only
|
|
|
|
if my_global_index() == 0 then
|
|
|
|
local validGoalY = ((sSoccerBall.oPosY + ballRadius) < -550)
|
|
|
|
local validGoalZ = (sSoccerBall.oPosX < sBallSpawnPos.x and sSoccerBall.oPosZ < -2846) or (sSoccerBall.oPosX > sBallSpawnPos.x and sSoccerBall.oPosZ > -1205)
|
|
|
|
if validGoalY and validGoalZ then
|
|
|
|
if sSoccerBall.oPosX < sBallSpawnPos.x and sSoccerBall.oPosZ < -2846 then
|
|
|
|
end
|
|
|
|
|
|
|
|
spawn_sync_object(id_bhvExplosion, E_MODEL_EXPLOSION, sSoccerBall.oPosX, sSoccerBall.oPosY, sSoccerBall.oPosZ, nil)
|
|
|
|
|
|
|
|
local scoringTeam = 0
|
|
|
|
if sSoccerBall.oPosX < sBallSpawnPos.x then
|
|
|
|
scoringTeam = 1
|
|
|
|
else
|
|
|
|
scoringTeam = 2
|
|
|
|
end
|
|
|
|
|
|
|
|
local gameOver = false
|
|
|
|
local displayName = ''
|
|
|
|
local scorerNp = network_player_from_global_index(sSoccerBall.oGlobalOwner)
|
|
|
|
if scorerNp ~= nil then
|
|
|
|
local scorerS = gPlayerSyncTable[scorerNp.localIndex]
|
|
|
|
if scorerS.team == scoringTeam then
|
|
|
|
displayName = ' (' .. scorerNp.name .. ')'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if scoringTeam == 1 then
|
2022-03-11 02:23:25 +00:00
|
|
|
gGlobalSyncTable.scoreRed = gGlobalSyncTable.scoreRed + 1
|
2022-03-08 06:23:05 +00:00
|
|
|
gGlobalSyncTable.displayFont = FONT_NORMAL
|
2022-03-11 02:23:25 +00:00
|
|
|
gGlobalSyncTable.displayColor = 0xFF9999
|
|
|
|
if gGlobalSyncTable.scoreRed >= sMaxScore then
|
|
|
|
gGlobalSyncTable.displayText = 'red team wins!'
|
2022-03-08 06:23:05 +00:00
|
|
|
gameOver = true
|
|
|
|
else
|
2022-03-11 02:23:25 +00:00
|
|
|
gGlobalSyncTable.displayText = 'red team scored' .. displayName
|
2022-03-08 06:23:05 +00:00
|
|
|
end
|
|
|
|
else
|
2022-03-11 02:23:25 +00:00
|
|
|
gGlobalSyncTable.scoreBlue = gGlobalSyncTable.scoreBlue + 1
|
2022-03-08 06:23:05 +00:00
|
|
|
gGlobalSyncTable.displayFont = FONT_NORMAL
|
|
|
|
gGlobalSyncTable.displayColor = 0x9999FF
|
2022-03-11 02:23:25 +00:00
|
|
|
if gGlobalSyncTable.scoreBlue >= sMaxScore then
|
|
|
|
gGlobalSyncTable.displayText = 'blue team wins!'
|
2022-03-08 06:23:05 +00:00
|
|
|
gameOver = true
|
|
|
|
else
|
2022-03-11 02:23:25 +00:00
|
|
|
gGlobalSyncTable.displayText = 'blue team scored' .. displayName
|
2022-03-08 06:23:05 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if gameOver then
|
|
|
|
-- set sparkle
|
|
|
|
for i=0,(MAX_PLAYERS-1) do
|
|
|
|
local im = gMarioStates[i]
|
|
|
|
local sm = gPlayerSyncTable[i]
|
|
|
|
if active_player(im) then
|
|
|
|
if sm.team == scoringTeam then
|
|
|
|
sm.sparkle = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
gGlobalSyncTable.gameState = GAME_STATE_OVER
|
|
|
|
sStateTimer = sOverTimeout
|
|
|
|
else
|
|
|
|
-- set sparkle
|
2022-03-09 05:34:14 +00:00
|
|
|
local scorerS = gPlayerSyncTable[scorerNp.localIndex]
|
|
|
|
if scorerNp ~= nil and scorerS.team == scoringTeam then
|
2022-03-08 06:23:05 +00:00
|
|
|
scorerS.sparkle = true
|
|
|
|
end
|
|
|
|
gGlobalSyncTable.gameState = GAME_STATE_SCORE
|
|
|
|
sStateTimer = sScoreTimeout
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- check for oob
|
|
|
|
local ignoreOob = (sSoccerBall.oPosX >= 3230 and sSoccerBall.oPosX <= 4460 and sSoccerBall.oPosZ >= -6230 and sSoccerBall.oPosZ <= -2725) -- ramp near bridge
|
|
|
|
if not ignoreOob and sSoccerBall.oPosY > 0 then
|
|
|
|
local floorHeight = find_floor_height(sSoccerBall.oPosX, sSoccerBall.oPosY, sSoccerBall.oPosZ)
|
|
|
|
if sSoccerBall.oPosY - ballRadius - 10 < floorHeight then
|
|
|
|
gGlobalSyncTable.gameState = GAME_STATE_OOB
|
|
|
|
sStateTimer = sOobTimeout
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- check for other OOB
|
|
|
|
if validGoalY and not validGoalZ then
|
|
|
|
gGlobalSyncTable.gameState = GAME_STATE_OOB
|
|
|
|
sStateTimer = sOobTimeout
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function gamemode_score()
|
|
|
|
sSoccerBall = spawn_or_move_ball(sBallHidePos.x, sBallHidePos.y, sBallHidePos.z)
|
|
|
|
|
|
|
|
-- server only
|
|
|
|
if my_global_index() == 0 then
|
|
|
|
-- decrement timer
|
|
|
|
sStateTimer = sStateTimer - 1
|
|
|
|
|
|
|
|
-- start the round
|
|
|
|
if sStateTimer <= 0 then
|
|
|
|
gGlobalSyncTable.gameState = GAME_STATE_WAIT
|
|
|
|
sStateTimer = sWaitTimeout
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function gamemode_oob()
|
|
|
|
sSoccerBall = spawn_or_move_ball(sBallHidePos.x, sBallHidePos.y, sBallHidePos.z)
|
|
|
|
|
|
|
|
-- server only
|
|
|
|
if my_global_index() == 0 then
|
|
|
|
-- decrement timer
|
|
|
|
sStateTimer = sStateTimer - 1
|
|
|
|
|
|
|
|
gGlobalSyncTable.displayFont = FONT_NORMAL
|
|
|
|
gGlobalSyncTable.displayText = 'out of bounds'
|
2022-03-11 02:23:25 +00:00
|
|
|
gGlobalSyncTable.displayColor = 0xFFFFFF
|
2022-03-08 06:23:05 +00:00
|
|
|
|
|
|
|
-- start the round
|
|
|
|
if sStateTimer <= 0 then
|
|
|
|
gGlobalSyncTable.gameState = GAME_STATE_WAIT
|
|
|
|
sStateTimer = sWaitTimeout
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function gamemode_over()
|
|
|
|
sSoccerBall = spawn_or_move_ball(sBallHidePos.x, sBallHidePos.y, sBallHidePos.z)
|
|
|
|
|
|
|
|
-- server only
|
|
|
|
if my_global_index() == 0 then
|
|
|
|
-- decrement timer
|
|
|
|
sStateTimer = sStateTimer - 1
|
|
|
|
|
|
|
|
-- start the round
|
|
|
|
if sStateTimer <= 0 then
|
|
|
|
-- shuffle teams
|
|
|
|
gamemode_shuffle()
|
2022-03-11 02:23:25 +00:00
|
|
|
gGlobalSyncTable.scoreRed = 0
|
|
|
|
gGlobalSyncTable.scoreBlue = 0
|
2022-03-08 06:23:05 +00:00
|
|
|
gGlobalSyncTable.gameState = GAME_STATE_WAIT
|
|
|
|
sStateTimer = sWaitTimeout
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function gamemode_update()
|
|
|
|
if not sGameModeInitialized then
|
|
|
|
gamemode_initialize()
|
|
|
|
end
|
|
|
|
|
|
|
|
if sSoccerBall == nil then
|
|
|
|
sSoccerBall = find_ball()
|
|
|
|
else
|
|
|
|
if gGlobalSyncTable.gameState == GAME_STATE_WAIT then
|
|
|
|
sSoccerBall.oFrozen = 1
|
|
|
|
else
|
|
|
|
sSoccerBall.oFrozen = 0
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if gGlobalSyncTable.gameState == GAME_STATE_WAIT then
|
|
|
|
gamemode_wait()
|
|
|
|
elseif gGlobalSyncTable.gameState == GAME_STATE_ACTIVE then
|
|
|
|
gamemode_active()
|
|
|
|
elseif gGlobalSyncTable.gameState == GAME_STATE_SCORE then
|
|
|
|
gamemode_score()
|
|
|
|
elseif gGlobalSyncTable.gameState == GAME_STATE_OOB then
|
|
|
|
gamemode_oob()
|
|
|
|
elseif gGlobalSyncTable.gameState == GAME_STATE_OVER then
|
|
|
|
gamemode_over()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-03-13 05:28:57 +00:00
|
|
|
--- @param m MarioState
|
2022-03-08 06:23:05 +00:00
|
|
|
function on_player_connected(m)
|
|
|
|
-- only run on server
|
|
|
|
if not network_is_server() then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
-- figure out team
|
|
|
|
local selectTeam = math.random(1, 2)
|
|
|
|
local playersTeam1 = 0
|
|
|
|
local playersTeam2 = 0
|
|
|
|
for i=0,(MAX_PLAYERS-1) do
|
|
|
|
local im = gMarioStates[i]
|
|
|
|
local sm = gPlayerSyncTable[i]
|
|
|
|
if active_player(im) and i ~= m.playerIndex then
|
|
|
|
if sm.team == 1 then
|
|
|
|
playersTeam1 = playersTeam1 + 1
|
|
|
|
elseif sm.team == 2 then
|
|
|
|
playersTeam2 = playersTeam2 + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if playersTeam1 < playersTeam2 then
|
|
|
|
selectTeam = 1
|
|
|
|
elseif playersTeam2 < playersTeam1 then
|
|
|
|
selectTeam = 2
|
|
|
|
end
|
|
|
|
|
|
|
|
-- set team
|
|
|
|
local s = gPlayerSyncTable[m.playerIndex]
|
|
|
|
local np = gNetworkPlayers[m.playerIndex]
|
|
|
|
s.team = selectTeam
|
|
|
|
end
|
|
|
|
|
2022-03-13 07:43:47 +00:00
|
|
|
--- @param m1 MarioState
|
|
|
|
--- @param m2 MarioState
|
|
|
|
function allow_pvp_attack(m1, m2)
|
|
|
|
local s1 = gPlayerSyncTable[m1.playerIndex]
|
|
|
|
local s2 = gPlayerSyncTable[m2.playerIndex]
|
|
|
|
if s1.team == s2.team then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
2022-03-08 06:23:05 +00:00
|
|
|
|
|
|
|
function hud_score_render()
|
|
|
|
djui_hud_set_font(FONT_HUD)
|
|
|
|
|
|
|
|
-- get width of screen and text
|
|
|
|
local screenWidth = djui_hud_get_screen_width()
|
|
|
|
|
|
|
|
local width = 32
|
|
|
|
local height = 16
|
|
|
|
local x = (screenWidth - width) / 2.0
|
|
|
|
local y = 5
|
|
|
|
local xOffset = 20
|
|
|
|
local textOffset = 8
|
|
|
|
|
|
|
|
if gPlayerSyncTable[0].team == 2 then
|
|
|
|
xOffset = xOffset * -1
|
|
|
|
end
|
|
|
|
|
|
|
|
-- render
|
2022-03-11 02:23:25 +00:00
|
|
|
djui_hud_set_color(255, 100, 100, 180);
|
2022-03-08 06:23:05 +00:00
|
|
|
djui_hud_render_rect(x - xOffset, y, width, height + 4);
|
|
|
|
|
|
|
|
djui_hud_set_color(100, 100, 255, 180);
|
|
|
|
djui_hud_render_rect(x + xOffset, y, width, height + 4);
|
|
|
|
|
|
|
|
djui_hud_set_color(255, 255, 255, 255);
|
2022-03-11 02:23:25 +00:00
|
|
|
djui_hud_print_text(tostring(gGlobalSyncTable.scoreRed), x - xOffset + textOffset, y + 2, 1);
|
|
|
|
djui_hud_print_text(tostring(gGlobalSyncTable.scoreBlue), x + xOffset + textOffset, y + 2, 1);
|
2022-03-08 06:23:05 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function on_hud_render()
|
|
|
|
-- render to N64 screen space, with the HUD font
|
|
|
|
djui_hud_set_resolution(RESOLUTION_N64)
|
|
|
|
|
|
|
|
hud_score_render()
|
|
|
|
|
|
|
|
if gGlobalSyncTable.displayText == ' ' then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
djui_hud_set_font(gGlobalSyncTable.displayFont)
|
|
|
|
|
|
|
|
-- set text
|
|
|
|
local text = gGlobalSyncTable.displayText
|
|
|
|
|
|
|
|
-- set scale
|
|
|
|
local scale = 1
|
|
|
|
|
|
|
|
local height = 1
|
|
|
|
if gGlobalSyncTable.displayFont == FONT_HUD then
|
|
|
|
height = 16 * scale
|
|
|
|
elseif gGlobalSyncTable.displayFont == FONT_NORMAL then
|
|
|
|
scale = 0.5
|
|
|
|
height = 32 * scale
|
|
|
|
end
|
|
|
|
|
|
|
|
-- get width of screen and text
|
|
|
|
local screenWidth = djui_hud_get_screen_width()
|
|
|
|
local screenHeight = djui_hud_get_screen_height()
|
|
|
|
local width = djui_hud_measure_text(text) * scale
|
|
|
|
|
|
|
|
local x = (screenWidth - width) / 2.0
|
|
|
|
local y = (screenHeight * 0.6 - height) / 2.0
|
|
|
|
|
|
|
|
-- render
|
|
|
|
djui_hud_set_color(0, 0, 0, 128);
|
|
|
|
djui_hud_render_rect(x - 6 * scale, y - 6 * scale, width + 12 * scale, height + 12 * scale);
|
|
|
|
|
|
|
|
local r = (gGlobalSyncTable.displayColor & 0xFF0000) >> (8 * 2)
|
|
|
|
local g = (gGlobalSyncTable.displayColor & 0x00FF00) >> (8 * 1)
|
|
|
|
local b = (gGlobalSyncTable.displayColor & 0x0000FF) >> (8 * 0)
|
|
|
|
djui_hud_set_color(r, g, b, 255);
|
|
|
|
djui_hud_print_text(text, x, y, scale);
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
function on_football_reset_command(msg)
|
|
|
|
if msg == 'ball' then
|
|
|
|
djui_chat_message_create('Resetting the ball.')
|
|
|
|
sSoccerBall = spawn_or_move_ball(sBallSpawnPos.x, sBallSpawnPos.y, sBallSpawnPos.z)
|
|
|
|
return true
|
|
|
|
elseif msg == 'game' then
|
|
|
|
djui_chat_message_create('Resetting the game.')
|
|
|
|
gamemode_shuffle()
|
2022-03-11 02:23:25 +00:00
|
|
|
gGlobalSyncTable.scoreRed = 0
|
|
|
|
gGlobalSyncTable.scoreBlue = 0
|
2022-03-08 06:23:05 +00:00
|
|
|
gGlobalSyncTable.displayText = ' '
|
|
|
|
gGlobalSyncTable.gameState = GAME_STATE_WAIT
|
|
|
|
sStateTimer = sWaitTimeout
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
------------------
|
|
|
|
-- update stuff --
|
|
|
|
------------------
|
|
|
|
|
2022-03-13 05:28:57 +00:00
|
|
|
--- @param m MarioState
|
2022-03-08 06:23:05 +00:00
|
|
|
function mario_update_local(m)
|
|
|
|
local np = gNetworkPlayers[m.playerIndex]
|
|
|
|
local s = gPlayerSyncTable[m.playerIndex]
|
|
|
|
|
2022-03-09 05:34:14 +00:00
|
|
|
if (m.controller.buttonPressed & D_JPAD) ~= 0 then
|
|
|
|
--print(m.pos.x, m.pos.y, m.pos.z)
|
|
|
|
--sSoccerBall = spawn_or_move_ball(m.pos.x, m.pos.y, m.pos.z)
|
|
|
|
end
|
2022-03-08 06:23:05 +00:00
|
|
|
|
|
|
|
-- force players into certain positions and angles
|
|
|
|
if gGlobalSyncTable.gameState == GAME_STATE_WAIT then
|
|
|
|
-- figure out team index
|
|
|
|
local teamIndex = 0
|
|
|
|
for i=1,(MAX_PLAYERS-1) do
|
|
|
|
local mi = gMarioStates[i]
|
|
|
|
local ni = gNetworkPlayers[i]
|
|
|
|
local si = gPlayerSyncTable[i]
|
|
|
|
if active_player(mi) and si.team == s.team then
|
|
|
|
if ni.globalIndex < np.globalIndex then
|
|
|
|
teamIndex = teamIndex + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-03-09 05:34:14 +00:00
|
|
|
-- center camera
|
|
|
|
m.controller.buttonDown = m.controller.buttonDown | L_TRIG
|
|
|
|
|
2022-03-08 06:23:05 +00:00
|
|
|
-- figure out spawn position
|
|
|
|
local teamTheta = (3.14 / -2)
|
|
|
|
local teamDistance = 1550
|
|
|
|
if (teamIndex % 2) == 0 then
|
|
|
|
teamTheta = teamTheta - (teamIndex % 4) * 0.1
|
|
|
|
teamDistance = teamDistance + math.floor(teamIndex / 4) * 500
|
|
|
|
else
|
|
|
|
teamTheta = teamTheta + (teamIndex % 4) * 0.1
|
|
|
|
teamDistance = teamDistance + math.floor(teamIndex / 4) * 500
|
|
|
|
end
|
|
|
|
|
|
|
|
-- set spawn position
|
|
|
|
local playerPos = { x = sBallSpawnPos.x, y = -511, z = sBallSpawnPos.z }
|
|
|
|
if s.team == 1 then
|
|
|
|
playerPos.x = playerPos.x - math.sin(teamTheta) * teamDistance
|
|
|
|
playerPos.z = playerPos.z - math.cos(teamTheta) * teamDistance
|
|
|
|
m.faceAngle.y = -0x4000
|
|
|
|
elseif s.team == 2 then
|
|
|
|
playerPos.x = playerPos.x + math.sin(teamTheta) * teamDistance
|
|
|
|
playerPos.z = playerPos.z + math.cos(teamTheta) * teamDistance
|
|
|
|
m.faceAngle.y = 0x4000
|
|
|
|
end
|
|
|
|
m.faceAngle.x = 0
|
|
|
|
m.faceAngle.z = 0
|
|
|
|
m.pos.x = playerPos.x
|
|
|
|
m.pos.y = playerPos.y
|
|
|
|
m.pos.z = playerPos.z
|
|
|
|
m.vel.x = 0
|
|
|
|
m.vel.y = 0
|
|
|
|
m.vel.z = 0
|
2022-03-09 05:34:14 +00:00
|
|
|
m.forwardVel = 0
|
|
|
|
m.slideVelX = 0
|
|
|
|
m.slideVelZ = 0
|
2022-03-08 06:23:05 +00:00
|
|
|
set_mario_action(m, ACT_READING_AUTOMATIC_DIALOG, 0)
|
|
|
|
|
|
|
|
-- fix vanilla camera
|
|
|
|
if m.area.camera.mode == CAMERA_MODE_WATER_SURFACE then
|
|
|
|
set_camera_mode(m.area.camera, CAMERA_MODE_FREE_ROAM, 1)
|
|
|
|
end
|
|
|
|
elseif m.action == ACT_READING_AUTOMATIC_DIALOG then
|
|
|
|
set_mario_action(m, ACT_IDLE, 0)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-03-13 05:28:57 +00:00
|
|
|
--- @param m MarioState
|
2022-03-08 06:23:05 +00:00
|
|
|
function mario_update(m)
|
|
|
|
if m.playerIndex == 0 then
|
|
|
|
mario_update_local(m)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- update pos/angle
|
|
|
|
if m.action == ACT_READING_AUTOMATIC_DIALOG then
|
|
|
|
vec3f_copy(m.marioObj.header.gfx.pos, m.pos)
|
|
|
|
vec3s_set(m.marioObj.header.gfx.angle, -m.faceAngle.x, m.faceAngle.y, m.faceAngle.z)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- set metal state and health
|
|
|
|
local s = gPlayerSyncTable[m.playerIndex]
|
2022-03-11 02:23:25 +00:00
|
|
|
local np = gNetworkPlayers[m.playerIndex]
|
2022-03-08 06:23:05 +00:00
|
|
|
if s.team == 2 then
|
2022-08-07 22:13:51 +00:00
|
|
|
np.overridePaletteIndex = 7
|
2022-03-11 02:23:25 +00:00
|
|
|
m.marioBodyState.modelState = 0
|
2022-03-08 06:23:05 +00:00
|
|
|
elseif s.team == 1 then
|
2022-08-07 22:13:51 +00:00
|
|
|
np.overridePaletteIndex = 15
|
2022-03-08 06:23:05 +00:00
|
|
|
m.marioBodyState.modelState = 0
|
|
|
|
else
|
2022-08-07 22:13:51 +00:00
|
|
|
np.overridePaletteIndex = np.paletteIndex
|
2022-03-08 06:23:05 +00:00
|
|
|
m.marioBodyState.modelState = MODEL_STATE_NOISE_ALPHA
|
|
|
|
end
|
|
|
|
m.health = 0x880
|
|
|
|
|
|
|
|
-- update description
|
|
|
|
if s.team == 1 then
|
2022-03-11 02:23:25 +00:00
|
|
|
network_player_set_description(np, "red", 255, 64, 64, 255)
|
2022-03-08 06:23:05 +00:00
|
|
|
elseif s.team == 2 then
|
2022-03-11 02:23:25 +00:00
|
|
|
network_player_set_description(np, "blue", 64, 64, 255, 255)
|
2022-03-08 06:23:05 +00:00
|
|
|
else
|
|
|
|
network_player_set_description(np, "unknown", 64, 64, 64, 255)
|
|
|
|
end
|
|
|
|
|
|
|
|
if gPlayerSyncTable[m.playerIndex].sparkle then
|
|
|
|
m.particleFlags = m.particleFlags | PARTICLE_SPARKLES
|
|
|
|
else
|
|
|
|
m.particleFlags = (m.particleFlags & (~PARTICLE_SPARKLES))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function update()
|
|
|
|
local m = gMarioStates[0]
|
|
|
|
local np = gNetworkPlayers[m.playerIndex]
|
|
|
|
|
|
|
|
if np.currAreaSyncValid then
|
|
|
|
gamemode_update()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-09-19 02:00:39 +00:00
|
|
|
function on_level_init()
|
|
|
|
-- lower water level on castle grounds
|
|
|
|
if gNetworkPlayers[0].currLevelNum == LEVEL_CASTLE_GROUNDS then
|
|
|
|
set_environment_region(1, -800)
|
|
|
|
set_environment_region(2, -800)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-27 21:36:07 +00:00
|
|
|
function on_sync_valid()
|
|
|
|
-- prevent warp doors from working
|
|
|
|
local wasRefreshed = false
|
|
|
|
local obj = obj_get_first(OBJ_LIST_SURFACE)
|
|
|
|
while obj ~= nil do
|
|
|
|
local behaviorId = get_id_from_behavior(obj.behavior)
|
|
|
|
if behaviorId == id_bhvDoorWarp then
|
|
|
|
obj.oInteractType = 0
|
|
|
|
end
|
|
|
|
|
|
|
|
-- hide exclamation box
|
|
|
|
if behaviorId == id_bhvExclamationBox then
|
|
|
|
obj.oPosX = sBallHidePos.x
|
|
|
|
obj.oPosY = sBallHidePos.y
|
|
|
|
obj.oPosZ = sBallHidePos.z
|
|
|
|
end
|
|
|
|
|
|
|
|
if behaviorId == id_bhvLllHexagonalMesh then
|
|
|
|
wasRefreshed = true
|
|
|
|
end
|
|
|
|
obj = obj_get_next(obj)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- server spawns objects
|
|
|
|
if my_global_index() == 0 and not wasRefreshed then
|
|
|
|
-- block vanish cap
|
|
|
|
spawn_sync_object(
|
|
|
|
id_bhvLllHexagonalMesh,
|
|
|
|
E_MODEL_TRAMPOLINE,
|
|
|
|
-3390, -512, -2023,
|
|
|
|
function (obj)
|
|
|
|
obj.oOpacity = 255
|
|
|
|
obj.header.gfx.scale.x = 3
|
|
|
|
obj.header.gfx.scale.z = 3;
|
|
|
|
end)
|
|
|
|
|
|
|
|
-- block basement door
|
|
|
|
spawn_sync_object(
|
|
|
|
id_bhvLllHexagonalMesh,
|
|
|
|
E_MODEL_TRAMPOLINE,
|
|
|
|
3279 + 45, -511, -2937 + 45,
|
|
|
|
function (obj)
|
|
|
|
obj.oOpacity = 255
|
|
|
|
obj.header.gfx.scale.x = 2
|
|
|
|
obj.header.gfx.scale.z = 2
|
|
|
|
obj.oFaceAngleYaw = 0x6000
|
|
|
|
obj.oFaceAngleRoll = 0x4000
|
|
|
|
end)
|
|
|
|
|
|
|
|
-- block area near ramp
|
|
|
|
spawn_sync_object(
|
|
|
|
id_bhvStaticCheckeredPlatform,
|
|
|
|
E_MODEL_CHECKERBOARD_PLATFORM,
|
|
|
|
4430 - 65, 545 + 250 - 5, -6000 - 65,
|
|
|
|
function (obj)
|
|
|
|
obj.oOpacity = 255
|
|
|
|
obj.oFaceAngleYaw = 0x2000
|
|
|
|
obj.oFaceAngleRoll = 0x4000
|
|
|
|
end)
|
|
|
|
|
|
|
|
-- block area near ramp
|
|
|
|
for i=0,6 do
|
|
|
|
spawn_sync_object(
|
|
|
|
id_bhvStaticCheckeredPlatform,
|
|
|
|
E_MODEL_CHECKERBOARD_PLATFORM,
|
|
|
|
4460, 545 + 250, -6000 + i * 305,
|
|
|
|
function (obj)
|
|
|
|
obj.oOpacity = 255
|
|
|
|
obj.oFaceAngleYaw = 0
|
|
|
|
obj.oFaceAngleRoll = 0x4000
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- block cannon
|
|
|
|
spawn_sync_object(
|
|
|
|
id_bhvLllHexagonalMesh,
|
|
|
|
E_MODEL_TRAMPOLINE,
|
|
|
|
2385, 88, 1956,
|
|
|
|
function (obj)
|
|
|
|
obj.oOpacity = 255
|
|
|
|
obj.header.gfx.scale.x = 0.9
|
|
|
|
obj.header.gfx.scale.z = 0.9
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-03-08 06:23:05 +00:00
|
|
|
-----------
|
|
|
|
-- hooks --
|
|
|
|
-----------
|
|
|
|
|
|
|
|
hook_event(HOOK_MARIO_UPDATE, mario_update)
|
|
|
|
hook_event(HOOK_UPDATE, update)
|
|
|
|
hook_event(HOOK_ON_HUD_RENDER, on_hud_render)
|
|
|
|
hook_event(HOOK_ON_PLAYER_CONNECTED, on_player_connected)
|
2022-03-13 07:43:47 +00:00
|
|
|
hook_event(HOOK_ALLOW_PVP_ATTACK, allow_pvp_attack)
|
2022-09-19 02:00:39 +00:00
|
|
|
hook_event(HOOK_ON_LEVEL_INIT, on_level_init)
|
2023-03-27 21:36:07 +00:00
|
|
|
hook_event(HOOK_ON_SYNC_VALID, on_sync_valid)
|
2022-05-22 06:07:29 +00:00
|
|
|
if network_is_server() then
|
|
|
|
hook_chat_command('football-reset', "[game|ball] resets the game or ball", on_football_reset_command)
|
|
|
|
end
|
2022-03-08 06:23:05 +00:00
|
|
|
|
|
|
|
for i=0,(MAX_PLAYERS-1) do
|
|
|
|
local s = gPlayerSyncTable[i]
|
|
|
|
s.team = 0
|
|
|
|
s.sparkle = false
|
|
|
|
end
|