Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
MorsGames
GitHub Repository: MorsGames/sm64plus
Path: blob/master/src/game/behaviors/chain_chomp.inc.c
7861 views
1
2
/**
3
* Behavior for bhvChainChomp, bhvChainChompChainPart, bhvWoodenPost, and bhvChainChompGate.
4
* bhvChainChomp spawns its bhvWoodenPost in its behavior script. It spawns 5 chain
5
* parts. Part 0 is the "pivot", which is positioned at the wooden post while
6
* the chomp is chained up. Parts 1-4 are the other parts, starting from the
7
* chain chomp and moving toward the pivot.
8
* Processing order is bhvWoodenPost, bhvChainChompGate, bhvChainChomp, bhvChainChompChainPart.
9
* The chain parts are processed starting at the post and ending at the chomp.
10
*/
11
#include "../settings.h"
12
13
/**
14
* Hitbox for chain chomp.
15
*/
16
static struct ObjectHitbox sChainChompHitbox = {
17
/* interactType: */ INTERACT_MR_BLIZZARD,
18
/* downOffset: */ 0,
19
/* damageOrCoinValue: */ 3,
20
/* health: */ 1,
21
/* numLootCoins: */ 0,
22
/* radius: */ 80,
23
/* height: */ 160,
24
/* hurtboxRadius: */ 80,
25
/* hurtboxHeight: */ 160,
26
};
27
28
/**
29
* Update function for chain chomp part / pivot.
30
*/
31
void bhv_chain_chomp_chain_part_update(void) {
32
if (o->parentObj->oAction == CHAIN_CHOMP_ACT_UNLOAD_CHAIN) {
33
obj_mark_for_deletion(o);
34
} else if (o->oBehParams2ndByte != CHAIN_CHOMP_CHAIN_PART_BP_PIVOT) {
35
struct ChainSegment *segment = &o->parentObj->oChainChompSegments[o->oBehParams2ndByte];
36
37
// Set position relative to the pivot
38
o->oPosX = o->parentObj->parentObj->oPosX + segment->posX;
39
o->oPosY = o->parentObj->parentObj->oPosY + segment->posY;
40
o->oPosZ = o->parentObj->parentObj->oPosZ + segment->posZ;
41
} else if (o->parentObj->oChainChompReleaseStatus != CHAIN_CHOMP_NOT_RELEASED) {
42
cur_obj_update_floor_and_walls();
43
cur_obj_move_standard(78);
44
}
45
}
46
47
/**
48
* When mario gets close enough, allocate chain segments and spawn their objects.
49
*/
50
static void chain_chomp_act_uninitialized(void) {
51
struct ChainSegment *segments;
52
s32 i;
53
54
if (o->oDistanceToMario < 3000.0f * configDrawDistanceMultiplier || configDrawDistanceMultiplier <= 0.0f) {
55
segments = mem_pool_alloc(gObjectMemoryPool, 5 * sizeof(struct ChainSegment));
56
if (segments != NULL) {
57
// Each segment represents the offset of a chain part to the pivot.
58
// Segment 0 connects the pivot to the chain chomp itself. Segment
59
// 1 connects the pivot to the chain part next to the chain chomp
60
// (chain part 1), etc.
61
o->oChainChompSegments = segments;
62
for (i = 0; i <= 4; i++) {
63
chain_segment_init(&segments[i]);
64
}
65
66
cur_obj_set_pos_to_home();
67
68
// Spawn the pivot and set to parent
69
if ((o->parentObj =
70
spawn_object(o, CHAIN_CHOMP_CHAIN_PART_BP_PIVOT, bhvChainChompChainPart))
71
!= NULL) {
72
// Spawn the non-pivot chain parts, starting from the chain
73
// chomp and moving toward the pivot
74
for (i = 1; i <= 4; i++) {
75
spawn_object_relative(i, 0, 0, 0, o, MODEL_METALLIC_BALL, bhvChainChompChainPart);
76
}
77
78
o->oAction = CHAIN_CHOMP_ACT_MOVE;
79
cur_obj_unhide();
80
}
81
}
82
}
83
}
84
85
/**
86
* Apply gravity to each chain part, and cap its distance to the previous
87
* part as well as from the pivot.
88
*/
89
static void chain_chomp_update_chain_segments(void) {
90
struct ChainSegment *prevSegment;
91
struct ChainSegment *segment;
92
f32 offsetX;
93
f32 offsetY;
94
f32 offsetZ;
95
f32 offset;
96
f32 segmentVelY;
97
f32 maxTotalOffset;
98
s32 i;
99
100
if (o->oVelY < 0.0f) {
101
segmentVelY = o->oVelY;
102
} else {
103
segmentVelY = -20.0f;
104
}
105
106
// Segment 0 connects the pivot to the chain chomp itself, and segment i>0
107
// connects the pivot to chain part i (1 is closest to the chain chomp).
108
109
for (i = 1; i <= 4; i++) {
110
prevSegment = &o->oChainChompSegments[i - 1];
111
segment = &o->oChainChompSegments[i];
112
113
// Apply gravity
114
115
if ((segment->posY += segmentVelY) < 0.0f) {
116
segment->posY = 0.0f;
117
}
118
119
// Cap distance to previous chain part (so that the tail follows the
120
// chomp)
121
122
offsetX = segment->posX - prevSegment->posX;
123
offsetY = segment->posY - prevSegment->posY;
124
offsetZ = segment->posZ - prevSegment->posZ;
125
offset = sqrtf(offsetX * offsetX + offsetY * offsetY + offsetZ * offsetZ);
126
127
if (offset > o->oChainChompMaxDistBetweenChainParts) {
128
offset = o->oChainChompMaxDistBetweenChainParts / offset;
129
offsetX *= offset;
130
offsetY *= offset;
131
offsetZ *= offset;
132
}
133
134
// Cap distance to pivot (so that it stretches when the chomp moves far
135
// from the wooden post)
136
137
offsetX += prevSegment->posX;
138
offsetY += prevSegment->posY;
139
offsetZ += prevSegment->posZ;
140
offset = sqrtf(offsetX * offsetX + offsetY * offsetY + offsetZ * offsetZ);
141
142
maxTotalOffset = o->oChainChompMaxDistFromPivotPerChainPart * (5 - i);
143
if (offset > maxTotalOffset) {
144
offset = maxTotalOffset / offset;
145
offsetX *= offset;
146
offsetY *= offset;
147
offsetZ *= offset;
148
}
149
150
segment->posX = offsetX;
151
segment->posY = offsetY;
152
segment->posZ = offsetZ;
153
}
154
}
155
156
/**
157
* Lunging increases the maximum distance from the pivot and changes the maximum
158
* distance between chain parts. Restore these values to normal.
159
*/
160
static void chain_chomp_restore_normal_chain_lengths(void) {
161
approach_f32_ptr(&o->oChainChompMaxDistFromPivotPerChainPart, 750.0f / 5, 4.0f);
162
o->oChainChompMaxDistBetweenChainParts = o->oChainChompMaxDistFromPivotPerChainPart;
163
}
164
165
/**
166
* Turn toward mario. Wait a bit and then enter the lunging sub-action.
167
*/
168
static void chain_chomp_sub_act_turn(void) {
169
o->oGravity = -4.0f;
170
171
chain_chomp_restore_normal_chain_lengths();
172
obj_move_pitch_approach(0, 0x100);
173
174
if (o->oMoveFlags & OBJ_MOVE_MASK_ON_GROUND) {
175
cur_obj_rotate_yaw_toward(o->oAngleToMario, 0x400);
176
if (abs_angle_diff(o->oAngleToMario, o->oMoveAngleYaw) < 0x800) {
177
if (o->oTimer > 30) {
178
if (cur_obj_check_anim_frame(0)) {
179
cur_obj_reverse_animation();
180
if (o->oTimer > 40) {
181
// Increase the maximum distance from the pivot and enter
182
// the lunging sub-action.
183
cur_obj_play_sound_2(SOUND_GENERAL_CHAIN_CHOMP2);
184
185
o->oSubAction = CHAIN_CHOMP_SUB_ACT_LUNGE;
186
o->oChainChompMaxDistFromPivotPerChainPart = 900.0f / 5;
187
188
o->oForwardVel = 140.0f;
189
o->oVelY = 20.0f;
190
o->oGravity = 0.0f;
191
o->oChainChompTargetPitch = obj_get_pitch_from_vel();
192
}
193
} else {
194
o->oTimer -= 1;
195
}
196
} else {
197
o->oForwardVel = 0.0f;
198
}
199
} else {
200
cur_obj_play_sound_2(SOUND_GENERAL_CHAIN_CHOMP1);
201
o->oForwardVel = 10.0f;
202
o->oVelY = 20.0f;
203
}
204
} else {
205
cur_obj_rotate_yaw_toward(o->oAngleToMario, 0x190);
206
o->oTimer = 0;
207
}
208
}
209
210
static void chain_chomp_sub_act_lunge(void) {
211
obj_face_pitch_approach(o->oChainChompTargetPitch, 0x400);
212
213
if (o->oForwardVel != 0.0f) {
214
f32 val04;
215
216
if (o->oChainChompRestrictedByChain == TRUE) {
217
o->oForwardVel = o->oVelY = 0.0f;
218
o->oChainChompUnk104 = 30.0f;
219
}
220
221
// TODO: What is this
222
if ((val04 = 900.0f - o->oChainChompDistToPivot) > 220.0f) {
223
val04 = 220.0f;
224
}
225
226
o->oChainChompMaxDistBetweenChainParts =
227
val04 / 220.0f * o->oChainChompMaxDistFromPivotPerChainPart;
228
o->oTimer = 0;
229
} else {
230
// Turn toward pivot
231
cur_obj_rotate_yaw_toward(atan2s(o->oChainChompSegments[0].posZ, o->oChainChompSegments[0].posX),
232
0x1000);
233
234
if (o->oChainChompUnk104 != 0.0f) {
235
approach_f32_ptr(&o->oChainChompUnk104, 0.0f, 0.8f);
236
} else {
237
o->oSubAction = CHAIN_CHOMP_SUB_ACT_TURN;
238
}
239
240
o->oChainChompMaxDistBetweenChainParts = o->oChainChompUnk104;
241
if (gGlobalTimer % 2 != 0) {
242
o->oChainChompMaxDistBetweenChainParts = -o->oChainChompUnk104;
243
}
244
}
245
246
if (o->oTimer < 30) {
247
cur_obj_reverse_animation();
248
}
249
}
250
251
/**
252
* Fall to the ground and interrupt mario into a cutscene action.
253
*/
254
static void chain_chomp_released_trigger_cutscene(void) {
255
o->oForwardVel = 0.0f;
256
o->oGravity = -4.0f;
257
258
//! Can delay this if we get into a cutscene-unfriendly action after the
259
// last post ground pound and before this
260
if (set_mario_npc_dialog(MARIO_DIALOG_LOOK_UP) == MARIO_DIALOG_STATUS_SPEAK
261
&& (o->oMoveFlags & OBJ_MOVE_MASK_ON_GROUND) && cutscene_object(CUTSCENE_STAR_SPAWN, o) == 1) {
262
o->oChainChompReleaseStatus = CHAIN_CHOMP_RELEASED_LUNGE_AROUND;
263
o->oTimer = 0;
264
}
265
}
266
267
/**
268
* Lunge 4 times, each time moving toward mario +/- 0x2000 angular units.
269
* Finally, begin a lunge toward x=1450, z=562 (near the gate).
270
*/
271
static void chain_chomp_released_lunge_around(void) {
272
chain_chomp_restore_normal_chain_lengths();
273
274
// Finish bounce
275
if (o->oMoveFlags & OBJ_MOVE_MASK_ON_GROUND) {
276
// Before first bounce, turn toward mario and wait 2 seconds
277
if (o->oChainChompNumLunges == 0) {
278
if (cur_obj_rotate_yaw_toward(o->oAngleToMario, 0x320)) {
279
if (o->oTimer > 60) {
280
o->oChainChompNumLunges += 1;
281
// enable wall collision
282
o->oWallHitboxRadius = 200.0f;
283
}
284
} else {
285
o->oTimer = 0;
286
}
287
} else {
288
if (++o->oChainChompNumLunges <= 5) {
289
cur_obj_play_sound_2(SOUND_GENERAL_CHAIN_CHOMP1);
290
o->oMoveAngleYaw = o->oAngleToMario + random_sign() * 0x2000;
291
o->oForwardVel = 30.0f;
292
o->oVelY = 50.0f;
293
} else {
294
o->oChainChompReleaseStatus = CHAIN_CHOMP_RELEASED_BREAK_GATE;
295
o->oHomeX = 1450.0f;
296
o->oHomeZ = 562.0f;
297
o->oMoveAngleYaw = cur_obj_angle_to_home();
298
o->oForwardVel = cur_obj_lateral_dist_to_home() / 8;
299
o->oVelY = 50.0f;
300
}
301
}
302
}
303
}
304
305
/**
306
* Continue lunging until a wall collision occurs. Mark the gate as destroyed,
307
* wait for the chain chomp to land, and then begin a jump toward the final
308
* target, x=3288, z=-1770.
309
*/
310
static void chain_chomp_released_break_gate(void) {
311
if (!o->oChainChompHitGate) {
312
// If hit wall, assume it's the gate and bounce off of it
313
//! The wall may not be the gate
314
//! If the chain chomp gets stuck, it may never hit a wall, resulting
315
// in a softlock
316
if (o->oMoveFlags & OBJ_MOVE_HIT_WALL) {
317
o->oChainChompHitGate = TRUE;
318
o->oMoveAngleYaw = cur_obj_reflect_move_angle_off_wall();
319
o->oForwardVel *= 0.4f;
320
}
321
} else if (o->oMoveFlags & OBJ_MOVE_MASK_ON_GROUND) {
322
o->oChainChompReleaseStatus = CHAIN_CHOMP_RELEASED_JUMP_AWAY;
323
o->oHomeX = 3288.0f;
324
o->oHomeZ = -1770.0f;
325
o->oMoveAngleYaw = cur_obj_angle_to_home();
326
o->oForwardVel = cur_obj_lateral_dist_to_home() / 50.0f;
327
o->oVelY = 120.0f;
328
}
329
}
330
331
/**
332
* Wait until the chain chomp lands.
333
*/
334
static void chain_chomp_released_jump_away(void) {
335
if (o->oMoveFlags & OBJ_MOVE_MASK_ON_GROUND) {
336
gObjCutsceneDone = TRUE;
337
o->oChainChompReleaseStatus = CHAIN_CHOMP_RELEASED_END_CUTSCENE;
338
}
339
}
340
341
/**
342
* Release mario and transition to the unload chain action.
343
*/
344
static void chain_chomp_released_end_cutscene(void) {
345
if (cutscene_object(CUTSCENE_STAR_SPAWN, o) == -1) {
346
set_mario_npc_dialog(MARIO_DIALOG_STOP);
347
o->oAction = CHAIN_CHOMP_ACT_UNLOAD_CHAIN;
348
}
349
}
350
351
/**
352
* All chain chomp movement behavior, including the cutscene after being
353
* released.
354
*/
355
static void chain_chomp_act_move(void) {
356
f32 maxDistToPivot;
357
358
// Unload chain if mario is far enough
359
if (o->oChainChompReleaseStatus == CHAIN_CHOMP_NOT_RELEASED && o->oDistanceToMario > 4000.0f * configDrawDistanceMultiplier && configDrawDistanceMultiplier > 0.0f) {
360
o->oAction = CHAIN_CHOMP_ACT_UNLOAD_CHAIN;
361
o->oForwardVel = o->oVelY = 0.0f;
362
} else {
363
cur_obj_update_floor_and_walls();
364
365
switch (o->oChainChompReleaseStatus) {
366
case CHAIN_CHOMP_NOT_RELEASED:
367
switch (o->oSubAction) {
368
case CHAIN_CHOMP_SUB_ACT_TURN:
369
chain_chomp_sub_act_turn();
370
break;
371
case CHAIN_CHOMP_SUB_ACT_LUNGE:
372
chain_chomp_sub_act_lunge();
373
break;
374
}
375
break;
376
case CHAIN_CHOMP_RELEASED_TRIGGER_CUTSCENE:
377
chain_chomp_released_trigger_cutscene();
378
break;
379
case CHAIN_CHOMP_RELEASED_LUNGE_AROUND:
380
chain_chomp_released_lunge_around();
381
break;
382
case CHAIN_CHOMP_RELEASED_BREAK_GATE:
383
chain_chomp_released_break_gate();
384
break;
385
case CHAIN_CHOMP_RELEASED_JUMP_AWAY:
386
chain_chomp_released_jump_away();
387
break;
388
case CHAIN_CHOMP_RELEASED_END_CUTSCENE:
389
chain_chomp_released_end_cutscene();
390
break;
391
}
392
393
cur_obj_move_standard(78);
394
395
// Segment 0 connects the pivot to the chain chomp itself
396
o->oChainChompSegments[0].posX = o->oPosX - o->parentObj->oPosX;
397
o->oChainChompSegments[0].posY = o->oPosY - o->parentObj->oPosY;
398
o->oChainChompSegments[0].posZ = o->oPosZ - o->parentObj->oPosZ;
399
400
o->oChainChompDistToPivot =
401
sqrtf(o->oChainChompSegments[0].posX * o->oChainChompSegments[0].posX
402
+ o->oChainChompSegments[0].posY * o->oChainChompSegments[0].posY
403
+ o->oChainChompSegments[0].posZ * o->oChainChompSegments[0].posZ);
404
405
// If the chain is fully stretched
406
maxDistToPivot = o->oChainChompMaxDistFromPivotPerChainPart * 5;
407
if (o->oChainChompDistToPivot > maxDistToPivot) {
408
f32 ratio = maxDistToPivot / o->oChainChompDistToPivot;
409
o->oChainChompDistToPivot = maxDistToPivot;
410
411
o->oChainChompSegments[0].posX *= ratio;
412
o->oChainChompSegments[0].posY *= ratio;
413
o->oChainChompSegments[0].posZ *= ratio;
414
415
if (o->oChainChompReleaseStatus == CHAIN_CHOMP_NOT_RELEASED) {
416
// Restrict chain chomp position
417
o->oPosX = o->parentObj->oPosX + o->oChainChompSegments[0].posX;
418
o->oPosY = o->parentObj->oPosY + o->oChainChompSegments[0].posY;
419
o->oPosZ = o->parentObj->oPosZ + o->oChainChompSegments[0].posZ;
420
421
o->oChainChompRestrictedByChain = TRUE;
422
} else {
423
// Move pivot like the chain chomp is pulling it along
424
f32 oldPivotY = o->parentObj->oPosY;
425
426
o->parentObj->oPosX = o->oPosX - o->oChainChompSegments[0].posX;
427
o->parentObj->oPosY = o->oPosY - o->oChainChompSegments[0].posY;
428
o->parentObj->oVelY = o->parentObj->oPosY - oldPivotY;
429
o->parentObj->oPosZ = o->oPosZ - o->oChainChompSegments[0].posZ;
430
}
431
} else {
432
o->oChainChompRestrictedByChain = FALSE;
433
}
434
435
chain_chomp_update_chain_segments();
436
437
// Begin a lunge if mario tries to attack
438
if (obj_check_attacks(&sChainChompHitbox, o->oAction)) {
439
o->oSubAction = CHAIN_CHOMP_SUB_ACT_LUNGE;
440
o->oChainChompMaxDistFromPivotPerChainPart = 900.0f / 5;
441
o->oForwardVel = 0.0f;
442
o->oVelY = 300.0f;
443
o->oGravity = -4.0f;
444
o->oChainChompTargetPitch = -0x3000;
445
}
446
}
447
}
448
449
/**
450
* Hide and free the chain chomp segments. The chain objects will unload
451
* themselves when they see that the chain chomp is in this action.
452
*/
453
static void chain_chomp_act_unload_chain(void) {
454
cur_obj_hide();
455
mem_pool_free(gObjectMemoryPool, o->oChainChompSegments);
456
457
o->oAction = CHAIN_CHOMP_ACT_UNINITIALIZED;
458
459
if (o->oChainChompReleaseStatus != CHAIN_CHOMP_NOT_RELEASED) {
460
obj_mark_for_deletion(o);
461
}
462
}
463
464
/**
465
* Update function for chain chomp.
466
*/
467
void bhv_chain_chomp_update(void) {
468
switch (o->oAction) {
469
case CHAIN_CHOMP_ACT_UNINITIALIZED:
470
chain_chomp_act_uninitialized();
471
break;
472
case CHAIN_CHOMP_ACT_MOVE:
473
chain_chomp_act_move();
474
break;
475
case CHAIN_CHOMP_ACT_UNLOAD_CHAIN:
476
chain_chomp_act_unload_chain();
477
break;
478
}
479
}
480
481
/**
482
* Update function for wooden post.
483
*/
484
void bhv_wooden_post_update(void) {
485
// When ground pounded by mario, drop by -45 + -20
486
if (!o->oWoodenPostMarioPounding) {
487
if ((o->oWoodenPostMarioPounding = cur_obj_is_mario_ground_pounding_platform())) {
488
cur_obj_play_sound_2(SOUND_GENERAL_POUND_WOOD_POST);
489
o->oWoodenPostSpeedY = -70.0f;
490
}
491
} else if (approach_f32_ptr(&o->oWoodenPostSpeedY, 0.0f, 25.0f)) {
492
// Stay still until mario is done ground pounding
493
o->oWoodenPostMarioPounding = cur_obj_is_mario_ground_pounding_platform();
494
} else if ((o->oWoodenPostOffsetY += o->oWoodenPostSpeedY) < -190.0f) {
495
// Once pounded, if this is the chain chomp's post, release the chain
496
// chomp
497
o->oWoodenPostOffsetY = -190.0f;
498
if (o->parentObj != o) {
499
play_puzzle_jingle();
500
o->parentObj->oChainChompReleaseStatus = CHAIN_CHOMP_RELEASED_TRIGGER_CUTSCENE;
501
o->parentObj = o;
502
}
503
}
504
505
if (o->oWoodenPostOffsetY != 0.0f) {
506
o->oPosY = o->oHomeY + o->oWoodenPostOffsetY;
507
} else if (!(o->oBehParams & WOODEN_POST_BP_NO_COINS_MASK)) {
508
// Reset the timer once mario is far enough
509
if (o->oDistanceToMario > 400.0f) {
510
o->oTimer = o->oWoodenPostTotalMarioAngle = 0;
511
} else {
512
// When mario runs around the post 3 times within 200 frames, spawn
513
// coins
514
o->oWoodenPostTotalMarioAngle += (s16)(o->oAngleToMario - o->oWoodenPostPrevAngleToMario);
515
if (absi(o->oWoodenPostTotalMarioAngle) > 0x30000 && o->oTimer < 200) {
516
obj_spawn_loot_yellow_coins(o, 5, 20.0f);
517
set_object_respawn_info_bits(o, 1);
518
}
519
}
520
521
o->oWoodenPostPrevAngleToMario = o->oAngleToMario;
522
}
523
}
524
525
/**
526
* Init function for chain chomp gate.
527
*/
528
void bhv_chain_chomp_gate_init(void) {
529
o->parentObj = cur_obj_nearest_object_with_behavior(bhvChainChomp);
530
}
531
532
/**
533
* Update function for chain chomp gate
534
*/
535
void bhv_chain_chomp_gate_update(void) {
536
if (o->parentObj->oChainChompHitGate) {
537
spawn_mist_particles_with_sound(SOUND_GENERAL_WALL_EXPLOSION);
538
set_camera_shake_from_point(SHAKE_POS_SMALL, o->oPosX, o->oPosY, o->oPosZ);
539
spawn_mist_particles_variable(0, 0x7F, 200.0f);
540
spawn_triangle_break_particles(30, MODEL_DIRT_ANIMATION, 3.0f, 4);
541
obj_mark_for_deletion(o);
542
}
543
}
544
545