sm64coopdx/mods/char-select-extra-chars-plus/moveset-pauline.lua
Yuyake 1cb8cb8e5e
Some checks failed
Build coop / build-linux (push) Has been cancelled
Build coop / build-steamos (push) Has been cancelled
Build coop / build-windows-opengl (push) Has been cancelled
Build coop / build-windows-directx (push) Has been cancelled
Build coop / build-macos-arm (push) Has been cancelled
Build coop / build-macos-intel (push) Has been cancelled
Extra Characters PLUS v1.0 (#1076)
* Replaces Extra Characters

* Folder inside another folder fix
2026-01-11 02:20:45 +01:00

1756 lines
68 KiB
Lua

---------------------
-- Pauline Moveset --
---------------------
local OmmEnabled = OmmEnabled
if not charSelect then return end
---------------
-- Constants --
---------------
-- Cappy constants
local CAPPY_LIFETIME = 120
local CAPPY_RETURN_VEL = 120
local CAPPY_SLOWDOWN_FACTOR = 1.15
local CAPPY_HITBOX_RADIUS = 88
local CAPPY_HITBOX_HEIGHT = 128
local CAPPY_HITBOX_OFFSET = 48
local CAPPY_WALL_RADIUS = 56
local CAPPY_GFX_SCALE_X = 1.1
local CAPPY_GFX_SCALE_Y = 1.0
local CAPPY_GFX_SCALE_Z = 1.1
local CAPPY_GFX_ANGLE_VEL = 0x2000
local CAPPY_HOMING_VELOCITY = 90
local CAPPY_HOMING_VELOCITY_COIN = 105
local CAPPY_HOMING_VELOCITY_SPIN = 120
local CAPPY_HOMING_DURATION = 5
local CAPPY_NUM_STEPS = 4
-- Default throw
local CAPPY_BHV_DEFAULT_GROUND = 0
local CAPPY_BHV_DEFAULT_AIR = 1
local CAPPY_BHV_DEFAULT_OFFSET = 50
local CAPPY_BHV_DEFAULT_VEL = 128
local CAPPY_BHV_DEFAULT_CALL_BACK_START = 10
-- Upwards throw
local CAPPY_BHV_UPWARDS_GROUND = 2
local CAPPY_BHV_UPWARDS_AIR = 3
local CAPPY_BHV_UPWARDS_OFFSET = 40
local CAPPY_BHV_UPWARDS_VEL = 80
local CAPPY_BHV_UPWARDS_CALL_BACK_START = 10
-- Downwards throw
local CAPPY_BHV_DOWNWARDS_GROUND = 4
local CAPPY_BHV_DOWNWARDS_AIR = 5
local CAPPY_BHV_DOWNWARDS_OFFSET = 40
local CAPPY_BHV_DOWNWARDS_VEL = 100
local CAPPY_BHV_DOWNWARDS_CALL_BACK_START = 10
-- Spin throw
local CAPPY_BHV_SPIN_GROUND = 6
local CAPPY_BHV_SPIN_AIR = 7
local CAPPY_BHV_SPIN_OFFSET = 60
local CAPPY_BHV_SPIN_RADIUS_MAX = 240
local CAPPY_BHV_SPIN_RADIUS_GROWTH = 16
local CAPPY_BHV_SPIN_ANGLE_VEL = 0x1000
local CAPPY_BHV_SPIN_CALL_BACK_START = 16
-- Flying throw
local CAPPY_BHV_FLYING = 8
local CAPPY_BHV_FLYING_OFFSET = 30
local CAPPY_BHV_FLYING_RADIUS_MAX = 180
local CAPPY_BHV_FLYING_RADIUS_GROWTH = 12
local CAPPY_BHV_FLYING_ANGLE_VEL = 0x1000
local CAPPY_BHV_FLYING_CALL_BACK_START = 16
-- Cappy collision handlers
local CAPPY_COL_WALL_DEFAULT = 1
local CAPPY_COL_WALL_FULL_STOP = 2
local CAPPY_COL_FLOOR_CHANGE_BEHAVIOR = 1
-- Events
local CAPPY_EVENT_SPAWN = 1
local CAPPY_EVENT_INIT = 2
local CAPPY_EVENT_HOMING = 3
local CAPPY_EVENT_RETURN = 4
local CAPPY_EVENT_UNLOAD = 5
local CAPPY_EVENT_BOUNCE = 6
-- Actions
_G.ACT_CAPPY_THROW_GROUND = allocate_mario_action(ACT_GROUP_MOVING | ACT_FLAG_MOVING)
_G.ACT_CAPPY_THROW_AIRBORNE = allocate_mario_action(ACT_GROUP_AIRBORNE | ACT_FLAG_AIR)
_G.ACT_CAPPY_BOUNCE = allocate_mario_action(ACT_GROUP_AIRBORNE | ACT_FLAG_AIR | ACT_FLAG_ALLOW_VERTICAL_WIND_ACTION)
_G.ACT_CAPPY_VAULT = allocate_mario_action(ACT_GROUP_AIRBORNE | ACT_FLAG_AIR | ACT_FLAG_ALLOW_VERTICAL_WIND_ACTION)
_G.ACT_CAPPY_RAINBOW_SPIN = allocate_mario_action(ACT_GROUP_AIRBORNE | ACT_FLAG_AIR | ACT_FLAG_ALLOW_VERTICAL_WIND_ACTION | ACT_FLAG_ATTACKING)
_G.ACT_CAPPY_THROW_WATER = allocate_mario_action(ACT_GROUP_SUBMERGED | ACT_FLAG_SWIMMING)
-- Animations
local MARIO_ANIM_PAULINE_CAPPY_VAULT = "anim_pauline_cappy_vault"
local MARIO_ANIM_PAULINE_CAPPY_UP_THROW = "anim_pauline_cappy_up_throw"
local MARIO_ANIM_PAULINE_CAPPY_DOWN_THROW = "anim_pauline_cappy_down_throw"
local MARIO_ANIM_PAULINE_CAPPY_SPIN_THROW = "anim_pauline_cappy_spin_throw"
local MARIO_ANIM_PAULINE_CAPPY_THROW = "anim_pauline_cappy_throw"
local MARIO_ANIM_PAULINE_CAPPY_RAINBOW_SPIN = "anim_pauline_cappy_rainbow_spin"
-----------
-- Utils --
-----------
local function if_then_else(cond, if_true, if_false)
if cond then return if_true end
return if_false
end
local function clamp(x, a, b)
if x < a then return a end
if x > b then return b end
return x
end
local function s16(x)
x = (math.floor(x) & 0xFFFF)
if x >= 32768 then return x - 65536 end
return x
end
local function mario_anim_clamp(animInfo, a, b)
local frame = animInfo.animFrame
if frame < a then
animInfo.animFrame = a
animInfo.animFrameAccelAssist = (a << 16)
end
if frame > b then
animInfo.animFrame = b
animInfo.animFrameAccelAssist = (b << 16)
end
end
local function mario_anim_reset(animInfo)
animInfo.animID = 0xFF
end
local function mario_anim_play_custom(m, animName, animAccel)
if smlua_anim_util_get_current_animation_name(m.marioObj) ~= animName or m.marioObj.header.gfx.animInfo.animID ~= -1 then
mario_anim_clamp(m.marioObj.header.gfx.animInfo, 0, 0)
end
m.marioObj.header.gfx.animInfo.animID = -1
smlua_anim_util_set_animation(m.marioObj, animName)
m.marioObj.header.gfx.animInfo.animAccel = animAccel or 0x10000
end
local function get_pauline()
for i = 1, #extraCharacters do
local extraCharacter = extraCharacters[i]
if extraCharacter.tablePos == CT_PAULINE then
return extraCharacter
end
end
return nil
end
local function is_pauline(m)
return character_get_current_number(m.playerIndex) == CT_PAULINE
end
-----------
-- Cappy --
-----------
-- oAction -> index (Mario's global index + 1)
-- oSubAction -> spawned
-- oTimer -> timer
-- oUnkBC -> timestamp
-- oPosX, oPosY, oPosZ -> position
-- oVelX, oVelY, oVelZ -> velocity
-- oMoveAngleYaw -> direction
-- oBehParams2ndByte -> behavior
-- oCapUnkF4 -> bounced
-- oCapUnkF8 -> flags: 0 = none, 1 = interact with local Mario, 2 = homing attack
-- oDragStrength -> cappy throw strength
local id_bhvCappy = hook_behavior(
nil,
OBJ_LIST_SPAWNER,
true,
function (o) o.oFlags = o.oFlags | OBJ_FLAG_UPDATE_GFX_POS_AND_ANGLE end,
function (o) bhv_cappy_loop(o) end,
"bhvPaulineCappy"
)
local function cappy_get_object(m)
local index = network_global_index_from_local(m.playerIndex) + 1
local cappy = obj_get_first_with_behavior_id_and_field_s32(id_bhvCappy, 0x31, index) -- oAction
if cappy == nil then
cappy = spawn_non_sync_object(id_bhvCappy, E_MODEL_NONE, m.pos.x, m.pos.y, m.pos.z, nil)
if cappy ~= nil then
cappy.oAction = index
cappy.globalPlayerIndex = index - 1
end
end
return cappy
end
------------
-- Events --
------------
-- A Cappy event is a "sync things now" event triggered by Mario or Cappy actions.
-- When received, the local game checks the whole value of the last event and compares it
-- with the received one. If they don't match, that means the received one is a new event.
--
-- An event fills specific Mario's object fields:
-- oUnk94 -> Event type
-- oUnkBC -> Target Id
-- oUnkC0 -> Timestamp
-- oUnk1A8 -> Behavior / Cappy throw strength
-- oHomeX -> Pos X
-- oHomeY -> Pos Y
-- oHomeZ -> Pos Z
-- oParentRelativePosX -> Vel X
-- oParentRelativePosY -> Vel Y
-- oParentRelativePosZ -> Vel Z
local gCappyEvents = {}
for i = 0, (MAX_PLAYERS - 1) do
gCappyEvents[i] = {
[CAPPY_EVENT_SPAWN] = 0,
[CAPPY_EVENT_INIT] = 0,
[CAPPY_EVENT_HOMING] = 0,
[CAPPY_EVENT_RETURN] = 0,
[CAPPY_EVENT_UNLOAD] = 0,
[CAPPY_EVENT_BOUNCE] = 0,
}
local marioObj = gMarioStates[i].marioObj
if marioObj ~= nil then marioObj.oUnk94 = 0 end
end
-- Spawn event: triggered when a player press (X) to throw Cappy
local function cappy_send_event_spawn(m, cappy)
local marioObj = m.marioObj
if m.playerIndex ~= 0 or marioObj == nil then return end
marioObj.oUnk94 = CAPPY_EVENT_SPAWN
marioObj.oUnkBC = cappy.oAction
marioObj.oUnkC0 = marioObj.oTimer
marioObj.oUnk1A8 = cappy.oBehParams2ndByte
gCappyEvents[m.playerIndex][CAPPY_EVENT_SPAWN] = marioObj.oTimer
end
local function cappy_process_event_spawn(m, cappy)
local marioObj = m.marioObj
if marioObj == nil then return end
if marioObj.oUnk94 ~= CAPPY_EVENT_SPAWN then return end
if marioObj.oUnkBC ~= cappy.oAction then return end
if marioObj.oUnkC0 == 0 then return end
if marioObj.oUnkC0 == gCappyEvents[m.playerIndex][CAPPY_EVENT_SPAWN] then return end
cappy.oBehParams2ndByte = marioObj.oUnk1A8
gCappyEvents[m.playerIndex][CAPPY_EVENT_SPAWN] = marioObj.oUnkC0
cappy.oSubAction = 1
cappy.oTimer = if_then_else(cappy.oBehParams2ndByte < CAPPY_BHV_SPIN_GROUND, -4, 0)
cappy.oCapUnkF8 = 0
end
-- Init event: triggered when Cappy timer reaches -1 (one frame before cappy_init_behavior)
local function cappy_send_event_init(m, cappy)
local marioObj = m.marioObj
if m.playerIndex ~= 0 or marioObj == nil then return end
marioObj.oUnk94 = CAPPY_EVENT_INIT
marioObj.oUnkBC = cappy.oAction
marioObj.oUnkC0 = marioObj.oTimer
marioObj.oUnk1A8 = cappy.oDragStrength
gCappyEvents[m.playerIndex][CAPPY_EVENT_INIT] = marioObj.oTimer
end
local function cappy_process_event_init(m, cappy)
local marioObj = m.marioObj
if marioObj == nil then return end
if marioObj.oUnk94 ~= CAPPY_EVENT_INIT then return end
if marioObj.oUnkBC ~= cappy.oAction then return end
if marioObj.oUnkC0 == 0 then return end
if marioObj.oUnkC0 == gCappyEvents[m.playerIndex][CAPPY_EVENT_INIT] then return end
cappy.oDragStrength = marioObj.oUnk1A8
cappy.oTimer = 0
gCappyEvents[m.playerIndex][CAPPY_EVENT_INIT] = marioObj.oUnkC0
end
-- Homing event: triggered when a player press a D-pad button to perform a homing attack
local function cappy_send_event_homing(m, cappy)
local marioObj = m.marioObj
if m.playerIndex ~= 0 or marioObj == nil then return end
marioObj.oUnk94 = CAPPY_EVENT_HOMING
marioObj.oUnkBC = cappy.oAction
marioObj.oUnkC0 = marioObj.oTimer
marioObj.oHomeX = cappy.oPosX
marioObj.oHomeY = cappy.oPosY
marioObj.oHomeZ = cappy.oPosZ
marioObj.oParentRelativePosX = cappy.oVelX
marioObj.oParentRelativePosY = cappy.oVelY
marioObj.oParentRelativePosZ = cappy.oVelZ
gCappyEvents[m.playerIndex][CAPPY_EVENT_HOMING] = marioObj.oTimer
end
local function cappy_process_event_homing(m, cappy)
local marioObj = m.marioObj
if marioObj == nil then return end
if marioObj.oUnk94 ~= CAPPY_EVENT_HOMING then return end
if marioObj.oUnkBC ~= cappy.oAction then return end
if marioObj.oUnkC0 == 0 then return end
if marioObj.oUnkC0 == gCappyEvents[m.playerIndex][CAPPY_EVENT_HOMING] then return end
cappy.oPosX = marioObj.oHomeX
cappy.oPosY = marioObj.oHomeY
cappy.oPosZ = marioObj.oHomeZ
cappy.oVelX = marioObj.oParentRelativePosX
cappy.oVelY = marioObj.oParentRelativePosY
cappy.oVelZ = marioObj.oParentRelativePosZ
gCappyEvents[m.playerIndex][CAPPY_EVENT_HOMING] = marioObj.oUnkC0
cappy.oTimer = math.max(20, CAPPY_LIFETIME - CAPPY_HOMING_DURATION - 1)
cappy.oCapUnkF8 = 2
end
-- Return event: triggered when a player's Cappy enters the state 'Return to Mario'
local function cappy_send_event_return(m, cappy)
local marioObj = m.marioObj
if m.playerIndex ~= 0 or marioObj == nil then return end
marioObj.oUnk94 = CAPPY_EVENT_RETURN
marioObj.oUnkBC = cappy.oAction
marioObj.oUnkC0 = marioObj.oTimer
marioObj.oHomeX = cappy.oPosX
marioObj.oHomeY = cappy.oPosY
marioObj.oHomeZ = cappy.oPosZ
end
local function cappy_process_event_return(m, cappy)
local marioObj = m.marioObj
if marioObj == nil then return end
if marioObj.oUnk94 ~= CAPPY_EVENT_RETURN then return end
if marioObj.oUnkBC ~= cappy.oAction then return end
if marioObj.oUnkC0 == 0 then return end
if marioObj.oUnkC0 == gCappyEvents[m.playerIndex][CAPPY_EVENT_RETURN] then return end
cappy.oPosX = marioObj.oHomeX
cappy.oPosY = marioObj.oHomeY
cappy.oPosZ = marioObj.oHomeZ
gCappyEvents[m.playerIndex][CAPPY_EVENT_RETURN] = marioObj.oUnkC0
end
-- Unload event: triggered when a player's Cappy must be unloaded
local function cappy_send_event_unload(m, cappy)
local marioObj = m.marioObj
if m.playerIndex ~= 0 or marioObj == nil then return end
marioObj.oUnk94 = CAPPY_EVENT_UNLOAD
marioObj.oUnkBC = cappy.oAction
marioObj.oUnkC0 = marioObj.oTimer
gCappyEvents[m.playerIndex][CAPPY_EVENT_UNLOAD] = marioObj.oTimer
end
local function cappy_process_event_unload(m, cappy)
local marioObj = m.marioObj
if marioObj == nil then return end
if marioObj.oUnk94 ~= CAPPY_EVENT_UNLOAD then return end
if marioObj.oUnkBC ~= cappy.oAction then return end
if marioObj.oUnkC0 == 0 then return end
if marioObj.oUnkC0 == gCappyEvents[m.playerIndex][CAPPY_EVENT_UNLOAD] then return end
gCappyEvents[m.playerIndex][CAPPY_EVENT_UNLOAD] = marioObj.oUnkC0
cappy.oSubAction = 0
end
-- Bounce event: triggered when a player bounces on a Cappy
local function cappy_send_event_bounce(m, cappy)
local marioObj = m.marioObj
if m.playerIndex ~= 0 or marioObj == nil then return end
marioObj.oUnk94 = CAPPY_EVENT_BOUNCE
marioObj.oUnkBC = cappy.oAction
marioObj.oUnkC0 = marioObj.oTimer
gCappyEvents[m.playerIndex][CAPPY_EVENT_BOUNCE] = marioObj.oTimer
end
local function cappy_process_event_bounce(m, cappy)
local marioObj = m.marioObj
if marioObj == nil then return end
if marioObj.oUnk94 ~= CAPPY_EVENT_BOUNCE then return end
if marioObj.oUnkBC ~= cappy.oAction then return end
if marioObj.oUnkC0 == 0 then return end
if marioObj.oUnkC0 == gCappyEvents[m.playerIndex][CAPPY_EVENT_BOUNCE] then return end
gCappyEvents[m.playerIndex][CAPPY_EVENT_BOUNCE] = marioObj.oUnkC0
cappy.oSubAction = 0
end
-- Process events
local function cappy_process_events(m)
local cappy = obj_get_first_with_behavior_id(id_bhvCappy)
while cappy ~= nil do
cappy_process_event_spawn (m, cappy)
cappy_process_event_init (m, cappy)
cappy_process_event_homing(m, cappy)
cappy_process_event_return(m, cappy)
cappy_process_event_unload(m, cappy)
cappy_process_event_bounce(m, cappy)
cappy = obj_get_next_with_same_behavior_id(cappy)
end
end
----------
-- Step --
----------
local function cappy_return_to_mario(m, cappy)
if cappy.oTimer <= CAPPY_LIFETIME then
cappy.oTimer = CAPPY_LIFETIME + 1
cappy_send_event_return(m, cappy)
end
end
local function cappy_bounce_back(m, cappy)
if cappy.oBehParams2ndByte < CAPPY_BHV_SPIN_GROUND then
spawn_non_sync_object(id_bhvHorStarParticleSpawner, E_MODEL_NONE, cappy.oPosX, cappy.oPosY, cappy.oPosZ, nil)
play_sound(SOUND_OBJ_DEFAULT_DEATH, cappy.header.gfx.cameraToObject)
cappy_return_to_mario(m, cappy)
end
end
local function cappy_perform_step(m, cappy, vx, vy, vz, wallColHandler, floorColHandler)
for _ = 1, CAPPY_NUM_STEPS do
local x = cappy.oPosX
local y = cappy.oPosY
local z = cappy.oPosZ
obj_move_xyz(cappy,
vx / CAPPY_NUM_STEPS,
vy / CAPPY_NUM_STEPS,
vz / CAPPY_NUM_STEPS
)
-- Walls
if (m.flags & MARIO_VANISH_CAP) == 0 then
local pos = { x = cappy.oPosX, y = cappy.oPosY, z = cappy.oPosZ }
local wall = resolve_and_return_wall_collisions(pos, CAPPY_HITBOX_HEIGHT / 2, CAPPY_WALL_RADIUS)
if wall ~= nil then
obj_set_pos(cappy, pos.x, pos.y, pos.z)
if wallColHandler == CAPPY_COL_WALL_DEFAULT then
local wallDYaw = s16(atan2s(wall.normal.z, wall.normal.x) - atan2s(cappy.oVelZ, cappy.oVelX))
if (wallDYaw < 0x2000 or wallDYaw > 0x6000) and (wallDYaw > -0x2000 or wallDYaw < -0x6000) then
obj_set_vel(cappy, 0, 0, 0)
return
end
elseif wallColHandler == CAPPY_COL_WALL_FULL_STOP then
return
end
end
end
-- Floor
local floorY = find_floor_height(cappy.oPosX, cappy.oPosY, cappy.oPosZ)
if floorY > gLevelValues.floorLowerLimit then
local diffY = cappy.oPosY - floorY
if diffY < 0 then
cappy.oPosY = floorY
if floorColHandler == CAPPY_COL_FLOOR_CHANGE_BEHAVIOR then
if cappy.oVelY ~= 0 then
vx = (CAPPY_BHV_DEFAULT_VEL / CAPPY_BHV_DOWNWARDS_VEL) * sins(cappy.oMoveAngleYaw) * math.abs(cappy.oVelY)
vz = (CAPPY_BHV_DEFAULT_VEL / CAPPY_BHV_DOWNWARDS_VEL) * coss(cappy.oMoveAngleYaw) * math.abs(cappy.oVelY)
vy = 0
obj_set_vel(cappy, vx, vy, vz)
end
end
end
else
obj_set_pos(cappy, x, y, z)
end
-- Ceiling
if (m.flags & MARIO_VANISH_CAP) == 0 then
local ceilY = find_ceil_height(cappy.oPosX, cappy.oPosY, cappy.oPosZ)
if ceilY < gLevelValues.cellHeightLimit then
local height = CAPPY_HITBOX_HEIGHT - CAPPY_HITBOX_OFFSET
if ceilY - height < cappy.oPosY and cappy.oPosY < ceilY then
cappy.oPosY = math.max(ceilY - height, floorY)
end
end
end
end
end
local function cappy_slowdown(cappy)
obj_set_vel(cappy,
cappy.oVelX / CAPPY_SLOWDOWN_FACTOR,
cappy.oVelY / CAPPY_SLOWDOWN_FACTOR,
cappy.oVelZ / CAPPY_SLOWDOWN_FACTOR
)
end
local function cappy_call_back(m, cappy, cbStart)
if cappy.oTimer >= cbStart then
local udlrx = m.controller.buttonPressed & (U_JPAD | D_JPAD | L_JPAD | R_JPAD | X_BUTTON)
if udlrx ~= 0 then
-- Homing attack
if (udlrx & X_BUTTON) == 0 then
cappy.oCapUnkF8 = 2
cappy.oTimer = math.max(20, CAPPY_LIFETIME - CAPPY_HOMING_DURATION - 1)
local cappyBhv = cappy.oBehParams2ndByte
local duration = CAPPY_LIFETIME - (cappy.oTimer + 1)
local ox, oy, oz, velocity
if cappyBhv == CAPPY_BHV_SPIN_GROUND or cappyBhv == CAPPY_BHV_SPIN_AIR or cappyBhv == CAPPY_BHV_FLYING then
ox, oy, oz, velocity = m.pos.x, m.pos.y, m.pos.z, CAPPY_HOMING_VELOCITY_SPIN
else
ox, oy, oz, velocity = cappy.oPosX, cappy.oPosY, cappy.oPosZ, CAPPY_HOMING_VELOCITY
end
target = cappy_find_target(m, ox, oy, oz, velocity * duration)
if target ~= nil then
local dx = target.oPosX - cappy.oPosX
local dy = target.oPosY - cappy.oPosY
local dz = target.oPosZ - cappy.oPosZ
local dv = math.sqrt(dx^2 + dy^2 + dz^2)
if dv > velocity * duration then
obj_set_vel(cappy, dx / duration, dy / duration, dz / duration)
cappy_send_event_homing(m, cappy)
elseif dv ~= 0 then
obj_set_vel(cappy, velocity * dx / dv, velocity * dy / dv, velocity * dz / dv)
cappy_send_event_homing(m, cappy)
else
cappy_return_to_mario(m, cappy)
end
else
if (cappyBhv == CAPPY_BHV_DEFAULT_GROUND or cappyBhv == CAPPY_BHV_DEFAULT_AIR or
cappyBhv == CAPPY_BHV_UPWARDS_GROUND or cappyBhv == CAPPY_BHV_UPWARDS_AIR or
cappyBhv == CAPPY_BHV_DOWNWARDS_GROUND or cappyBhv == CAPPY_BHV_DOWNWARDS_AIR) then
if udlrx == U_JPAD then
obj_set_vel(cappy, 0, velocity, 0)
elseif udlrx == D_JPAD then
obj_set_vel(cappy, 0, -velocity, 0)
elseif udlrx == L_JPAD then
obj_set_vel(cappy, velocity * sins(cappy.oMoveAngleYaw + 0x4000), 0, velocity * coss(cappy.oMoveAngleYaw + 0x4000))
elseif udlrx == R_JPAD then
obj_set_vel(cappy, velocity * sins(cappy.oMoveAngleYaw - 0x4000), 0, velocity * coss(cappy.oMoveAngleYaw - 0x4000))
else
cappy_return_to_mario(m, cappy)
return
end
cappy_send_event_homing(m, cappy)
else
cappy_return_to_mario(m, cappy)
end
end
else
cappy_return_to_mario(m, cappy)
end
end
end
end
local function cappy_perform_step_return_to_mario(m, cappy)
local marioObj = m.marioObj
local marioGfx = marioObj.header.gfx
-- Disable interactions
cappy.oCapUnkF8 = 0
-- Move Cappy closer to Mario
local dx = m.pos.x - cappy.oPosX
local dy = m.pos.y - cappy.oPosY + (0.4 * marioObj.hitboxHeight * marioGfx.scale.y)
local dz = m.pos.z - cappy.oPosZ
local dv = math.sqrt(dx^2 + dy^2 + dz^2)
if dv > CAPPY_RETURN_VEL then
obj_move_xyz(cappy,
(dx / dv) * CAPPY_RETURN_VEL,
(dy / dv) * CAPPY_RETURN_VEL,
(dz / dv) * CAPPY_RETURN_VEL
)
else
obj_move_xyz(cappy, dx, dy, dz)
end
-- Unloads Cappy if he's close enough to Mario
local marioRadius = marioGfx.scale.x * 50
local cappyRadius = CAPPY_GFX_SCALE_X * CAPPY_HITBOX_RADIUS / 2
return dv - CAPPY_RETURN_VEL <= marioRadius + cappyRadius
end
-------------------------
-- Object interactions --
-------------------------
local CAPPY_INTERACTION_LISTS = {
OBJ_LIST_DEFAULT,
OBJ_LIST_LEVEL,
OBJ_LIST_PLAYER,
OBJ_LIST_SURFACE,
OBJ_LIST_PUSHABLE,
OBJ_LIST_GENACTOR,
OBJ_LIST_DESTRUCTIVE,
}
local m0 = gMarioStates[0]
local obj_is_valid_for_interaction = obj_is_valid_for_interaction
local obj_is_coin = obj_is_coin
local obj_is_mushroom_1up = obj_is_mushroom_1up
local obj_is_exclamation_box = obj_is_exclamation_box
local obj_is_bully = obj_is_bully
local obj_is_grabbable = obj_is_grabbable
local obj_is_attackable = obj_is_attackable
local function obj_is_remote_mario(obj)
if obj == gMarioStates[0].marioObj then return false end
if obj_has_behavior_id(obj, id_bhvMario) == 0 then return false end
if (obj.header.gfx.node.flags & GRAPH_RENDER_ACTIVE) == 0 then return false end
if (obj.header.gfx.node.flags & GRAPH_RENDER_INVISIBLE) ~= 0 then return false end
if gMarioStates[obj.oBehParams - 1].action == ACT_BUBBLED then return true end
if obj.oIntangibleTimer ~= 0 then return false end
if obj.oInteractStatus ~= 0 then return false end
return true
end
local function obj_is_treasure_chest(obj)
return obj_has_behavior_id(obj, id_bhvTreasureChestBottom) == 1 and obj.oAction == 0
end
local function obj_is_breakable(obj)
return (
obj_has_behavior_id(obj, id_bhvBreakableBox) == 1 or
obj_has_behavior_id(obj, id_bhvBreakableBoxSmall) == 1 or
obj_has_behavior_id(obj, id_bhvHiddenObject) == 1 or
obj_has_behavior_id(obj, id_bhvJumpingBox) == 1
)
end
local function cappy_is_obj_targetable(m, obj)
if obj_is_treasure_chest(obj) then return true end
if obj_is_remote_mario(obj) then return true end
if obj_is_valid_for_interaction(obj) == false then return false end
if obj_is_coin(obj) then return true end
if obj_is_mushroom_1up(obj) then return true end
if obj_is_exclamation_box(obj) then return true end
if obj_is_bully(obj) then return true end
if obj_is_grabbable(obj) then return true end
if obj_is_breakable(obj) then return true end
if obj_is_attackable(obj) then return true end
return false
end
function cappy_find_target(m, ox, oy, oz, distmax)
local target = nil
local targetCoin = nil
local distmin = distmax
local distminCoin = distmax
for _, objList in pairs(CAPPY_INTERACTION_LISTS) do
local obj = obj_get_first(objList)
while obj ~= nil do
if cappy_is_obj_targetable(m, obj) then
local distToCappy = math.sqrt((ox - obj.oPosX)^2 + (oy - obj.oPosY)^2 + (oz - obj.oPosZ)^2) - (CAPPY_HITBOX_RADIUS + obj.hitboxRadius)
if obj_is_coin(obj) then
if distToCappy < distminCoin then
distminCoin = distToCappy
targetCoin = obj
end
else
if distToCappy < distmin then
distmin = distToCappy
target = obj
end
end
end
obj = obj_get_next(obj)
end
end
return if_then_else(targetCoin ~= nil, targetCoin, target)
end
local function cappy_mario_can_grab(m, obj)
local marioAction = m.action
if m.heldObj ~= nil or m.riddenObj ~= nil then return false end
if marioAction == ACT_FIRST_PERSON then return false end
if (marioAction & ACT_GROUP_MASK) == ACT_GROUP_CUTSCENE then return false end
if (marioAction & ACT_GROUP_MASK) == ACT_GROUP_AUTOMATIC then return false end
if (marioAction & ACT_FLAG_AIR) ~= 0 then return false end
if (marioAction & ACT_FLAG_SWIMMING) ~= 0 then return false end
if (marioAction & ACT_FLAG_METAL_WATER) ~= 0 then return false end
if (marioAction & ACT_FLAG_ON_POLE) ~= 0 then return false end
if (marioAction & ACT_FLAG_HANGING) ~= 0 then return false end
if (marioAction & ACT_FLAG_INTANGIBLE) ~= 0 then return false end
if (marioAction & ACT_FLAG_INVULNERABLE) ~= 0 then return false end
if (marioAction & ACT_FLAG_RIDING_SHELL) ~= 0 then return false end
if (obj_has_behavior_id(obj, id_bhvBowser) == 1) then return false end
if (obj_has_behavior_id(obj, id_bhvBowserTailAnchor) == 1) then return false end
return true
end
local function cappy_process_object_interaction(m, cappy, obj)
local marioObj = m.marioObj
-- Coin
--- Teleport it to Mario
--- Homing attack: target the next nearest coin or object
if obj_is_coin(obj) then
-- Target next nearest object
if cappy.oCapUnkF8 == 2 then
obj.oInteractStatus = INT_STATUS_INTERACTED
local duration = CAPPY_HOMING_DURATION
local velocity = CAPPY_HOMING_VELOCITY_COIN
local target = cappy_find_target(m, cappy.oPosX, cappy.oPosY, cappy.oPosZ, velocity * duration)
obj.oInteractStatus = 0
if target ~= nil then
local dx = target.oPosX - cappy.oPosX
local dy = target.oPosY - cappy.oPosY
local dz = target.oPosZ - cappy.oPosZ
local dv = math.sqrt(dx^2 + dy^2 + dz^2)
if dv > velocity * duration then
obj_set_vel(cappy, dx / duration, dy / duration, dz / duration)
cappy.oTimer = math.max(20, CAPPY_LIFETIME - CAPPY_HOMING_DURATION - 1)
cappy_send_event_homing(m, cappy)
elseif dv ~= 0 then
obj_set_vel(cappy, velocity * dx / dv, velocity * dy / dv, velocity * dz / dv)
cappy.oTimer = math.max(20, CAPPY_LIFETIME - CAPPY_HOMING_DURATION - 1)
cappy_send_event_homing(m, cappy)
end
end
end
-- Collect coin
if m.playerIndex == 0 then
if interact_coin then
interact_coin(m, 0, obj)
else
obj_set_pos(obj,
m.pos.x + m.vel.x,
m.pos.y + m.vel.y + 60,
m.pos.z + m.vel.z
)
end
else
obj.oIntangibleTimer = -1
obj.oInteractStatus = INT_STATUS_INTERACTED
end
obj.header.gfx.node.flags = obj.header.gfx.node.flags | GRAPH_RENDER_INVISIBLE
return true
end
-- Secret
--- Set interacted flag to collect it
if obj_is_secret(obj) then
obj.oInteractStatus = INT_STATUS_INTERACTED
return false
end
-- Exclamation box
--- Attack it to break it
if obj_is_exclamation_box(obj) then
obj.oInteractStatus = INT_STATUS_INTERACTED | INT_STATUS_WAS_ATTACKED
cappy_bounce_back(m, cappy)
return true
end
-- Bully
--- Bully the bully
if obj_is_bully(obj) then
local angle = obj_angle_to_object(obj, marioObj)
obj.oForwardVel = 3600 / obj.hitboxRadius
obj.oFaceAngleYaw = angle
obj.oMoveAngleYaw = angle + 0x8000
obj.oInteractStatus = ATTACK_KICK_OR_TRIP | INT_STATUS_INTERACTED | INT_STATUS_WAS_ATTACKED
play_sound(SOUND_OBJ_BULLY_METAL, obj.header.gfx.cameraToObject)
cappy_bounce_back(m, cappy)
return true
end
-- Grabbable
--- Make Mario instantly grab the interacted object
--- Change the current animation to the first punch, and make it end next frame
if obj_is_grabbable(obj) then
if cappy_mario_can_grab(m, obj) then
if m.playerIndex == 0 then
m.usedObj = obj
mario_grab_used_object(m)
mario_set_action(m, ACT_PICKING_UP, 0, 0)
set_mario_animation(m, MARIO_ANIM_FIRST_PUNCH)
local animInfo = marioObj.header.gfx.animInfo
mario_anim_clamp(animInfo, animInfo.curAnim.loopEnd - 2, animInfo.curAnim.loopEnd - 2)
end
cappy_return_to_mario(m, cappy)
return true
end
end
-- Breakable
--- Set some specific flags to send a signal 'break that box'
if obj_is_breakable(obj) then
obj.oInteractStatus = ATTACK_KICK_OR_TRIP | INT_STATUS_INTERACTED | INT_STATUS_WAS_ATTACKED | INT_STATUS_STOP_RIDING
cappy_bounce_back(m, cappy)
return true
end
-- Attackable
--- Attack it to damage it
--- Do a ground-pound type attack to huge goombas to make them spawn their blue coin
if obj_is_attackable(obj) then
if obj_has_behavior_id(obj, id_bhvGoomba) and (obj.oGoombaSize & 1) ~= 0 then
obj.oInteractStatus = ATTACK_GROUND_POUND_OR_TWIRL | INT_STATUS_INTERACTED | INT_STATUS_WAS_ATTACKED
elseif obj_has_behavior_id(obj, id_bhvWigglerHead) then
obj.oInteractStatus = ATTACK_GROUND_POUND_OR_TWIRL | INT_STATUS_INTERACTED | INT_STATUS_WAS_ATTACKED
else
obj.oInteractStatus = ATTACK_KICK_OR_TRIP | INT_STATUS_INTERACTED | INT_STATUS_WAS_ATTACKED
end
cappy_bounce_back(m, cappy)
return true
end
-- Mushroom 1up
--- Teleport it to Mario
if obj_is_mushroom_1up(obj) then
obj_set_pos(obj,
m.pos.x + m.vel.x,
m.pos.y + m.vel.y + 60,
m.pos.z + m.vel.z
)
obj.header.gfx.node.flags = obj.header.gfx.node.flags | GRAPH_RENDER_INVISIBLE
return false
end
-- No interaction
return false
end
local function cappy_process_object_interactions(m, cappy)
-- Basic interactions
for _, objList in pairs(CAPPY_INTERACTION_LISTS) do
local obj = obj_get_first(objList)
while obj ~= nil do
if (obj_is_valid_for_interaction(obj) and
obj_check_hitbox_overlap(cappy, obj) and
cappy_process_object_interaction(m, cappy, obj)) then
return
end
obj = obj_get_next(obj)
end
end
-- Treasure chests
local obj = obj_get_first_with_behavior_id(id_bhvTreasureChestBottom)
while obj ~= nil do
local parentObj = obj.parentObj
if obj.oAction == 0 and parentObj ~= nil then -- Not open
obj.hitboxRadius = 120
obj.hitboxHeight = 150
obj.hurtboxRadius = 120
obj.hurtboxHeight = 150
obj.hitboxDownOffset = 0
if obj_check_hitbox_overlap(cappy, obj) then
if m.playerIndex == 0 then
if parentObj.oTreasureChestCurrentAnswer == obj.oBehParams2ndByte then
play_sound(SOUND_GENERAL2_RIGHT_ANSWER, obj.header.gfx.cameraToObject)
parentObj.oTreasureChestIsLastInteractionIncorrect = 0
parentObj.oTreasureChestCurrentAnswer = parentObj.oTreasureChestCurrentAnswer + 1
parentObj.oTreasureChestSound = 1
obj.oAction = 1
else
play_sound(SOUND_MENU_CAMERA_BUZZ, obj.header.gfx.cameraToObject)
parentObj.oTreasureChestIsLastInteractionIncorrect = 1
parentObj.oTreasureChestCurrentAnswer = 1
parentObj.oTreasureChestSound = 2
obj.oAction = 2
obj.oDamageOrCoinValue = 1
take_damage_and_knock_back(m, obj)
end
parentObj.oTreasureChestLastNetworkPlayerIndex = gNetworkPlayers[m.playerIndex].globalIndex
if (parentObj.coopFlags & COOP_OBJ_FLAG_NON_SYNC) == 0 and parentObj.oSyncID > 0 then
network_send_object(parentObj, false)
end
parentObj.oTreasureChestSound = 0
end
cappy_bounce_back(m, cappy)
obj.hitboxRadius = 300
obj.hitboxHeight = 300
obj.hurtboxRadius = 310
obj.hurtboxHeight = 310
obj.hitboxDownOffset = 0
return
end
end
obj.hitboxRadius = 300
obj.hitboxHeight = 300
obj.hurtboxRadius = 310
obj.hurtboxHeight = 310
obj.hitboxDownOffset = 0
obj = obj_get_next_with_same_behavior_id(obj)
end
end
------------------------
-- Mario interactions --
------------------------
local function cappy_mario_can_bounce()
local marioAction = m0.action
if m0.heldObj ~= nil or m0.riddenObj ~= nil then return false end
if m0.vel.y <= 0 and (marioAction == ACT_LAVA_BOOST or marioAction == ACT_LAVA_BOOST_LAND) then return true end
if marioAction == ACT_BUBBLED then return true end
if marioAction == ACT_FIRST_PERSON then return false end
if (marioAction & ACT_GROUP_MASK) == ACT_GROUP_CUTSCENE then return false end
if (marioAction & ACT_GROUP_MASK) == ACT_GROUP_AUTOMATIC then return false end
if (marioAction & ACT_FLAG_ON_POLE) ~= 0 then return false end
if (marioAction & ACT_FLAG_HANGING) ~= 0 then return false end
if (marioAction & ACT_FLAG_INTANGIBLE) ~= 0 then return false end
if (marioAction & ACT_FLAG_INVULNERABLE) ~= 0 then return false end
if (marioAction & ACT_FLAG_RIDING_SHELL) ~= 0 then return false end
if (marioAction & ACT_FLAG_METAL_WATER) ~= 0 then return false end
return true
end
local function cappy_process_mario_interactions(cappy)
if cappy_mario_can_bounce() then
local marioObj = m0.marioObj
obj_set_pos(marioObj, m0.pos.x, m0.pos.y, m0.pos.z)
local obj = obj_get_first_with_behavior_id(id_bhvCappy)
while obj ~= nil do
if (obj.oSubAction == 1 and -- Cappy is spawned
obj.oTimer > 0 and -- Cappy is active
obj.oTimer < CAPPY_LIFETIME and -- Cappy is not returning to Mario
obj.oCapUnkF4 == 0 and ( -- Mario can bounce on that Cappy
obj.oCapUnkF8 == 1 or -- Cappy can interact with Mario
obj.oAction ~= cappy.oAction) -- That Cappy is not local Mario's
) then
local mAction = m0.action
-- Check that Cappy interaction setting
if (marioObj.oIntangibleTimer == 0 or mAction == ACT_BUBBLED) then
-- Check hitbox overlap
local marioHitboxRadius = marioObj.hitboxRadius
marioObj.hitboxRadius = 50
local hitboxOverlap = obj_check_hitbox_overlap(marioObj, obj)
marioObj.hitboxRadius = marioHitboxRadius
if hitboxOverlap then
local marioGfx = marioObj.header.gfx
-- Pop bubble
if mAction == ACT_BUBBLED then
m0.vel.x = 0
m0.vel.y = 0
m0.vel.z = 0
m0.health = 0x100
m0.hurtCounter = 0
m0.healCounter = 31
m0.peakHeight = m0.pos.y
marioObj.oIntangibleTimer = 0
marioObj.activeFlags = marioObj.activeFlags & ~ACTIVE_FLAG_MOVE_THROUGH_GRATE
marioGfx.node.flags = marioGfx.node.flags & ~GRAPH_RENDER_INVISIBLE
if m0.pos.y < m0.waterLevel then
mario_set_action(m0, ACT_WATER_IDLE, 0, 0)
else
mario_set_action(m0, ACT_FREEFALL, 0, 0)
end
play_sound(SOUND_OBJ_DEFAULT_DEATH, marioGfx.cameraToObject)
-- Cappy bounce
elseif ((mAction & ACT_FLAG_AIR) ~= 0 or m0.floor.type == SURFACE_BURNING) and (mAction & ACT_FLAG_SWIMMING) == 0 then
obj.oCapUnkF4 = 1
if m0.framesSinceA < 3 then
mario_set_action(m0, ACT_CAPPY_VAULT, 0, 0)
m0.particleFlags = m0.particleFlags | PARTICLE_SPARKLES
else
mario_set_action(m0, ACT_CAPPY_BOUNCE, 0, 0)
end
play_sound(SOUND_GENERAL_BOING1, marioGfx.cameraToObject)
m0.particleFlags = m0.particleFlags | PARTICLE_HORIZONTAL_STAR
-- Cappy vault
elseif (mAction & ACT_FLAG_SWIMMING) == 0 then
mario_set_action(m0, ACT_CAPPY_VAULT, 0, 0)
play_sound(SOUND_GENERAL_BOING1, marioGfx.cameraToObject)
m0.particleFlags = m0.particleFlags | PARTICLE_HORIZONTAL_STAR
else
return
end
-- Unload interacted Cappy
cappy_send_event_bounce(m0, obj)
obj.oSubAction = 0
return
end
end
end
obj = obj_get_next_with_same_behavior_id(obj)
end
end
end
--------------
-- Behavior --
--------------
local function cappy_get_behavior(m)
local action = m.action
local controller = m.controller
local air = (action & (ACT_FLAG_AIR | ACT_FLAG_SWIMMING)) ~= 0 and 1 or 0
if (action & ACT_FLAG_FLYING) == ACT_FLAG_FLYING then return CAPPY_BHV_FLYING end
if (action & ACT_FLAG_RIDING_SHELL) ~= 0 then return CAPPY_BHV_SPIN_GROUND + air end
if (controller.buttonDown & U_JPAD) ~= 0 then return CAPPY_BHV_UPWARDS_GROUND + air end
if (controller.buttonDown & D_JPAD) ~= 0 then return CAPPY_BHV_DOWNWARDS_GROUND + air end
if (controller.buttonDown & L_JPAD) ~= 0 then return CAPPY_BHV_SPIN_GROUND + air end
if (controller.buttonDown & R_JPAD) ~= 0 then return CAPPY_BHV_SPIN_GROUND + air end
if (controller.buttonPressed & A_BUTTON) ~= 0 then return CAPPY_BHV_UPWARDS_GROUND + air end
if action == ACT_GROUND_POUND_LAND then return CAPPY_BHV_DOWNWARDS_GROUND end
return CAPPY_BHV_DEFAULT_GROUND + air
end
local function cappy_init_behavior(m, cappy)
local cappyBhv = cappy.oBehParams2ndByte
local isWater = if_then_else((m.action & ACT_GROUP_MASK) == ACT_GROUP_SUBMERGED and (m.action & ACT_FLAG_METAL_WATER) == 0, 1, 0)
local throwStrength = cappy.oDragStrength / 4
cappy.hitboxRadius = CAPPY_HITBOX_RADIUS
cappy.hitboxHeight = CAPPY_HITBOX_HEIGHT
cappy.hitboxDownOffset = CAPPY_HITBOX_OFFSET
cappy.oInteractType = 0
cappy.oDamageOrCoinValue = 0
obj_set_angle(cappy, 0, 0, 0)
-- Regular throw
if cappyBhv == CAPPY_BHV_DEFAULT_GROUND or cappyBhv == CAPPY_BHV_DEFAULT_AIR then
obj_set_pos(cappy,
m.pos.x,
m.pos.y + CAPPY_BHV_DEFAULT_OFFSET * (1 - isWater),
m.pos.z
)
obj_set_vel(cappy,
CAPPY_BHV_DEFAULT_VEL * throwStrength * coss(m.faceAngle.x * isWater) * sins(m.faceAngle.y),
CAPPY_BHV_DEFAULT_VEL * throwStrength * sins(m.faceAngle.x * isWater),
CAPPY_BHV_DEFAULT_VEL * throwStrength * coss(m.faceAngle.x * isWater) * coss(m.faceAngle.y)
)
-- Up-throw
elseif cappyBhv == CAPPY_BHV_UPWARDS_GROUND or cappyBhv == CAPPY_BHV_UPWARDS_AIR then
obj_set_pos(cappy,
m.pos.x + CAPPY_BHV_UPWARDS_OFFSET * sins(m.faceAngle.y),
m.pos.y + CAPPY_BHV_UPWARDS_OFFSET,
m.pos.z + CAPPY_BHV_UPWARDS_OFFSET * coss(m.faceAngle.y)
)
obj_set_vel(cappy,
0,
CAPPY_BHV_UPWARDS_VEL,
0
)
-- Down-throw
elseif cappyBhv == CAPPY_BHV_DOWNWARDS_GROUND or cappyBhv == CAPPY_BHV_DOWNWARDS_AIR then
obj_set_pos(cappy,
m.pos.x + CAPPY_BHV_DOWNWARDS_OFFSET * sins(m.faceAngle.y),
m.pos.y + CAPPY_BHV_DOWNWARDS_OFFSET,
m.pos.z + CAPPY_BHV_DOWNWARDS_OFFSET * coss(m.faceAngle.y)
)
obj_set_vel(cappy,
0,
-CAPPY_BHV_DOWNWARDS_VEL,
0
)
-- Spin throw
elseif cappyBhv == CAPPY_BHV_SPIN_GROUND or cappyBhv == CAPPY_BHV_SPIN_AIR then
obj_set_pos(cappy,
m.pos.x,
m.pos.y + CAPPY_BHV_SPIN_OFFSET,
m.pos.z
)
-- Flying throw
elseif cappyBhv == CAPPY_BHV_FLYING then
obj_set_pos(cappy,
m.pos.x,
m.pos.y + CAPPY_BHV_FLYING_OFFSET,
m.pos.z
)
end
end
local function cappy_update_behavior(m, cappy)
local cappyBhv = cappy.oBehParams2ndByte
-- Homing attack
if cappy.oCapUnkF8 == 2 then
if (m.controller.buttonPressed & X_BUTTON) ~= 0 then
cappy_return_to_mario(m, cappy)
else
cappy_perform_step(m, cappy, cappy.oVelX, cappy.oVelY, cappy.oVelZ, 0, 0)
end
return
end
-- Regular throw
if cappyBhv == CAPPY_BHV_DEFAULT_GROUND or cappyBhv == CAPPY_BHV_DEFAULT_AIR then
if cappy.oTimer < CAPPY_BHV_DEFAULT_CALL_BACK_START then
cappy.oCapUnkF8 = 0
cappy_perform_step(m, cappy, cappy.oVelX, cappy.oVelY, cappy.oVelZ, CAPPY_COL_WALL_DEFAULT, 0)
cappy_slowdown(cappy)
else
cappy.oCapUnkF8 = 1
cappy_call_back(m, cappy, CAPPY_BHV_DEFAULT_CALL_BACK_START)
end
-- Up-throw
elseif cappyBhv == CAPPY_BHV_UPWARDS_GROUND or cappyBhv == CAPPY_BHV_UPWARDS_AIR then
if cappy.oTimer < CAPPY_BHV_UPWARDS_CALL_BACK_START then
cappy.oCapUnkF8 = 0
cappy_perform_step(m, cappy, cappy.oVelX, cappy.oVelY, cappy.oVelZ, 0, 0)
cappy_slowdown(cappy)
else
cappy.oCapUnkF8 = 1
cappy_call_back(m, cappy, CAPPY_BHV_UPWARDS_CALL_BACK_START)
end
-- Down-throw
elseif cappyBhv == CAPPY_BHV_DOWNWARDS_GROUND or cappyBhv == CAPPY_BHV_DOWNWARDS_AIR then
if cappy.oTimer < CAPPY_BHV_DOWNWARDS_CALL_BACK_START then
cappy.oCapUnkF8 = 0
cappy_perform_step(m, cappy, cappy.oVelX, cappy.oVelY, cappy.oVelZ, 0, CAPPY_COL_FLOOR_CHANGE_BEHAVIOR)
cappy_slowdown(cappy)
if cappy.oVelY == 0 then
obj_set_vel(cappy, 0, -(math.sqrt(cappy.oVelX^2 + cappy.oVelZ^2) * CAPPY_BHV_DOWNWARDS_VEL) / CAPPY_BHV_DEFAULT_VEL, 0)
end
else
cappy.oCapUnkF8 = 1
cappy_call_back(m, cappy, CAPPY_BHV_DOWNWARDS_CALL_BACK_START)
end
-- Spin throw
elseif cappyBhv == CAPPY_BHV_SPIN_GROUND or cappyBhv == CAPPY_BHV_SPIN_AIR then
local r = math.min(cappy.oTimer * CAPPY_BHV_SPIN_RADIUS_GROWTH, CAPPY_BHV_SPIN_RADIUS_MAX)
local dy = if_then_else((m.action & ACT_FLAG_RIDING_SHELL) ~= 0, 42, 0)
obj_set_pos(cappy, m.pos.x, m.pos.y + dy + CAPPY_BHV_SPIN_OFFSET, m.pos.z)
cappy.oMoveAngleYaw = s16(cappy.oMoveAngleYaw + CAPPY_BHV_SPIN_ANGLE_VEL)
cappy.oCapUnkF8 = 0
cappy_perform_step(m, cappy, r * coss(cappy.oMoveAngleYaw), 0, r * sins(cappy.oMoveAngleYaw), CAPPY_COL_WALL_FULL_STOP, 0)
cappy_call_back(m, cappy, CAPPY_BHV_SPIN_CALL_BACK_START)
-- Flying throw
elseif cappyBhv == CAPPY_BHV_FLYING then
if (m.action & ACT_FLAG_FLYING) == ACT_FLAG_FLYING then
local r = math.min(cappy.oTimer * CAPPY_BHV_FLYING_RADIUS_GROWTH, CAPPY_BHV_FLYING_RADIUS_MAX)
local a = s16(cappy.oTimer * CAPPY_BHV_FLYING_ANGLE_VEL)
local v = { x = r * coss(a), y = r * sins(a), z = 0 }
vec3f_rotate_zxy(v, { x = -m.faceAngle.x, y = m.faceAngle.y, z = 0 })
obj_set_pos(cappy, m.pos.x, m.pos.y + CAPPY_BHV_FLYING_OFFSET, m.pos.z)
cappy_perform_step(m, cappy, v.x, v.y, v.z, CAPPY_COL_WALL_FULL_STOP, 0)
else
cappy_return_to_mario(m, cappy)
end
cappy.oMoveAngleYaw = m.faceAngle.y
cappy.oCapUnkF8 = 0
cappy_call_back(m, cappy, CAPPY_BHV_FLYING_CALL_BACK_START)
end
end
local function cappy_spawn(m)
local cappy = cappy_get_object(m)
if cappy ~= nil and cappy.oSubAction == 0 and (m.flags & MARIO_CAP_ON_HEAD) ~= 0 then
if m.playerIndex == 0 then
local behavior = cappy_get_behavior(m)
cappy.oBehParams2ndByte = behavior
cappy.oSubAction = 1
cappy.oTimer = if_then_else(behavior < CAPPY_BHV_SPIN_GROUND, -4, 0)
cappy.oUnkBC = m.marioObj.oTimer
cappy.oCapUnkF8 = 0
cappy.oDragStrength = 0
cappy_send_event_spawn(m, cappy)
end
return true
end
return false
end
------------
-- Update --
------------
local PAULINE_CAP_MODELS = {
[0] = "normal",
[MARIO_WING_CAP] = "wing",
[MARIO_METAL_CAP] = "metal",
[MARIO_METAL_CAP | MARIO_WING_CAP] = "metalWing",
}
local function get_pauline_cap_model(m)
return get_pauline().caps[PAULINE_CAP_MODELS[m.flags & (MARIO_METAL_CAP | MARIO_WING_CAP)]]
end
local function cappy_unload(m, cappy)
if cappy.oSubAction ~= 0 then
cappy.oSubAction = 0
cappy_send_event_unload(m, cappy)
end
end
local function cappy_update(m, cappy)
-- Unload Cappy if...
if (OmmEnabled or -- OMM Rebirth is enabled
not is_pauline(m) or -- Not Pauline
not gNetworkPlayers[m.playerIndex].connected or -- Not connected
not is_player_active(m) -- Not active
) then
cappy_unload(m, cappy)
cappy.oCapUnkF4 = 0
return
end
-- If not spawned, reset some values
if cappy.oSubAction == 0 then
obj_set_pos(cappy, m.pos.x, m.pos.y, m.pos.z)
obj_set_vel(cappy, 0, 0, 0)
cappy.oMoveAngleYaw = 0
cappy.oCapUnkF8 = 0
cappy.oTimer = -255
return
end
-- Unload Cappy if Mario bubbled
if m.action == ACT_BUBBLED then
cappy_unload(m, cappy)
cappy.oCapUnkF4 = 0
return
end
-- Unload Cappy if Mario lost his cap
if (m.flags & MARIO_CAP_ON_HEAD) == 0 then
cappy_unload(m, cappy)
return
end
-- Call Cappy back if Mario is dead
if m.health < 0x100 then
cappy_return_to_mario(m, cappy)
end
-- Init Cappy
if cappy.oTimer == 0 then
cappy_init_behavior(m, cappy)
cappy.oMoveAngleYaw = m.faceAngle.y
end
-- Update Cappy's behavior
if cappy.oTimer >= 0 then
if cappy.oTimer < CAPPY_LIFETIME and m.marioObj.header.gfx.node.flags & GRAPH_RENDER_ACTIVE ~= 0 then
cappy_update_behavior(m, cappy)
cappy_process_object_interactions(m, cappy)
elseif cappy.oTimer == CAPPY_LIFETIME then
cappy_return_to_mario(m, cappy)
elseif cappy_perform_step_return_to_mario(m, cappy) then
cappy_unload(m, cappy)
return
end
end
-- Increase Cappy throw strength
if m.playerIndex == 0 then
if cappy.oTimer < 0 and (m.controller.buttonDown & X_BUTTON) ~= 0 then
cappy.oDragStrength = cappy.oDragStrength + 1
end
if m.controller.stickMag > 60 then
cappy.oDragStrength = 4
else
cappy.oDragStrength = clamp(cappy.oDragStrength, 1, 4)
end
end
-- Send an init event when about to init
if cappy.oTimer == -1 then
cappy_send_event_init(m, cappy)
end
end
local function cappy_update_gfx(m, cappy)
if cappy.oSubAction == 1 and cappy.oTimer >= 0 then
cappy.oFaceAngleYaw = cappy.oFaceAngleYaw + CAPPY_GFX_ANGLE_VEL
obj_scale_xyz(cappy, CAPPY_GFX_SCALE_X, CAPPY_GFX_SCALE_Y, CAPPY_GFX_SCALE_Z)
cappy.header.gfx.node.flags = cappy.header.gfx.node.flags & ~GRAPH_RENDER_INVISIBLE
obj_set_model_extended(cappy, get_pauline_cap_model(m))
cappy.oOpacity = if_then_else((m.marioBodyState.modelState & 0x100) ~= 0, m.marioBodyState.modelState & 0xFF, 0xFF)
if m.playerIndex == 0 then
spawn_non_sync_object(id_bhvSparkleSpawn, E_MODEL_NONE, cappy.oPosX, cappy.oPosY, cappy.oPosZ, nil)
end
else
cappy.header.gfx.node.flags = cappy.header.gfx.node.flags | GRAPH_RENDER_INVISIBLE
end
end
function bhv_cappy_loop(o)
if o.oAction ~= 0 then
local i = network_local_index_from_global(o.oAction - 1)
local m = gMarioStates[i]
cappy_update(m, o)
cappy_update_gfx(m, o)
end
end
function pauline_update(m)
if gNetworkPlayers[m.playerIndex].connected then
-- Super bounce (3-frame window)
if (m.controller.buttonDown & A_BUTTON) == 0 then
m.framesSinceA = 0xFF
end
-- Process Mario interactions with Cappy's and update Mario's state
local cappy = cappy_get_object(m)
if cappy ~= nil then
if m.playerIndex == 0 then
-- Reset bounced flag for all Cappy's when grounded
if ((m.action & ACT_FLAG_AIR) == 0 and m.floor ~= nil and m.floor.type ~= SURFACE_BURNING) then
local obj = obj_get_first_with_behavior_id(id_bhvCappy)
while obj ~= nil do
obj.oCapUnkF4 = 0
obj = obj_get_next_with_same_behavior_id(obj)
end
end
cappy_process_mario_interactions(cappy)
end
-- Process Cappy events
cappy_process_events(m)
-- Update Mario's animation during a spin throw on a shell
if cappy.oSubAction == 1 and (cappy.oBehParams2ndByte == CAPPY_BHV_SPIN_GROUND or cappy.oBehParams2ndByte == CAPPY_BHV_SPIN_AIR) and (m.action & ACT_FLAG_RIDING_SHELL) ~= 0 then
local anim = CAPPY_THROW_ANIMS[cappy.oBehParams2ndByte]
local frame = anim.frameStart + cappy.oTimer
if anim.frameStart <= frame and frame < anim.frameEnd then
mario_anim_play_custom(m, anim.animName, anim.animAccel)
mario_anim_clamp(m.marioObj.header.gfx.animInfo, frame, frame)
m.marioBodyState.torsoAngle.x = 0
m.marioBodyState.torsoAngle.z = 0
m.marioObj.header.gfx.angle.z = 0
end
end
-- Update Mario's roll during a flying throw
if cappy.oSubAction == 1 and cappy.oBehParams2ndByte == CAPPY_BHV_FLYING and (m.action & ACT_FLAG_FLYING) == ACT_FLAG_FLYING and cappy.oTimer < CAPPY_BHV_FLYING_CALL_BACK_START then
m.marioObj.header.gfx.angle.z = s16(m.marioObj.header.gfx.angle.z + (cappy.oTimer * 0x10000) / CAPPY_BHV_FLYING_CALL_BACK_START)
end
-- Update Mario's cap state
if cappy.oSubAction == 1 and cappy.oTimer >= 0 then
m.marioBodyState.capState = MARIO_HAS_DEFAULT_CAP_OFF
end
end
end
end
-------------
-- Actions --
-------------
local function mario_init_action(m, forwardVel, upwardsVel, particles, sfx, charSfx)
mario_set_forward_vel(m, forwardVel)
m.vel.y = upwardsVel
m.particleFlags = m.particleFlags | particles
if sfx ~= nil then play_sound(sfx, m.marioObj.header.gfx.cameraToObject) end
if charSfx ~= nil then
if charSfx == CHAR_SOUND_YAHOO_WAHA_YIPPEE then play_character_sound_offset(m, charSfx, (2 + (m.marioObj.oTimer % 3)) << 16)
else play_character_sound(m, charSfx) end
end
end
function mario_set_action(m, action, actionArg, buttons)
-- Set action and remove buttons pressed
if (action ~= 0) then set_mario_action(m, action, actionArg) end
if (buttons & A_BUTTON) then m.input = m.input & (~(INPUT_A_PRESSED)) end
if (buttons & B_BUTTON) then m.input = m.input & (~(INPUT_B_PRESSED)) end
if (buttons & Z_TRIG ) then m.input = m.input & (~(INPUT_Z_PRESSED)) end
m.controller.buttonPressed = m.controller.buttonPressed & (~(buttons))
-- Set Mario's facing direction
if (m.controller.stickMag > 32 and (
(m.action == ACT_DIVE and m.prevAction == ACT_GROUND_POUND) or
(m.action == ACT_CAPPY_THROW_GROUND) or
(m.action == ACT_CAPPY_THROW_AIRBORNE)))
then
m.faceAngle.y = m.intendedYaw
end
end
local function mario_set_action_if(m, condition, nextAction, actionArg)
if condition then
mario_set_action(m, nextAction, actionArg, 0)
return true
end
return false
end
local function mario_set_action_if_a_pressed(m, nextAction, actionArg)
return mario_set_action_if(m, (m.controller.buttonPressed & A_BUTTON) ~= 0 and not is_game_paused(), nextAction, actionArg)
end
local function mario_set_action_if_b_pressed(m, nextAction, actionArg)
return mario_set_action_if(m, (m.controller.buttonPressed & B_BUTTON) ~= 0 and not is_game_paused(), nextAction, actionArg)
end
local function mario_set_action_if_z_pressed(m, nextAction, actionArg)
return mario_set_action_if(m, (m.controller.buttonPressed & Z_TRIG) ~= 0 and not is_game_paused(), nextAction, actionArg)
end
------------------
-- Cappy throws --
------------------
CAPPY_THROW_ANIMS = {
[CAPPY_BHV_DEFAULT_GROUND] = { animName = MARIO_ANIM_PAULINE_CAPPY_THROW, animAccel = 0x18000, frameStart = 0, frameEnd = 28 },
[CAPPY_BHV_DEFAULT_AIR] = { animName = MARIO_ANIM_PAULINE_CAPPY_THROW, animAccel = 0x18000, frameStart = 31, frameEnd = 71 },
[CAPPY_BHV_UPWARDS_GROUND] = { animName = MARIO_ANIM_PAULINE_CAPPY_UP_THROW, animAccel = 0x14000, frameStart = 0, frameEnd = 27 },
[CAPPY_BHV_UPWARDS_AIR] = { animName = MARIO_ANIM_PAULINE_CAPPY_UP_THROW, animAccel = 0x14000, frameStart = 29, frameEnd = 46 },
[CAPPY_BHV_DOWNWARDS_GROUND] = { animName = MARIO_ANIM_PAULINE_CAPPY_DOWN_THROW, animAccel = 0x14000, frameStart = 0, frameEnd = 27 },
[CAPPY_BHV_DOWNWARDS_AIR] = { animName = MARIO_ANIM_PAULINE_CAPPY_DOWN_THROW, animAccel = 0x14000, frameStart = 29, frameEnd = 47 },
[CAPPY_BHV_SPIN_GROUND] = { animName = MARIO_ANIM_PAULINE_CAPPY_SPIN_THROW, animAccel = 0x14000, frameStart = 0, frameEnd = 37 },
[CAPPY_BHV_SPIN_AIR] = { animName = MARIO_ANIM_PAULINE_CAPPY_SPIN_THROW, animAccel = 0x14000, frameStart = 39, frameEnd = 76 },
[CAPPY_BHV_FLYING] = { animID = MARIO_ANIM_WING_CAP_FLY, animAccel = 0x10000, frameStart = 0, frameEnd = 99 },
}
local function play_cappy_throw_sound(m)
local cappy = cappy_get_object(m)
if cappy ~= nil then
local t = m.marioObj.oTimer
if cappy.oBehParams2ndByte >= CAPPY_BHV_SPIN_GROUND then
play_character_sound_offset(m, CHAR_SOUND_YAHOO_WAHA_YIPPEE, (2 + (t % 3)) << 16)
else
if (t % 3) == 0 then play_character_sound(m, CHAR_SOUND_PUNCH_YAH) end
if (t % 3) == 1 then play_character_sound(m, CHAR_SOUND_PUNCH_WAH) end
if (t % 3) == 2 then play_character_sound(m, CHAR_SOUND_PUNCH_HOO) end
end
end
end
local function update_cappy_throw_anim(m)
local cappy = cappy_get_object(m)
if cappy ~= nil then
local anim = CAPPY_THROW_ANIMS[cappy.oBehParams2ndByte]
if anim ~= nil then
if anim.animName ~= nil then
mario_anim_play_custom(m, anim.animName, anim.animAccel)
else
set_mario_anim_with_accel(m, anim.animID, anim.animAccel)
end
mario_anim_clamp(m.marioObj.header.gfx.animInfo, anim.frameStart, anim.frameEnd)
return is_anim_past_frame(m, anim.frameEnd) == 1
end
end
return true
end
----------
-- Init --
----------
local PAULINE_ACTIONS_INIT = {
[ACT_CAPPY_THROW_GROUND] = function(m)
mario_init_action(m, m.forwardVel, 0, 0, nil, nil)
play_cappy_throw_sound(m)
mario_anim_reset(m.marioObj.header.gfx.animInfo)
end,
[ACT_CAPPY_THROW_AIRBORNE] = function(m)
mario_init_action(m, math.min(m.forwardVel, 8), 16, 0, nil, nil)
play_cappy_throw_sound(m)
mario_anim_reset(m.marioObj.header.gfx.animInfo)
end,
[ACT_CAPPY_THROW_WATER] = function(m)
mario_init_action(m, m.forwardVel, m.vel.y, 0, nil, nil)
play_cappy_throw_sound(m)
mario_anim_reset(m.marioObj.header.gfx.animInfo)
end,
[ACT_CAPPY_BOUNCE] = function(m)
mario_init_action(m, m.forwardVel * 0.8, 56, 0, nil, CHAR_SOUND_HOOHOO)
end,
[ACT_CAPPY_VAULT] = function(m)
mario_init_action(m, m.forwardVel * 0.8, 68, 0, nil, CHAR_SOUND_YAHOO_WAHA_YIPPEE)
mario_anim_reset(m.marioObj.header.gfx.animInfo)
end,
[ACT_CAPPY_RAINBOW_SPIN] = function(m)
play_character_sound(m, CHAR_SOUND_PUNCH_HOO)
m.vel.y = 30
mario_anim_reset(m.marioObj.header.gfx.animInfo)
m.actionTimer = 0
end,
}
function pauline_init_action(m)
if gNetworkPlayers[m.playerIndex].connected then
if PAULINE_ACTIONS_INIT[m.action] ~= nil then
PAULINE_ACTIONS_INIT[m.action](m)
end
end
end
------------
-- Before --
------------
local PAULINE_ACTIONS_BEFORE = {
[ACT_JUMP_KICK] = function(m)
local cappy = cappy_get_object(m)
if cappy ~= nil and cappy.oSubAction == 1 then
return ACT_CAPPY_RAINBOW_SPIN
end
end,
}
function pauline_before_action(m, action)
if gNetworkPlayers[m.playerIndex].connected then
if PAULINE_ACTIONS_BEFORE[action] ~= nil then
return PAULINE_ACTIONS_BEFORE[action](m)
end
end
end
------------
-- Cancel --
------------
local PAULINE_ACTIONS_CANCEL = {
[ACT_GROUND_POUND] = function(m)
if mario_set_action_if_b_pressed(m, ACT_DIVE, 0) then
mario_init_action(m, 30, 30, PARTICLE_MIST_CIRCLE, nil, nil)
vec3s_set(m.faceAngle, 0, m.faceAngle.y, 0)
return
end
end,
[ACT_JUMP_KICK] = function(m)
if mario_set_action_if_z_pressed(m, ACT_GROUND_POUND, 0) then return end
end,
}
local PAULINE_ACTIONS_NO_CAPPY_THROW = {
[ACT_FIRST_PERSON] = true,
[ACT_JUMP_KICK] = true,
[ACT_CAPPY_RAINBOW_SPIN] = true,
[ACT_DIVE] = true,
[ACT_SHOT_FROM_CANNON] = true,
[ACT_VERTICAL_WIND] = true,
[ACT_TWIRLING] = true,
[ACT_AIR_HIT_WALL] = true,
[ACT_GROUND_POUND] = true,
[ACT_SLIDE_KICK] = true,
[ACT_AIR_THROW] = true,
[ACT_WATER_THROW] = true,
[ACT_PICKING_UP] = true,
[ACT_DIVE_PICKING_UP] = true,
[ACT_STOMACH_SLIDE_STOP] = true,
[ACT_PLACING_DOWN] = true,
[ACT_THROWING] = true,
[ACT_HEAVY_THROW] = true,
[ACT_HOLD_PANTING_UNUSED] = true,
[ACT_HOLD_IDLE] = true,
[ACT_HOLD_HEAVY_IDLE] = true,
[ACT_HOLD_JUMP_LAND_STOP] = true,
[ACT_HOLD_FREEFALL_LAND_STOP] = true,
[ACT_HOLD_BUTT_SLIDE_STOP] = true,
[ACT_HOLD_WALKING] = true,
[ACT_HOLD_HEAVY_WALKING] = true,
[ACT_HOLD_DECELERATING] = true,
[ACT_HOLD_BEGIN_SLIDING] = true,
[ACT_HOLD_BUTT_SLIDE] = true,
[ACT_HOLD_STOMACH_SLIDE] = true,
[ACT_HOLD_JUMP_LAND] = true,
[ACT_HOLD_FREEFALL_LAND] = true,
[ACT_HOLD_QUICKSAND_JUMP_LAND] = true,
[ACT_HOLD_JUMP] = true,
[ACT_HOLD_FREEFALL] = true,
[ACT_HOLD_BUTT_SLIDE_AIR] = true,
[ACT_HOLD_WATER_JUMP] = true,
[ACT_HOLD_WATER_IDLE] = true,
[ACT_HOLD_WATER_ACTION_END] = true,
[ACT_HOLD_BREASTSTROKE] = true,
[ACT_HOLD_SWIMMING_END] = true,
[ACT_HOLD_FLUTTER_KICK] = true,
[ACT_HOLD_METAL_WATER_STANDING] = true,
[ACT_HOLD_METAL_WATER_WALKING] = true,
[ACT_HOLD_METAL_WATER_FALLING] = true,
[ACT_HOLD_METAL_WATER_FALL_LAND] = true,
[ACT_HOLD_METAL_WATER_JUMP] = true,
[ACT_HOLD_METAL_WATER_JUMP_LAND] = true,
[ACT_HOLDING_POLE] = true,
[ACT_PICKING_UP_BOWSER] = true,
[ACT_HOLDING_BOWSER] = true,
[ACT_RELEASING_BOWSER] = true,
[ACT_CRAZY_BOX_BOUNCE] = true,
[ACT_WATER_SHELL_SWIMMING] = true,
}
function pauline_cancel_action(m)
if gNetworkPlayers[m.playerIndex].connected then
-- Action cancels
if PAULINE_ACTIONS_CANCEL[m.action] then
PAULINE_ACTIONS_CANCEL[m.action](m)
end
-- Check Cappy throw
if (m.controller.buttonPressed & X_BUTTON) ~= 0 and not is_game_paused() then
if m.riddenObj ~= nil and (m.action & ACT_FLAG_RIDING_SHELL) == 0 then return end
if m.riddenObj == nil and (m.action & ACT_FLAG_RIDING_SHELL) ~= 0 then return end
if (m.action & ACT_GROUP_MASK) == ACT_GROUP_CUTSCENE then return end
if (m.action & ACT_GROUP_MASK) == ACT_GROUP_AUTOMATIC then return end
if (m.action & ACT_FLAG_BUTT_OR_STOMACH_SLIDE) ~= 0 then return end
if (m.action & ACT_FLAG_ON_POLE) ~= 0 then return end
if (m.action & ACT_FLAG_HANGING) ~= 0 then return end
if (m.action & ACT_FLAG_INTANGIBLE) ~= 0 then return end
if (m.action & ACT_FLAG_INVULNERABLE) ~= 0 then return end
if (m.action & ACT_FLAG_METAL_WATER) ~= 0 then return end
if PAULINE_ACTIONS_NO_CAPPY_THROW[m.action] ~= nil then return end
-- Cappy throw
if cappy_spawn(m) then
-- Shell ride
if (m.action & ACT_FLAG_RIDING_SHELL) ~= 0 then
play_character_sound_offset(m, CHAR_SOUND_YAHOO_WAHA_YIPPEE, (2 + (m.marioObj.oTimer % 3)) << 16)
if (m.action & ACT_FLAG_AIR) ~= 0 then
m.action = ACT_RIDING_SHELL_FALL -- prevent ACT_FLAG_CONTROL_JUMP_HEIGHT on ACT_RIDING_SHELL_JUMP from killing the upwards velocity
m.vel.y = math.max(m.vel.y, 32)
end
return
end
-- Flying throw
if (m.action & ACT_FLAG_FLYING) == ACT_FLAG_FLYING then
play_character_sound_offset(m, CHAR_SOUND_YAHOO_WAHA_YIPPEE, (2 + (m.marioObj.oTimer % 3)) << 16)
return
end
-- Water throw
if (m.action & ACT_GROUP_MASK) == ACT_GROUP_SUBMERGED then
mario_set_action(m, ACT_CAPPY_THROW_WATER, 0, X_BUTTON | A_BUTTON | B_BUTTON)
return
end
-- Airborne throw
if (m.action & ACT_FLAG_AIR) ~= 0 then
mario_set_action(m, ACT_CAPPY_THROW_AIRBORNE, 0, X_BUTTON | A_BUTTON | B_BUTTON)
return
end
-- Ground throw
mario_set_action(m, ACT_CAPPY_THROW_GROUND, 0, X_BUTTON | A_BUTTON | B_BUTTON)
return
end
end
end
end
--------------------
-- Custom actions --
--------------------
hook_mario_action(ACT_CAPPY_THROW_GROUND, function(m)
if mario_set_action_if_a_pressed(m, ACT_JUMP, 0) then return 1 end
if mario_set_action_if_b_pressed(m, ACT_MOVE_PUNCHING, 0) then return 1 end
if mario_set_action_if_z_pressed(m, ACT_CROUCH_SLIDE, 0) then return 1 end
local f = (coss(math.abs(m.faceAngle.y - m.intendedYaw)) * m.controller.stickMag) / 64
mario_set_forward_vel(m, m.forwardVel * clamp(f, 0.80, 0.98))
local step = perform_ground_step(m)
if mario_set_action_if(m, step == GROUND_STEP_LEFT_GROUND, ACT_FREEFALL, 0) then return 0 end
if mario_set_action_if(m, step == GROUND_STEP_HIT_WALL, ACT_IDLE, 0) then return 0 end
if mario_set_action_if(m, update_cappy_throw_anim(m), ACT_WALKING, 0) then return 0 end
return 0
end)
hook_mario_action(ACT_CAPPY_THROW_AIRBORNE, {
every_frame = function(m)
if mario_set_action_if_z_pressed(m, ACT_GROUND_POUND, 0) then return 1 end
if check_kick_or_dive_in_air(m) == 1 then return 1 end
local step = common_air_action_step(m, ACT_FREEFALL_LAND, m.marioObj.header.gfx.animInfo.animID, 0)
if mario_set_action_if(m, update_cappy_throw_anim(m), ACT_FREEFALL, 0) then return 0 end
if mario_set_action_if(m, step ~= AIR_STEP_NONE, 0, 0) then return 0 end
return 0
end,
gravity = function(m)
m.vel.y = clamp(m.vel.y - 2, -75, 100)
end
})
hook_mario_action(ACT_CAPPY_THROW_WATER, function(m)
if mario_set_action_if(m, (m.flags & MARIO_METAL_CAP) ~= 0, ACT_METAL_WATER_FALLING, 0) then return 1 end
if mario_set_action_if_a_pressed(m, ACT_BREASTSTROKE, 0) then return 1 end
if mario_set_action_if_b_pressed(m, ACT_WATER_PUNCH, 0) then return 1 end
if m.actionTimer == 0 then
m.angleVel.x = m.vel.x
m.angleVel.y = m.vel.y
m.angleVel.z = m.vel.z
end
m.vel.x = m.angleVel.x * 0.9 ^ (m.actionTimer)
m.vel.y = m.angleVel.y * 0.9 ^ (m.actionTimer)
m.vel.z = m.angleVel.z * 0.9 ^ (m.actionTimer)
mario_set_forward_vel(m, math.sqrt(m.vel.x ^ 2 + m.vel.z ^ 2))
m.actionTimer = m.actionTimer + 1
perform_water_step(m)
if mario_set_action_if(m, update_cappy_throw_anim(m), ACT_WATER_ACTION_END, 0) then return 0 end
return 0
end)
hook_mario_action(ACT_CAPPY_BOUNCE, function(m)
if mario_set_action_if_z_pressed(m, ACT_GROUND_POUND, 0) then return 1 end
if check_kick_or_dive_in_air(m) == 1 then return 1 end
if m.vel.y < 0 then
set_mario_animation(m, MARIO_ANIM_DOUBLE_JUMP_FALL)
m.action = ACT_DOUBLE_JUMP
m.prevAction = ACT_DOUBLE_JUMP
m.actionArg = 0
m.actionState = 1
m.actionTimer = 1
m.flags = m.flags | MARIO_ACTION_SOUND_PLAYED | MARIO_MARIO_SOUND_PLAYED
return 1
end
m.actionTimer = m.actionTimer + 1
local step = common_air_action_step(m, ACT_DOUBLE_JUMP_LAND, MARIO_ANIM_DOUBLE_JUMP_RISE, AIR_STEP_CHECK_LEDGE_GRAB)
if mario_set_action_if(m, step ~= AIR_STEP_NONE, 0, 0) then return 0 end
return 0
end)
hook_mario_action(ACT_CAPPY_VAULT, function(m)
if mario_set_action_if_z_pressed(m, ACT_GROUND_POUND, 0) then return 1 end
if check_kick_or_dive_in_air(m) == 1 then return 1 end
m.actionTimer = m.actionTimer + 1
local step = common_air_action_step(m, ACT_DOUBLE_JUMP_LAND, m.marioObj.header.gfx.animInfo.animID, AIR_STEP_CHECK_LEDGE_GRAB)
mario_anim_play_custom(m, MARIO_ANIM_PAULINE_CAPPY_VAULT)
if mario_set_action_if(m, step ~= AIR_STEP_NONE, 0, 0) then return 0 end
if is_anim_past_frame(m, 6) == 1 then
play_sound(SOUND_ACTION_SIDE_FLIP_UNK, m.marioObj.header.gfx.cameraToObject)
end
return 0
end)
hook_mario_action(ACT_CAPPY_RAINBOW_SPIN, function(m)
if mario_set_action_if_z_pressed(m, ACT_GROUND_POUND, 0) then return 1 end
m.controller.buttonPressed = m.controller.buttonPressed & (~(B_BUTTON))
m.actionTimer = m.actionTimer + 1
update_air_without_turn(m)
local step = perform_air_step(m, AIR_STEP_CHECK_LEDGE_GRAB)
if mario_set_action_if(m, step == AIR_STEP_LANDED, ACT_FREEFALL_LAND, 0) then return 0 end
if mario_set_action_if(m, step == AIR_STEP_HIT_LAVA_WALL and lava_boost_on_wall(m), ACT_LAVA_BOOST, 1) then return 0 end
if mario_set_action_if(m, step == AIR_STEP_GRABBED_LEDGE, ACT_LEDGE_GRAB, 0) then return 0 end
mario_anim_play_custom(m, MARIO_ANIM_PAULINE_CAPPY_RAINBOW_SPIN)
local frame = m.marioObj.header.gfx.animInfo.animFrameAccelAssist >> 0x10
if frame < 20 then m.flags = m.flags | MARIO_KICKING end
m.marioBodyState.punchState = 0
return 0
end)