Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rubberduckycooly
GitHub Repository: rubberduckycooly/Sonic-Mania-Decompilation
Path: blob/master/SonicMania/Objects/SBZ/Caterkiller.c
338 views
1
// ---------------------------------------------------------------------
2
// RSDK Project: Sonic Mania
3
// Object Description: Caterkiller Object
4
// Object Author: Christian Whitehead/Simon Thomley/Hunter Bridges
5
// Decompiled by: Rubberduckycooly & RMGRich
6
// ---------------------------------------------------------------------
7
8
#include "Game.h"
9
10
ObjectCaterkiller *Caterkiller = NULL;
11
12
void Caterkiller_Update(void)
13
{
14
RSDK_THIS(Caterkiller);
15
16
StateMachine_Run(self->state);
17
}
18
19
void Caterkiller_LateUpdate(void) {}
20
21
void Caterkiller_StaticUpdate(void) {}
22
23
void Caterkiller_Draw(void)
24
{
25
RSDK_THIS(Caterkiller);
26
27
StateMachine_Run(self->stateDraw);
28
}
29
30
void Caterkiller_Create(void *data)
31
{
32
RSDK_THIS(Caterkiller);
33
34
self->visible = true;
35
if (self->planeFilter > 0 && ((uint8)(self->planeFilter - 1) & 2))
36
self->drawGroup = Zone->objectDrawGroup[1];
37
else
38
self->drawGroup = Zone->objectDrawGroup[0];
39
40
self->drawFX |= FX_FLIP;
41
self->active = ACTIVE_BOUNDS;
42
self->updateRange.x = 0x800000;
43
self->updateRange.y = 0x800000;
44
if (data) {
45
self->state = (Type_StateMachine)data;
46
47
if (self->state == Caterkiller_StateSplit_Head)
48
RSDK.SetSpriteAnimation(Caterkiller->aniFrames, 0, &self->headAnimator, true, 0);
49
else
50
RSDK.SetSpriteAnimation(Caterkiller->aniFrames, 1, &self->headAnimator, true, 0);
51
52
self->stateDraw = Caterkiller_Draw_Segment;
53
}
54
else {
55
self->startPos = self->position;
56
self->startDir = self->direction;
57
58
self->headOffset = 0;
59
int32 offset = self->startDir ? -0xC0000 : 0xC0000;
60
61
int32 posX = self->position.x;
62
for (int32 i = 0; i < CATERKILLER_BODY_COUNT; ++i) {
63
posX += offset;
64
self->bodyPosition[i].x = posX;
65
self->bodyPosition[i].y = self->position.y;
66
self->bodyDirection[i] = self->direction;
67
}
68
69
self->timer = 0;
70
RSDK.SetSpriteAnimation(Caterkiller->aniFrames, 0, &self->headAnimator, true, 0);
71
RSDK.SetSpriteAnimation(Caterkiller->aniFrames, 1, &self->bodyAnimator, true, 0);
72
self->state = Caterkiller_State_Init;
73
self->stateDraw = Caterkiller_Draw_Body;
74
}
75
}
76
77
void Caterkiller_StageLoad(void)
78
{
79
if (RSDK.CheckSceneFolder("MMZ"))
80
Caterkiller->aniFrames = RSDK.LoadSpriteAnimation("MMZ/Caterkiller.bin", SCOPE_STAGE);
81
82
Caterkiller->hitbox.left = -8;
83
Caterkiller->hitbox.top = -8;
84
Caterkiller->hitbox.right = 8;
85
Caterkiller->hitbox.bottom = 8;
86
87
DEBUGMODE_ADD_OBJ(Caterkiller);
88
}
89
90
void Caterkiller_DebugSpawn(void)
91
{
92
RSDK_THIS(DebugMode);
93
94
CREATE_ENTITY(Caterkiller, NULL, self->position.x, self->position.y);
95
}
96
97
void Caterkiller_DebugDraw(void)
98
{
99
RSDK.SetSpriteAnimation(Caterkiller->aniFrames, 0, &DebugMode->animator, true, 0);
100
RSDK.DrawSprite(&DebugMode->animator, NULL, false);
101
}
102
103
void Caterkiller_CheckOffScreen(void)
104
{
105
RSDK_THIS(Caterkiller);
106
107
if (!RSDK.CheckOnScreen(self, NULL) && !RSDK.CheckPosOnScreen(&self->startPos, &self->updateRange)) {
108
self->position = self->startPos;
109
self->direction = self->startDir;
110
Caterkiller_Create(NULL);
111
}
112
}
113
114
void Caterkiller_CheckTileCollisions(void)
115
{
116
RSDK_THIS(Caterkiller);
117
118
int32 storeX = 0;
119
int32 storeY = 0;
120
121
if (self->state == Caterkiller_State_LiftHead) {
122
storeX = self->position.x;
123
storeY = self->position.y;
124
}
125
else {
126
if (!RSDK.ObjectTileGrip(self, Zone->collisionLayers, CMODE_FLOOR, 0, 0, 0x80000, 8))
127
self->direction ^= FLIP_X;
128
129
storeX = self->position.x;
130
storeY = self->position.y;
131
132
if (Caterkiller_CheckTileAngle(self->position.x, self->position.y, self->direction))
133
self->direction ^= FLIP_X;
134
}
135
136
for (int32 i = 0; i < CATERKILLER_BODY_COUNT; ++i) {
137
if (self->state != Caterkiller_State_LowerHead || i != (CATERKILLER_BODY_COUNT - 1)) {
138
self->position.x = self->bodyPosition[i].x;
139
self->position.y = self->bodyPosition[i].y;
140
141
if (!RSDK.ObjectTileGrip(self, Zone->collisionLayers, CMODE_FLOOR, 0, 0, 0x80000, 8))
142
self->bodyDirection[i] = self->direction;
143
144
self->bodyPosition[i].y = self->position.y;
145
if (Caterkiller_CheckTileAngle(self->bodyPosition[i].x, self->bodyPosition[i].y, self->bodyDirection[i]))
146
self->bodyDirection[i] = self->direction;
147
}
148
}
149
150
self->position.x = storeX;
151
self->position.y = storeY;
152
}
153
154
void Caterkiller_Draw_Body(void)
155
{
156
RSDK_THIS(Caterkiller);
157
158
int32 storeDir = self->direction;
159
for (int32 i = CATERKILLER_BODY_COUNT - 1; i >= 0; --i) {
160
Vector2 drawPos = self->bodyPosition[i];
161
drawPos.y -= self->bodyOffset[i] << 15;
162
self->direction = self->bodyDirection[i];
163
RSDK.DrawSprite(&self->bodyAnimator, &drawPos, false);
164
}
165
166
Vector2 drawPos = self->position;
167
drawPos.y -= self->headOffset << 15;
168
self->direction = storeDir;
169
RSDK.DrawSprite(&self->headAnimator, &drawPos, false);
170
}
171
172
void Caterkiller_Draw_Segment(void)
173
{
174
RSDK_THIS(Caterkiller);
175
176
RSDK.DrawSprite(&self->headAnimator, NULL, false);
177
}
178
179
void Caterkiller_HandlePlayerInteractions(void)
180
{
181
RSDK_THIS(Caterkiller);
182
183
int32 storeX = self->position.x;
184
int32 storeY = self->position.y;
185
186
foreach_active(Player, player)
187
{
188
if (self->planeFilter <= 0 || player->collisionPlane == ((self->planeFilter - 1) & 1)) {
189
if (Player_CheckBadnikTouch(player, self, &Caterkiller->hitbox)) {
190
Player_CheckBadnikBreak(player, self, true);
191
}
192
else {
193
for (int32 i = 0; i < CATERKILLER_BODY_COUNT; ++i) {
194
self->position.x = self->bodyPosition[i].x;
195
self->position.y = self->bodyPosition[i].y;
196
197
if (Player_CheckCollisionTouch(player, self, &Caterkiller->hitbox)) {
198
Player_Hurt(player, self);
199
200
for (int32 d = 0; d < CATERKILLER_BODY_COUNT + 1; ++d) {
201
int32 spawnX = storeX;
202
int32 spawnY = storeY;
203
int32 spawnDir = self->direction;
204
StateMachine(spawnState) = Caterkiller_StateSplit_Head;
205
206
if (d) {
207
spawnX = self->bodyPosition[d - 1].x;
208
spawnY = self->bodyPosition[d - 1].y;
209
spawnDir = self->bodyDirection[d - 1];
210
spawnState = Caterkiller_StateSplit_Body;
211
}
212
213
EntityCaterkiller *segment = CREATE_ENTITY(Caterkiller, spawnState, spawnX, spawnY);
214
segment->direction = spawnDir;
215
if (!segment->direction)
216
segment->velocity.x = (d & 1) ? -0x18000 : -0x20000;
217
else
218
segment->velocity.x = (d & 1) ? 0x18000 : 0x20000;
219
220
if ((d & 3) >= 2)
221
segment->velocity.x = -segment->velocity.x;
222
segment->velocity.y = -0x40000;
223
224
if (!d)
225
segment->headAnimator.frameID = self->headAnimator.frameID;
226
227
segment->planeFilter = self->planeFilter;
228
segment->drawGroup = self->drawGroup;
229
}
230
231
destroyEntity(self);
232
self->active = ACTIVE_DISABLED;
233
break;
234
}
235
}
236
}
237
238
self->position.x = storeX;
239
self->position.y = storeY;
240
}
241
}
242
}
243
244
bool32 Caterkiller_CheckTileAngle(int32 x, int32 y, int32 dir)
245
{
246
int32 tx = x >> 16;
247
int32 ty = (y >> 16) + 8;
248
249
uint16 tile = RSDK.GetTile(Zone->fgLayer[1], tx, ty);
250
if (tile == (uint16)-1)
251
tile = RSDK.GetTile(Zone->fgLayer[0], tx, ty);
252
253
uint8 angle = RSDK.GetTileAngle(tile, 0, CMODE_FLOOR);
254
255
if (dir) {
256
if (angle > 0x80 && angle < 0xE8)
257
return true;
258
}
259
else {
260
if (angle > 0x18 && angle < 0x80)
261
return true;
262
}
263
264
return false;
265
}
266
267
void Caterkiller_State_Init(void)
268
{
269
RSDK_THIS(Caterkiller);
270
271
self->active = ACTIVE_NORMAL;
272
273
self->state = Caterkiller_State_Contract;
274
Caterkiller_State_Contract();
275
}
276
277
void Caterkiller_State_Contract(void)
278
{
279
RSDK_THIS(Caterkiller);
280
281
if (self->timer) {
282
self->timer--;
283
Caterkiller_HandlePlayerInteractions();
284
Caterkiller_CheckOffScreen();
285
}
286
else {
287
self->timer = 15;
288
self->headAnimator.frameID = 1;
289
290
self->state = Caterkiller_State_LiftHead;
291
Caterkiller_State_LiftHead();
292
}
293
}
294
295
void Caterkiller_State_LiftHead(void)
296
{
297
RSDK_THIS(Caterkiller);
298
299
if (self->timer) {
300
self->timer--;
301
for (int32 b = 0; b < CATERKILLER_BODY_COUNT; ++b) {
302
if (self->bodyDirection[b])
303
self->bodyPosition[b].x += 0x4000 * (b + 1);
304
else
305
self->bodyPosition[b].x -= 0x4000 * (b + 1);
306
}
307
308
++self->headOffset;
309
++self->bodyOffset[CATERKILLER_BODY_COUNT / 2];
310
}
311
else {
312
self->timer = 7;
313
self->state = Caterkiller_State_Uncontract;
314
}
315
316
Caterkiller_CheckTileCollisions();
317
Caterkiller_HandlePlayerInteractions();
318
Caterkiller_CheckOffScreen();
319
}
320
321
void Caterkiller_State_Uncontract(void)
322
{
323
RSDK_THIS(Caterkiller);
324
325
if (self->timer) {
326
self->timer--;
327
328
Caterkiller_HandlePlayerInteractions();
329
Caterkiller_CheckOffScreen();
330
}
331
else {
332
self->timer = 15;
333
self->headAnimator.frameID = 0;
334
335
self->state = Caterkiller_State_LowerHead;
336
Caterkiller_State_LowerHead();
337
}
338
}
339
340
void Caterkiller_State_LowerHead(void)
341
{
342
RSDK_THIS(Caterkiller);
343
344
if (self->timer) {
345
self->timer--;
346
347
int32 mult = 1;
348
for (int32 b = CATERKILLER_BODY_COUNT - 2; b >= 0; --b) {
349
self->bodyPosition[b].x += self->bodyDirection[b] ? (0x4000 * mult) : (-0x4000 * mult);
350
++mult;
351
}
352
353
self->position.x += self->direction ? (0x4000 * mult) : (-0x4000 * mult);
354
355
--self->headOffset;
356
--self->bodyOffset[CATERKILLER_BODY_COUNT / 2];
357
}
358
else {
359
self->timer = 7;
360
self->state = Caterkiller_State_Contract;
361
}
362
363
Caterkiller_CheckTileCollisions();
364
Caterkiller_HandlePlayerInteractions();
365
Caterkiller_CheckOffScreen();
366
}
367
368
void Caterkiller_StateSplit_Head(void)
369
{
370
RSDK_THIS(Caterkiller);
371
372
self->position.x += self->velocity.x;
373
self->position.y += self->velocity.y;
374
self->velocity.y += 0x3800;
375
376
if (RSDK.CheckOnScreen(self, &self->updateRange)) {
377
if (self->velocity.y > 0 && RSDK.ObjectTileGrip(self, Zone->collisionLayers, CMODE_FLOOR, 0, 0, 0x80000, 4)) {
378
self->velocity.y = -0x40000;
379
}
380
381
foreach_active(Player, player)
382
{
383
if ((self->planeFilter <= 0 || player->collisionPlane == ((uint8)(self->planeFilter - 1) & 1))
384
&& Player_CheckBadnikTouch(player, self, &Caterkiller->hitbox)) {
385
Player_CheckBadnikBreak(player, self, true);
386
}
387
}
388
}
389
else {
390
destroyEntity(self);
391
}
392
}
393
394
void Caterkiller_StateSplit_Body(void)
395
{
396
RSDK_THIS(Caterkiller);
397
398
self->position.x += self->velocity.x;
399
self->position.y += self->velocity.y;
400
self->velocity.y += 0x3800;
401
402
if (RSDK.CheckOnScreen(self, &self->updateRange)) {
403
if (self->velocity.y > 0 && RSDK.ObjectTileGrip(self, Zone->collisionLayers, CMODE_FLOOR, 0, 0, 0x80000, 4)) {
404
self->velocity.y = -0x40000;
405
}
406
407
foreach_active(Player, player)
408
{
409
if (self->planeFilter <= 0 || player->collisionPlane == ((self->planeFilter - 1) & 1)) {
410
if (Player_CheckCollisionTouch(player, self, &Caterkiller->hitbox))
411
Player_Hurt(player, self);
412
}
413
}
414
}
415
else {
416
destroyEntity(self);
417
}
418
}
419
420
#if GAME_INCLUDE_EDITOR
421
void Caterkiller_EditorDraw(void)
422
{
423
RSDK_THIS(Caterkiller);
424
425
self->startPos = self->position;
426
427
self->headOffset = 0;
428
int32 offset = self->startDir ? -0xC0000 : 0xC0000;
429
430
int32 posX = self->position.x;
431
for (int32 i = 0; i < CATERKILLER_BODY_COUNT; ++i) {
432
posX += offset;
433
self->bodyPosition[i].x = posX;
434
self->bodyPosition[i].y = self->position.y;
435
self->bodyDirection[i] = self->direction;
436
}
437
438
RSDK.SetSpriteAnimation(Caterkiller->aniFrames, 0, &self->headAnimator, true, 0);
439
RSDK.SetSpriteAnimation(Caterkiller->aniFrames, 1, &self->bodyAnimator, true, 0);
440
441
Caterkiller_Draw_Body();
442
}
443
444
void Caterkiller_EditorLoad(void)
445
{
446
Caterkiller->aniFrames = RSDK.LoadSpriteAnimation("MMZ/Caterkiller.bin", SCOPE_STAGE);
447
448
RSDK_ACTIVE_VAR(Caterkiller, planeFilter);
449
RSDK_ENUM_VAR("None", PLANEFILTER_NONE);
450
RSDK_ENUM_VAR("AL", PLANEFILTER_AL);
451
RSDK_ENUM_VAR("BL", PLANEFILTER_BL);
452
RSDK_ENUM_VAR("AH", PLANEFILTER_AH);
453
RSDK_ENUM_VAR("BH", PLANEFILTER_BH);
454
}
455
#endif
456
457
void Caterkiller_Serialize(void) { RSDK_EDITABLE_VAR(Caterkiller, VAR_ENUM, planeFilter); }
458
459