sm64-roblox-liberty-prime/client/Mario/init.lua
2023-07-17 19:19:03 -05:00

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