Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rubberduckycooly
GitHub Repository: rubberduckycooly/Sonic-Mania-Decompilation
Path: blob/master/SonicMania/Objects/OOZ/BallCannon.c
338 views
1
// ---------------------------------------------------------------------
2
// RSDK Project: Sonic Mania
3
// Object Description: BallCannon Object
4
// Object Author: Christian Whitehead/Simon Thomley/Hunter Bridges
5
// Decompiled by: Rubberduckycooly & RMGRich
6
// ---------------------------------------------------------------------
7
8
#include "Game.h"
9
10
ObjectBallCannon *BallCannon;
11
12
void BallCannon_Update(void)
13
{
14
RSDK_THIS(BallCannon);
15
16
StateMachine_Run(self->state);
17
}
18
19
void BallCannon_LateUpdate(void) {}
20
21
void BallCannon_StaticUpdate(void) {}
22
23
void BallCannon_Draw(void)
24
{
25
RSDK_THIS(BallCannon);
26
27
RSDK.DrawSprite(&self->animator, NULL, false);
28
}
29
30
void BallCannon_Create(void *data)
31
{
32
RSDK_THIS(BallCannon);
33
34
self->drawGroup = Zone->playerDrawGroup[0];
35
self->visible = true;
36
37
if (!SceneInfo->inEditor) {
38
if (data) {
39
self->visible = true;
40
self->drawFX = FX_ROTATE;
41
self->active = ACTIVE_NORMAL;
42
self->rotationSpeed = RSDK.Rand(-8, 8);
43
44
RSDK.SetSpriteAnimation(BallCannon->aniFrames, 3, &self->animator, true, VOID_TO_INT(data));
45
self->state = BallCannon_State_CorkDebris;
46
}
47
else {
48
self->drawFX = FX_ROTATE | FX_FLIP;
49
self->active = ACTIVE_BOUNDS;
50
self->updateRange.x = 0x400000;
51
self->updateRange.y = 0x400000;
52
53
switch (self->type) {
54
case BALLCANNON_CANNON:
55
if (self->angle >= 4)
56
self->direction = FLIP_X;
57
58
self->rotation = (self->angle + self->direction + 1) << 7;
59
60
switch (self->angle) {
61
case BALLCANNON_DIR_RIGHT_CW: // Right -> Down
62
case BALLCANNON_DIR_LEFT_CCW: // Left -> Down
63
self->velocity.y = 0x100000;
64
break;
65
66
case BALLCANNON_DIR_DOWN_CW: // Down -> Left
67
case BALLCANNON_DIR_UP_CCW: // Up -> Left
68
self->velocity.x = -0x100000;
69
break;
70
71
case BALLCANNON_DIR_LEFT_CW: // Left -> Up
72
case BALLCANNON_DIR_RIGHT_CCW: // Right -> Up
73
self->velocity.y = -0x100000;
74
break;
75
76
case BALLCANNON_DIR_UP_CW: // Up -> Right
77
case BALLCANNON_DIR_DOWN_CCW: // Down -> Right
78
self->velocity.x = 0x100000;
79
break;
80
81
default: break;
82
}
83
RSDK.SetSpriteAnimation(BallCannon->aniFrames, 0, &self->animator, true, 0);
84
self->state = BallCannon_State_Idle;
85
break;
86
87
case BALLCANNON_CORKV:
88
RSDK.SetSpriteAnimation(BallCannon->aniFrames, 3, &self->animator, true, 0);
89
self->velocity.y = -0x80000;
90
self->state = BallCannon_State_CorkBlocked;
91
break;
92
93
case BALLCANNON_CORKH:
94
RSDK.SetSpriteAnimation(BallCannon->aniFrames, 4, &self->animator, true, 0);
95
self->velocity.x = 0x80000;
96
self->state = BallCannon_State_CorkBlocked;
97
break;
98
}
99
}
100
}
101
}
102
103
void BallCannon_StageLoad(void)
104
{
105
BallCannon->aniFrames = RSDK.LoadSpriteAnimation("OOZ/BallCannon.bin", SCOPE_STAGE);
106
107
BallCannon->hitboxCannon.top = -4;
108
BallCannon->hitboxCannon.left = -4;
109
BallCannon->hitboxCannon.right = 4;
110
BallCannon->hitboxCannon.bottom = 4;
111
112
BallCannon->hitboxCorkBlock.top = -16;
113
BallCannon->hitboxCorkBlock.left = -16;
114
BallCannon->hitboxCorkBlock.right = 16;
115
BallCannon->hitboxCorkBlock.bottom = 16;
116
117
BallCannon->hitboxCorkEntry.top = -4;
118
BallCannon->hitboxCorkEntry.left = -8;
119
BallCannon->hitboxCorkEntry.right = 8;
120
BallCannon->hitboxCorkEntry.bottom = 4;
121
122
BallCannon->sfxLedgeBreak = RSDK.GetSfx("Stage/LedgeBreak.wav");
123
BallCannon->sfxFire = RSDK.GetSfx("Stage/CannonFire.wav");
124
}
125
126
void BallCannon_CheckPlayerEntry(void)
127
{
128
RSDK_THIS(BallCannon);
129
130
if (RSDK.CheckOnScreen(self, NULL)) {
131
foreach_all(Player, player)
132
{
133
if (Player_CheckValidState(player)) {
134
int32 playerID = RSDK.GetEntitySlot(player);
135
136
if (self->playerTimers[playerID]) {
137
self->playerTimers[playerID]--;
138
}
139
else {
140
if ((1 << playerID) & self->activePlayers) {
141
if (player->state != Player_State_Static)
142
self->activePlayers &= ~(1 << playerID);
143
}
144
else {
145
if (Player_CheckCollisionTouch(player, self, &BallCannon->hitboxCannon)) {
146
RSDK.PlaySfx(Player->sfxRoll, false, 0xFF);
147
RSDK.SetSpriteAnimation(player->aniFrames, ANI_JUMP, &player->animator, false, 0);
148
149
player->position.x = self->position.x;
150
player->position.y = self->position.y;
151
player->velocity.x = 0;
152
player->velocity.y = 0;
153
player->tileCollisions = TILECOLLISION_NONE;
154
player->interaction = false;
155
player->blinkTimer = 0;
156
player->visible = false;
157
player->state = Player_State_Static;
158
self->activePlayers |= 1 << playerID;
159
self->active = ACTIVE_NORMAL;
160
self->rotation = (self->angle + self->direction + 1) << 7;
161
self->drawFX = FX_ROTATE;
162
RSDK.SetSpriteAnimation(BallCannon->aniFrames, 0, &self->animator, true, 0);
163
self->state = BallCannon_State_Inserted;
164
}
165
}
166
}
167
}
168
}
169
}
170
else {
171
self->active = ACTIVE_BOUNDS;
172
self->rotation = (self->angle + self->direction + 1) << 7;
173
}
174
}
175
176
void BallCannon_State_Idle(void) { BallCannon_CheckPlayerEntry(); }
177
178
void BallCannon_State_Inserted(void)
179
{
180
RSDK_THIS(BallCannon);
181
182
BallCannon_CheckPlayerEntry();
183
184
RSDK.ProcessAnimation(&self->animator);
185
186
if (self->animator.frameID == self->animator.frameCount - 1) {
187
RSDK.SetSpriteAnimation(BallCannon->aniFrames, 1, &self->animator, true, 0);
188
self->drawFX = FX_FLIP;
189
self->state = BallCannon_State_Turning;
190
}
191
}
192
193
void BallCannon_State_Turning(void)
194
{
195
RSDK_THIS(BallCannon);
196
197
BallCannon_CheckPlayerEntry();
198
199
RSDK.ProcessAnimation(&self->animator);
200
201
if (self->animator.frameID == self->animator.frameCount - 1) {
202
RSDK.SetSpriteAnimation(BallCannon->aniFrames, 2, &self->animator, true, 0);
203
self->drawFX = FX_ROTATE;
204
self->state = BallCannon_State_EjectPlayer;
205
self->rotation = (self->angle - self->direction + 2) << 7;
206
}
207
}
208
209
void BallCannon_State_EjectPlayer(void)
210
{
211
RSDK_THIS(BallCannon);
212
213
BallCannon_CheckPlayerEntry();
214
215
RSDK.ProcessAnimation(&self->animator);
216
217
if (self->animator.frameID == self->animator.frameCount - 1) {
218
foreach_all(Player, player)
219
{
220
if (Player_CheckValidState(player)) {
221
int32 playerID = RSDK.GetEntitySlot(player);
222
223
if (((1 << playerID) & self->activePlayers)) {
224
RSDK.PlaySfx(BallCannon->sfxFire, false, 0xFF);
225
226
player->velocity = self->velocity;
227
player->visible = true;
228
229
if (self->exit) {
230
player->onGround = false;
231
player->applyJumpCap = false;
232
player->state = Player_State_Air;
233
player->tileCollisions = TILECOLLISION_DOWN;
234
player->interaction = true;
235
}
236
237
self->activePlayers &= ~(1 << playerID);
238
self->playerTimers[playerID] = 15;
239
}
240
}
241
}
242
243
self->state = BallCannon_State_Idle;
244
}
245
}
246
247
void BallCannon_State_CorkBlocked(void)
248
{
249
RSDK_THIS(BallCannon);
250
251
foreach_active(Player, player)
252
{
253
Animator animator;
254
255
memcpy(&animator, &player->animator, sizeof(Animator));
256
int32 storeX = player->position.x;
257
int32 storeY = player->position.y;
258
int32 storeVelX = player->velocity.x;
259
int32 storeVelY = player->velocity.y;
260
261
if (Player_CheckCollisionBox(player, self, &BallCannon->hitboxCorkBlock) == C_TOP) {
262
if (player->animator.animationID == ANI_JUMP || player->state == Player_State_DropDash
263
#if MANIA_USE_PLUS
264
|| player->state == Player_State_MightyHammerDrop
265
#endif
266
) {
267
if (storeVelY >= 0 && !player->groundedStore) {
268
for (int32 i = 0; i < 16; ++i) {
269
// Bug Details:
270
// The original starts iterating 1 element before each corkDebris____ array.
271
// The code below was fixed. To reproduce the bug, change the offsets: "+ 0" ---> "- 1" and "+ 1" ---> "+ 0"
272
EntityBallCannon *debris =
273
CREATE_ENTITY(BallCannon, INT_TO_VOID((i & 3) + 1), self->position.x + BallCannon->corkDebrisOffset[(i * 2) + 0],
274
self->position.y + BallCannon->corkDebrisOffset[(i * 2) + 1]);
275
debris->velocity.x = BallCannon->corkDebrisVelocity[(i * 2) + 0];
276
debris->velocity.y = BallCannon->corkDebrisVelocity[(i * 2) + 1];
277
}
278
279
RSDK.PlaySfx(BallCannon->sfxLedgeBreak, false, 0xFF);
280
281
memcpy(&player->animator, &animator, sizeof(Animator));
282
player->velocity.x = storeVelX;
283
player->velocity.y = storeVelY;
284
player->position.x = storeX;
285
player->position.y = storeY;
286
player->onGround = false;
287
self->active = ACTIVE_NORMAL;
288
self->visible = false;
289
self->state = BallCannon_State_CorkOpened;
290
291
foreach_break;
292
}
293
}
294
}
295
}
296
}
297
298
void BallCannon_State_CorkOpened(void)
299
{
300
RSDK_THIS(BallCannon);
301
302
if (RSDK.CheckOnScreen(self, NULL)) {
303
foreach_active(Player, player)
304
{
305
int32 playerID = RSDK.GetEntitySlot(player);
306
307
if (self->playerTimers[playerID]) {
308
self->playerTimers[playerID]--;
309
}
310
else {
311
if (Player_CheckCollisionTouch(player, self, &BallCannon->hitboxCorkEntry)) {
312
RSDK.SetSpriteAnimation(player->aniFrames, ANI_JUMP, &player->animator, false, 0);
313
RSDK.PlaySfx(BallCannon->sfxFire, false, 0xFF);
314
315
player->state = Player_State_Static;
316
player->nextGroundState = StateMachine_None;
317
player->nextAirState = StateMachine_None;
318
player->position = self->position;
319
player->velocity = self->velocity;
320
player->tileCollisions = TILECOLLISION_NONE;
321
player->interaction = false;
322
player->onGround = false;
323
self->playerTimers[playerID] = 15;
324
}
325
}
326
}
327
}
328
else {
329
self->visible = true;
330
for (int32 i = 0; i < Player->playerCount; ++i) self->playerTimers[i] = 0;
331
self->state = BallCannon_State_CorkBlocked;
332
}
333
}
334
335
void BallCannon_State_CorkDebris(void)
336
{
337
RSDK_THIS(BallCannon);
338
339
self->position.x += self->velocity.x;
340
self->position.y += self->velocity.y;
341
self->velocity.y += 0x3800;
342
343
self->rotation += self->rotationSpeed;
344
345
if (!RSDK.CheckOnScreen(self, &self->updateRange))
346
destroyEntity(self);
347
}
348
349
#if GAME_INCLUDE_EDITOR
350
void BallCannon_EditorDraw(void)
351
{
352
RSDK_THIS(BallCannon);
353
354
self->drawFX = FX_ROTATE | FX_FLIP;
355
self->rotation = 0;
356
self->active = ACTIVE_BOUNDS;
357
self->updateRange.x = 0x400000;
358
self->updateRange.y = 0x400000;
359
self->velocity.x = 0;
360
self->velocity.y = 0;
361
362
switch (self->type) {
363
default: break;
364
365
case BALLCANNON_CANNON:
366
if (self->angle >= 4)
367
self->direction = FLIP_X;
368
369
self->rotation = (self->angle + self->direction + 1) << 7;
370
371
switch (self->angle) {
372
case BALLCANNON_DIR_RIGHT_CW: // Right -> Down
373
case BALLCANNON_DIR_LEFT_CCW: // Left -> Down
374
self->velocity.y = 0x200000;
375
break;
376
377
case BALLCANNON_DIR_DOWN_CW: // Down -> Left
378
case BALLCANNON_DIR_UP_CCW: // Up -> Left
379
self->velocity.x = -0x200000;
380
break;
381
382
case BALLCANNON_DIR_LEFT_CW: // Left -> Up
383
case BALLCANNON_DIR_RIGHT_CCW: // Right -> Up
384
self->velocity.y = -0x200000;
385
break;
386
387
case BALLCANNON_DIR_UP_CW: // Up -> Right
388
case BALLCANNON_DIR_DOWN_CCW: // Down -> Right
389
self->velocity.x = 0x200000;
390
break;
391
392
default: break;
393
}
394
395
RSDK.SetSpriteAnimation(BallCannon->aniFrames, 0, &self->animator, true, 0);
396
break;
397
398
case BALLCANNON_CORKV: RSDK.SetSpriteAnimation(BallCannon->aniFrames, 3, &self->animator, true, 0); break;
399
400
case BALLCANNON_CORKH: RSDK.SetSpriteAnimation(BallCannon->aniFrames, 4, &self->animator, true, 0); break;
401
}
402
403
BallCannon_Draw();
404
405
if (showGizmos() && self->type == BALLCANNON_CANNON) {
406
self->rotation = (self->angle - self->direction + 2) << 7;
407
408
self->inkEffect = INK_BLEND;
409
410
BallCannon_Draw();
411
412
self->inkEffect = INK_NONE;
413
414
// Draw the direction the player will be shot from (the names are a little confusing on their own)
415
DrawHelpers_DrawArrow(self->position.x, self->position.y, self->position.x + self->velocity.x, self->position.y + self->velocity.y, 0x00FF00,
416
INK_NONE, 0xFF);
417
}
418
}
419
420
void BallCannon_EditorLoad(void)
421
{
422
BallCannon->aniFrames = RSDK.LoadSpriteAnimation("OOZ/BallCannon.bin", SCOPE_STAGE);
423
424
RSDK_ACTIVE_VAR(BallCannon, type);
425
RSDK_ENUM_VAR("Cannon", BALLCANNON_CANNON);
426
RSDK_ENUM_VAR("Cork V", BALLCANNON_CORKV);
427
RSDK_ENUM_VAR("Cork H", BALLCANNON_CORKH);
428
429
RSDK_ACTIVE_VAR(BallCannon, angle);
430
RSDK_ENUM_VAR("Right (Rotates Clockwise)", BALLCANNON_DIR_RIGHT_CW);
431
RSDK_ENUM_VAR("Down (Rotates Clockwise)", BALLCANNON_DIR_DOWN_CW);
432
RSDK_ENUM_VAR("Left (Rotates Clockwise)", BALLCANNON_DIR_LEFT_CW);
433
RSDK_ENUM_VAR("Up (Rotates Clockwise)", BALLCANNON_DIR_UP_CW);
434
RSDK_ENUM_VAR("Down (Rotates Anti-Clockwise)", BALLCANNON_DIR_DOWN_CCW);
435
RSDK_ENUM_VAR("Left (Rotates Anti-Clockwise)", BALLCANNON_DIR_LEFT_CCW);
436
RSDK_ENUM_VAR("Up (Rotates Anti-Clockwise)", BALLCANNON_DIR_UP_CCW);
437
RSDK_ENUM_VAR("Right (Rotates Anti-Clockwise)", BALLCANNON_DIR_RIGHT_CCW);
438
}
439
#endif
440
441
void BallCannon_Serialize(void)
442
{
443
RSDK_EDITABLE_VAR(BallCannon, VAR_UINT8, type);
444
RSDK_EDITABLE_VAR(BallCannon, VAR_ENUM, angle);
445
RSDK_EDITABLE_VAR(BallCannon, VAR_BOOL, exit);
446
}
447
448