papermario/src/world/action/hammer.c

430 lines
13 KiB
C

#include "common.h"
#include "effects.h"
#include "sprite.h"
#include "sprite/player.h"
enum {
SUBSTATE_HAMMER_0 = 0,
SUBSTATE_HAMMER_1 = 1,
SUBSTATE_HAMMER_2 = 2
};
typedef struct HammerHitData {
/* 0x00 */ Vec3f hitPos;
/* 0x0C */ s32 unk_0C;
/* 0x10 */ s32 hitID;
/* 0x14 */ s32 unk_14;
/* 0x18 */ s32 timer;
/* 0x1C */ s32 unk_1C;
} HammerHitData;
BSS HammerHitData D_802B6E90;
HammerHitData* HammerHit = &D_802B6E90;
void action_hammer_end_swing(void);
s32 action_hammer_is_swinging_away(s32 animID) {
if (animID & SPRITE_ID_BACK_FACING) {
return TRUE;
}
// back facing swing anims
switch (animID) {
case ANIM_MarioW1_Smash1_Miss_Back:
case ANIM_MarioW1_Smash1_Hit_Back:
case ANIM_MarioW1_Smash2_Miss_Back:
case ANIM_MarioW1_Smash2_Hit_Back:
case ANIM_MarioW1_Smash3_Miss_Back:
case ANIM_MarioW1_Smash3_Hit_Back:
return TRUE;
}
return FALSE;
}
void action_hammer_play_hit_fx(s32 hitID) {
PlayerStatus* playerStatus = &gPlayerStatus;
f32 shakeAmt;
s32 time;
s32 radius;
s32 soundID;
f32 theta, sinTheta, cosTheta;
s32 numParticles;
f32 x, y, z;
if (gPlayerData.hammerLevel == 2) {
shakeAmt = 1.2f;
time = 1;
radius = 28;
soundID = SOUND_HAMMER_STRIKE_3;
} else if (gPlayerData.hammerLevel == 1) {
shakeAmt = 0.8f;
time = 1;
radius = 16;
soundID = SOUND_HAMMER_STRIKE_2;
} else {
shakeAmt = 0.4f;
time = 1;
radius = 4;
soundID = SOUND_HAMMER_STRIKE_1;
}
theta = DEG_TO_RAD(player_get_side_angle());
sinTheta = sin_rad(theta) * 25.0f;
cosTheta = cos_rad(theta) * -25.0f;
if (hitID <= NO_COLLIDER) {
numParticles = 6;
x = playerStatus->pos.x + sinTheta;
y = playerStatus->pos.y;
z = playerStatus->pos.z + cosTheta;
} else {
numParticles = 3;
x = HammerHit->hitPos.x + sinTheta;
y = HammerHit->hitPos.y + playerStatus->colliderHeight - 5.0f;
z = HammerHit->hitPos.z + cosTheta;
radius = 1;
}
exec_ShakeCamX(CAM_DEFAULT, CAM_SHAKE_DECAYING_VERTICAL, time, shakeAmt);
fx_smoke_impact(0, x, y, z, radius, numParticles, 0, (time + 3) * 3);
switch (is_ability_active(ABILITY_ATTACK_FX)) {
case 1:
soundID = SOUND_LIFE_SHROOM_CHIME;
break;
case 2:
soundID = SOUND_PLANTS_BELL;
break;
case 3:
soundID = SOUND_SLIDE_WHISTLE_OUT;
break;
case 4:
soundID = SOUND_YOSHI;
break;
case 5:
soundID = SOUND_HIT_WHACKA;
break;
case 6:
soundID = SOUND_FLOWERS_LAUGH;
break;
}
sfx_play_sound_at_player(soundID, SOUND_SPACE_DEFAULT);
start_rumble(256, 50);
}
HitID action_hammer_test_swing_collision(void) {
PlayerStatus* playerStatus = &gPlayerStatus;
HitID ret;
f32 yaw;
f32 angle;
f32 outSinTheta;
f32 outCosTheta;
f32 playerX, playerY, playerZ;
f32 x, y, z;
s32 i;
// first attempt
yaw = player_get_side_angle();
if (action_hammer_is_swinging_away(playerStatus->trueAnimation)) {
angle = clamp_angle(yaw + 90.0f - gCameras[gCurrentCameraID].curYaw);
if (angle >= 90.0f && angle < 270.0f) {
yaw += -30.0f;
} else {
yaw += 30.0f;
}
}
sin_cos_rad(DEG_TO_RAD(yaw), &outSinTheta, &outCosTheta);
playerX = playerStatus->pos.x;
playerY = playerStatus->pos.y;
playerZ = playerStatus->pos.z;
// check collision along 16 points in a line away from the player
for (i = 1; i < 16; i++) {
x = playerX + (outSinTheta * i);
y = playerY;
z = playerZ - (outCosTheta * i);
ret = player_test_lateral_overlap(PLAYER_COLLISION_HAMMER, playerStatus, &x, &y, &z, 4.0f, yaw);
if (ret > NO_COLLIDER) {
HammerHit->hitPos.x = x;
HammerHit->hitPos.y = y;
HammerHit->hitPos.z = z;
break;
}
}
// second attempt
if (i >= 16) {
yaw = player_get_side_angle();
if (!action_hammer_is_swinging_away(playerStatus->trueAnimation)) {
angle = clamp_angle(yaw + 90.0f - gCameras[gCurrentCameraID].curYaw);
if (angle >= 90.0f && angle < 270.0f) {
yaw += 15.0f;
} else {
yaw += -15.0f;
}
}
// check collision along 16 points in a line away from the player
sin_cos_rad(DEG_TO_RAD(yaw), &outSinTheta, &outCosTheta);
for (i = 1; i < 16; i++) {
x = playerX + (outSinTheta * i);
y = playerY;
z = playerZ - (outCosTheta * i);
ret = player_test_lateral_overlap(PLAYER_COLLISION_HAMMER, playerStatus, &x, &y, &z, 4.0f, yaw);
if (ret > NO_COLLIDER) {
HammerHit->hitPos.x = x;
HammerHit->hitPos.y = y;
HammerHit->hitPos.z = z;
break;
}
}
}
if (ret > NO_COLLIDER && (ret & COLLISION_WITH_ENTITY_BIT)) {
s32 hammerLevel = gPlayerData.hammerLevel;
switch (get_entity_type(ret)) {
case ENTITY_TYPE_HAMMER1_BLOCK:
case ENTITY_TYPE_HAMMER1_BLOCK_TINY:
if (hammerLevel >= 0) {
ret = -1;
} else {
playerStatus->animFlags |= PA_FLAG_SHIVERING;
}
break;
case ENTITY_TYPE_HAMMER2_BLOCK:
case ENTITY_TYPE_HAMMER2_BLOCK_TINY:
if (hammerLevel >= 1) {
ret = -1;
} else {
playerStatus->animFlags |= PA_FLAG_SHIVERING;
}
break;
case ENTITY_TYPE_HAMMER3_BLOCK:
case ENTITY_TYPE_HAMMER3_BLOCK_TINY:
if (hammerLevel >= 2) {
ret = -1;
} else {
playerStatus->animFlags |= PA_FLAG_SHIVERING;
}
break;
case ENTITY_TYPE_WOODEN_CRATE:
case ENTITY_TYPE_BOMBABLE_ROCK:
playerStatus->animFlags |= PA_FLAG_SHIVERING;
break;
case ENTITY_TYPE_BLUE_SWITCH:
case ENTITY_TYPE_RED_SWITCH:
case ENTITY_TYPE_BRICK_BLOCK:
ret = -1;
break;
}
}
return ret;
}
void action_update_hammer(void) {
PlayerStatus* playerStatus = &gPlayerStatus;
HammerHit->unk_1C = 0;
if (playerStatus->flags & PS_FLAG_ACTION_STATE_CHANGED) {
AnimID anim;
s32 soundID;
playerStatus->flags &= ~PS_FLAG_ACTION_STATE_CHANGED;
playerStatus->flags |= PS_FLAG_NO_FLIPPING;
HammerHit->timer = 0;
playerStatus->actionSubstate = SUBSTATE_HAMMER_0;
playerStatus->curSpeed = 0.0f;
playerStatus->animNotifyValue = 0;
HammerHit->hitID = action_hammer_test_swing_collision();
if (gPlayerData.hammerLevel == 2) {
soundID = SOUND_HAMMER_SWING_3;
anim = ANIM_MarioW1_Smash3_Hit;
if (HammerHit->hitID <= NO_COLLIDER) {
soundID = SOUND_HAMMER_SWING_3;
anim = ANIM_MarioW1_Smash3_Miss;
}
} else if (gPlayerData.hammerLevel == 1) {
soundID = SOUND_HAMMER_SWING_2;
anim = ANIM_MarioW1_Smash2_Hit;
if (HammerHit->hitID <= NO_COLLIDER) {
soundID = SOUND_HAMMER_SWING_2;
anim = ANIM_MarioW1_Smash2_Miss;
}
} else {
soundID = SOUND_HAMMER_SWING_1;
anim = ANIM_MarioW1_Smash1_Hit;
if (HammerHit->hitID <= NO_COLLIDER) {
soundID = SOUND_HAMMER_SWING_1;
anim = ANIM_MarioW1_Smash1_Miss;
}
}
suggest_player_anim_allow_backward(anim);
sfx_play_sound_at_player(soundID, SOUND_SPACE_DEFAULT);
HammerHit->unk_0C = 0;
HammerHit->unk_14 = 0;
}
playerStatus->flags &= ~PS_FLAG_HAMMER_CHECK;
if (HammerHit->timer < 3 && (playerStatus->flags & PS_FLAG_ENTERING_BATTLE)) {
// This is probably to stop Mario from triggering multiple battles at once by hammering while one is starting.
playerStatus->flags |= PS_FLAG_TIME_STOPPED;
} else if (HammerHit->timer < 2) {
HammerHit->timer++;
} else {
action_hammer_end_swing();
}
}
void action_hammer_end_swing(void) {
PlayerStatus* playerStatus = &gPlayerStatus;
CollisionStatus* collisionStatus;
f32 yaw;
f32 angle;
f32 outSinTheta;
f32 outCosTheta;
f32 playerX, playerY, playerZ;
f32 x, y, z;
s32 result;
s32 hammerLevel;
s32 soundID;
u32 unk_BC;
s32 ten;
s32 ret;
s32 i;
do {
collisionStatus = &gCollisionStatus;
} while (0); // required to match;
yaw = player_get_side_angle();
if (action_hammer_is_swinging_away(playerStatus->trueAnimation)) {
angle = clamp_angle(yaw + 90.0f - gCameras[gCurrentCameraID].curYaw);
if (angle >= 90.0f && angle < 270.0f) {
yaw += -30.0f;
} else {
yaw += 30.0f;
}
}
sin_cos_rad(DEG_TO_RAD(yaw), &outSinTheta, &outCosTheta);
playerX = playerStatus->pos.x;
playerY = playerStatus->pos.y;
playerZ = playerStatus->pos.z;
// check collision along 16 points in a line away from the player
for (i = 1; i < 16; i++) {
x = playerX + (outSinTheta * i);
y = playerY;
z = playerZ - (outCosTheta * i);
result = player_test_lateral_overlap(PLAYER_COLLISION_HAMMER, playerStatus, &x, &y, &z, 4.0f, yaw);
if (HammerHit->unk_14 == 0) {
collisionStatus->lastWallHammered = result;
if (result > NO_COLLIDER) {
if (result & COLLISION_WITH_ENTITY_BIT) {
get_entity_by_index(result)->collisionTimer = 0;
}
}
}
if (result > NO_COLLIDER) {
break;
}
}
if (i >= 16) {
yaw = player_get_side_angle();
if (action_hammer_is_swinging_away(playerStatus->trueAnimation) == 0) {
angle = clamp_angle(yaw + 90.0f - gCameras[gCurrentCameraID].curYaw);
if (angle >= 90.0f && angle < 270.0f) {
yaw += 15.0f;
} else {
yaw += -15.0f;
}
}
sin_cos_rad(DEG_TO_RAD(yaw), &outSinTheta, &outCosTheta);
// check collision along 16 points in a line away from the player
for (i = 1; i < 16; i++) {
x = playerX + (outSinTheta * i);
y = playerY;
z = playerZ - (outCosTheta * i);
result = player_test_lateral_overlap(PLAYER_COLLISION_HAMMER, playerStatus, &x, &y, &z, 4.0f, yaw);
if (HammerHit->unk_14 == 0) {
collisionStatus->lastWallHammered = result;
if (result > NO_COLLIDER) {
if (result & COLLISION_WITH_ENTITY_BIT) {
get_entity_by_index(result)->collisionTimer = 0;
}
}
}
if (result > NO_COLLIDER) {
break;
}
}
}
if (HammerHit->timer == 2) {
hammerLevel = gPlayerData.hammerLevel;
if (hammerLevel == 2) {
soundID = SOUND_HAMMER_SWING_3;
} else if (hammerLevel == 1) {
soundID = SOUND_HAMMER_SWING_2;
} else {
soundID = SOUND_HAMMER_SWING_1;
}
sfx_play_sound_at_player(soundID, SOUND_SPACE_DEFAULT);
action_hammer_play_hit_fx(HammerHit->hitID);
if (collisionStatus->lastWallHammered > NO_COLLIDER && (collisionStatus->lastWallHammered & COLLISION_WITH_ENTITY_BIT)) {
get_entity_by_index(collisionStatus->lastWallHammered)->collisionTimer = 0;
playerStatus->flags |= PS_FLAG_HAMMER_CHECK;
} else if (HammerHit->hitID <= NO_COLLIDER) {
playerStatus->flags |= PS_FLAG_HAMMER_CHECK;
}
if (HammerHit->hitID <= NO_COLLIDER && gPlayerData.hammerLevel >= 2) {
gCurrentHiddenPanels.tryFlipTrigger = TRUE;
gCurrentHiddenPanels.flipTriggerPosY = playerStatus->pos.y;
}
}
if (playerStatus->actionSubstate == SUBSTATE_HAMMER_0 && result > NO_COLLIDER && HammerHit->unk_14 == 0) {
playerStatus->actionSubstate++;
}
if (playerStatus->actionSubstate == SUBSTATE_HAMMER_1 && result <= NO_COLLIDER) {
playerStatus->actionSubstate = SUBSTATE_HAMMER_2;
}
HammerHit->timer++;
if (result > NO_COLLIDER) {
HammerHit->unk_14 = 1;
}
if (HammerHit->timer == 6) {
playerStatus->flags &= ~PS_FLAG_NO_FLIPPING;
}
if (playerStatus->animNotifyValue == 1) {
if (HammerHit->timer >= 7 && (playerStatus->pressedButtons & BUTTON_B)) {
HammerHit->unk_1C = 1;
}
HammerHit->unk_14 = 0;
ten = 10; // required to match
if (HammerHit->unk_1C != 0 || HammerHit->timer > ten) {
playerStatus->flags &= ~PS_FLAG_HAMMER_CHECK;
set_action_state(ACTION_STATE_IDLE);
}
playerStatus->flags &= ~PS_FLAG_NO_FLIPPING;
}
}