sm64pc/src/game/behaviors/amp.inc.c

345 lines
11 KiB
C

/**
* Behavior for bhvHomingAmp and bhvCirclingAmp.
* These are distinct objects; one chases (homes in on) Mario,
* while the other circles around a fixed location with a radius
* of 200, 300, 400, or 0 (stationary).
*/
static struct ObjectHitbox sAmpHitbox = {
/* interactType: */ INTERACT_SHOCK,
/* downOffset: */ 40,
/* damageOrCoinValue: */ 1,
/* health: */ 0,
/* numLootCoins: */ 0,
/* radius: */ 40,
/* height: */ 50,
/* hurtboxRadius: */ 50,
/* hurtboxHeight: */ 60,
};
/**
* Homing amp initialization function.
*/
void bhv_homing_amp_init(void) {
o->oHomeX = o->oPosX;
o->oHomeY = o->oPosY;
o->oHomeZ = o->oPosZ;
o->oGravity = 0;
o->oFriction = 1.0;
o->oBuoyancy = 1.0;
o->oHomingAmpAvgY = o->oHomeY;
// Homing amps start at 1/10th their normal size.
// They grow when they "appear" to Mario.
obj_scale(0.1f);
// Hide the amp (until Mario gets near).
o->header.gfx.node.flags |= GRAPH_RENDER_INVISIBLE;
}
/**
* Amps' attack handler, shared by both types of amp.
*/
static void check_amp_attack(void) {
// Strange placement for this call. The hitbox is never cleared.
// For perspective, this code is run every frame of bhv_circling_amp_loop
// and every frame of a homing amp's HOMING_AMP_ACT_CHASE action.
set_object_hitbox(o, &sAmpHitbox);
if (o->oInteractStatus & INT_STATUS_INTERACTED) {
// Unnecessary if statement, maybe caused by a macro for
// if (o->oInteractStatus & INT_STATUS_INTERACTED)
// o->oAction = X;
// ?
if (o->oInteractStatus & INT_STATUS_INTERACTED) {
// This function is used for both normal amps and homing amps,
// AMP_ACT_ATTACK_COOLDOWN == HOMING_AMP_ACT_ATTACK_COOLDOWN
o->oAction = AMP_ACT_ATTACK_COOLDOWN;
}
// Clear interact status
o->oInteractStatus = 0;
}
}
/**
* Unhide the amp and grow until normal size, then begin chasing Mario.
*/
static void homing_amp_appear_loop(void) {
// gLakituState.goalPos is the position lakitu is moving towards.
// In Lakitu and Mario cam, it is usually very close to the current camera position.
// In Fixed cam, it is the point behind Mario the camera will go to when transitioning
// to Lakitu cam. Homing amps will point themselves towards this point when appearing.
f32 relativeTargetX = gLakituState.goalPos[0] - o->oPosX;
f32 relativeTargetZ = gLakituState.goalPos[2] - o->oPosZ;
s16 targetYaw = atan2s(relativeTargetZ, relativeTargetX);
o->oMoveAngleYaw = approach_s16_symmetric(o->oMoveAngleYaw, targetYaw, 0x1000);
// For 30 frames, make the amp "appear" by increasing its size by 0.03 each frame,
// except for the first frame (when oTimer == 0) because the expression in obj_scale
// evaluates to 0.1, which is the same as it was before. After 30 frames, it ends at
// a scale factor of 0.97. The amp remains at 97% of its real height for 60 more frames.
if (o->oTimer < 30) {
obj_scale(0.1 + 0.9 * (f32)(o->oTimer / 30.0f));
} else {
o->oAnimState = 1;
}
// Once the timer becomes greater than 90, i.e. 91 frames have passed,
// reset the amp's size and start chasing Mario.
if (o->oTimer >= 91) {
obj_scale(1.0f);
o->oAction = HOMING_AMP_ACT_CHASE;
o->oAmpYPhase = 0;
}
}
/**
* Chase Mario.
*/
static void homing_amp_chase_loop(void) {
// Lock on to Mario if he ever goes within 11.25 degrees of the amp's line of sight
if ((o->oAngleToMario - 0x400 < o->oMoveAngleYaw)
&& (o->oMoveAngleYaw < o->oAngleToMario + 0x400)) {
o->oHomingAmpLockedOn = TRUE;
o->oTimer = 0;
}
// If the amp is locked on to Mario, start "chasing" him by moving
// in a straight line at 15 units/second for 32 frames.
if (o->oHomingAmpLockedOn == TRUE) {
o->oForwardVel = 15.0f;
// Move the amp's average Y (the Y value it oscillates around) to align with
// Mario's head. Mario's graphics' Y + 150 is around the top of his head.
// Note that the average Y will slowly go down to approach his head if the amp
// is above his head, but if the amp is below it will instantly snap up.
if (o->oHomingAmpAvgY > gMarioObject->header.gfx.pos[1] + 150.0f) {
o->oHomingAmpAvgY -= 10.0f;
} else {
o->oHomingAmpAvgY = gMarioObject->header.gfx.pos[1] + 150.0f;
}
if (o->oTimer >= 31) {
o->oHomingAmpLockedOn = FALSE;
}
} else {
// If the amp is not locked on to Mario, move forward at 10 units/second
// while curving towards him.
o->oForwardVel = 10.0f;
obj_turn_toward_object(o, gMarioObject, 16, 0x400);
// The amp's average Y will approach Mario's graphical Y position + 250
// at a rate of 10 units per frame. Interestingly, this is different from
// the + 150 used while chasing him. Could this be a typo?
if (o->oHomingAmpAvgY < gMarioObject->header.gfx.pos[1] + 250.0f) {
o->oHomingAmpAvgY += 10.0f;
}
}
// The amp's position will sinusoidally oscillate 40 units around its average Y.
o->oPosY = o->oHomingAmpAvgY + sins(o->oAmpYPhase * 0x400) * 20.0f;
// Handle attacks
check_amp_attack();
// Give up if Mario goes further than 1500 units from the amp's original position
if (is_point_within_radius_of_mario(o->oHomeX, o->oHomeY, o->oHomeZ, 1500) == FALSE) {
o->oAction = HOMING_AMP_ACT_GIVE_UP;
}
}
/**
* Give up on chasing Mario.
*/
static void homing_amp_give_up_loop(void) {
UNUSED u8 filler[8];
// Move forward for 152 frames
o->oForwardVel = 15.0f;
if (o->oTimer >= 151) {
// Hide the amp and reset it back to its inactive state
o->oPosX = o->oHomeX;
o->oPosY = o->oHomeY;
o->oPosZ = o->oHomeZ;
o->header.gfx.node.flags |= GRAPH_RENDER_INVISIBLE;
o->oAction = HOMING_AMP_ACT_INACTIVE;
o->oAnimState = 0;
o->oForwardVel = 0;
o->oHomingAmpAvgY = o->oHomeY;
}
}
/**
* Cool down after a successful attack, shared by both types of amp.
*/
static void amp_attack_cooldown_loop(void) {
// Turn intangible and wait for 90 frames before chasing Mario again after hitting him.
o->header.gfx.unk38.animFrame += 2;
o->oForwardVel = 0;
obj_become_intangible();
if (o->oTimer >= 31) {
o->oAnimState = 0;
}
if (o->oTimer >= 91) {
o->oAnimState = 1;
obj_become_tangible();
o->oAction = HOMING_AMP_ACT_CHASE;
}
}
/**
* Homing amp update function.
*/
void bhv_homing_amp_loop(void) {
switch (o->oAction) {
case HOMING_AMP_ACT_INACTIVE:
if (is_point_within_radius_of_mario(o->oHomeX, o->oHomeY, o->oHomeZ, 800) == TRUE) {
// Make the amp start to appear, and un-hide it.
o->oAction = HOMING_AMP_ACT_APPEAR;
o->header.gfx.node.flags &= ~GRAPH_RENDER_INVISIBLE;
}
break;
case HOMING_AMP_ACT_APPEAR:
homing_amp_appear_loop();
break;
case HOMING_AMP_ACT_CHASE:
homing_amp_chase_loop();
PlaySound(SOUND_AIR_AMP_BUZZ);
break;
case HOMING_AMP_ACT_GIVE_UP:
homing_amp_give_up_loop();
break;
case HOMING_AMP_ACT_ATTACK_COOLDOWN:
amp_attack_cooldown_loop();
break;
}
object_step();
// Oscillate
o->oAmpYPhase++;
}
/**
* Circling amp initialization function.
*/
void bhv_circling_amp_init(void) {
o->oHomeX = o->oPosX;
o->oHomeY = o->oPosY;
o->oHomeZ = o->oPosZ;
o->oAnimState = 1;
// Determine the radius of the circling amp's circle
switch (o->oBehParams2ndByte) {
case AMP_BP_ROT_RADIUS_200:
o->oAmpRadiusOfRotation = 200.0f;
break;
case AMP_BP_ROT_RADIUS_300:
o->oAmpRadiusOfRotation = 300.0f;
break;
case AMP_BP_ROT_RADIUS_400:
o->oAmpRadiusOfRotation = 400.0f;
break;
case AMP_BP_ROT_RADIUS_0:
break;
}
// Choose a random point along the amp's circle.
// The amp's move angle represents its angle along the circle.
o->oMoveAngleYaw = RandomU16();
o->oAction = AMP_ACT_IDLE;
}
/**
* Main update function for fixed amps.
* Fixed amps are a sub-species of circling amps, with circle radius 0.
*/
static void fixed_circling_amp_idle_loop(void) {
// Turn towards Mario, in both yaw and pitch.
f32 xToMario = gMarioObject->header.gfx.pos[0] - o->oPosX;
f32 yToMario = gMarioObject->header.gfx.pos[1] + 120.0f - o->oPosY;
f32 zToMario = gMarioObject->header.gfx.pos[2] - o->oPosZ;
s16 vAngleToMario = atan2s(sqrtf(xToMario * xToMario + zToMario * zToMario), -yToMario);
obj_turn_toward_object(o, gMarioObject, 19, 0x1000);
o->oFaceAnglePitch = approach_s16_symmetric(o->oFaceAnglePitch, vAngleToMario, 0x1000);
// Oscillate 40 units up and down.
// Interestingly, 0x458 (1112 in decimal) is a magic number with no apparent significance.
// It is slightly larger than the 0x400 figure used for homing amps, which makes
// fixed amps oscillate slightly quicker.
// Also, this uses the cosine, which starts at 1 instead of 0.
o->oPosY = o->oHomeY + coss(o->oAmpYPhase * 0x458) * 20.0f;
// Handle attacks
check_amp_attack();
// Oscillate
o->oAmpYPhase++;
// Where there is a PlaySound call in the main circling amp update function,
// there is nothing here. Fixed amps are the only amps that never play
// the "amp buzzing" sound.
}
/**
* Main update function for regular circling amps.
*/
static void circling_amp_idle_loop(void) {
// Move in a circle.
// The Y oscillation uses the magic number 0x8B0 (2224), which is
// twice that of the fixed amp. In other words, circling amps will
// oscillate twice as fast. Also, unlike all other amps, circling
// amps oscillate 60 units around their average Y instead of 40.
o->oPosX = o->oHomeX + sins(o->oMoveAngleYaw) * o->oAmpRadiusOfRotation;
o->oPosZ = o->oHomeZ + coss(o->oMoveAngleYaw) * o->oAmpRadiusOfRotation;
o->oPosY = o->oHomeY + coss(o->oAmpYPhase * 0x8B0) * 30.0f;
o->oMoveAngleYaw += 0x400;
o->oFaceAngleYaw = o->oMoveAngleYaw + 0x4000;
// Handle attacks
check_amp_attack();
// Oscillate
o->oAmpYPhase++;
PlaySound(SOUND_AIR_AMP_BUZZ);
}
/**
* Circling amp update function.
* This calls the main update functions for both types of circling amps,
* and calls the common amp cooldown function when the amp is cooling down.
*/
void bhv_circling_amp_loop(void) {
switch (o->oAction) {
case AMP_ACT_IDLE:
if (o->oBehParams2ndByte == AMP_BP_ROT_RADIUS_0) {
fixed_circling_amp_idle_loop();
} else {
circling_amp_idle_loop();
}
break;
case AMP_ACT_ATTACK_COOLDOWN:
amp_attack_cooldown_loop();
break;
}
}