sm64coopdx/mods/football.lua

1319 lines
40 KiB
Lua
Raw Normal View History

2022-03-08 06:23:05 +00:00
-- name: Football (soccer)
-- description: Play football in the castle grounds.\nOnly use a save that has lowered the water in the moat.
-- 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
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 --
-----------
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',
})
--- @param obj Object
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
--- @param obj Object
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
--- @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
--- @param obj Object
2022-03-08 06:23:05 +00:00
function bhv_ball_player_collision(obj)
local alterPos = { x = 0, y = 0, z = 0}
2022-03-08 06:23:05 +00:00
local m = nearest_mario_state_to_object(obj)
if m == nil then return alterPos end
2022-03-08 06:23:05 +00:00
local player = m.marioObj
if player == nil then return alterPos end
2022-03-08 06:23:05 +00:00
local playerRadius = 37
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
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
------------------------------------------------
-- calculate position and determine collision --
------------------------------------------------
2022-03-08 06:23:05 +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 }
if vec3f_length(differenceDir) ~= 0 then
vec3f_normalize(differenceDir)
end
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
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
end
-- make sure player has a velocity
if not doReflection and vPlayerMag == 0 then
doReflection = true
end
--------------------------------------
-- calculate velocity (interaction) --
--------------------------------------
2022-03-08 06:23:05 +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 }
-- 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 = {
x = parallel.x + perpendicular.x,
y = parallel.y + perpendicular.y,
z = parallel.z + perpendicular.z
2022-03-08 06:23:05 +00:00
}
-- set new velocity
2022-03-08 06:23:05 +00:00
obj.oVelX = reflect.x
obj.oVelY = reflect.y
2022-03-08 06:23:05 +00:00
obj.oVelZ = reflect.z
end
2022-03-08 06:23:05 +00:00
-------------------------------------
-- calculate velocity (reflection) --
-------------------------------------
2022-03-08 06:23:05 +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
-- 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
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
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
--- @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
--- @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
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 }
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)
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 })
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
-- 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
gGlobalSyncTable.scoreRed = 0
gGlobalSyncTable.scoreBlue = 0
2022-03-08 06:23:05 +00:00
function gamemode_initialize()
-- hide the SM64 HUD
hud_hide()
2022-03-08 06:23:05 +00:00
-- 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()
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
gGlobalSyncTable.scoreRed = gGlobalSyncTable.scoreRed + 1
2022-03-08 06:23:05 +00:00
gGlobalSyncTable.displayFont = FONT_NORMAL
gGlobalSyncTable.displayColor = 0xFF9999
if gGlobalSyncTable.scoreRed >= sMaxScore then
gGlobalSyncTable.displayText = 'red team wins!'
2022-03-08 06:23:05 +00:00
gameOver = true
else
gGlobalSyncTable.displayText = 'red team scored' .. displayName
2022-03-08 06:23:05 +00:00
end
else
gGlobalSyncTable.scoreBlue = gGlobalSyncTable.scoreBlue + 1
2022-03-08 06:23:05 +00:00
gGlobalSyncTable.displayFont = FONT_NORMAL
gGlobalSyncTable.displayColor = 0x9999FF
if gGlobalSyncTable.scoreBlue >= sMaxScore then
gGlobalSyncTable.displayText = 'blue team wins!'
2022-03-08 06:23:05 +00:00
gameOver = true
else
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
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'
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()
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
--- @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
--- @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
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);
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()
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 --
------------------
--- @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]
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
-- 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
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
--- @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]
local np = gNetworkPlayers[m.playerIndex]
2022-03-08 06:23:05 +00:00
if s.team == 2 then
Arbitrary shirt, pants, glove colors + settings menu (#145) * Support for more granular player colors You can now configure RGB values for shirt, pants, gloves, and shoes. Due to some limitations, configuring shoes does nothing at the moment. * Remove paletteIndex and friends Restructured and filled in some remaining code to account for that. * Add Edit Palette panel to Player panel * Change PlayerPalette contents to an enum-indexed array, remove shoes This gets rid of all the hokey code doing switch cases on the different parts. * Fix goof with player model selection box Should actually have affect now even if a custom palette is being used. * Fix gap in player color display list commands The extra space was leftover from when I was trying to get shoes working. Forgot to clean it up. * Standardize PlayerParts enum, including for lua constants autogen * djui_panel_player.c: Properly hook sending palette changes on unpause Editing the palette and then unpausing should send out the packet to everyone with the new palette changes (and update the palette preset selection box), but since we weren't hooking that situation before, it would stay changed only for you. You would have had to press the Back button for it to work right. * Allow Lua mods to continue using `paletteIndex`, `overridePaletteIndex` This lets mod code like this still work unchanged: if s.team == 2 then np.overridePaletteIndex = 7 elseif s.team == 1 then np.overridePaletteIndex = 15 else np.overridePaletteIndex = np.paletteIndex end It's essentially faked, and would work strangely if the value of either variable was inspected more closely directly. This should at least handle the typical use case, though. Every frame, `overridePaletteIndex` is checked to see if it was modified from its previous value. If so, `overridePalette` is set to the preset corresponding to the index. `paletteIndex` contains a special value that when used to assign to `overridePaletteIndex`, it copies `palette` into `overridePalette` to restore the real colors, which of course may not follow the presets at all. * characters.h: Pack `PlayerPalette` to eliminate size differences between computers * mario_misc.c: Remove remaining "TODO GAG"
2022-08-07 22:13:19 +00:00
np.overridePalette = gPalettePresets[7]
m.marioBodyState.modelState = 0
2022-03-08 06:23:05 +00:00
elseif s.team == 1 then
Arbitrary shirt, pants, glove colors + settings menu (#145) * Support for more granular player colors You can now configure RGB values for shirt, pants, gloves, and shoes. Due to some limitations, configuring shoes does nothing at the moment. * Remove paletteIndex and friends Restructured and filled in some remaining code to account for that. * Add Edit Palette panel to Player panel * Change PlayerPalette contents to an enum-indexed array, remove shoes This gets rid of all the hokey code doing switch cases on the different parts. * Fix goof with player model selection box Should actually have affect now even if a custom palette is being used. * Fix gap in player color display list commands The extra space was leftover from when I was trying to get shoes working. Forgot to clean it up. * Standardize PlayerParts enum, including for lua constants autogen * djui_panel_player.c: Properly hook sending palette changes on unpause Editing the palette and then unpausing should send out the packet to everyone with the new palette changes (and update the palette preset selection box), but since we weren't hooking that situation before, it would stay changed only for you. You would have had to press the Back button for it to work right. * Allow Lua mods to continue using `paletteIndex`, `overridePaletteIndex` This lets mod code like this still work unchanged: if s.team == 2 then np.overridePaletteIndex = 7 elseif s.team == 1 then np.overridePaletteIndex = 15 else np.overridePaletteIndex = np.paletteIndex end It's essentially faked, and would work strangely if the value of either variable was inspected more closely directly. This should at least handle the typical use case, though. Every frame, `overridePaletteIndex` is checked to see if it was modified from its previous value. If so, `overridePalette` is set to the preset corresponding to the index. `paletteIndex` contains a special value that when used to assign to `overridePaletteIndex`, it copies `palette` into `overridePalette` to restore the real colors, which of course may not follow the presets at all. * characters.h: Pack `PlayerPalette` to eliminate size differences between computers * mario_misc.c: Remove remaining "TODO GAG"
2022-08-07 22:13:19 +00:00
np.overridePalette = gPalettePresets[15]
2022-03-08 06:23:05 +00:00
m.marioBodyState.modelState = 0
else
Arbitrary shirt, pants, glove colors + settings menu (#145) * Support for more granular player colors You can now configure RGB values for shirt, pants, gloves, and shoes. Due to some limitations, configuring shoes does nothing at the moment. * Remove paletteIndex and friends Restructured and filled in some remaining code to account for that. * Add Edit Palette panel to Player panel * Change PlayerPalette contents to an enum-indexed array, remove shoes This gets rid of all the hokey code doing switch cases on the different parts. * Fix goof with player model selection box Should actually have affect now even if a custom palette is being used. * Fix gap in player color display list commands The extra space was leftover from when I was trying to get shoes working. Forgot to clean it up. * Standardize PlayerParts enum, including for lua constants autogen * djui_panel_player.c: Properly hook sending palette changes on unpause Editing the palette and then unpausing should send out the packet to everyone with the new palette changes (and update the palette preset selection box), but since we weren't hooking that situation before, it would stay changed only for you. You would have had to press the Back button for it to work right. * Allow Lua mods to continue using `paletteIndex`, `overridePaletteIndex` This lets mod code like this still work unchanged: if s.team == 2 then np.overridePaletteIndex = 7 elseif s.team == 1 then np.overridePaletteIndex = 15 else np.overridePaletteIndex = np.paletteIndex end It's essentially faked, and would work strangely if the value of either variable was inspected more closely directly. This should at least handle the typical use case, though. Every frame, `overridePaletteIndex` is checked to see if it was modified from its previous value. If so, `overridePalette` is set to the preset corresponding to the index. `paletteIndex` contains a special value that when used to assign to `overridePaletteIndex`, it copies `palette` into `overridePalette` to restore the real colors, which of course may not follow the presets at all. * characters.h: Pack `PlayerPalette` to eliminate size differences between computers * mario_misc.c: Remove remaining "TODO GAG"
2022-08-07 22:13:19 +00:00
np.overridePalette = np.palette
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
network_player_set_description(np, "red", 255, 64, 64, 255)
2022-03-08 06:23:05 +00:00
elseif s.team == 2 then
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
-----------
-- 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_event(HOOK_ALLOW_PVP_ATTACK, allow_pvp_attack)
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