From 02ffca130e7bf7110222d63ba13830ae2704a669 Mon Sep 17 00:00:00 2001 From: MysterD Date: Tue, 8 Mar 2022 21:34:14 -0800 Subject: [PATCH] Football improvements 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 --- mods/football.lua | 292 ++++++++++++++++++++++++++++++---------------- 1 file changed, 193 insertions(+), 99 deletions(-) diff --git a/mods/football.lua b/mods/football.lua index d65ecac7..37579581 100644 --- a/mods/football.lua +++ b/mods/football.lua @@ -13,6 +13,19 @@ 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 -- --------------- @@ -25,6 +38,17 @@ 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 @@ -182,116 +206,183 @@ function bhv_ball_init(obj) 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 / 2 - local alterPos = { x = 0, y = 0, z = 0 } + 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 } - local playerBallRadius = ballRadius -- figure out player-to-ball radius - if (m.action & ACT_FLAG_ATTACKING) ~= 0 then + 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 - -- figure out if our height collides - local heightOverlap = math.abs((player.oPosY + playerHeight * 0.5) - obj.oPosY) < (playerHeight + playerBallRadius) + ------------------------------------------------ + -- calculate position and determine collision -- + ------------------------------------------------ - -- figure out if our radius collides - local xdiff = player.oPosX - obj.oPosX - local zdiff = player.oPosZ - obj.oPosZ - local xzmag = math.sqrt(xdiff * xdiff + zdiff * zdiff) - local radiusOverlap = xzmag <= (playerRadius + playerBallRadius) - local xzdiff = (playerRadius + playerBallRadius) - xzmag + -- 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 if player should affect ball - if heightOverlap and radiusOverlap then - -- detect if the local player touched it - if m.playerIndex == 0 then - gBallTouchedLocal = true + -- 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 - -- detect hit from top - local heightMag = math.abs(obj.oPosY - player.oPosY - 167.5) - if obj.oVelY <= 0 and (heightMag < xzmag or xzmag < 1) and heightMag < playerBallRadius then - alterPos.y = heightMag + 1 - obj.oVelY = obj.oVelY * -ballRestitution - if math.abs(obj.oVelX) < math.abs(xdiff * 0.2) then obj.oVelX = -xdiff * 0.2 end - if obj.oVelY < player.oVelY + 5 then obj.oVelY = player.oVelY + 5 end - if math.abs(obj.oVelZ) < math.abs(zdiff * 0.2) then obj.oVelZ = -zdiff * 0.2 end - return alterPos - end + -------------------------------------- + -- calculate velocity (interaction) -- + -------------------------------------- - -- prevent division by zero - if xzmag < 1 then - xdiff = 1 - zdiff = 0 - xzmag = 1 - end + if not doReflection then + local vPlayerDir = { x = vPlayer.x, y = vPlayer.y, z = vPlayer.z } + vec3f_normalize(vPlayerDir) - -- reflect the current velocity against the player - local n = { x = xdiff / xzmag, y = 0, z = zdiff / xzmag } - local perpendicular = vec3f_project(v, n) + -- 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 } - obj.oVelX = reflect.x - obj.oVelZ = reflect.z - - -- move ball outside of player - alterPos.x = -xdiff / xzmag * (xzdiff + 1) - alterPos.z = -zdiff / xzmag * (xzdiff + 1) - - -- when ball is moved outside of player, give it speed in that direction - local addVel = { x = alterPos.x * 0.25, y = 0, z = alterPos.z * 0.25 } - local addVelMag = math.sqrt(addVel.x * addVel.x + addVel.z * addVel.z) - if addVelMag > 10 then - addVel.x = addVel.x / addVelMag * 10 - addVel.z = addVel.z / addVelMag * 10 - end - - -- also obtain the player's speed - addVel.x = addVel.x + player.oVelX - addVel.z = addVel.z + player.oVelZ - - -- have attacks alter velocity further - if (m.action == ACT_WATER_PUNCH) then - -- water punch launches it - addVel.x = addVel.x + xdiff / xzmag * -20 - addVel.z = addVel.z + zdiff / xzmag * -20 - obj.oVelY = obj.oVelY + 40 - if obj.oVelY > 40 then obj.oVelY = 40 end - elseif (m.action & ACT_FLAG_ATTACKING) ~= 0 then - if (m.action == ACT_MOVE_PUNCHING) or (m.action == ACT_PUNCHING) or (m.action == ACT_GROUND_POUND) then - -- normal punch or ground pound launches it - addVel.x = addVel.x + xdiff / xzmag * -30 - addVel.z = addVel.z + zdiff / xzmag * -30 - if m.action == ACT_GROUND_POUND then - obj.oVelY = obj.oVelY + 25 - if obj.oVelY > 25 then obj.oVelY = 25 end - end - else - -- other attacks send it upward, and decrease the xz force - addVel.x = addVel.x - player.oVelX * 0.3 - addVel.z = addVel.z - player.oVelZ * 0.3 - obj.oVelY = obj.oVelY + 25 - if obj.oVelY > 25 then obj.oVelY = 25 end - end - end - - -- apply attack velocity - -- this isn't the correct way to do this but whatever - addVelMag = math.sqrt(addVel.x * addVel.x + addVel.z * addVel.z) - local v = { x = obj.oVelX, y = 0, z = obj.oVelZ } - if vec3f_length(v) < addVelMag * 1.5 then - obj.oVelX = addVel.x - obj.oVelZ = addVel.z - end + -- 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 @@ -346,13 +437,13 @@ function bhv_ball_loop(obj) -- detect player collisions - local alter = bhv_ball_player_collision(obj) + 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, alter) + vec3f_sum(b, b, alterPos) -- regular movement local info = collision_find_surface_on_ray( @@ -772,8 +863,8 @@ function gamemode_active() sStateTimer = sOverTimeout else -- set sparkle - if scorerNp ~= nil then - local scorerS = gPlayerSyncTable[scorerNp.localIndex] + local scorerS = gPlayerSyncTable[scorerNp.localIndex] + if scorerNp ~= nil and scorerS.team == scoringTeam then scorerS.sparkle = true end gGlobalSyncTable.gameState = GAME_STATE_SCORE @@ -1021,12 +1112,10 @@ 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.oPosX = m.pos.x - -- sSoccerBall.oPosY = m.pos.y - 100 - -- sSoccerBall.oPosZ = m.pos.z - --end + 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 @@ -1043,6 +1132,9 @@ function mario_update_local(m) end end + -- center camera + m.controller.buttonDown = m.controller.buttonDown | L_TRIG + -- figure out spawn position local teamTheta = (3.14 / -2) local teamDistance = 1550 @@ -1073,11 +1165,13 @@ function mario_update_local(m) 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 - print(m.area.camera.mode) set_camera_mode(m.area.camera, CAMERA_MODE_FREE_ROAM, 1) end elseif m.action == ACT_READING_AUTOMATIC_DIALOG then