Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rubberduckycooly
GitHub Repository: rubberduckycooly/Sonic-Mania-Decompilation
Path: blob/master/SonicMania/Objects/ERZ/KleptoMobile.c
338 views
1
// ---------------------------------------------------------------------
2
// RSDK Project: Sonic Mania
3
// Object Description: KleptoMobile Object
4
// Object Author: Christian Whitehead/Simon Thomley/Hunter Bridges
5
// Decompiled by: Rubberduckycooly & RMGRich
6
// ---------------------------------------------------------------------
7
8
#include "Game.h"
9
10
ObjectKleptoMobile *KleptoMobile;
11
12
void KleptoMobile_Update(void)
13
{
14
RSDK_THIS(KleptoMobile);
15
16
KleptoMobile_HandleAnimations();
17
18
StateMachine_Run(self->state);
19
}
20
21
void KleptoMobile_LateUpdate(void) {}
22
23
void KleptoMobile_StaticUpdate(void) {}
24
25
void KleptoMobile_Draw(void)
26
{
27
RSDK_THIS(KleptoMobile);
28
29
if (self->stateDraw) {
30
StateMachine_Run(self->stateDraw);
31
}
32
else {
33
RSDK.DrawSprite(&self->basicAnimator, NULL, false);
34
}
35
}
36
37
void KleptoMobile_Create(void *data)
38
{
39
RSDK_THIS(KleptoMobile);
40
41
if (!SceneInfo->inEditor) {
42
if (globals->gameMode < MODE_TIMEATTACK) {
43
self->updateRange.x = 0x800000;
44
self->updateRange.y = 0x800000;
45
self->visible = true;
46
self->drawGroup = Zone->objectDrawGroup[0];
47
self->drawFX = FX_FLIP;
48
self->explosionVolume = 0x200;
49
50
self->type = VOID_TO_INT(data);
51
switch (self->type) {
52
case KLEPTOMOBILE_EGGMAN:
53
self->hitbox.left = -22;
54
self->hitbox.top = -22;
55
self->hitbox.right = 22;
56
self->hitbox.bottom = 22;
57
58
self->active = ACTIVE_NORMAL;
59
self->health = 8;
60
61
RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 0, &self->mobileTopAnimator, true, 1);
62
RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 13, &self->eggmanAnimator, true, 0);
63
RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 0, &self->mobileAnimator, true, 0);
64
RSDK.SetSpriteAnimation(PhantomKing->aniFrames, 8, &self->rubyAnimator, true, 0);
65
66
KleptoMobile->defeated = false;
67
68
self->stateDraw = KleptoMobile_Draw_KleptoMobile;
69
self->state = KleptoMobile_State_SetupArena;
70
break;
71
72
case KLEPTOMOBILE_ARM_L:
73
case KLEPTOMOBILE_ARM_R:
74
self->hitbox.left = -32;
75
self->hitbox.top = -8;
76
self->hitbox.right = 32;
77
self->hitbox.bottom = 8;
78
79
self->active = ACTIVE_NORMAL;
80
self->visible = true;
81
82
if (self->type == KLEPTOMOBILE_ARM_R) {
83
RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 7, &self->orbAnimator, true, 0);
84
RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 8, &self->handAnimator, true, 0);
85
RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 9, &self->finger1Animator, true, 0);
86
RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 10, &self->finger2Animator, true, 0);
87
}
88
else {
89
self->drawGroup = Zone->playerDrawGroup[0];
90
RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 3, &self->orbAnimator, true, 0);
91
RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 4, &self->handAnimator, true, 0);
92
RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 5, &self->finger1Animator, true, 0);
93
RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 6, &self->finger2Animator, true, 0);
94
}
95
96
self->stateDraw = KleptoMobile_Draw_Arm;
97
self->state = KleptoMobile_StateArm_Cutscene;
98
break;
99
100
case KLEPTOMOBILE_HAND:
101
self->active = ACTIVE_NORMAL;
102
self->visible = true;
103
104
RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 12, &self->handAnimator, true, 5);
105
RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 17, &self->orbAnimator, true, 0);
106
107
self->stateDraw = KleptoMobile_Draw_Hand;
108
self->state = KleptoMobile_StateHand_Cutscene;
109
break;
110
111
default: break;
112
}
113
}
114
else {
115
destroyEntity(self);
116
}
117
}
118
}
119
120
void KleptoMobile_StageLoad(void)
121
{
122
KleptoMobile->aniFrames = RSDK.LoadSpriteAnimation("Phantom/KleptoMobile.bin", SCOPE_STAGE);
123
124
KleptoMobile->sfxHit = RSDK.GetSfx("Stage/BossHit.wav");
125
KleptoMobile->sfxExplosion = RSDK.GetSfx("Stage/Explosion2.wav");
126
KleptoMobile->sfxFlail = RSDK.GetSfx("SSZ1/Flail.wav");
127
KleptoMobile->sfxWhack = RSDK.GetSfx("Stage/Whack.wav");
128
KleptoMobile->sfxPowerUp = RSDK.GetSfx("Stage/PowerUp.wav");
129
KleptoMobile->sfxRocketJet = RSDK.GetSfx("Stage/RocketJet.wav");
130
}
131
132
void KleptoMobile_HandleAnimations(void)
133
{
134
RSDK_THIS(KleptoMobile);
135
136
bool32 laughing = false;
137
RSDK.ProcessAnimation(&self->eggmanAnimator);
138
139
if (self->invincibilityTimer > 0)
140
self->invincibilityTimer--;
141
142
if (self->eggmanAnimator.animationID == 13) {
143
foreach_active(Player, player)
144
{
145
if (player->state == Player_State_Hurt || player->state == Player_State_Death || player->state == ERZStart_State_PlayerRebound)
146
laughing = true;
147
}
148
149
if (laughing)
150
RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 14, &self->eggmanAnimator, true, 0);
151
}
152
else if (self->eggmanAnimator.animationID == 14) {
153
foreach_active(Player, player)
154
{
155
if (player->state == Player_State_Hurt || player->state == Player_State_Death || player->state == ERZStart_State_PlayerRebound)
156
laughing = true;
157
}
158
159
if (self->eggmanAnimator.frameID >= 14) {
160
if (laughing)
161
RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 14, &self->eggmanAnimator, true, 7);
162
else
163
RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 13, &self->eggmanAnimator, true, 0);
164
}
165
}
166
else {
167
if (self->eggmanAnimator.animationID == 15 && !self->invincibilityTimer)
168
RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 13, &self->eggmanAnimator, true, 0);
169
}
170
}
171
172
void KleptoMobile_CheckPlayerCollisions(void)
173
{
174
RSDK_THIS(KleptoMobile);
175
176
foreach_active(Player, player)
177
{
178
if (!self->invincibilityTimer && Player_CheckBadnikTouch(player, self, &self->hitbox) && Player_CheckBossHit(player, self)) {
179
KleptoMobile_Hit();
180
}
181
}
182
}
183
184
void KleptoMobile_Hit(void)
185
{
186
RSDK_THIS(KleptoMobile);
187
188
if (--self->health <= 0) {
189
RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 16, &self->eggmanAnimator, true, 0);
190
191
self->state = KleptoMobile_State_Destroyed;
192
self->timer = 0;
193
194
if (PhantomKing->defeated)
195
SceneInfo->timeEnabled = false;
196
197
Player_GiveScore(RSDK_GET_ENTITY(SLOT_PLAYER1, Player), 1000);
198
}
199
else {
200
self->invincibilityTimer = 48;
201
RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 15, &self->eggmanAnimator, true, 0);
202
RSDK.PlaySfx(KleptoMobile->sfxHit, false, 255);
203
204
foreach_all(PhantomRuby, ruby)
205
{
206
ruby->position.x = self->rubyPos.x;
207
ruby->position.y = self->rubyPos.y;
208
ruby->velocity.x = -0x20000;
209
ruby->velocity.y = -0x40000;
210
ruby->state = PhantomRuby_State_MoveGravity;
211
212
EntityKleptoMobile *arm1 = RSDK_GET_ENTITY(SceneInfo->entitySlot - 1, KleptoMobile);
213
arm1->timer = 0;
214
arm1->state = KleptoMobile_StateArm_Cutscene;
215
216
EntityKleptoMobile *arm2 = RSDK_GET_ENTITY(SceneInfo->entitySlot + 1, KleptoMobile);
217
arm2->timer = 0;
218
arm2->state = KleptoMobile_StateArm_Cutscene;
219
}
220
221
self->holdingRuby = false;
222
self->state = KleptoMobile_State_HitFall;
223
}
224
}
225
226
void KleptoMobile_Explode(void)
227
{
228
RSDK_THIS(KleptoMobile);
229
230
if (!(Zone->timer % 3)) {
231
int32 channel = RSDK.PlaySfx(KleptoMobile->sfxExplosion, false, 255);
232
233
if (self->state == KleptoMobile_State_CutsceneExplode)
234
RSDK.SetChannelAttributes(channel, self->explosionVolume * (1 / 512.0f), 0.0, 1.0);
235
236
if (Zone->timer & 4) {
237
int32 x = self->position.x + (RSDK.Rand(self->hitbox.left, self->hitbox.right) << 16);
238
int32 y = self->position.y + (RSDK.Rand(self->hitbox.top, self->hitbox.bottom) << 16);
239
CREATE_ENTITY(Explosion, INT_TO_VOID((RSDK.Rand(0, 256) > 192) + EXPLOSION_BOSS), x, y)->drawGroup = Zone->objectDrawGroup[1];
240
}
241
}
242
}
243
244
void KleptoMobile_HandleFrames(void)
245
{
246
RSDK_THIS(KleptoMobile);
247
248
self->rotation = RSDK.Sin512(2 * Zone->timer) >> 6;
249
self->armAngle = (self->armAngle + 12) & 0x3FF;
250
251
int32 moveX = 0x1C00 * RSDK.Sin512(-self->rotation) + self->position.x;
252
int32 moveY = 0x1C00 * RSDK.Cos512(-self->rotation) + self->position.y;
253
254
int32 angle = self->armAngle;
255
256
for (int32 i = 0; i < 10; i += 2) {
257
self->armPositions[i].x = moveX + 2 * RSDK.Cos1024(angle) * RSDK.Cos512(self->rotation);
258
self->armPositions[i].y = moveY + 2 * RSDK.Cos1024(angle) * RSDK.Sin512(self->rotation);
259
self->armAngles[i] = angle & 0x3FF;
260
angle += 0x200;
261
262
self->armPositions[i + 1].x = moveX + 2 * RSDK.Cos1024(angle) * RSDK.Cos512(self->rotation);
263
self->armPositions[i + 1].y = moveY + 2 * RSDK.Cos1024(angle) * RSDK.Sin512(self->rotation);
264
self->armAngles[i + 1] = angle & 0x3FF;
265
266
moveX += RSDK.Sin512(-self->rotation) << 10;
267
moveY += RSDK.Cos512(-self->rotation) << 10;
268
angle += 0x240;
269
}
270
}
271
272
void KleptoMobile_SwitchToKing(void)
273
{
274
EntityPhantomKing *kingPtr = NULL;
275
276
foreach_active(PhantomKing, king)
277
{
278
if (king->type == PHANTOMKING_KING) {
279
EntityPlayer *player1 = RSDK_GET_ENTITY(SLOT_PLAYER1, Player);
280
281
king->direction = FLIP_X;
282
if (RSDK.Rand(0, 2))
283
king->position.x = player1->position.x + 0x1000000;
284
else
285
king->position.x = player1->position.x - 0x1000000;
286
287
king->position.y = player1->position.y + 0x800000;
288
king->originPos.y = player1->position.y + 0x800000;
289
king->rubyPos = king->position;
290
king->rubyPos.x -= 0x1400 * RSDK.Sin512(-king->rotation);
291
king->rubyPos.y -= 0x1400 * RSDK.Cos512(-king->rotation);
292
king->velocity.x = 0;
293
king->velocity.y = 0;
294
king->drawRuby = true;
295
296
for (int32 i = 0; i < 0x3FC; i += 0xAA) {
297
EntityPKingAttack *attack = CREATE_ENTITY(PKingAttack, INT_TO_VOID(1), king->rubyPos.x, king->rubyPos.y);
298
attack->angle = i;
299
attack->target = (Entity *)king;
300
}
301
302
king->state = PhantomKing_State_FlyAround;
303
kingPtr = king;
304
305
foreach_break;
306
}
307
}
308
309
// NOTE: this will crash if kingPtr is NULL, make sure there's a phantom king in the scene!!!
310
foreach_active(PhantomKing, kingArm)
311
{
312
if (kingArm->type != PHANTOMKING_KING) {
313
kingArm->position.x = kingPtr->position.x;
314
kingArm->position.y = kingPtr->position.y;
315
316
for (int32 i = 0; i < 10; ++i) kingArm->armPositions[i] = kingPtr->position;
317
318
kingArm->velocity.x = 0;
319
kingArm->velocity.y = 0;
320
kingArm->state = PhantomKing_StateArm_Idle;
321
}
322
}
323
}
324
325
void KleptoMobile_Draw_KleptoMobile(void)
326
{
327
RSDK_THIS(KleptoMobile);
328
329
if (self->invincibilityTimer & 1) {
330
RSDK.CopyPalette(2, 112, 0, 136, 8);
331
RSDK.CopyPalette(2, 120, 0, 11, 1);
332
RSDK.CopyPalette(2, 121, 0, 25, 1);
333
RSDK.CopyPalette(2, 122, 0, 197, 6);
334
335
RSDK.DrawSprite(&self->mobileTopAnimator, NULL, false);
336
RSDK.DrawSprite(&self->eggmanAnimator, NULL, false);
337
338
self->mobileAnimator.frameID = 2;
339
RSDK.DrawSprite(&self->mobileAnimator, NULL, false);
340
341
self->mobileAnimator.frameID = 0;
342
RSDK.DrawSprite(&self->mobileAnimator, NULL, false);
343
344
RSDK.CopyPalette(1, 136, 0, 136, 8);
345
RSDK.CopyPalette(1, 11, 0, 11, 1);
346
RSDK.CopyPalette(1, 25, 0, 25, 1);
347
RSDK.CopyPalette(1, 197, 0, 197, 6);
348
}
349
else {
350
self->basicAnimator.frameID = 1; // this sets basicAnimator to anim 0, frame 1... and then draws it using mobileTopAnimator instead
351
RSDK.DrawSprite(&self->mobileTopAnimator, NULL, false);
352
RSDK.DrawSprite(&self->eggmanAnimator, NULL, false);
353
354
self->mobileAnimator.frameID = 2;
355
RSDK.DrawSprite(&self->mobileAnimator, NULL, false);
356
357
self->mobileAnimator.frameID = 0;
358
RSDK.DrawSprite(&self->mobileAnimator, NULL, false);
359
}
360
361
RSDK.DrawCircle(self->rubyPos.x, self->rubyPos.y, self->circleRadius, 0x000000, 0xFF, INK_TINT, false);
362
}
363
364
void KleptoMobile_State_SetupArena(void)
365
{
366
RSDK_THIS(KleptoMobile);
367
368
self->active = ACTIVE_NORMAL;
369
370
EntityKleptoMobile *hand = RSDK_GET_ENTITY(SceneInfo->entitySlot - 2, KleptoMobile);
371
RSDK.ResetEntity(hand, KleptoMobile->classID, INT_TO_VOID(KLEPTOMOBILE_HAND));
372
hand->position.x = self->position.x;
373
hand->position.y = self->position.y;
374
hand->parent = self;
375
376
EntityKleptoMobile *arm2 = RSDK_GET_ENTITY(SceneInfo->entitySlot - 1, KleptoMobile);
377
RSDK.ResetEntity(arm2, KleptoMobile->classID, INT_TO_VOID(KLEPTOMOBILE_ARM_R));
378
arm2->position.x = self->position.x;
379
arm2->position.y = self->position.y;
380
arm2->parent = self;
381
382
EntityKleptoMobile *arm1 = RSDK_GET_ENTITY(SceneInfo->entitySlot + 1, KleptoMobile);
383
RSDK.ResetEntity(arm1, KleptoMobile->classID, INT_TO_VOID(KLEPTOMOBILE_ARM_L));
384
arm1->position.x = self->position.x;
385
arm1->position.y = self->position.y;
386
arm1->parent = self;
387
388
self->originPos.x = self->position.x;
389
self->originPos.y = self->position.y;
390
KleptoMobile->boundsM = self->position.x;
391
KleptoMobile->boundsL = KleptoMobile->boundsM - 0x800000;
392
KleptoMobile->boundsR = KleptoMobile->boundsM + 0x800000;
393
KleptoMobile->boundsT = (Zone->cameraBoundsT[0] + 48) << 16;
394
KleptoMobile->boundsB = (Zone->cameraBoundsB[0] - 96) << 16;
395
self->state = StateMachine_None;
396
}
397
398
void KleptoMobile_State_CutsceneControlled(void)
399
{
400
RSDK_THIS(KleptoMobile);
401
402
self->position.y = BadnikHelpers_Oscillate(self->originPos.y, 2, 9);
403
404
self->position.x += self->velocity.x;
405
self->position.y += self->velocity.y;
406
self->originPos.x += self->velocity.x;
407
self->originPos.y += self->velocity.y;
408
409
KleptoMobile_HandleFrames();
410
}
411
412
void KleptoMobile_State_MoveAround(void)
413
{
414
RSDK_THIS(KleptoMobile);
415
416
EntityPlayer *player1 = RSDK_GET_ENTITY(SLOT_PLAYER1, Player);
417
418
self->position.y = BadnikHelpers_Oscillate(self->originPos.y, 2, 9);
419
KleptoMobile_CheckPlayerCollisions();
420
421
int32 angle = RSDK.ATan2(self->position.x - player1->position.x, self->originPos.y - player1->position.y);
422
int32 x = (RSDK.Cos256(angle) << 15) + player1->position.x;
423
int32 y = (RSDK.Sin256(angle) << 15) + player1->position.y;
424
425
if (self->circleRadius > 0)
426
self->circleRadius -= 8;
427
428
if (x <= self->position.x) {
429
if (self->velocity.x > -0x20000)
430
self->velocity.x -= 0x800;
431
}
432
else {
433
if (self->velocity.x < 0x20000)
434
self->velocity.x += 0x800;
435
}
436
437
int32 bottom = Zone->cameraBoundsB[0] << 16;
438
int32 boundary = bottom - 0x800000;
439
440
if (y <= bottom - 0x800000) {
441
boundary = bottom + 0x800000;
442
if (y >= (Zone->cameraBoundsT[0] + 128) << 16)
443
boundary = y;
444
}
445
446
if (boundary <= self->originPos.y) {
447
if (self->velocity.y > -0x20000)
448
self->velocity.y -= 0x800;
449
}
450
else {
451
if (self->velocity.y < 0x20000)
452
self->velocity.y += 0x800;
453
}
454
455
self->position.x += self->velocity.x;
456
self->originPos.y += self->velocity.y;
457
self->direction = player1->position.x > self->position.x;
458
459
if (--self->bashArmDelay == 1) {
460
self->bashArmDelay = 0;
461
self->timer = 0;
462
RSDK.PlaySfx(KleptoMobile->sfxPowerUp, false, 255);
463
464
EntityKleptoMobile *arm2 = RSDK_GET_ENTITY(SceneInfo->entitySlot - 1, KleptoMobile);
465
arm2->timer = 0;
466
arm2->state = KleptoMobile_StateArm_ChargeAttack;
467
468
EntityKleptoMobile *arm1 = RSDK_GET_ENTITY(SceneInfo->entitySlot + 1, KleptoMobile);
469
arm1->timer = 0;
470
arm1->state = KleptoMobile_StateArm_ChargeAttack;
471
self->state = KleptoMobile_State_Hover;
472
}
473
else {
474
if (self->canBashAttack) {
475
if (self->bashArmDelay <= 0 && ++self->timer == 15) {
476
self->timer = 0;
477
RSDK.PlaySfx(KleptoMobile->sfxFlail, false, 0xFF);
478
int32 armSlot = self->bashArmID ? SceneInfo->entitySlot + 1 : SceneInfo->entitySlot - 1;
479
480
EntityKleptoMobile *arm = RSDK_GET_ENTITY(armSlot, KleptoMobile);
481
482
x = self->position.x + (self->direction == FLIP_X ? -0x30000 : 0x30000);
483
if (arm->type == KLEPTOMOBILE_ARM_R)
484
x += 0x180000;
485
y = self->position.y + 0xD0000;
486
487
angle = RSDK.ATan2(player1->position.x - x, player1->position.y - y);
488
arm->bashArmTargetPos.x = x + (RSDK.Cos256(angle) << 15);
489
arm->bashArmTargetPos.y = y + (RSDK.Sin256(angle) << 15);
490
arm->timer = 0;
491
arm->state = KleptoMobile_StateArm_BashAttack;
492
self->bashArmID ^= 1;
493
494
if (++self->attackCount >= 5) {
495
self->attackCount = 0;
496
self->bashArmDelay = 90;
497
self->canBashAttack = false;
498
}
499
}
500
}
501
else {
502
if (abs(player1->position.x - self->position.x) < 0x1800000 && abs(player1->position.y - self->position.y) < 0x1800000)
503
self->canBashAttack = true;
504
}
505
506
KleptoMobile_HandleFrames();
507
}
508
}
509
510
void KleptoMobile_State_Hover(void)
511
{
512
RSDK_THIS(KleptoMobile);
513
514
self->position.y = BadnikHelpers_Oscillate(self->originPos.y, 2, 9);
515
516
KleptoMobile_CheckPlayerCollisions();
517
518
if (++self->timer < 180) {
519
EntityPlayer *player1 = RSDK_GET_ENTITY(SLOT_PLAYER1, Player);
520
if (abs(player1->position.x - self->position.x) < 0xC00000 && abs(player1->position.y - self->position.y) < 0xC00000) {
521
RSDK.StopSfx(KleptoMobile->sfxPowerUp);
522
523
self->timer = 14;
524
self->bashArmDelay = 0;
525
self->canBashAttack = true;
526
self->state = KleptoMobile_State_MoveAround;
527
}
528
}
529
else {
530
self->timer = 0;
531
self->velocity.x = self->direction == FLIP_NONE ? -0xA0000 : 0xA0000;
532
533
PhantomRuby_PlaySfx(RUBYSFX_REDCUBE);
534
RSDK.PlaySfx(KleptoMobile->sfxRocketJet, false, 255);
535
self->state = KleptoMobile_State_FirstChargeAttack;
536
}
537
538
KleptoMobile_HandleFrames();
539
}
540
541
void KleptoMobile_HandleArmPositions(void)
542
{
543
foreach_active(KleptoMobile, eggman)
544
{
545
if (eggman->type != KLEPTOMOBILE_EGGMAN) {
546
EntityKleptoMobile *parent = eggman->parent;
547
eggman->position.x = parent->position.x;
548
eggman->position.y = parent->position.y;
549
550
for (int32 i = 0; i < 10; ++i) eggman->armPositions[i] = parent->position;
551
552
eggman->velocity.x = 0;
553
eggman->velocity.y = 0;
554
}
555
}
556
}
557
558
void KleptoMobile_HandleChargeFinish(void)
559
{
560
RSDK_THIS(KleptoMobile);
561
562
EntityPlayer *player1 = RSDK_GET_ENTITY(SLOT_PLAYER1, Player);
563
564
self->timer = 0;
565
566
PhantomRuby_PlaySfx(RSDK.Rand(RUBYSFX_ATTACK1, RUBYSFX_REDCUBE));
567
if (!RSDK.Rand(0, 2))
568
self->position.x = player1->position.x - 0x1800000;
569
else
570
self->position.x = player1->position.x + 0x1800000;
571
572
self->originPos.y = (RSDK.Rand(-2, 3) << 21) + player1->position.y;
573
while (self->originPos.y > (Zone->cameraBoundsB[0] - 64) << 16 && self->originPos.y < (Zone->cameraBoundsT[0] + 64) << 16) {
574
self->originPos.y = (RSDK.Rand(-2, 3) << 21) + player1->position.y;
575
}
576
577
self->circleRadius = 128;
578
579
if (++self->attackCount == 4) {
580
self->attackCount = 0;
581
self->velocity.x = 0;
582
self->velocity.y = 0;
583
self->state = KleptoMobile_State_MoveAround;
584
}
585
else if (player1->position.x >= self->position.x) {
586
self->velocity.x = 0xA0000;
587
self->direction = FLIP_X;
588
}
589
else {
590
self->velocity.x = -0xA0000;
591
self->direction = FLIP_NONE;
592
}
593
594
KleptoMobile_HandleArmPositions();
595
}
596
597
void KleptoMobile_State_FirstChargeAttack(void)
598
{
599
RSDK_THIS(KleptoMobile);
600
601
self->position.y = BadnikHelpers_Oscillate(self->originPos.y, 2, 9);
602
603
if (self->circleRadius < 128)
604
self->circleRadius += 8;
605
606
if (++self->timer >= 180) {
607
KleptoMobile_HandleChargeFinish();
608
self->state = KleptoMobile_State_NextChargeAttacks;
609
}
610
611
self->position.x += self->velocity.x;
612
self->originPos.y += self->velocity.y;
613
614
KleptoMobile_HandleFrames();
615
}
616
617
void KleptoMobile_State_NextChargeAttacks(void)
618
{
619
RSDK_THIS(KleptoMobile);
620
621
self->position.y = BadnikHelpers_Oscillate(self->originPos.y, 2, 9);
622
623
KleptoMobile_CheckPlayerCollisions();
624
625
if (self->onScreen) {
626
if (self->circleRadius > 0)
627
self->circleRadius -= 8;
628
}
629
630
if (++self->timer >= 180)
631
KleptoMobile_HandleChargeFinish();
632
633
self->position.x += self->velocity.x;
634
self->originPos.y += self->velocity.y;
635
636
KleptoMobile_HandleFrames();
637
}
638
639
void KleptoMobile_State_Switch(void)
640
{
641
RSDK_THIS(KleptoMobile);
642
643
if (++self->timer >= 120) {
644
self->timer = 0;
645
KleptoMobile_SwitchToKing();
646
self->state = StateMachine_None;
647
}
648
}
649
650
void KleptoMobile_State_HitFall(void)
651
{
652
RSDK_THIS(KleptoMobile);
653
654
self->position.y += self->velocity.y;
655
self->velocity.y -= 0x3800;
656
657
if (self->position.y < -0x1000000) {
658
self->canBashAttack = false;
659
self->bashArmDelay = 0;
660
self->velocity.y = 0;
661
self->timer = 0;
662
self->state = KleptoMobile_State_Switch;
663
}
664
665
KleptoMobile_HandleFrames();
666
}
667
668
void KleptoMobile_StateHand_Cutscene(void)
669
{
670
RSDK_THIS(KleptoMobile);
671
EntityKleptoMobile *parent = self->parent;
672
673
int32 parentX = parent->position.x;
674
int32 parentY = parent->position.y;
675
676
int32 moveX = 0;
677
int32 moveY = -0x200000 - (RSDK.Sin256((((self->type << 7) - 0x80) ^ 0x80) + 2 * Zone->timer) << 12);
678
679
self->direction = parent->direction;
680
int32 x = parent->position.x;
681
int32 y = parent->position.y - 0x180000;
682
683
int32 x2 = 0;
684
int32 y2 = 0;
685
if (self->direction == FLIP_X) {
686
moveX = parentX + 0x200000;
687
x2 = ((self->position.x + parent->position.x) >> 1) + 0x80000;
688
}
689
else {
690
moveX = parentX - 0x200000;
691
x2 = ((self->position.x + parent->position.x) >> 1) - 0x80000;
692
}
693
694
y2 = ((parent->position.y - 0x180000 + self->position.y) >> 1) - 0x300000;
695
696
self->velocity.x += ((moveX - self->position.x) >> 5) - (self->velocity.x >> 3);
697
self->velocity.y += (((moveY + parentY) - self->position.y) >> 5) - (self->velocity.y >> 3);
698
self->position.y += self->velocity.y;
699
self->position.x += self->velocity.x;
700
701
int32 percent = 0x1800;
702
for (int32 i = 0; i < 7; ++i) {
703
self->armPositions[i] = MathHelpers_GetBezierPoint(percent, x, y, x2, y2, x2, y2, self->position.x, self->position.y);
704
percent += 0x2000;
705
}
706
707
parent->rubyPos.x = self->armPositions[6].x;
708
parent->rubyPos.y = self->armPositions[6].y;
709
710
if (self->direction)
711
parent->rubyPos.x += 0x80000;
712
else
713
parent->rubyPos.x -= 0x80000;
714
715
parent->rubyPos.y += 0x80000;
716
}
717
718
void KleptoMobile_StateHand_Boss(void)
719
{
720
RSDK_THIS(KleptoMobile);
721
722
EntityKleptoMobile *parent = self->parent;
723
724
self->direction = parent->direction;
725
int32 parentX = parent->position.x;
726
int32 parentY = parent->position.y - 0x180000;
727
728
int32 x = 0;
729
if (self->direction == FLIP_X)
730
x = ((parent->position.x + self->position.x) >> 1) + 0x80000;
731
else
732
x = ((parent->position.x + self->position.x) >> 1) - 0x80000;
733
int32 y = ((parentY + self->position.y) >> 1) - 0x300000;
734
735
int32 percent = 0x1800;
736
for (int32 i = 0; i < 7; ++i) {
737
self->armPositions[i] = MathHelpers_GetBezierPoint(percent, parentX, parentY, x, y, x, y, self->position.x, self->position.y);
738
percent += 0x2000;
739
}
740
741
parent->rubyPos.x = self->armPositions[6].x;
742
parent->rubyPos.y = self->armPositions[6].y;
743
744
parent->rubyPos.x += self->direction ? 0x80000 : -0x80000;
745
parent->rubyPos.y += 0x80000;
746
}
747
748
void KleptoMobile_Draw_Hand(void)
749
{
750
RSDK_THIS(KleptoMobile);
751
752
EntityKleptoMobile *parent = self->parent;
753
754
if (parent->holdingRuby)
755
RSDK.DrawSprite(&parent->rubyAnimator, &parent->rubyPos, false);
756
757
for (int32 i = 0; i < 6; ++i) RSDK.DrawSprite(&self->orbAnimator, &self->armPositions[i], false);
758
759
RSDK.DrawSprite(&self->handAnimator, &self->armPositions[6], false);
760
}
761
762
void KleptoMobile_CheckPlayerCollisions_Arm(void)
763
{
764
RSDK_THIS(KleptoMobile);
765
766
foreach_active(Player, player)
767
{
768
if (Player_CheckCollisionTouch(player, self, &self->hitbox)) {
769
if (player->superState == SUPERSTATE_SUPER) {
770
if (!player->blinkTimer) {
771
RSDK.PlaySfx(KleptoMobile->sfxWhack, false, 255);
772
player->blinkTimer = 120;
773
774
if (self->state == KleptoMobile_StateArm_ChargeAttack) {
775
player->velocity.x = self->parent->velocity.x >> 2;
776
player->groundVel = player->velocity.x;
777
778
if (player->position.y <= self->position.y || player->onGround) {
779
player->onGround = false;
780
player->velocity.y = -0xA0000;
781
}
782
else {
783
player->velocity.y = 0xA0000;
784
}
785
}
786
else {
787
player->velocity.x = self->velocity.x >> 2;
788
player->velocity.y = self->velocity.y >> 2;
789
player->groundVel = player->velocity.x;
790
}
791
792
player->rotation = 0;
793
RSDK.SetSpriteAnimation(player->aniFrames, ANI_RUN, &player->animator, false, 0);
794
player->state = ERZStart_State_PlayerRebound;
795
}
796
}
797
else {
798
Player_Hurt(player, self);
799
}
800
}
801
}
802
}
803
804
void KleptoMobile_StateArm_Cutscene(void)
805
{
806
RSDK_THIS(KleptoMobile);
807
EntityKleptoMobile *parent = self->parent;
808
809
int32 parentX = parent->position.x;
810
int32 parentY = parent->position.y;
811
812
int32 moveX = 0;
813
int32 moveY = ((RSDK.Sin256((((self->type << 7) - 128) ^ 0x80) + 2 * Zone->timer) + 512) << 12) + parentY;
814
self->direction = parent->direction;
815
816
int32 x = 0, y = 0;
817
int32 x2 = 0, y2 = 0;
818
819
if (self->direction == FLIP_X) {
820
moveX = parentX + 0x300000;
821
x = parentX - 0x30000;
822
y = parentY + 0xD0000;
823
824
if (self->type == KLEPTOMOBILE_ARM_R) {
825
x += 0x180000;
826
moveX = parentX + 0x600000;
827
}
828
829
x2 = ((self->position.x + x) >> 1) - 0x100000;
830
y2 = ((self->position.y + y) >> 1) + 0x100000;
831
}
832
else {
833
moveX = parentX - 0x300000;
834
x = parentX + 0x30000;
835
y = parentY + 0xD0000;
836
837
if (self->type == KLEPTOMOBILE_ARM_R) {
838
x -= 0x180000;
839
moveX = parentX - 0x600000;
840
}
841
842
x2 = ((self->position.x + x) >> 1) + 0x200000;
843
y2 = ((self->position.y + y) >> 1) + 0x200000;
844
}
845
846
self->velocity.x += ((moveX - self->position.x) >> 5) - (self->velocity.x >> 3);
847
self->velocity.y += ((moveY - self->position.y) >> 5) - (self->velocity.y >> 3);
848
self->position.x += self->velocity.x;
849
self->position.y += self->velocity.y;
850
851
int32 percent = 0x1800;
852
for (int32 i = 0; i < 7; ++i) {
853
self->armPositions[i] = MathHelpers_GetBezierPoint(percent, x, y, x2, y2, x2, y2, self->position.x, self->position.y);
854
percent += 0x2000;
855
}
856
857
RSDK.ProcessAnimation(&self->finger1Animator);
858
RSDK.ProcessAnimation(&self->finger2Animator);
859
}
860
861
void KleptoMobile_StateArm_Idle(void)
862
{
863
RSDK_THIS(KleptoMobile);
864
865
EntityKleptoMobile *parent = self->parent;
866
867
self->direction = parent->direction;
868
869
int32 x = 0;
870
int32 y = parent->position.y + 0x60000;
871
if (self->direction == FLIP_X) {
872
x = parent->position.x - 0x90000;
873
if (self->type == KLEPTOMOBILE_ARM_R)
874
x += 0x180000;
875
}
876
else {
877
x = parent->position.x + 0x90000;
878
if (self->type == KLEPTOMOBILE_ARM_R)
879
x -= 0x180000;
880
}
881
882
int32 percent = 0x1800;
883
for (int32 i = 0; i < 7; ++i) {
884
self->armPositions[i] = MathHelpers_GetBezierPoint(percent, x, y, self->armBezierPos.x, self->armBezierPos.y, self->armBezierPos.x,
885
self->armBezierPos.y, self->position.x, self->position.y);
886
percent += 0x2000;
887
}
888
889
RSDK.ProcessAnimation(&self->finger1Animator);
890
RSDK.ProcessAnimation(&self->finger2Animator);
891
}
892
893
void KleptoMobile_StateArm_BashAttack(void)
894
{
895
RSDK_THIS(KleptoMobile);
896
897
EntityKleptoMobile *parent = self->parent;
898
899
if (!self->timer) {
900
self->bashArmStartPos.x = self->position.x;
901
self->bashArmStartPos.y = self->position.y;
902
}
903
904
self->direction = parent->direction;
905
906
int32 x = 0;
907
int32 y = parent->position.y + 0xD0000;
908
909
int32 x2 = 0, y2 = 0;
910
int32 x3 = 0, y3 = 0;
911
912
if (self->direction == FLIP_X) {
913
x = parent->position.x - 0x30000;
914
if (self->type == KLEPTOMOBILE_ARM_R)
915
x += 0x180000;
916
}
917
else {
918
x = parent->position.x + 0x30000;
919
if (self->type == KLEPTOMOBILE_ARM_R)
920
x -= 0x180000;
921
}
922
923
if (self->timer >= 4) {
924
x2 = x;
925
y2 = y;
926
x3 = self->position.x;
927
y3 = self->position.y;
928
self->position.x = self->bashArmTargetPos.x;
929
self->position.y = self->bashArmTargetPos.y;
930
}
931
else {
932
self->velocity.x = (self->bashArmTargetPos.x - self->bashArmStartPos.x) / 4;
933
self->velocity.y = (self->bashArmTargetPos.y - self->bashArmStartPos.y) / 4;
934
935
self->position.x += self->velocity.x;
936
self->position.y += self->velocity.y;
937
938
int32 distX = self->position.x - x;
939
int32 distY = self->position.y - y;
940
941
int32 distValX = -distX;
942
int32 distValY = distY;
943
if (distX > 0) {
944
distValX = distX;
945
distValY = -distY;
946
}
947
948
int32 timerVal = 4 - self->timer;
949
x2 = x + ((x + (distX >> 1) + distValY) - x) * timerVal / 4;
950
y2 = y + (((distY >> 1) + distValX) * timerVal) / 4;
951
x3 = self->position.x + ((x + (distX >> 1) + distValY) - self->position.x) * timerVal / 4;
952
y3 = self->position.y + (((distY >> 1) + distValX - self->position.y) * timerVal / 4);
953
}
954
955
int32 percent = 0x1800;
956
for (int32 i = 0; i < 7; ++i) {
957
self->armPositions[i] = MathHelpers_GetBezierPoint(percent, x, y, x2, y2, x3, y3, self->position.x, self->position.y);
958
percent += 0x2000;
959
}
960
961
if (++self->timer > 15) {
962
self->timer = 0;
963
self->state = KleptoMobile_StateArm_Cutscene;
964
}
965
966
RSDK.ProcessAnimation(&self->finger1Animator);
967
RSDK.ProcessAnimation(&self->finger2Animator);
968
969
if (parent->health > 0)
970
KleptoMobile_CheckPlayerCollisions_Arm();
971
}
972
973
void KleptoMobile_StateArm_ChargeAttack(void)
974
{
975
RSDK_THIS(KleptoMobile);
976
977
EntityKleptoMobile *parent = self->parent;
978
979
int32 parentX = parent->position.x;
980
int32 parentY = parent->position.y;
981
982
int32 moveX = 0;
983
int32 moveY = parentY + 0x180000;
984
if (self->type == KLEPTOMOBILE_ARM_R)
985
moveY = parentY - 0x180000;
986
987
moveX = parentX - 0x400000;
988
if (self->direction == FLIP_X)
989
moveX = parentX + 0x400000;
990
991
self->direction = parent->direction;
992
993
int32 x = 0;
994
int32 y = parent->position.y + 0xD0000;
995
if (self->direction == FLIP_X) {
996
moveX += 0x300000;
997
x = parent->position.x - 0x30000;
998
999
if (self->type == KLEPTOMOBILE_ARM_R) {
1000
x += 0x180000;
1001
moveX += 0x300000;
1002
}
1003
}
1004
else {
1005
moveX -= 0x300000;
1006
x = parent->position.x + 0x30000;
1007
1008
if (self->type == KLEPTOMOBILE_ARM_R) {
1009
x -= 0x180000;
1010
moveX -= 0x300000;
1011
}
1012
}
1013
1014
int32 x2 = (x + self->position.x) >> 1;
1015
int32 y2 = (y + self->position.y) >> 1;
1016
1017
self->velocity.x += ((moveX - self->position.x) >> 5) - (self->velocity.x >> 3);
1018
self->velocity.y += ((moveY - self->position.y) >> 5) - (self->velocity.y >> 3);
1019
self->position.x += self->velocity.x;
1020
self->position.y += self->velocity.y;
1021
1022
int32 percent = 0x1800;
1023
for (int32 i = 0; i < 7; ++i) {
1024
self->armPositions[i] = MathHelpers_GetBezierPoint(percent, x, y, x2, y2, x2, y2, self->position.x, self->position.y);
1025
percent += 0x2000;
1026
}
1027
1028
RSDK.ProcessAnimation(&self->finger1Animator);
1029
RSDK.ProcessAnimation(&self->finger2Animator);
1030
1031
if (parent->health > 0)
1032
KleptoMobile_CheckPlayerCollisions_Arm();
1033
}
1034
1035
void KleptoMobile_Draw_Arm(void)
1036
{
1037
RSDK_THIS(KleptoMobile);
1038
1039
for (int32 i = 0; i < 6; ++i) RSDK.DrawSprite(&self->orbAnimator, &self->armPositions[i], false);
1040
1041
RSDK.DrawSprite(&self->handAnimator, &self->armPositions[6], false);
1042
1043
if (self->direction) {
1044
if (self->type == KLEPTOMOBILE_ARM_R) {
1045
self->armPositions[6].x += 0x280000;
1046
self->armPositions[6].y -= 0x80000;
1047
RSDK.DrawSprite(&self->finger1Animator, &self->armPositions[6], false);
1048
1049
self->armPositions[6].y += 0x100000;
1050
RSDK.DrawSprite(&self->finger1Animator, &self->armPositions[6], false);
1051
1052
self->armPositions[6].x -= 0x40000;
1053
self->armPositions[6].y -= 0x80000;
1054
RSDK.DrawSprite(&self->finger2Animator, &self->armPositions[6], false);
1055
}
1056
else {
1057
self->armPositions[6].x += 0x280000;
1058
RSDK.DrawSprite(&self->finger2Animator, &self->armPositions[6], false);
1059
1060
self->armPositions[6].x -= 0x40000;
1061
self->armPositions[6].y -= 0x80000;
1062
RSDK.DrawSprite(&self->finger1Animator, &self->armPositions[6], false);
1063
1064
self->armPositions[6].y += 0x100000;
1065
RSDK.DrawSprite(&self->finger1Animator, &self->armPositions[6], false);
1066
1067
self->armPositions[6].y -= 0x80000;
1068
}
1069
}
1070
else {
1071
if (self->type == KLEPTOMOBILE_ARM_R) {
1072
self->armPositions[6].x -= 0x280000;
1073
self->armPositions[6].y -= 0x80000;
1074
RSDK.DrawSprite(&self->finger1Animator, &self->armPositions[6], false);
1075
1076
self->armPositions[6].y += 0x100000;
1077
RSDK.DrawSprite(&self->finger1Animator, &self->armPositions[6], false);
1078
1079
self->armPositions[6].x += 0x40000;
1080
self->armPositions[6].y -= 0x80000;
1081
RSDK.DrawSprite(&self->finger2Animator, &self->armPositions[6], false);
1082
}
1083
else {
1084
self->armPositions[6].x -= 0x280000;
1085
RSDK.DrawSprite(&self->finger2Animator, &self->armPositions[6], false);
1086
1087
self->armPositions[6].x += 0x40000;
1088
self->armPositions[6].y -= 0x80000;
1089
RSDK.DrawSprite(&self->finger1Animator, &self->armPositions[6], false);
1090
1091
self->armPositions[6].y += 0x100000;
1092
RSDK.DrawSprite(&self->finger1Animator, &self->armPositions[6], false);
1093
1094
self->armPositions[6].y -= 0x80000;
1095
}
1096
}
1097
}
1098
1099
void KleptoMobile_State_Destroyed(void)
1100
{
1101
RSDK_THIS(KleptoMobile);
1102
1103
RSDK.ProcessAnimation(&self->eggmanAnimator);
1104
1105
KleptoMobile_Explode();
1106
1107
if (++self->timer == 96) {
1108
Debris_CreateFromEntries(KleptoMobile->aniFrames, KleptoMobile->debrisInfo, 18);
1109
}
1110
else if (self->timer == 144) {
1111
KleptoMobile->defeated = true;
1112
if (PhantomKing->defeated) {
1113
self->position.y += 0x1000000;
1114
foreach_all(ERZOutro, outro)
1115
{
1116
outro->active = ACTIVE_NORMAL;
1117
foreach_break;
1118
}
1119
}
1120
self->state = KleptoMobile_State_Explode;
1121
}
1122
}
1123
1124
void KleptoMobile_State_Explode(void)
1125
{
1126
RSDK_THIS(KleptoMobile);
1127
1128
self->velocity.y += 0x2800;
1129
self->position.y += self->velocity.y;
1130
1131
KleptoMobile_Explode();
1132
1133
if (self->position.y >= 0x2800000) {
1134
if (!PhantomKing->defeated) {
1135
KleptoMobile_SwitchToKing();
1136
self->state = StateMachine_None;
1137
}
1138
}
1139
}
1140
1141
void KleptoMobile_State_CutsceneExplode(void)
1142
{
1143
RSDK_THIS(KleptoMobile);
1144
1145
RSDK.ProcessAnimation(&self->eggmanAnimator);
1146
1147
self->originPos.x += self->velocity.x;
1148
self->originPos.y += self->velocity.y;
1149
self->position.x = self->originPos.x;
1150
self->position.y = self->originPos.y;
1151
1152
if (self->explosionVolume > 0) {
1153
KleptoMobile_Explode();
1154
1155
if (self->timer >= 60)
1156
self->explosionVolume -= 2;
1157
self->timer++;
1158
}
1159
}
1160
1161
#if GAME_INCLUDE_EDITOR
1162
void KleptoMobile_EditorDraw(void)
1163
{
1164
RSDK_THIS(KleptoMobile);
1165
self->updateRange.x = 0x800000;
1166
self->updateRange.y = 0x800000;
1167
self->visible = true;
1168
self->drawFX = FX_FLIP;
1169
RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 0, &self->mobileTopAnimator, false, 1);
1170
RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 13, &self->eggmanAnimator, false, 0);
1171
RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 0, &self->mobileAnimator, false, 0);
1172
1173
KleptoMobile_Draw_KleptoMobile();
1174
}
1175
1176
void KleptoMobile_EditorLoad(void)
1177
{
1178
KleptoMobile->aniFrames = RSDK.LoadSpriteAnimation("Phantom/KleptoMobile.bin", SCOPE_STAGE);
1179
1180
RSDK_ACTIVE_VAR(KleptoMobile, type);
1181
RSDK_ENUM_VAR("Eggman", KLEPTOMOBILE_EGGMAN);
1182
}
1183
#endif
1184
1185
void KleptoMobile_Serialize(void) { RSDK_EDITABLE_VAR(KleptoMobile, VAR_ENUM, type); }
1186
1187