1697 lines
41 KiB
Lua
1697 lines
41 KiB
Lua
--!strict
|
|
local Mario = {}
|
|
Mario.__index = Mario
|
|
|
|
local SM64 = script.Parent
|
|
local Core = SM64.Parent
|
|
|
|
local Util = require(SM64.Util)
|
|
local Enums = require(SM64.Enums)
|
|
local Shared = require(Core.Shared)
|
|
|
|
local Sounds = Shared.Sounds
|
|
local Animations = Shared.Animations
|
|
|
|
local Types = require(SM64.Types)
|
|
local Flags = Types.Flags
|
|
|
|
local Action = Enums.Action
|
|
local Buttons = Enums.Buttons
|
|
local ActionFlags = Enums.ActionFlags
|
|
local ActionGroups = Enums.ActionGroups
|
|
|
|
local MarioCap = Enums.MarioCap
|
|
local MarioEyes = Enums.MarioEyes
|
|
local MarioFlags = Enums.MarioFlags
|
|
local MarioHands = Enums.MarioHands
|
|
|
|
local InputFlags = Enums.InputFlags
|
|
local ModelFlags = Enums.ModelFlags
|
|
local TerrainType = Enums.TerrainType
|
|
local SurfaceClass = Enums.SurfaceClass
|
|
local ParticleFlags = Enums.ParticleFlags
|
|
|
|
local AirStep = Enums.AirStep
|
|
local GroundStep = Enums.GroundStep
|
|
|
|
export type BodyState = Types.BodyState
|
|
export type Controller = Types.Controller
|
|
export type MarioState = Types.MarioState
|
|
|
|
export type Mario = typeof(setmetatable({} :: MarioState, Mario))
|
|
export type MarioAction = (Mario) -> boolean
|
|
export type Flags = Types.Flags
|
|
export type Class = Mario
|
|
|
|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- BINDINGS
|
|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
local actions: { MarioAction } = {}
|
|
Mario.Animations = Animations
|
|
Mario.Actions = actions
|
|
Mario.Sounds = Sounds
|
|
Mario.Enums = Enums
|
|
Mario.Types = Types
|
|
Mario.Util = Util
|
|
|
|
function Mario.RegisterAction(actionType: number, action: MarioAction)
|
|
if actions[actionType] then
|
|
warn("Action", Enums.GetName(Action, actionType), "was registered twice!")
|
|
end
|
|
|
|
actions[actionType] = action
|
|
end
|
|
|
|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- ANIMATIONS
|
|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
function Mario.IsAnimAtEnd(m: Mario): boolean
|
|
return m.AnimFrame >= m.AnimFrameCount
|
|
end
|
|
|
|
function Mario.IsAnimPastEnd(m: Mario): boolean
|
|
return m.AnimFrame >= m.AnimFrameCount - 2
|
|
end
|
|
|
|
function Mario.SetAnimation(m: Mario, anim: Animation): number
|
|
if anim and typeof(anim) == "Instance" and anim:IsA("Animation") then
|
|
if m.AnimCurrent == anim then
|
|
return m.AnimFrame
|
|
end
|
|
|
|
m.AnimFrameCount = anim:GetAttribute("NumFrames")
|
|
m.AnimCurrent = anim
|
|
else
|
|
warn("Invalid animation provided in SetAnimation:", anim, debug.traceback())
|
|
m.AnimFrameCount = 0
|
|
m.AnimCurrent = nil
|
|
end
|
|
|
|
local startFrame: number = anim:GetAttribute("StartFrame") or 0
|
|
m.AnimAccelAssist = 0
|
|
m.AnimAccel = 0
|
|
|
|
m.AnimReset = true
|
|
m.AnimDirty = true
|
|
m.AnimFrame = startFrame
|
|
|
|
return startFrame
|
|
end
|
|
|
|
function Mario.SetAnimationWithAccel(m: Mario, anim: Animation, accel: number)
|
|
if m.AnimCurrent ~= anim then
|
|
m:SetAnimation(anim)
|
|
m.AnimAccelAssist = -accel
|
|
end
|
|
|
|
m.AnimAccel = accel
|
|
return m.AnimFrame
|
|
end
|
|
|
|
function Mario.SetAnimToFrame(m: Mario, frame: number)
|
|
if m.AnimAccel ~= 0 then
|
|
m.AnimAccelAssist = bit32.lshift(frame, 0x10) + m.AnimAccel
|
|
m.AnimFrame = bit32.rshift(m.AnimAccelAssist, 0x10)
|
|
else
|
|
m.AnimFrame = frame + 1
|
|
end
|
|
|
|
m.AnimDirty = true
|
|
m.AnimSetFrame = m.AnimFrame
|
|
end
|
|
|
|
function Mario.IsAnimPastFrame(m: Mario, frame: number): boolean
|
|
local isPastFrame: boolean = false
|
|
local accel = m.AnimAccel
|
|
|
|
if accel ~= 0 then
|
|
local assist = m.AnimAccelAssist
|
|
local accelFrame = bit32.lshift(frame, 0x10)
|
|
isPastFrame = (assist > accelFrame and accelFrame >= assist - accel)
|
|
else
|
|
isPastFrame = (m.AnimFrame == (frame + 1))
|
|
end
|
|
|
|
return isPastFrame
|
|
end
|
|
|
|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- AUDIO
|
|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
function Mario.PlaySound(m: Mario, sound: Instance?)
|
|
if not sound then
|
|
return
|
|
end
|
|
|
|
assert(sound)
|
|
|
|
if sound:IsA("Sound") then
|
|
sound:SetAttribute("Play", true)
|
|
else
|
|
local rollTable = {}
|
|
local chances = sound:GetAttributes()
|
|
|
|
for name, chance in pairs(chances) do
|
|
for i = 1, chance do
|
|
table.insert(rollTable, name)
|
|
end
|
|
end
|
|
|
|
if #rollTable > 0 then
|
|
local pick = rollTable[math.random(1, #rollTable)]
|
|
sound = Sounds[pick]
|
|
|
|
if sound then
|
|
sound:SetAttribute("Play", true)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function Mario.PlaySoundIfNoFlag(m: Mario, sound: Instance?, flags: number)
|
|
if not m.Flags:Has(flags) and sound then
|
|
m:PlaySound(sound)
|
|
m.Flags:Add(flags)
|
|
end
|
|
end
|
|
|
|
function Mario.PlayJumpSound(m: Mario)
|
|
if m.Flags:Has(MarioFlags.MARIO_SOUND_PLAYED) then
|
|
return
|
|
end
|
|
|
|
if m.Action() == Action.TRIPLE_JUMP then
|
|
m:PlaySound(Sounds.MARIO_YAHOO_WAHA_YIPPEE)
|
|
elseif m.Action() == Action.JUMP_KICK then
|
|
m:PlaySound(Sounds.MARIO_PUNCH_HOO)
|
|
else
|
|
m:PlaySound(Sounds.MARIO_YAH_WAH_HOO)
|
|
end
|
|
|
|
m.Flags:Add(MarioFlags.MARIO_SOUND_PLAYED)
|
|
end
|
|
|
|
function Mario.AdjustSoundForSpeed(m: Mario)
|
|
local _absForwardVel = math.abs(m.ForwardVel)
|
|
-- TODO: Adjust Moving Speed Pitch
|
|
end
|
|
|
|
function Mario.PlaySoundAndSpawnParticles(m: Mario, sound: Instance?, wave: number?)
|
|
local particles: number?
|
|
|
|
if m.TerrainType == TerrainType.WATER then
|
|
if wave ~= 0 then
|
|
particles = ParticleFlags.SHALLOW_WATER_SPLASH
|
|
else
|
|
particles = ParticleFlags.SHALLOW_WATER_WAVE
|
|
end
|
|
else
|
|
if m.TerrainType == TerrainType.SAND then
|
|
particles = ParticleFlags.DIRT
|
|
elseif m.TerrainType == TerrainType.SNOW then
|
|
particles = ParticleFlags.SNOW
|
|
end
|
|
end
|
|
|
|
if particles then
|
|
m.ParticleFlags:Add(particles)
|
|
end
|
|
|
|
if sound then
|
|
local terrainType = Enums.GetName(TerrainType, m.TerrainType)
|
|
local stepSound = Sounds[sound.Name .. "_" .. terrainType]
|
|
|
|
if stepSound then
|
|
m:PlaySound(stepSound)
|
|
else
|
|
m:PlaySound(sound)
|
|
end
|
|
end
|
|
end
|
|
|
|
function Mario.PlayActionSound(m: Mario, sound: Instance?, wave: number?)
|
|
if not m.Flags:Has(MarioFlags.ACTION_SOUND_PLAYED) then
|
|
m:PlaySoundAndSpawnParticles(sound, wave)
|
|
m.Flags:Add(MarioFlags.ACTION_SOUND_PLAYED)
|
|
end
|
|
end
|
|
|
|
function Mario.PlayLandingSound(m: Mario, maybeSound: Instance?)
|
|
local sound = maybeSound or Sounds.ACTION_TERRAIN_LANDING
|
|
|
|
-- stylua: ignore
|
|
local landSound = if m.Flags:Has(MarioFlags.METAL_CAP)
|
|
then Sounds.ACTION_METAL_LANDING
|
|
else sound
|
|
|
|
m:PlaySoundAndSpawnParticles(landSound, 1)
|
|
end
|
|
|
|
function Mario.PlayLandingSoundOnce(m: Mario, sound: Instance?)
|
|
-- stylua: ignore
|
|
local landSound = if m.Flags:Has(MarioFlags.METAL_CAP)
|
|
then Sounds.ACTION_METAL_LANDING
|
|
else sound
|
|
|
|
m:PlayActionSound(landSound, 1)
|
|
end
|
|
|
|
function Mario.PlayHeavyLandingSound(m: Mario, sound: Instance?)
|
|
-- stylua: ignore
|
|
local landSound = if m.Flags:Has(MarioFlags.METAL_CAP)
|
|
then Sounds.ACTION_METAL_HEAVY_LANDING
|
|
else sound
|
|
|
|
m:PlaySoundAndSpawnParticles(landSound, 1)
|
|
end
|
|
|
|
function Mario.PlayHeavyLandingSoundOnce(m: Mario, sound: Instance?)
|
|
-- stylua: ignore
|
|
local landSound = if m.Flags:Has(MarioFlags.METAL_CAP)
|
|
then Sounds.ACTION_METAL_HEAVY_LANDING
|
|
else sound
|
|
|
|
m:PlayActionSound(landSound, 1)
|
|
end
|
|
|
|
function Mario.PlayMarioSound(m: Mario, actionSound: Instance, marioSound: Instance?)
|
|
if marioSound == nil then
|
|
marioSound = Sounds.MARIO_JUMP
|
|
end
|
|
|
|
if actionSound == Sounds.ACTION_TERRAIN_JUMP then
|
|
-- stylua: ignore
|
|
local sound = if m.Flags:Has(MarioFlags.METAL_CAP)
|
|
then Sounds.ACTION_METAL_JUMP
|
|
else actionSound
|
|
|
|
m:PlayActionSound(sound)
|
|
else
|
|
m:PlaySoundIfNoFlag(actionSound, MarioFlags.ACTION_SOUND_PLAYED)
|
|
end
|
|
|
|
if marioSound == Sounds.MARIO_JUMP or marioSound == nil then
|
|
m:PlayJumpSound()
|
|
elseif marioSound then
|
|
m:PlaySoundIfNoFlag(marioSound, MarioFlags.MARIO_SOUND_PLAYED)
|
|
end
|
|
end
|
|
|
|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- ACTION STATE
|
|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
function Mario.SetForwardVel(m: Mario, forwardVel: number)
|
|
m.ForwardVel = forwardVel
|
|
|
|
m.SlideVelX = Util.Sins(m.FaceAngle.Y) * forwardVel
|
|
m.SlideVelZ = Util.Coss(m.FaceAngle.Y) * forwardVel
|
|
|
|
m.Velocity = Vector3.new(m.SlideVelX, m.Velocity.Y, m.SlideVelZ)
|
|
end
|
|
|
|
function Mario.GetFloorClass(m: Mario): number
|
|
local floor = m.Floor
|
|
|
|
if floor then
|
|
local hit = floor.Instance
|
|
|
|
if hit and hit:IsA("BasePart") then
|
|
local physics = hit.CurrentPhysicalProperties
|
|
local friction = physics.Friction
|
|
|
|
if friction <= 0.025 then
|
|
return SurfaceClass.VERY_SLIPPERY
|
|
elseif friction <= 0.5 then
|
|
return SurfaceClass.SLIPPERY
|
|
elseif friction >= 0.9 then
|
|
return SurfaceClass.NOT_SLIPPERY
|
|
end
|
|
end
|
|
end
|
|
|
|
return SurfaceClass.DEFAULT
|
|
end
|
|
|
|
function Mario.GetTerrainType(m: Mario): number
|
|
local floor = m.Floor
|
|
|
|
if floor then
|
|
local material = floor.Material
|
|
local value = TerrainType.FROM_MATERIAL[material]
|
|
|
|
if value then
|
|
return value
|
|
end
|
|
end
|
|
|
|
return TerrainType.DEFAULT
|
|
end
|
|
|
|
function Mario.GetFloorType(m: Mario): number
|
|
-- TODO
|
|
return 0
|
|
end
|
|
|
|
function Mario.FacingDownhill(m: Mario, turnYaw: boolean?): boolean
|
|
local faceAngleYaw = m.FaceAngle.Y
|
|
|
|
if turnYaw and m.ForwardVel < 0 then
|
|
faceAngleYaw += 0x8000
|
|
end
|
|
|
|
return math.abs(m.FloorAngle - faceAngleYaw) < 0x4000
|
|
end
|
|
|
|
function Mario.FloorIsSlippery(m: Mario)
|
|
local floor = m.Floor
|
|
|
|
if floor then
|
|
local floorClass = m:GetFloorClass()
|
|
local deg = 90
|
|
|
|
if floorClass == SurfaceClass.VERY_SLIPPERY then
|
|
deg = 10
|
|
elseif floorClass == SurfaceClass.SLIPPERY then
|
|
deg = 20
|
|
elseif floorClass == SurfaceClass.NOT_SLIPPERY then
|
|
deg = 38
|
|
end
|
|
|
|
local rad = math.rad(deg)
|
|
return floor.Normal.Y <= math.cos(rad)
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function Mario.FloorIsSlope(m: Mario)
|
|
local floor = m.Floor
|
|
|
|
if floor then
|
|
local floorClass = m:GetFloorClass()
|
|
local deg = 15
|
|
|
|
if floorClass == SurfaceClass.VERY_SLIPPERY then
|
|
deg = 5
|
|
elseif floorClass == SurfaceClass.SLIPPERY then
|
|
deg = 10
|
|
elseif floorClass == SurfaceClass.NOT_SLIPPERY then
|
|
deg = 20
|
|
end
|
|
|
|
local rad = math.rad(deg)
|
|
return floor.Normal.Y <= math.cos(rad)
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function Mario.FloorIsSteep(m: Mario)
|
|
local floor = m.Floor
|
|
|
|
if floor and not m:FacingDownhill() then
|
|
local floorClass = m:GetFloorClass()
|
|
local deg = 30
|
|
|
|
if floorClass == SurfaceClass.VERY_SLIPPERY then
|
|
deg = 15
|
|
elseif floorClass == SurfaceClass.SLIPPERY then
|
|
deg = 20
|
|
elseif floorClass == SurfaceClass.NOT_SLIPPERY then
|
|
deg = 30
|
|
end
|
|
|
|
local rad = math.rad(deg)
|
|
return floor.Normal.Y <= math.cos(rad)
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function Mario.FindFloorHeightRelativePolar(
|
|
m: Mario,
|
|
angleFromMario: number,
|
|
distFromMario: number
|
|
): (number, RaycastResult?)
|
|
local y = Util.Sins(m.FaceAngle.Y + angleFromMario) * distFromMario
|
|
local x = Util.Coss(m.FaceAngle.Y + angleFromMario) * distFromMario
|
|
|
|
local marioPos = m.Position
|
|
local testPos = marioPos + Vector3.new(y, 100, x)
|
|
|
|
return Util.FindFloor(testPos)
|
|
end
|
|
|
|
function Mario.FindFloorSlope(m: Mario, yawOffset: number)
|
|
local x = Util.Sins(m.FaceAngle.Y + yawOffset) * 5
|
|
local z = Util.Coss(m.FaceAngle.Y + yawOffset) * 5
|
|
|
|
local forwardFloorY = Util.FindFloor(m.Position + Vector3.new(x, 100, z))
|
|
local backwardFloorY = Util.FindFloor(m.Position + Vector3.new(-x, 100, -z))
|
|
local result = 0
|
|
|
|
if forwardFloorY and backwardFloorY then
|
|
local forwardYDelta = forwardFloorY - m.Position.Y
|
|
local backwardYDelta = m.Position.Y - backwardFloorY
|
|
|
|
if forwardYDelta ^ 2 < backwardYDelta ^ 2 then
|
|
result = Util.Atan2s(5, forwardYDelta)
|
|
else
|
|
result = Util.Atan2s(5, backwardYDelta)
|
|
end
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
function Mario.SetSteepJumpAction(m: Mario)
|
|
m.SteepJumpYaw = m.FaceAngle.Y
|
|
|
|
if m.ForwardVel > 0 then
|
|
local angleTemp = m.FloorAngle + 0x8000
|
|
local faceAngleTemp = m.FaceAngle.Y - angleTemp
|
|
|
|
local y = Util.Sins(faceAngleTemp) * m.ForwardVel
|
|
local x = Util.Coss(faceAngleTemp) * m.ForwardVel * 0.75
|
|
|
|
m.ForwardVel = math.sqrt(y * y + x * x)
|
|
m.FaceAngle = Util.SetY(m.FaceAngle, Util.Atan2s(x, y) + angleTemp)
|
|
end
|
|
|
|
m:SetAction(Action.STEEP_JUMP, 0)
|
|
end
|
|
|
|
function Mario.SetYVelBasedOnFSpeed(m: Mario, initialVelY: number, multiplier: number)
|
|
m.Velocity = Util.SetY(m.Velocity, initialVelY + m.ForwardVel * multiplier)
|
|
|
|
if m.SquishTimer ~= 0 or m.QuicksandDepth > 1 then
|
|
m.Velocity *= Vector3.new(1, 0.5, 1)
|
|
end
|
|
end
|
|
|
|
function Mario.SetActionAirborne(m: Mario, action: number, actionArg: number)
|
|
if m.SquishTimer ~= 0 or m.QuicksandDepth > 1 then
|
|
if action == Action.DOUBLE_JUMP or action == Action.TWIRLING then
|
|
action = Action.JUMP
|
|
end
|
|
end
|
|
|
|
if action == Action.DOUBLE_JUMP then
|
|
m:SetYVelBasedOnFSpeed(52, 0.25)
|
|
m.ForwardVel *= 0.8
|
|
elseif action == Action.BACKFLIP then
|
|
m.AnimReset = true
|
|
m.ForwardVel = -16
|
|
m:SetYVelBasedOnFSpeed(62, 0)
|
|
elseif action == Action.TRIPLE_JUMP then
|
|
m:SetYVelBasedOnFSpeed(69, 0)
|
|
m.ForwardVel *= 0.8
|
|
elseif action == Action.FLYING_TRIPLE_JUMP then
|
|
m:SetYVelBasedOnFSpeed(82, 0)
|
|
elseif action == Action.WATER_JUMP or action == Action.HOLD_WATER_JUMP then
|
|
if actionArg == 0 then
|
|
m:SetYVelBasedOnFSpeed(42, 0)
|
|
end
|
|
elseif action == Action.BURNING_JUMP then
|
|
m.Velocity = Util.SetY(m.Velocity, 31.5)
|
|
m.ForwardVel = 8
|
|
elseif action == Action.RIDING_SHELL_JUMP then
|
|
m:SetYVelBasedOnFSpeed(42, 0.25)
|
|
elseif action == Action.JUMP or action == Action.HOLD_JUMP then
|
|
m.AnimReset = true
|
|
m:SetYVelBasedOnFSpeed(42, 0.25)
|
|
m.ForwardVel *= 0.8
|
|
elseif action == Action.WALL_KICK_AIR or action == Action.TOP_OF_POLE_JUMP then
|
|
m:SetYVelBasedOnFSpeed(62, 0)
|
|
|
|
if m.ForwardVel < 24 then
|
|
m.ForwardVel = 24
|
|
end
|
|
|
|
m.WallKickTimer = 0
|
|
elseif action == Action.SIDE_FLIP then
|
|
m:SetYVelBasedOnFSpeed(62, 0)
|
|
m.ForwardVel = 8
|
|
m.FaceAngle = Util.SetY(m.FaceAngle, m.IntendedYaw)
|
|
elseif action == Action.STEEP_JUMP then
|
|
m.AnimReset = true
|
|
m:SetYVelBasedOnFSpeed(42, 0.25)
|
|
m.FaceAngle = Util.SetX(m.FaceAngle, -0x2000)
|
|
elseif action == Action.LAVA_BOOST then
|
|
m.Velocity = Util.SetY(m.Velocity, 84)
|
|
|
|
if actionArg == 0 then
|
|
m.ForwardVel = 0
|
|
end
|
|
elseif action == Action.DIVE then
|
|
local forwardVel = m.ForwardVel + 15
|
|
|
|
if forwardVel > 48 then
|
|
forwardVel = 48
|
|
end
|
|
|
|
m:SetForwardVel(forwardVel)
|
|
elseif action == Action.LONG_JUMP then
|
|
m.AnimReset = true
|
|
m:SetYVelBasedOnFSpeed(30, 0)
|
|
|
|
m.LongJumpIsSlow = if m.ForwardVel > 16 then false else true
|
|
|
|
--! (BLJ's) This properly handles long jumps from getting forward speed with
|
|
-- too much velocity, but misses backwards longs allowing high negative speeds.
|
|
m.ForwardVel *= 1.5
|
|
|
|
if m.ForwardVel > 48 then
|
|
m.ForwardVel = 48
|
|
end
|
|
elseif action == Action.SLIDE_KICK then
|
|
m.Velocity = Util.SetY(m.Velocity, 12)
|
|
|
|
if m.ForwardVel < 32 then
|
|
m.ForwardVel = 32
|
|
end
|
|
elseif action == Action.JUMP_KICK then
|
|
m.Velocity = Util.SetY(m.Velocity, 20)
|
|
end
|
|
|
|
m.PeakHeight = m.Position.Y
|
|
m.Flags:Add(MarioFlags.MOVING_UP_IN_AIR)
|
|
|
|
return action
|
|
end
|
|
|
|
function Mario.SetActionMoving(m: Mario, action: number, actionArg: number): number
|
|
local forwardVel = m.ForwardVel
|
|
local floorClass = m:GetFloorClass()
|
|
local mag = math.min(m.IntendedMag, 8)
|
|
|
|
if action == Action.WALKING then
|
|
if floorClass ~= SurfaceClass.VERY_SLIPPERY then
|
|
if 0.0 <= forwardVel and forwardVel < mag then
|
|
m.ForwardVel = mag
|
|
end
|
|
end
|
|
|
|
m.WalkingPitch = 0
|
|
elseif action == Action.HOLD_WALKING then
|
|
if 0.0 <= forwardVel and forwardVel < mag / 2 then
|
|
m.ForwardVel = mag / 2
|
|
end
|
|
elseif action == Action.BEGIN_SLIDING then
|
|
if m:FacingDownhill() then
|
|
action = Action.BUTT_SLIDE
|
|
else
|
|
action = Action.STOMACH_SLIDE
|
|
end
|
|
elseif action == Action.HOLD_BEGIN_SLIDING then
|
|
if m:FacingDownhill() then
|
|
action = Action.HOLD_BUTT_SLIDE
|
|
else
|
|
action = Action.HOLD_STOMACH_SLIDE
|
|
end
|
|
end
|
|
|
|
return action
|
|
end
|
|
|
|
function Mario.SetActionSubmerged(m: Mario, action: number, actionArg: number): number
|
|
if action == Action.METAL_WATER_JUMP or action == Action.HOLD_METAL_WATER_JUMP then
|
|
m.Velocity = Util.SetY(m.Velocity, 32)
|
|
end
|
|
|
|
return action
|
|
end
|
|
|
|
function Mario.SetActionCutscene(m: Mario, action: number, actionArg: number): number
|
|
if action == Action.EMERGE_FROM_PIPE then
|
|
m.Velocity = Util.SetY(m.Velocity, 52)
|
|
elseif action == Action.FALL_AFTER_STAR_GRAB then
|
|
m:SetForwardVel(0)
|
|
elseif action == Action.SPAWN_SPIN_AIRBORNE then
|
|
m:SetForwardVel(2)
|
|
elseif action == Action.SPECIAL_EXIT_AIRBORNE or action == Action.SPECIAL_DEATH_EXIT then
|
|
m.Velocity = Util.SetY(m.Velocity, 64)
|
|
end
|
|
|
|
return action
|
|
end
|
|
|
|
function Mario.SetAction(m: Mario, action: number, maybeActionArg: number?): boolean
|
|
local group = bit32.band(action, ActionGroups.GROUP_MASK)
|
|
local actionArg = maybeActionArg or 0
|
|
|
|
if group == ActionGroups.MOVING then
|
|
action = m:SetActionMoving(action, actionArg)
|
|
elseif group == ActionGroups.AIRBORNE then
|
|
action = m:SetActionAirborne(action, actionArg)
|
|
elseif group == ActionGroups.SUBMERGED then
|
|
action = m:SetActionSubmerged(action, actionArg)
|
|
elseif group == ActionGroups.CUTSCENE then
|
|
action = m:SetActionCutscene(action, actionArg)
|
|
end
|
|
|
|
m.Flags:Remove(MarioFlags.ACTION_SOUND_PLAYED, MarioFlags.MARIO_SOUND_PLAYED)
|
|
|
|
if not m.Action:Has(ActionFlags.AIR) then
|
|
m.Flags:Remove(MarioFlags.FALLING_FAR)
|
|
end
|
|
|
|
m.PrevAction:Copy(m.Action)
|
|
m.Action:Set(action)
|
|
|
|
m.ActionArg = actionArg
|
|
m.ActionState = 0
|
|
m.ActionTimer = 0
|
|
|
|
return true
|
|
end
|
|
|
|
function Mario.SetJumpFromLanding(m: Mario)
|
|
if m:FloorIsSteep() then
|
|
m:SetSteepJumpAction()
|
|
elseif m.DoubleJumpTimer == 0 or m.SquishTimer ~= 0 then
|
|
m:SetAction(Action.JUMP, 0)
|
|
else
|
|
local prev = m.PrevAction()
|
|
|
|
if prev == Action.JUMP_LAND then
|
|
m:SetAction(Action.DOUBLE_JUMP)
|
|
elseif prev == Action.FREEFALL_LAND then
|
|
m:SetAction(Action.DOUBLE_JUMP)
|
|
elseif prev == Action.SIDE_FLIP_LAND_STOP then
|
|
m:SetAction(Action.DOUBLE_JUMP)
|
|
elseif prev == Action.DOUBLE_JUMP_LAND then
|
|
if m.Flags:Has(MarioFlags.WING_CAP) then
|
|
m:SetAction(Action.FLYING_TRIPLE_JUMP)
|
|
elseif m.ForwardVel > 20 then
|
|
m:SetAction(Action.TRIPLE_JUMP)
|
|
else
|
|
m:SetAction(Action.JUMP)
|
|
end
|
|
else
|
|
m:SetAction(Action.JUMP)
|
|
end
|
|
end
|
|
|
|
m.DoubleJumpTimer = 0
|
|
return true
|
|
end
|
|
|
|
function Mario.SetJumpingAction(m: Mario, action: number, actionArg: number?)
|
|
if m:FloorIsSteep() then
|
|
m:SetSteepJumpAction()
|
|
else
|
|
m:SetAction(action, actionArg)
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function Mario.HurtAndSetAction(m: Mario, action: number, actionArg: number, hurtCounter: number)
|
|
m.HurtCounter = hurtCounter
|
|
m:SetAction(action, actionArg)
|
|
end
|
|
|
|
function Mario.CheckCommonActionExits(m: Mario)
|
|
if m.Input:Has(InputFlags.A_PRESSED) then
|
|
return m:SetAction(Action.JUMP)
|
|
end
|
|
|
|
if m.Input:Has(InputFlags.OFF_FLOOR) then
|
|
return m:SetAction(Action.FREEFALL)
|
|
end
|
|
|
|
if m.Input:Has(InputFlags.NONZERO_ANALOG) then
|
|
return m:SetAction(Action.WALKING)
|
|
end
|
|
|
|
if m.Input:Has(InputFlags.ABOVE_SLIDE) then
|
|
return m:SetAction(Action.BEGIN_SLIDING)
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function Mario.UpdatePunchSequence(m: Mario)
|
|
local endAction, crouchEndAction
|
|
local animFrame
|
|
|
|
if m.Action:Has(ActionFlags.MOVING) then
|
|
endAction = Action.WALKING
|
|
crouchEndAction = Action.CROUCH_SLIDE
|
|
else
|
|
endAction = Action.IDLE
|
|
crouchEndAction = Action.CROUCHING
|
|
end
|
|
|
|
local actionArg = m.ActionArg
|
|
|
|
if actionArg == 0 or actionArg == 1 then
|
|
if actionArg == 0 then
|
|
m:PlaySound(Sounds.MARIO_PUNCH_YAH)
|
|
end
|
|
|
|
m:SetAnimation(Animations.FIRST_PUNCH)
|
|
m.ActionArg = m:IsAnimAtEnd() and 2 or 1
|
|
|
|
if m.AnimFrame >= 2 then
|
|
m.Flags:Add(MarioFlags.PUNCHING)
|
|
end
|
|
elseif actionArg == 2 then
|
|
m:SetAnimation(Animations.FIRST_PUNCH_FAST)
|
|
|
|
if m.AnimFrame <= 0 then
|
|
m.Flags:Add(MarioFlags.PUNCHING)
|
|
end
|
|
|
|
if m.Input:Has(InputFlags.B_PRESSED) then
|
|
m.ActionArg = 3
|
|
end
|
|
|
|
if m:IsAnimAtEnd() then
|
|
m:SetAction(endAction)
|
|
end
|
|
elseif actionArg == 3 or actionArg == 4 then
|
|
if actionArg == 3 then
|
|
m:PlaySound(Sounds.MARIO_PUNCH_WAH)
|
|
end
|
|
|
|
m:SetAnimation(Animations.SECOND_PUNCH)
|
|
m.ActionArg = m:IsAnimPastEnd() and 5 or 4
|
|
|
|
if m.AnimFrame > 0 then
|
|
m.Flags:Add(MarioFlags.PUNCHING)
|
|
end
|
|
|
|
if m.ActionArg == 5 then
|
|
m.BodyState.PunchType = 1
|
|
m.BodyState.PunchTimer = 4
|
|
end
|
|
elseif actionArg == 5 then
|
|
m:SetAnimation(Animations.SECOND_PUNCH_FAST)
|
|
|
|
if m.AnimFrame <= 0 then
|
|
m.Flags:Add(MarioFlags.PUNCHING)
|
|
end
|
|
|
|
if m.Input:Has(InputFlags.B_PRESSED) then
|
|
m.ActionArg = 6
|
|
end
|
|
|
|
if m:IsAnimAtEnd() then
|
|
m:SetAction(endAction)
|
|
end
|
|
elseif actionArg == 6 then
|
|
m:PlayActionSound(Sounds.MARIO_PUNCH_HOO, 1)
|
|
animFrame = m:SetAnimation(Animations.GROUND_KICK)
|
|
|
|
if animFrame == 0 then
|
|
m.BodyState.PunchType = 2
|
|
m.BodyState.PunchTimer = 6
|
|
end
|
|
|
|
if animFrame >= 0 and animFrame < 8 then
|
|
m.Flags:Add(MarioFlags.KICKING)
|
|
end
|
|
|
|
if m:IsAnimAtEnd() then
|
|
m:SetAction(endAction)
|
|
end
|
|
elseif actionArg == 9 then
|
|
m:PlayActionSound(Sounds.MARIO_PUNCH_HOO, 1)
|
|
m:SetAnimation(Animations.BREAKDANCE)
|
|
animFrame = m.AnimFrame
|
|
|
|
if animFrame >= 2 and animFrame < 8 then
|
|
m.Flags:Add(MarioFlags.TRIPPING)
|
|
end
|
|
|
|
if m:IsAnimAtEnd() then
|
|
m:SetAction(crouchEndAction)
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- PHYSICS STEP
|
|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
function Mario.BonkReflection(m: Mario, negateSpeed: boolean?)
|
|
local wall = m.Wall
|
|
|
|
if wall ~= nil then
|
|
local wallAngle = Util.Atan2s(wall.Normal.Z, wall.Normal.X)
|
|
m.FaceAngle = Util.SetY(m.FaceAngle, wallAngle - (m.FaceAngle.Y - wallAngle))
|
|
m:PlaySound(if m.Flags:Has(MarioFlags.METAL_CAP) then Sounds.ACTION_METAL_BONK else Sounds.ACTION_BONK)
|
|
else
|
|
m:PlaySound(Sounds.ACTION_HIT)
|
|
end
|
|
|
|
if negateSpeed then
|
|
m:SetForwardVel(-m.ForwardVel)
|
|
else
|
|
m.FaceAngle += Vector3int16.new(0, 0x8000, 0)
|
|
end
|
|
end
|
|
|
|
function Mario.PushOffSteepFloor(m: Mario, action: number, actionArg: number?)
|
|
local floorDYaw = m.FloorAngle - m.FaceAngle.Y
|
|
|
|
if floorDYaw > -0x4000 and floorDYaw < 0x4000 then
|
|
m.ForwardVel = 16
|
|
m.FaceAngle = Util.SetY(m.FaceAngle, m.FloorAngle)
|
|
else
|
|
m.ForwardVel = -16
|
|
m.FaceAngle = Util.SetY(m.FaceAngle, m.FloorAngle + 0x8000)
|
|
end
|
|
|
|
m:SetAction(action, actionArg)
|
|
end
|
|
|
|
function Mario.StopAndSetHeightToFloor(m: Mario)
|
|
m:SetForwardVel(0)
|
|
m.Velocity *= Vector3.new(1, 0, 1)
|
|
m.Position = Util.SetY(m.Position, m.FloorHeight)
|
|
m.GfxAngle = Vector3int16.new(0, m.FaceAngle.Y, 0)
|
|
end
|
|
|
|
function Mario.StationaryGroundStep(m: Mario): number
|
|
m:SetForwardVel(0)
|
|
return m:PerformGroundStep()
|
|
end
|
|
|
|
function Mario.PerformGroundQuarterStep(m: Mario, nextPos: Vector3): number
|
|
local lowerPos, _lowerWall = Util.FindWallCollisions(nextPos, 30, 24)
|
|
nextPos = lowerPos
|
|
|
|
local upperPos, upperWall = Util.FindWallCollisions(nextPos, 60, 50)
|
|
nextPos = upperPos
|
|
|
|
local floorHeight, floor = Util.FindFloor(nextPos)
|
|
local ceilHeight, _ceil = Util.FindCeil(nextPos, floorHeight)
|
|
|
|
m.Wall = upperWall
|
|
|
|
if floor == nil then
|
|
return GroundStep.HIT_WALL_STOP_QSTEPS
|
|
end
|
|
|
|
if nextPos.Y > floorHeight + 100 then
|
|
if nextPos.Y + 160 >= ceilHeight then
|
|
return GroundStep.HIT_WALL_STOP_QSTEPS
|
|
end
|
|
|
|
m.Floor = floor
|
|
m.FloorHeight = floorHeight
|
|
|
|
return GroundStep.LEFT_GROUND
|
|
end
|
|
|
|
if floorHeight + 160 >= ceilHeight then
|
|
return GroundStep.HIT_WALL_STOP_QSTEPS
|
|
end
|
|
|
|
m.Floor = floor
|
|
m.FloorHeight = floorHeight
|
|
m.Position = Vector3.new(nextPos.X, floorHeight, nextPos.Z)
|
|
|
|
if upperWall then
|
|
local wallDYaw = Util.SignedShort(Util.Atan2s(upperWall.Normal.Z, upperWall.Normal.X) - m.FaceAngle.Y)
|
|
|
|
if math.abs(wallDYaw) >= 0x2AAA and math.abs(wallDYaw) <= 0x5555 then
|
|
return GroundStep.NONE
|
|
end
|
|
|
|
return GroundStep.HIT_WALL_CONTINUE_QSTEPS
|
|
end
|
|
|
|
return GroundStep.NONE
|
|
end
|
|
|
|
function Mario.PerformGroundStep(m: Mario): number
|
|
local floor = m.Floor
|
|
|
|
if not floor then
|
|
return GroundStep.NONE
|
|
end
|
|
|
|
local stepResult: number
|
|
assert(floor)
|
|
|
|
for i = 1, 4 do
|
|
local intendedX = m.Position.X + floor.Normal.Y * (m.Velocity.X / 4)
|
|
local intendedZ = m.Position.Z + floor.Normal.Y * (m.Velocity.Z / 4)
|
|
local intendedY = m.Position.Y
|
|
|
|
local intendedPos = Vector3.new(intendedX, intendedY, intendedZ)
|
|
stepResult = m:PerformGroundQuarterStep(intendedPos)
|
|
|
|
if stepResult == GroundStep.LEFT_GROUND or stepResult == GroundStep.HIT_WALL_STOP_QSTEPS then
|
|
break
|
|
end
|
|
end
|
|
|
|
m.TerrainType = m:GetTerrainType()
|
|
m.GfxAngle = Vector3int16.new(0, m.FaceAngle.Y, 0)
|
|
|
|
if stepResult == GroundStep.HIT_WALL_CONTINUE_QSTEPS then
|
|
stepResult = GroundStep.HIT_WALL
|
|
end
|
|
|
|
return stepResult
|
|
end
|
|
|
|
function Mario.CheckLedgeGrab(m: Mario, wall: RaycastResult, intendedPos: Vector3, nextPos: Vector3): boolean
|
|
if m.Velocity.Y > 0 then
|
|
return false
|
|
end
|
|
|
|
local dispX = nextPos.X - intendedPos.X
|
|
local dispZ = nextPos.Z - intendedPos.Z
|
|
|
|
if dispX * m.Velocity.X + dispZ * m.Velocity.Z > 0 then
|
|
return false
|
|
end
|
|
|
|
local ledgeX = nextPos.X - (wall.Normal.X * 60)
|
|
local ledgeZ = nextPos.Z - (wall.Normal.Z * 60)
|
|
|
|
local ledgePos = Vector3.new(ledgeX, nextPos.Y + 160, ledgeZ)
|
|
local ledgeY, ledgeFloor = Util.FindFloor(ledgePos)
|
|
|
|
if ledgeY - nextPos.Y < 100 then
|
|
return false
|
|
end
|
|
|
|
if ledgeFloor then
|
|
ledgePos = ledgeFloor.Position
|
|
m.Position = ledgePos
|
|
|
|
m.Floor = ledgeFloor
|
|
m.FloorHeight = ledgeY
|
|
m.FloorAngle = Util.Atan2s(ledgeFloor.Normal.Z, ledgeFloor.Normal.X)
|
|
|
|
m.FaceAngle *= Vector3int16.new(0, 1, 1)
|
|
m.FaceAngle = Util.SetY(m.FaceAngle, Util.Atan2s(wall.Normal.Z, wall.Normal.X) + 0x8000)
|
|
end
|
|
|
|
return ledgeFloor ~= nil
|
|
end
|
|
|
|
function Mario.PerformAirQuarterStep(m: Mario, intendedPos: Vector3, stepArg: number)
|
|
local nextPos = intendedPos
|
|
|
|
local upperPos, upperWall = Util.FindWallCollisions(nextPos, 150, 50)
|
|
nextPos = upperPos
|
|
|
|
local lowerPos, lowerWall = Util.FindWallCollisions(nextPos, 30, 50)
|
|
nextPos = lowerPos
|
|
|
|
local floorHeight, floor = Util.FindFloor(nextPos)
|
|
local ceilHeight = Util.FindCeil(nextPos, floorHeight)
|
|
|
|
m.Wall = nil
|
|
|
|
if floor == nil then
|
|
if nextPos.Y <= m.FloorHeight then
|
|
m.Position = Util.SetY(m.Position, m.FloorHeight)
|
|
return AirStep.LANDED
|
|
end
|
|
|
|
m.Position = Util.SetY(m.Position, nextPos.Y)
|
|
return AirStep.HIT_WALL
|
|
end
|
|
|
|
if nextPos.Y <= floorHeight then
|
|
if ceilHeight - floorHeight > 160 then
|
|
m.Floor = floor
|
|
m.FloorHeight = floorHeight
|
|
m.Position = Vector3.new(nextPos.X, m.Position.Y, nextPos.Z)
|
|
end
|
|
|
|
m.Position = Util.SetY(m.Position, floorHeight)
|
|
return AirStep.LANDED
|
|
end
|
|
|
|
if nextPos.Y + 160 > ceilHeight then
|
|
if m.Velocity.Y > 0 then
|
|
m.Velocity = Util.SetY(m.Velocity, 0)
|
|
return AirStep.NONE
|
|
end
|
|
|
|
if nextPos.Y <= m.FloorHeight then
|
|
m.Position = Util.SetY(m.Position, floorHeight)
|
|
return AirStep.LANDED
|
|
end
|
|
|
|
m.Position = Util.SetY(m.Position, nextPos.Y)
|
|
return AirStep.HIT_WALL
|
|
end
|
|
|
|
if bit32.btest(stepArg, AirStep.CHECK_LEDGE_GRAB) and upperWall == nil and lowerWall ~= nil then
|
|
if m:CheckLedgeGrab(lowerWall, intendedPos, nextPos) then
|
|
return AirStep.GRABBED_LEDGE
|
|
end
|
|
|
|
m.Floor = floor
|
|
m.Position = nextPos
|
|
m.FloorHeight = floorHeight
|
|
|
|
return AirStep.NONE
|
|
end
|
|
|
|
m.Floor = floor
|
|
m.Position = nextPos
|
|
m.FloorHeight = floorHeight
|
|
|
|
if upperWall or lowerWall then
|
|
local wall = assert(upperWall or lowerWall)
|
|
local wallDYaw = Util.SignedShort(Util.Atan2s(wall.Normal.Z, wall.Normal.X) - m.FaceAngle.Y)
|
|
m.Wall = wall
|
|
|
|
if math.abs(wallDYaw) > 0x6000 then
|
|
return AirStep.HIT_WALL
|
|
end
|
|
end
|
|
|
|
return AirStep.NONE
|
|
end
|
|
|
|
function Mario.ApplyTwirlGravity(m: Mario)
|
|
local heaviness = 1
|
|
|
|
if m.AngleVel.Y > 1024 then
|
|
heaviness = 1024 / m.AngleVel.Y
|
|
end
|
|
|
|
local terminalVelocity = -75 * heaviness
|
|
m.Velocity -= Vector3.new(0, 4 * heaviness, 0)
|
|
|
|
if m.Velocity.Y < terminalVelocity then
|
|
m.Velocity = Util.SetY(m.Velocity, terminalVelocity)
|
|
end
|
|
end
|
|
|
|
function Mario.ShouldStrengthenGravityForJumpAscent(m: Mario): boolean
|
|
if not m.Flags:Has(MarioFlags.MOVING_UP_IN_AIR) then
|
|
return false
|
|
end
|
|
|
|
if m.Action:Has(ActionFlags.INTANGIBLE, ActionFlags.INVULNERABLE) then
|
|
return false
|
|
end
|
|
|
|
if not m.Input:Has(InputFlags.A_DOWN) and m.Velocity.Y > 20 then
|
|
return m.Action:Has(ActionFlags.CONTROL_JUMP_HEIGHT)
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function Mario.ApplyGravity(m: Mario)
|
|
local action = m.Action()
|
|
|
|
if action == Action.TWIRLING and m.Velocity.Y < 0 then
|
|
m:ApplyTwirlGravity()
|
|
elseif action == Action.SHOT_FROM_CANNON then
|
|
m.Velocity -= Vector3.yAxis
|
|
|
|
if m.Velocity.Y < -75 then
|
|
m.Velocity = Util.SetY(m.Velocity, -75)
|
|
end
|
|
elseif action == Action.LONG_JUMP or action == Action.SLIDE_KICK or action == Action.BBH_ENTER_SPIN then
|
|
m.Velocity -= (Vector3.yAxis * 2)
|
|
|
|
if m.Velocity.Y < -75 then
|
|
m.Velocity = Util.SetY(m.Velocity, -75)
|
|
end
|
|
elseif action == Action.LAVA_BOOST or action == Action.FALL_AFTER_STAR_GRAB then
|
|
m.Velocity -= (Vector3.yAxis * 3.2)
|
|
|
|
if m.Velocity.Y < -65 then
|
|
m.Velocity = Util.SetY(m.Velocity, -65)
|
|
end
|
|
elseif m:ShouldStrengthenGravityForJumpAscent() then
|
|
m.Velocity *= Vector3.new(1, 0.25, 1)
|
|
elseif m.Action:Has(ActionFlags.METAL_WATER) then
|
|
m.Velocity -= (Vector3.yAxis * 1.6)
|
|
|
|
if m.Velocity.Y < -16 then
|
|
m.Velocity = Util.SetY(m.Velocity, -16)
|
|
end
|
|
elseif m.Flags:Has(MarioFlags.WING_CAP) and m.Velocity.Y < 0 and m.Input:Has(InputFlags.A_DOWN) then
|
|
m.BodyState.WingFlutter = true
|
|
m.Velocity -= (Vector3.yAxis * 2)
|
|
|
|
if m.Velocity.Y < -37.5 then
|
|
m.Velocity += (Vector3.yAxis * 4)
|
|
|
|
if m.Velocity.Y > -37.5 then
|
|
m.Velocity = Util.SetY(m.Velocity, -37.5)
|
|
end
|
|
end
|
|
else
|
|
m.Velocity -= (Vector3.yAxis * 4)
|
|
|
|
if m.Velocity.Y < -75 then
|
|
m.Velocity = Util.SetY(m.Velocity, -75)
|
|
end
|
|
end
|
|
end
|
|
|
|
function Mario.PerformAirStep(m: Mario, maybeStepArg: number?)
|
|
local stepArg = maybeStepArg or 0
|
|
local stepResult = AirStep.NONE
|
|
m.Wall = nil
|
|
|
|
for i = 1, 4 do
|
|
local intendedPos = m.Position + (m.Velocity / 4)
|
|
local result = m:PerformAirQuarterStep(intendedPos, stepArg)
|
|
|
|
if result ~= AirStep.NONE then
|
|
stepResult = result
|
|
end
|
|
|
|
if
|
|
result == AirStep.LANDED
|
|
or result == AirStep.GRABBED_LEDGE
|
|
or result == AirStep.GRABBED_CEILING
|
|
or result == AirStep.HIT_LAVA_WALL
|
|
then
|
|
break
|
|
end
|
|
end
|
|
|
|
if m.Velocity.Y >= 0 then
|
|
m.PeakHeight = m.Position.Y
|
|
end
|
|
|
|
m.TerrainType = m:GetTerrainType()
|
|
|
|
if m.Action() ~= Action.FLYING then
|
|
m:ApplyGravity()
|
|
end
|
|
|
|
m.GfxAngle = Vector3int16.new(0, m.FaceAngle.Y, 0)
|
|
|
|
return stepResult
|
|
end
|
|
|
|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- UPDATE ROUTINES
|
|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
function Mario.UpdateButtonInputs(m: Mario)
|
|
if m.Controller.ButtonPressed:Has(Buttons.A_BUTTON) then
|
|
m.Input:Add(InputFlags.A_PRESSED)
|
|
end
|
|
|
|
if m.Controller.ButtonDown:Has(Buttons.A_BUTTON) then
|
|
m.Input:Add(InputFlags.A_DOWN)
|
|
end
|
|
|
|
if m.SquishTimer == 0 then
|
|
if m.Controller.ButtonPressed:Has(Buttons.B_BUTTON) then
|
|
m.Input:Add(InputFlags.B_PRESSED)
|
|
end
|
|
|
|
if m.Controller.ButtonDown:Has(Buttons.Z_TRIG) then
|
|
m.Input:Add(InputFlags.Z_DOWN)
|
|
end
|
|
|
|
if m.Controller.ButtonPressed:Has(Buttons.Z_TRIG) then
|
|
m.Input:Add(InputFlags.Z_PRESSED)
|
|
end
|
|
end
|
|
|
|
if m.Input:Has(InputFlags.A_PRESSED) then
|
|
m.FramesSinceA = 0
|
|
elseif m.FramesSinceA < 255 then
|
|
m.FramesSinceA += 1
|
|
end
|
|
|
|
if m.Input:Has(InputFlags.B_PRESSED) then
|
|
m.FramesSinceB = 0
|
|
elseif m.FramesSinceB < 255 then
|
|
m.FramesSinceB += 1
|
|
end
|
|
end
|
|
|
|
function Mario.UpdateJoystickInputs(m: Mario)
|
|
local controller = m.Controller
|
|
local mag = ((controller.StickMag / 64) * (controller.StickMag / 64)) * 64
|
|
|
|
if m.SquishTimer == 0 then
|
|
m.IntendedMag = mag / 2
|
|
else
|
|
m.IntendedMag = mag / 8
|
|
end
|
|
|
|
if m.IntendedMag > 0 then
|
|
local camera = workspace.CurrentCamera
|
|
local lookVector = camera.CFrame.LookVector
|
|
local cameraYaw = Util.Atan2s(-lookVector.Z, -lookVector.X)
|
|
|
|
m.IntendedYaw = Util.SignedShort(Util.Atan2s(-controller.StickY, controller.StickX) + cameraYaw)
|
|
m.Input:Add(InputFlags.NONZERO_ANALOG)
|
|
else
|
|
m.IntendedYaw = m.FaceAngle.Y
|
|
end
|
|
end
|
|
|
|
function Mario.UpdateGeometryInputs(m: Mario)
|
|
local floorHeight, floor = Util.FindFloor(m.Position)
|
|
local ceilHeight, ceil = Util.FindCeil(m.Position, m.FloorHeight)
|
|
|
|
m.FloorHeight = floorHeight
|
|
m.CeilHeight = ceilHeight
|
|
m.Floor = floor
|
|
m.Ceil = ceil
|
|
|
|
if floor then
|
|
m.FloorAngle = Util.Atan2s(floor.Normal.Z, floor.Normal.X)
|
|
m.TerrainType = m:GetTerrainType()
|
|
|
|
if m:FloorIsSlippery() then
|
|
m.Input:Add(InputFlags.ABOVE_SLIDE)
|
|
end
|
|
|
|
if ceil then
|
|
local ceilToFloorDist = m.CeilHeight - m.FloorHeight
|
|
|
|
if 0 < ceilToFloorDist and ceilToFloorDist < 150 then
|
|
m.Input:Add(InputFlags.SQUISHED)
|
|
end
|
|
end
|
|
|
|
if m.Position.Y > m.FloorHeight + 100 then
|
|
m.Input:Add(InputFlags.OFF_FLOOR)
|
|
end
|
|
|
|
if m.Position.Y < m.WaterLevel - 10 then
|
|
m.Input:Add(InputFlags.IN_WATER)
|
|
end
|
|
end
|
|
end
|
|
|
|
function Mario.UpdateInputs(m: Mario)
|
|
m.ParticleFlags:Clear()
|
|
m.Flags:Band(0xFFFFFF)
|
|
m.Input:Clear()
|
|
|
|
m:UpdateButtonInputs()
|
|
m:UpdateJoystickInputs()
|
|
m:UpdateGeometryInputs()
|
|
|
|
if not m.Input:Has(InputFlags.NONZERO_ANALOG, InputFlags.A_PRESSED) then
|
|
m.Input:Add(InputFlags.NO_MOVEMENT)
|
|
end
|
|
|
|
if m.WallKickTimer > 0 then
|
|
m.WallKickTimer -= 1
|
|
end
|
|
|
|
if m.DoubleJumpTimer > 0 then
|
|
m.DoubleJumpTimer -= 1
|
|
end
|
|
end
|
|
|
|
function Mario.ResetBodyState(m: Mario)
|
|
local bodyState = m.BodyState
|
|
bodyState.CapState:Set(MarioCap.DEFAULT_CAP_OFF)
|
|
bodyState.EyeState = MarioEyes.BLINK
|
|
bodyState.HandState:Set(MarioHands.FISTS)
|
|
bodyState.ModelState:Clear()
|
|
bodyState.WingFlutter = false
|
|
|
|
m.Flags:Remove(MarioFlags.METAL_SHOCK)
|
|
end
|
|
|
|
function Mario.UpdateCaps(m: Mario): Flags
|
|
local flags = m.Flags
|
|
|
|
if m.CapTimer > 0 then
|
|
if m.CapTimer <= 60 then
|
|
m.CapTimer -= 1
|
|
end
|
|
|
|
if m.CapTimer == 0 then
|
|
m.Flags:Remove(MarioFlags.SPECIAL_CAPS)
|
|
|
|
if not m.Flags:Has(MarioFlags.CAPS) then
|
|
m.Flags:Remove(MarioFlags.CAP_ON_HEAD)
|
|
end
|
|
end
|
|
end
|
|
|
|
return flags
|
|
end
|
|
|
|
function Mario.UpdateModel(m: Mario)
|
|
local modelState = Flags.new()
|
|
local bodyState = m.BodyState
|
|
local flags = m:UpdateCaps()
|
|
|
|
if flags:Has(MarioFlags.VANISH_CAP) then
|
|
modelState:Add(ModelFlags.NOISE_ALPHA)
|
|
end
|
|
|
|
if flags:Has(MarioFlags.METAL_CAP, MarioFlags.METAL_SHOCK) then
|
|
modelState:Add(ModelFlags.METAL)
|
|
end
|
|
|
|
if m.InvincTimer >= 3 and bit32.band(Util.GlobalTimer, 1) > 0 then
|
|
modelState:Add(ModelFlags.INVISIBLE)
|
|
end
|
|
|
|
if flags:Has(MarioFlags.CAP_IN_HAND) then
|
|
if flags:Has(MarioFlags.WING_CAP) then
|
|
bodyState.HandState:Set(MarioHands.HOLDING_WING_CAP)
|
|
else
|
|
bodyState.HandState:Set(MarioHands.HOLDING_CAP)
|
|
end
|
|
end
|
|
|
|
if flags:Has(MarioFlags.CAP_ON_HEAD) then
|
|
if flags:Has(MarioFlags.WING_CAP) then
|
|
bodyState.CapState:Set(MarioCap.WING_CAP_ON)
|
|
else
|
|
bodyState.CapState:Set(MarioCap.DEFAULT_CAP_ON)
|
|
end
|
|
end
|
|
|
|
if m.Action:Has(ActionFlags.SHORT_HITBOX) then
|
|
m.HitboxHeight = 100
|
|
else
|
|
m.HitboxHeight = 160
|
|
end
|
|
end
|
|
|
|
function Mario.CheckKickOrPunchWall(m: Mario)
|
|
if m.Flags:Has(MarioFlags.PUNCHING, MarioFlags.KICKING, MarioFlags.TRIPPING) then
|
|
-- stylua: ignore
|
|
local range = Vector3.new(
|
|
Util.Sins(m.FaceAngle.Y),
|
|
0,
|
|
Util.Coss(m.FaceAngle.Y)
|
|
)
|
|
|
|
local detector = m.Position + (range * 50)
|
|
local _disp, wall = Util.FindWallCollisions(detector, 80, 5)
|
|
|
|
if wall then
|
|
if m.Action() ~= Action.MOVE_PUNCHING or m.ForwardVel >= 0 then
|
|
if m.Action() == Action.PUNCHING then
|
|
m.Action:Set(Action.MOVE_PUNCHING)
|
|
end
|
|
|
|
m:SetForwardVel(-48)
|
|
m:PlaySound(Sounds.ACTION_HIT)
|
|
m.ParticleFlags:Add(ParticleFlags.TRIANGLE)
|
|
elseif m.Action:Has(ActionFlags.AIR) then
|
|
m:SetForwardVel(-16)
|
|
m:PlaySound(Sounds.ACTION_HIT)
|
|
m.ParticleFlags:Add(ParticleFlags.TRIANGLE)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function Mario.ProcessInteractions(m: Mario)
|
|
if m.InvincTimer > 0 then
|
|
m.InvincTimer -= 1
|
|
end
|
|
|
|
m:CheckKickOrPunchWall()
|
|
m.Flags:Remove(MarioFlags.PUNCHING, MarioFlags.KICKING, MarioFlags.TRIPPING)
|
|
end
|
|
|
|
function Mario.HandleSpecialFloors(m: Mario)
|
|
local floor = m.Floor
|
|
|
|
if floor and not m.Action:Has(ActionFlags.AIR, ActionFlags.SWIMMING) then
|
|
if floor.Material == Enum.Material.CrackedLava then
|
|
if not m.Flags:Has(MarioFlags.METAL_CAP) then
|
|
m.HurtCounter += m.Flags:Has(MarioFlags.CAP_ON_HEAD) and 12 or 18
|
|
end
|
|
|
|
m:SetAction(Action.LAVA_BOOST)
|
|
end
|
|
end
|
|
end
|
|
|
|
function Mario.SetWaterPlungeAction(m: Mario)
|
|
m.ForwardVel /= 4
|
|
m.Velocity *= Vector3.new(1, 0.5, 1)
|
|
|
|
-- This behavior sucks, feel free to enable if you want.
|
|
-- m.Position = Util.SetY(m.Position, m.WaterLevel - 100)
|
|
|
|
m.FaceAngle *= Vector3int16.new(1, 1, 0)
|
|
m.AngleVel *= 0
|
|
|
|
if not m.Action:Has(ActionFlags.DIVING) then
|
|
m.FaceAngle *= Vector3int16.new(0, 1, 1)
|
|
end
|
|
|
|
return m:SetAction(Action.WATER_PLUNGE)
|
|
end
|
|
|
|
function Mario.PlayFarFallSound(m: Mario)
|
|
if m.Flags:Has(MarioFlags.FALLING_FAR) then
|
|
return
|
|
end
|
|
|
|
local action = m.Action
|
|
|
|
if action() == Action.TWIRLING then
|
|
return
|
|
end
|
|
|
|
if action() == Action.FLYING then
|
|
return
|
|
end
|
|
|
|
if action:Has(ActionFlags.INVULNERABLE) then
|
|
return
|
|
end
|
|
|
|
if m.PeakHeight - m.Position.Y > 1150 then
|
|
m:PlaySound(Sounds.MARIO_WAAAOOOW)
|
|
m.Flags:Add(MarioFlags.FALLING_FAR)
|
|
end
|
|
end
|
|
|
|
function Mario.ExecuteAction(m: Mario): number
|
|
if m.Action() == 0 then
|
|
return 0
|
|
end
|
|
|
|
m.AnimFrame += 1
|
|
m.AnimFrame %= (m.AnimFrameCount + 1)
|
|
|
|
if m.AnimAccel > 0 then
|
|
m.AnimAccelAssist += m.AnimAccel
|
|
m.AnimAccelAssist %= bit32.lshift(m.AnimFrameCount + 1, 0x10)
|
|
end
|
|
|
|
if m.SquishTimer > 0 then
|
|
m.SquishTimer -= 1
|
|
end
|
|
|
|
m.GfxAngle *= 0
|
|
m.AnimDirty = true
|
|
m.ThrowMatrix = nil
|
|
m.AnimSkipInterp = math.max(0, m.AnimSkipInterp - 1)
|
|
|
|
m:ResetBodyState()
|
|
m:UpdateInputs()
|
|
|
|
m:HandleSpecialFloors()
|
|
m:ProcessInteractions()
|
|
|
|
if m.Floor == nil then
|
|
return 0
|
|
end
|
|
|
|
while m.Action() > 0 do
|
|
local id = m.Action()
|
|
local action = actions[id]
|
|
|
|
if action then
|
|
local group = bit32.band(id, ActionGroups.GROUP_MASK)
|
|
local cancel
|
|
|
|
if group ~= ActionGroups.SUBMERGED and m.Position.Y < m.WaterLevel - 100 then
|
|
cancel = m:SetWaterPlungeAction()
|
|
else
|
|
if group == ActionGroups.AIRBORNE then
|
|
m:PlayFarFallSound()
|
|
elseif group == ActionGroups.SUBMERGED then
|
|
if m.Position.Y > m.WaterLevel - 80 then
|
|
if m.WaterLevel - 80 > m.FloorHeight then
|
|
m.Position = Util.SetY(m.Position, m.WaterLevel - 80)
|
|
else
|
|
m.AngleVel *= 0
|
|
cancel = m:SetAction(Action.WALKING)
|
|
end
|
|
end
|
|
|
|
m.QuicksandDepth = 0
|
|
m.BodyState.HeadAngle *= Vector3int16.new(1, 0, 0)
|
|
end
|
|
|
|
if cancel == nil then
|
|
cancel = action(m)
|
|
end
|
|
end
|
|
|
|
if not cancel then
|
|
if m.Input:Has(InputFlags.IN_WATER) then
|
|
if group == ActionGroups.MOVING then
|
|
m.ParticleFlags:Add(ParticleFlags.WAVE_TRAIL)
|
|
m.ParticleFlags:Remove(ParticleFlags.DUST)
|
|
elseif group == ActionGroups.STATIONARY then
|
|
m.ParticleFlags:Add(ParticleFlags.IDLE_WATER_WAVE)
|
|
end
|
|
end
|
|
|
|
break
|
|
end
|
|
else
|
|
local name = Enums.GetName(Action, id)
|
|
|
|
if name then
|
|
warn("Unhandled Action:", name)
|
|
else
|
|
warn("UNKNOWN ACTION:", id)
|
|
end
|
|
|
|
m.Action:Set(Action.IDLE)
|
|
break
|
|
end
|
|
end
|
|
|
|
-- m:SinkInQuicksand()
|
|
-- m:SquishModel()
|
|
-- m:UpdateHealth()
|
|
m:UpdateModel()
|
|
|
|
return m.ParticleFlags()
|
|
end
|
|
|
|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- INITIALIZATION
|
|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
function Mario.new(): Mario
|
|
local bodyState: BodyState = {
|
|
Action = 0,
|
|
CapState = Flags.new(),
|
|
EyeState = 0,
|
|
HandState = Flags.new(),
|
|
WingFlutter = false,
|
|
ModelState = Flags.new(),
|
|
GrabPos = 0,
|
|
PunchType = 0,
|
|
PunchTimer = 0,
|
|
HeadAngle = Vector3int16.new(),
|
|
TorsoAngle = Vector3int16.new(),
|
|
HeldObjLastPos = Vector3.zero,
|
|
}
|
|
|
|
local controller: Controller = {
|
|
RawStickX = 0,
|
|
RawStickY = 0,
|
|
|
|
StickX = 0,
|
|
StickY = 0,
|
|
StickMag = 0,
|
|
|
|
ButtonDown = Flags.new(),
|
|
ButtonPressed = Flags.new(),
|
|
}
|
|
|
|
local state: MarioState = {
|
|
Input = Flags.new(),
|
|
|
|
Flags = Flags.new(MarioFlags.NORMAL_CAP, MarioFlags.CAP_ON_HEAD),
|
|
Action = Flags.new(Action.SPAWN_SPIN_AIRBORNE),
|
|
|
|
PrevAction = Flags.new(),
|
|
ParticleFlags = Flags.new(),
|
|
HitboxHeight = 0,
|
|
TerrainType = 0,
|
|
|
|
ActionState = 0,
|
|
ActionTimer = 0,
|
|
ActionArg = 0,
|
|
|
|
IntendedMag = 0,
|
|
IntendedYaw = 0,
|
|
InvincTimer = 0,
|
|
|
|
FramesSinceA = 255,
|
|
FramesSinceB = 255,
|
|
|
|
WallKickTimer = 0,
|
|
DoubleJumpTimer = 0,
|
|
|
|
FaceAngle = Vector3int16.new(),
|
|
AngleVel = Vector3int16.new(),
|
|
ThrowMatrix = CFrame.identity,
|
|
|
|
GfxAngle = Vector3int16.new(),
|
|
GfxPos = Vector3.zero,
|
|
|
|
SlideYaw = 0,
|
|
TwirlYaw = 0,
|
|
|
|
Position = Vector3.yAxis * 500,
|
|
Velocity = Vector3.zero,
|
|
|
|
ForwardVel = 0,
|
|
SlideVelX = 0,
|
|
SlideVelZ = 0,
|
|
|
|
CeilHeight = 0,
|
|
FloorHeight = 0,
|
|
FloorAngle = 0,
|
|
WaterLevel = 0,
|
|
|
|
Health = 0x880,
|
|
HurtCounter = 0,
|
|
HealCounter = 0,
|
|
SquishTimer = 0,
|
|
|
|
CapTimer = 0,
|
|
BurnTimer = 0,
|
|
PeakHeight = 0,
|
|
SteepJumpYaw = 0,
|
|
WalkingPitch = 0,
|
|
QuicksandDepth = 0,
|
|
LongJumpIsSlow = false,
|
|
|
|
BodyState = bodyState,
|
|
Controller = controller,
|
|
|
|
AnimAccel = 0,
|
|
AnimFrame = -1,
|
|
AnimSetFrame = -1,
|
|
AnimDirty = false,
|
|
AnimReset = false,
|
|
AnimFrameCount = 0,
|
|
AnimAccelAssist = 0,
|
|
AnimSkipInterp = 0,
|
|
}
|
|
|
|
return setmetatable(state, Mario)
|
|
end
|
|
|
|
return Mario
|