345 lines
11 KiB
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.
|
|
cur_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.
|
|
obj_set_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 cur_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) {
|
|
cur_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) {
|
|
cur_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;
|
|
|
|
cur_obj_become_intangible();
|
|
|
|
if (o->oTimer >= 31) {
|
|
o->oAnimState = 0;
|
|
}
|
|
|
|
if (o->oTimer >= 91) {
|
|
o->oAnimState = 1;
|
|
cur_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();
|
|
cur_obj_play_sound_1(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 cur_obj_play_sound_1 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++;
|
|
|
|
cur_obj_play_sound_1(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;
|
|
}
|
|
}
|