mirror of
https://github.com/coop-deluxe/sm64coopdx.git
synced 2026-02-19 03:51:25 +00:00
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
* Replaces Extra Characters * Folder inside another folder fix
1756 lines
68 KiB
Lua
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)
|