Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
MorsGames
GitHub Repository: MorsGames/sm64plus
Path: blob/master/src/game/behaviors/amp.inc.c
7861 views
1
/**
2
* Behavior for bhvHomingAmp and bhvCirclingAmp.
3
* These are distinct objects; one chases (homes in on) Mario,
4
* while the other circles around a fixed location with a radius
5
* of 200, 300, 400, or 0 (stationary).
6
*/
7
8
static struct ObjectHitbox sAmpHitbox = {
9
/* interactType: */ INTERACT_SHOCK,
10
/* downOffset: */ 40,
11
/* damageOrCoinValue: */ 1,
12
/* health: */ 0,
13
/* numLootCoins: */ 0,
14
/* radius: */ 40,
15
/* height: */ 50,
16
/* hurtboxRadius: */ 50,
17
/* hurtboxHeight: */ 60,
18
};
19
20
/**
21
* Homing amp initialization function.
22
*/
23
void bhv_homing_amp_init(void) {
24
o->oHomeX = o->oPosX;
25
o->oHomeY = o->oPosY;
26
o->oHomeZ = o->oPosZ;
27
o->oGravity = 0;
28
o->oFriction = 1.0;
29
o->oBuoyancy = 1.0;
30
o->oHomingAmpAvgY = o->oHomeY;
31
32
// Homing amps start at 1/10th their normal size.
33
// They grow when they "appear" to Mario.
34
cur_obj_scale(0.1f);
35
36
// Hide the amp (until Mario gets near).
37
o->header.gfx.node.flags |= GRAPH_RENDER_INVISIBLE;
38
}
39
40
/**
41
* Amps' attack handler, shared by both types of amp.
42
*/
43
static void check_amp_attack(void) {
44
// Strange placement for this call. The hitbox is never cleared.
45
// For perspective, this code is run every frame of bhv_circling_amp_loop
46
// and every frame of a homing amp's HOMING_AMP_ACT_CHASE action.
47
obj_set_hitbox(o, &sAmpHitbox);
48
49
if (o->oInteractStatus & INT_STATUS_INTERACTED) {
50
// Unnecessary if statement, maybe caused by a macro for
51
// if (o->oInteractStatus & INT_STATUS_INTERACTED)
52
// o->oAction = X;
53
// ?
54
if (o->oInteractStatus & INT_STATUS_INTERACTED) {
55
// This function is used for both normal amps and homing amps,
56
// AMP_ACT_ATTACK_COOLDOWN == HOMING_AMP_ACT_ATTACK_COOLDOWN
57
o->oAction = AMP_ACT_ATTACK_COOLDOWN;
58
}
59
60
// Clear interact status
61
o->oInteractStatus = 0;
62
}
63
}
64
65
/**
66
* Unhide the amp and grow until normal size, then begin chasing Mario.
67
*/
68
static void homing_amp_appear_loop(void) {
69
// gLakituState.goalPos is the position lakitu is moving towards.
70
// In Lakitu and Mario cam, it is usually very close to the current camera position.
71
// In Fixed cam, it is the point behind Mario the camera will go to when transitioning
72
// to Lakitu cam. Homing amps will point themselves towards this point when appearing.
73
f32 relativeTargetX = gLakituState.goalPos[0] - o->oPosX;
74
f32 relativeTargetZ = gLakituState.goalPos[2] - o->oPosZ;
75
s16 targetYaw = atan2s(relativeTargetZ, relativeTargetX);
76
77
o->oMoveAngleYaw = approach_s16_symmetric(o->oMoveAngleYaw, targetYaw, 0x1000);
78
79
// For 30 frames, make the amp "appear" by increasing its size by 0.03 each frame,
80
// except for the first frame (when oTimer == 0) because the expression in cur_obj_scale
81
// evaluates to 0.1, which is the same as it was before. After 30 frames, it ends at
82
// a scale factor of 0.97. The amp remains at 97% of its real height for 60 more frames.
83
if (o->oTimer < 30) {
84
cur_obj_scale(0.1 + 0.9 * (f32)(o->oTimer / 30.0f));
85
} else {
86
o->oAnimState = 1;
87
}
88
89
// Once the timer becomes greater than 90, i.e. 91 frames have passed,
90
// reset the amp's size and start chasing Mario.
91
if (o->oTimer >= 91) {
92
cur_obj_scale(1.0f);
93
o->oAction = HOMING_AMP_ACT_CHASE;
94
o->oAmpYPhase = 0;
95
}
96
}
97
98
/**
99
* Chase Mario.
100
*/
101
static void homing_amp_chase_loop(void) {
102
// Lock on to Mario if he ever goes within 11.25 degrees of the amp's line of sight
103
if ((o->oAngleToMario - 0x400 < o->oMoveAngleYaw)
104
&& (o->oMoveAngleYaw < o->oAngleToMario + 0x400)) {
105
o->oHomingAmpLockedOn = TRUE;
106
o->oTimer = 0;
107
}
108
109
// If the amp is locked on to Mario, start "chasing" him by moving
110
// in a straight line at 15 units/second for 32 frames.
111
if (o->oHomingAmpLockedOn == TRUE) {
112
o->oForwardVel = 15.0f;
113
114
// Move the amp's average Y (the Y value it oscillates around) to align with
115
// Mario's head. Mario's graphics' Y + 150 is around the top of his head.
116
// Note that the average Y will slowly go down to approach his head if the amp
117
// is above his head, but if the amp is below it will instantly snap up.
118
if (o->oHomingAmpAvgY > gMarioObject->header.gfx.pos[1] + 150.0f) {
119
o->oHomingAmpAvgY -= 10.0f;
120
} else {
121
o->oHomingAmpAvgY = gMarioObject->header.gfx.pos[1] + 150.0f;
122
}
123
124
if (o->oTimer >= 31) {
125
o->oHomingAmpLockedOn = FALSE;
126
}
127
} else {
128
// If the amp is not locked on to Mario, move forward at 10 units/second
129
// while curving towards him.
130
o->oForwardVel = 10.0f;
131
132
obj_turn_toward_object(o, gMarioObject, 16, 0x400);
133
134
// The amp's average Y will approach Mario's graphical Y position + 250
135
// at a rate of 10 units per frame. Interestingly, this is different from
136
// the + 150 used while chasing him. Could this be a typo?
137
if (o->oHomingAmpAvgY < gMarioObject->header.gfx.pos[1] + 250.0f) {
138
o->oHomingAmpAvgY += 10.0f;
139
}
140
}
141
142
// The amp's position will sinusoidally oscillate 40 units around its average Y.
143
o->oPosY = o->oHomingAmpAvgY + sins(o->oAmpYPhase * 0x400) * 20.0f;
144
145
// Handle attacks
146
check_amp_attack();
147
148
// Give up if Mario goes further than 1500 units from the amp's original position
149
if (!is_point_within_radius_of_mario(o->oHomeX, o->oHomeY, o->oHomeZ, 1500)) {
150
o->oAction = HOMING_AMP_ACT_GIVE_UP;
151
}
152
}
153
154
/**
155
* Give up on chasing Mario.
156
*/
157
static void homing_amp_give_up_loop(void) {
158
UNUSED u8 filler[8];
159
160
// Move forward for 152 frames
161
o->oForwardVel = 15.0f;
162
163
if (o->oTimer >= 151) {
164
// Hide the amp and reset it back to its inactive state
165
o->oPosX = o->oHomeX;
166
o->oPosY = o->oHomeY;
167
o->oPosZ = o->oHomeZ;
168
o->header.gfx.node.flags |= GRAPH_RENDER_INVISIBLE;
169
o->oAction = HOMING_AMP_ACT_INACTIVE;
170
o->oAnimState = 0;
171
o->oForwardVel = 0;
172
o->oHomingAmpAvgY = o->oHomeY;
173
}
174
}
175
176
/**
177
* Cool down after a successful attack, shared by both types of amp.
178
*/
179
static void amp_attack_cooldown_loop(void) {
180
// Turn intangible and wait for 90 frames before chasing Mario again after hitting him.
181
o->header.gfx.animInfo.animFrame += 2;
182
o->oForwardVel = 0;
183
184
cur_obj_become_intangible();
185
186
if (o->oTimer >= 31) {
187
o->oAnimState = 0;
188
}
189
190
if (o->oTimer >= 91) {
191
o->oAnimState = 1;
192
cur_obj_become_tangible();
193
o->oAction = HOMING_AMP_ACT_CHASE;
194
}
195
}
196
197
/**
198
* Homing amp update function.
199
*/
200
void bhv_homing_amp_loop(void) {
201
switch (o->oAction) {
202
case HOMING_AMP_ACT_INACTIVE:
203
if (is_point_within_radius_of_mario(o->oHomeX, o->oHomeY, o->oHomeZ, 800) == TRUE) {
204
// Make the amp start to appear, and un-hide it.
205
o->oAction = HOMING_AMP_ACT_APPEAR;
206
o->header.gfx.node.flags &= ~GRAPH_RENDER_INVISIBLE;
207
}
208
break;
209
210
case HOMING_AMP_ACT_APPEAR:
211
homing_amp_appear_loop();
212
break;
213
214
case HOMING_AMP_ACT_CHASE:
215
homing_amp_chase_loop();
216
cur_obj_play_sound_1(SOUND_AIR_AMP_BUZZ);
217
break;
218
219
case HOMING_AMP_ACT_GIVE_UP:
220
homing_amp_give_up_loop();
221
break;
222
223
case HOMING_AMP_ACT_ATTACK_COOLDOWN:
224
amp_attack_cooldown_loop();
225
break;
226
}
227
228
object_step();
229
230
// Oscillate
231
o->oAmpYPhase++;
232
}
233
234
/**
235
* Circling amp initialization function.
236
*/
237
void bhv_circling_amp_init(void) {
238
o->oHomeX = o->oPosX;
239
o->oHomeY = o->oPosY;
240
o->oHomeZ = o->oPosZ;
241
o->oAnimState = 1;
242
243
// Determine the radius of the circling amp's circle
244
switch (o->oBehParams2ndByte) {
245
case AMP_BP_ROT_RADIUS_200:
246
o->oAmpRadiusOfRotation = 200.0f;
247
break;
248
249
case AMP_BP_ROT_RADIUS_300:
250
o->oAmpRadiusOfRotation = 300.0f;
251
break;
252
253
case AMP_BP_ROT_RADIUS_400:
254
o->oAmpRadiusOfRotation = 400.0f;
255
break;
256
257
case AMP_BP_ROT_RADIUS_0:
258
break;
259
}
260
261
// Choose a random point along the amp's circle.
262
// The amp's move angle represents its angle along the circle.
263
o->oMoveAngleYaw = random_u16();
264
265
o->oAction = AMP_ACT_IDLE;
266
}
267
268
/**
269
* Main update function for fixed amps.
270
* Fixed amps are a sub-species of circling amps, with circle radius 0.
271
*/
272
static void fixed_circling_amp_idle_loop(void) {
273
// Turn towards Mario, in both yaw and pitch.
274
f32 xToMario = gMarioObject->header.gfx.pos[0] - o->oPosX;
275
f32 yToMario = gMarioObject->header.gfx.pos[1] + 120.0f - o->oPosY;
276
f32 zToMario = gMarioObject->header.gfx.pos[2] - o->oPosZ;
277
s16 vAngleToMario = atan2s(sqrtf(xToMario * xToMario + zToMario * zToMario), -yToMario);
278
279
obj_turn_toward_object(o, gMarioObject, 19, 0x1000);
280
o->oFaceAnglePitch = approach_s16_symmetric(o->oFaceAnglePitch, vAngleToMario, 0x1000);
281
282
// Oscillate 40 units up and down.
283
// Interestingly, 0x458 (1112 in decimal) is a magic number with no apparent significance.
284
// It is slightly larger than the 0x400 figure used for homing amps, which makes
285
// fixed amps oscillate slightly quicker.
286
// Also, this uses the cosine, which starts at 1 instead of 0.
287
o->oPosY = o->oHomeY + coss(o->oAmpYPhase * 0x458) * 20.0f;
288
289
// Handle attacks
290
check_amp_attack();
291
292
// Oscillate
293
o->oAmpYPhase++;
294
295
// Where there is a cur_obj_play_sound_1 call in the main circling amp update function,
296
// there is nothing here. Fixed amps are the only amps that never play
297
// the "amp buzzing" sound.
298
}
299
300
/**
301
* Main update function for regular circling amps.
302
*/
303
static void circling_amp_idle_loop(void) {
304
// Move in a circle.
305
// The Y oscillation uses the magic number 0x8B0 (2224), which is
306
// twice that of the fixed amp. In other words, circling amps will
307
// oscillate twice as fast. Also, unlike all other amps, circling
308
// amps oscillate 60 units around their average Y instead of 40.
309
o->oPosX = o->oHomeX + sins(o->oMoveAngleYaw) * o->oAmpRadiusOfRotation;
310
o->oPosZ = o->oHomeZ + coss(o->oMoveAngleYaw) * o->oAmpRadiusOfRotation;
311
o->oPosY = o->oHomeY + coss(o->oAmpYPhase * 0x8B0) * 30.0f;
312
o->oMoveAngleYaw += 0x400;
313
o->oFaceAngleYaw = o->oMoveAngleYaw + 0x4000;
314
315
// Handle attacks
316
check_amp_attack();
317
318
// Oscillate
319
o->oAmpYPhase++;
320
321
cur_obj_play_sound_1(SOUND_AIR_AMP_BUZZ);
322
}
323
324
/**
325
* Circling amp update function.
326
* This calls the main update functions for both types of circling amps,
327
* and calls the common amp cooldown function when the amp is cooling down.
328
*/
329
void bhv_circling_amp_loop(void) {
330
switch (o->oAction) {
331
case AMP_ACT_IDLE:
332
if (o->oBehParams2ndByte == AMP_BP_ROT_RADIUS_0) {
333
fixed_circling_amp_idle_loop();
334
} else {
335
circling_amp_idle_loop();
336
}
337
338
break;
339
340
case AMP_ACT_ATTACK_COOLDOWN:
341
amp_attack_cooldown_loop();
342
break;
343
}
344
}
345
346