mirror of
https://github.com/coop-deluxe/sm64coopdx.git
synced 2024-12-01 16:27:27 +00:00
02ffca130e
Rewrote all of the ball-to-player interactions Player collisions are better now Attacks affect the ball in different ways Centered camera during the countdown period Players lose their velocity during the countdown period
1246 lines
38 KiB
Lua
1246 lines
38 KiB
Lua
-- name: Football (soccer)
|
|
-- description: Play football in the castle grounds.\nOnly use a save that has lowered the water in the moat.
|
|
|
|
---------------
|
|
-- constants --
|
|
---------------
|
|
|
|
ballGravity = 1.5
|
|
ballRadius = 50
|
|
ballFriction = 0.988
|
|
ballRestitution = 0.65
|
|
ballWaterDrag = 0.9
|
|
ballParallelInertia = 0.3
|
|
ballPerpendicularInertia = 1
|
|
|
|
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 },
|
|
}
|
|
|
|
---------------
|
|
-- globals --
|
|
---------------
|
|
|
|
gBallTouchedLocal = false
|
|
gCachedBalls = {}
|
|
gInitializeBalls = {}
|
|
|
|
-----------
|
|
-- utils --
|
|
-----------
|
|
|
|
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
|
|
|
|
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()
|
|
obj.oHitTime = obj.areaTimer
|
|
obj.oNetworkTime = obj.areaTimer
|
|
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',
|
|
})
|
|
|
|
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
|
|
|
|
-- area timer
|
|
table.insert(gInitializeBalls, obj)
|
|
|
|
-- 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
|
|
|
|
function bhv_ball_player_collision(obj)
|
|
local alterPos = { x = 0, y = 0, z = 0}
|
|
|
|
local m = nearest_mario_state_to_object(obj)
|
|
if m == nil then return alterPos end
|
|
local player = m.marioObj
|
|
if player == nil then return alterPos end
|
|
|
|
local playerRadius = 37
|
|
local playerHeight = 160
|
|
|
|
local objPoint = { x = obj.oPosX, y = obj.oPosY, z = obj.oPosZ }
|
|
local v = { x = obj.oVelX, y = obj.oVelY, z = obj.oVelZ }
|
|
|
|
-- figure out player-to-ball radius
|
|
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
|
|
playerBallRadius = playerBallRadius + 50
|
|
end
|
|
|
|
------------------------------------------------
|
|
-- calculate position and determine collision --
|
|
------------------------------------------------
|
|
|
|
-- 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 }
|
|
vec3f_normalize(differenceDir)
|
|
|
|
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
|
|
|
|
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
|
|
end
|
|
end
|
|
|
|
--------------------------------------
|
|
-- calculate velocity (interaction) --
|
|
--------------------------------------
|
|
|
|
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)
|
|
local parallel = { x = v.x - perpendicular.x, y = v.y - perpendicular.y, z = v.z - perpendicular.z }
|
|
|
|
-- 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
|
|
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
|
|
|
|
-------------------------------------
|
|
-- calculate velocity (reflection) --
|
|
-------------------------------------
|
|
|
|
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 }
|
|
|
|
-- apply friction and restitution
|
|
vec3f_mul(parallel, ballFriction)
|
|
vec3f_mul(perpendicular, ballRestitution)
|
|
|
|
-- play sounds
|
|
local parallelLength = vec3f_length(parallel)
|
|
local perpendicularLength = vec3f_length(perpendicular)
|
|
|
|
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
|
|
|
|
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
|
|
end
|
|
|
|
return alterPos
|
|
end
|
|
|
|
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
|
|
|
|
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
|
|
local alterPos = bhv_ball_player_collision(obj)
|
|
|
|
-- 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 }
|
|
vec3f_sum(b, b, alterPos)
|
|
|
|
-- 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)
|
|
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) }
|
|
|
|
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 })
|
|
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
|
|
local updateRateSend = (obj.oGlobalOwner == my_global_index() and (obj.areaTimer - obj.oNetworkTime) > 5)
|
|
if gBallTouchedLocal or updateRateSend then
|
|
if gBallTouchedLocal then
|
|
obj.oGlobalOwner = my_global_index()
|
|
obj.oHitTime = obj.areaTimer
|
|
end
|
|
obj.oNetworkTime = obj.areaTimer
|
|
network_send_object(obj, false)
|
|
end
|
|
|
|
-- 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
|
|
|
|
function ball_update()
|
|
-- hack: we have to set the area timer outside of the behavior or bad things happen
|
|
for i, obj in ipairs(gInitializeBalls) do
|
|
if obj ~= nil then
|
|
obj.areaTimerDuration = 0
|
|
obj.areaTimerType = AREA_TIMER_TYPE_MAXIMUM
|
|
obj.areaTimer = 0
|
|
end
|
|
gInitializeBalls[i] = nil
|
|
end
|
|
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
|
|
gGlobalSyncTable.scoreNormal = 0
|
|
gGlobalSyncTable.scoreMetal = 0
|
|
|
|
function gamemode_initialize()
|
|
-- 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
|
|
|
|
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()
|
|
sSoccerBall.oHitTime = sSoccerBall.areaTimer
|
|
sSoccerBall.oNetworkTime = sSoccerBall.areaTimer
|
|
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
|
|
gGlobalSyncTable.scoreNormal = gGlobalSyncTable.scoreNormal + 1
|
|
gGlobalSyncTable.displayFont = FONT_NORMAL
|
|
gGlobalSyncTable.displayColor = 0x99FF99
|
|
if gGlobalSyncTable.scoreNormal >= sMaxScore then
|
|
gGlobalSyncTable.displayText = 'normal team wins!'
|
|
gameOver = true
|
|
else
|
|
gGlobalSyncTable.displayText = 'normal team scored' .. displayName
|
|
end
|
|
else
|
|
gGlobalSyncTable.scoreMetal = gGlobalSyncTable.scoreMetal + 1
|
|
gGlobalSyncTable.displayFont = FONT_NORMAL
|
|
gGlobalSyncTable.displayColor = 0x9999FF
|
|
if gGlobalSyncTable.scoreMetal >= sMaxScore then
|
|
gGlobalSyncTable.displayText = 'metal team wins!'
|
|
gameOver = true
|
|
else
|
|
gGlobalSyncTable.displayText = 'metal team scored' .. displayName
|
|
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
|
|
local scorerS = gPlayerSyncTable[scorerNp.localIndex]
|
|
if scorerNp ~= nil and scorerS.team == scoringTeam then
|
|
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'
|
|
gGlobalSyncTable.displayColor = 0xFF9999
|
|
|
|
-- 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()
|
|
gGlobalSyncTable.scoreMetal = 0
|
|
gGlobalSyncTable.scoreNormal = 0
|
|
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
|
|
|
|
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
|
|
|
|
|
|
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
|
|
djui_hud_set_color(100, 255, 100, 180);
|
|
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);
|
|
djui_hud_print_text(tostring(gGlobalSyncTable.scoreNormal), x - xOffset + textOffset, y + 2, 1);
|
|
djui_hud_print_text(tostring(gGlobalSyncTable.scoreMetal), x + xOffset + textOffset, y + 2, 1);
|
|
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 not network_is_server() then
|
|
djui_chat_message_create('Only the server can change this setting!')
|
|
return true
|
|
end
|
|
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()
|
|
gGlobalSyncTable.scoreMetal = 0
|
|
gGlobalSyncTable.scoreNormal = 0
|
|
gGlobalSyncTable.displayText = ' '
|
|
gGlobalSyncTable.gameState = GAME_STATE_WAIT
|
|
sStateTimer = sWaitTimeout
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
------------------
|
|
-- update stuff --
|
|
------------------
|
|
|
|
function mario_update_local(m)
|
|
local np = gNetworkPlayers[m.playerIndex]
|
|
local s = gPlayerSyncTable[m.playerIndex]
|
|
|
|
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
|
|
|
|
-- 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
|
|
|
|
-- center camera
|
|
m.controller.buttonDown = m.controller.buttonDown | L_TRIG
|
|
|
|
-- 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
|
|
m.forwardVel = 0
|
|
m.slideVelX = 0
|
|
m.slideVelZ = 0
|
|
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
|
|
|
|
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]
|
|
if s.team == 2 then
|
|
m.marioBodyState.modelState = MODEL_STATE_METAL
|
|
elseif s.team == 1 then
|
|
m.marioBodyState.modelState = 0
|
|
else
|
|
m.marioBodyState.modelState = MODEL_STATE_NOISE_ALPHA
|
|
end
|
|
m.health = 0x880
|
|
|
|
-- update description
|
|
local np = gNetworkPlayers[m.playerIndex]
|
|
if s.team == 1 then
|
|
network_player_set_description(np, "normal", 64, 255, 64, 255)
|
|
elseif s.team == 2 then
|
|
network_player_set_description(np, "metal", 64, 64, 255, 255)
|
|
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]
|
|
|
|
ball_update()
|
|
|
|
if np.currAreaSyncValid then
|
|
gamemode_update()
|
|
end
|
|
end
|
|
|
|
-----------
|
|
-- 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)
|
|
hook_chat_command('football-reset', "[game|ball] resets the game or ball", on_football_reset_command)
|
|
|
|
for i=0,(MAX_PLAYERS-1) do
|
|
local s = gPlayerSyncTable[i]
|
|
s.team = 0
|
|
s.sparkle = false
|
|
end
|