Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rubberduckycooly
GitHub Repository: rubberduckycooly/Sonic-Mania-Decompilation
Path: blob/master/SonicMania/Objects/Common/Water.c
338 views
1
// ---------------------------------------------------------------------
2
// RSDK Project: Sonic Mania
3
// Object Description: Water Object
4
// Object Author: Christian Whitehead/Simon Thomley/Hunter Bridges
5
// Decompiled by: Rubberduckycooly & RMGRich
6
// ---------------------------------------------------------------------
7
8
#include "Game.h"
9
10
ObjectWater *Water;
11
12
void Water_Update(void)
13
{
14
RSDK_THIS(Water);
15
16
StateMachine_Run(self->state);
17
}
18
19
void Water_LateUpdate(void) {}
20
21
void Water_StaticUpdate(void)
22
{
23
EntityPauseMenu *pauseMenu = RSDK_GET_ENTITY(SLOT_PAUSEMENU, PauseMenu);
24
25
if (pauseMenu->classID != PauseMenu->classID) {
26
if (Water->newWaterLevel == Water->targetWaterLevel) {
27
Water->moveWaterLevel = 0;
28
29
if (Water->waterLevelVolume > 0)
30
Water->waterLevelVolume--;
31
Water->waterLevelVolume = CLAMP(Water->waterLevelVolume, 0, 30);
32
}
33
else {
34
if (RSDK.CheckSceneFolder("HCZ") && Water->moveWaterLevel) {
35
if (!(Water->waterLevelVolume % 5))
36
Camera_ShakeScreen(0, 0, 1);
37
++Water->waterLevelVolume;
38
}
39
40
if (Water->newWaterLevel >= Water->targetWaterLevel) {
41
Water->newWaterLevel -= Water->waterMoveSpeed;
42
if (Water->newWaterLevel < Water->targetWaterLevel)
43
Water->newWaterLevel = Water->targetWaterLevel;
44
}
45
else {
46
Water->newWaterLevel += Water->waterMoveSpeed;
47
if (Water->newWaterLevel > Water->targetWaterLevel)
48
Water->newWaterLevel = Water->targetWaterLevel;
49
}
50
}
51
}
52
53
if (Water->playingWaterLevelSfx) {
54
if (Water->waterLevelVolume > 30)
55
Water->waterLevelVolume = 30;
56
float volume = Water->waterLevelVolume / 30.0;
57
RSDK.SetChannelAttributes(Water->waterLevelChannelL, volume, -1.0, 1.0);
58
RSDK.SetChannelAttributes(Water->waterLevelChannelR, volume, 1.0, 1.0);
59
}
60
61
if (Water->waterLevelVolume > 0) {
62
if (!Water->playingWaterLevelSfx) {
63
Water->waterLevelChannelL = RSDK.PlaySfx(Water->sfxWaterLevelL, 1, 255);
64
RSDK.SetChannelAttributes(Water->waterLevelChannelL, 0.0, -1.0, 1.0);
65
66
Water->waterLevelChannelR = RSDK.PlaySfx(Water->sfxWaterLevelR, 1, 255);
67
RSDK.SetChannelAttributes(Water->waterLevelChannelR, 0.0, 1.0, 1.0);
68
69
Water->playingWaterLevelSfx = true;
70
}
71
}
72
else if (!Water->waterLevelVolume && Water->playingWaterLevelSfx) {
73
RSDK.StopSfx(Water->sfxWaterLevelL);
74
RSDK.StopSfx(Water->sfxWaterLevelR);
75
Water->playingWaterLevelSfx = false;
76
}
77
78
bool32 wakeActive = false;
79
for (int32 i = 0; i < PLAYER_COUNT; ++i) wakeActive |= Water->wakePosX[i] > 0;
80
81
if (wakeActive) {
82
if (pauseMenu->classID != PauseMenu->classID)
83
RSDK.ProcessAnimation(&Water->wakeAnimator);
84
85
if (!Water->playingSkimSfx) {
86
RSDK.PlaySfx(Water->sfxSkim, 3570, 0xFF);
87
Water->playingSkimSfx = true;
88
}
89
}
90
else if (Water->playingSkimSfx) {
91
RSDK.StopSfx(Water->sfxSkim);
92
Water->playingSkimSfx = false;
93
}
94
}
95
96
void Water_Draw(void)
97
{
98
RSDK_THIS(Water);
99
100
StateMachine_Run(self->stateDraw);
101
}
102
103
void Water_Create(void *data)
104
{
105
RSDK_THIS(Water);
106
107
self->visible = true;
108
if (!SceneInfo->inEditor) {
109
if (data)
110
self->type = VOID_TO_INT(data);
111
112
switch (self->type) {
113
case WATER_WATERLEVEL:
114
if (globals->gameMode == MODE_COMPETITION && RSDK.CheckSceneFolder("CPZ")) {
115
destroyEntity(self);
116
}
117
else {
118
self->active = ACTIVE_NORMAL;
119
self->inkEffect = INK_ADD;
120
self->drawGroup = Zone->hudDrawGroup - 1;
121
self->alpha = RSDK.CheckSceneFolder("AIZ") ? 0x60 : 0xE0;
122
RSDK.SetSpriteAnimation(Water->aniFrames, 0, &self->animator, true, 0);
123
self->state = Water_State_Water;
124
self->stateDraw = Water_Draw_Water;
125
self->size.x >>= 16;
126
Water->newWaterLevel = self->position.y;
127
Water->targetWaterLevel = self->position.y;
128
}
129
break;
130
131
case WATER_POOL:
132
self->active = ACTIVE_BOUNDS;
133
self->drawFX = FX_FLIP;
134
switch (self->priority) {
135
case WATER_PRIORITY_LOWEST: self->drawGroup = Zone->objectDrawGroup[0] - 1; break;
136
case WATER_PRIORITY_LOW: self->drawGroup = Zone->playerDrawGroup[0]; break;
137
case WATER_PRIORITY_HIGH: self->drawGroup = Zone->playerDrawGroup[1]; break;
138
case WATER_PRIORITY_HIGHEST: self->drawGroup = Zone->hudDrawGroup - 1; break;
139
default: break;
140
}
141
142
self->updateRange.x = self->size.x >> 1;
143
self->updateRange.y = self->size.y >> 1;
144
145
self->hitbox.right = (self->size.x >> 17);
146
self->hitbox.left = -(self->size.x >> 17);
147
self->hitbox.bottom = (self->size.y >> 17);
148
self->hitbox.top = -(self->size.y >> 17);
149
150
self->state = Water_State_Pool;
151
self->stateDraw = Water_Draw_Pool;
152
break;
153
154
case WATER_BUBBLER:
155
case WATER_BIG_BUBBLER:
156
self->drawGroup = Zone->objectDrawGroup[0];
157
self->inkEffect = INK_ADD;
158
self->alpha = 0x100;
159
self->active = ACTIVE_BOUNDS;
160
self->updateRange.x = TO_FIXED(16);
161
self->updateRange.y = TO_FIXED(16);
162
163
RSDK.SetSpriteAnimation(Water->aniFrames, 2, &self->animator, true, 0);
164
165
self->state = Water_State_Bubbler;
166
self->stateDraw = Water_Draw_Bubbler;
167
break;
168
169
case WATER_HEIGHT_TRIGGER:
170
self->active = ACTIVE_BOUNDS;
171
self->visible = false;
172
if (self->buttonTag > 0) {
173
self->active = ACTIVE_NORMAL;
174
Water_SetupTagLink();
175
}
176
177
self->updateRange.x = self->size.x >> 1;
178
self->updateRange.y = self->size.y >> 1;
179
180
self->state = Water_State_HeightTrigger;
181
break;
182
183
case WATER_BTN_BIG_BUBBLE:
184
self->drawGroup = Zone->objectDrawGroup[0];
185
self->active = ACTIVE_BOUNDS;
186
self->updateRange.x = TO_FIXED(16);
187
self->updateRange.y = TO_FIXED(16);
188
189
self->state = Water_State_BtnBigBubble;
190
self->stateDraw = StateMachine_None;
191
192
Water_SetupTagLink();
193
break;
194
195
case WATER_SPLASH:
196
self->active = ACTIVE_NORMAL;
197
self->drawGroup = Zone->hudDrawGroup - 2;
198
RSDK.SetSpriteAnimation(Water->aniFrames, 1, &self->animator, true, 0);
199
200
self->state = Water_State_Splash;
201
self->stateDraw = Water_Draw_Splash;
202
break;
203
204
case WATER_BUBBLE:
205
self->active = ACTIVE_NORMAL;
206
self->drawGroup = Zone->playerDrawGroup[1];
207
self->drawFX = FX_SCALE;
208
self->inkEffect = INK_ADD;
209
self->alpha = 0x100;
210
self->updateRange.x = TO_FIXED(128);
211
self->updateRange.y = TO_FIXED(128);
212
self->isPermanent = true;
213
214
self->scale.x = 0x200;
215
self->scale.y = 0x200;
216
RSDK.SetSpriteAnimation(Water->aniFrames, 5, &self->animator, true, 0);
217
218
self->state = Water_State_Bubble;
219
self->stateDraw = Water_Draw_Bubble;
220
break;
221
222
case WATER_COUNTDOWN:
223
self->active = ACTIVE_NORMAL;
224
self->drawGroup = Zone->playerDrawGroup[1];
225
self->drawFX = FX_SCALE;
226
self->inkEffect = INK_ADD;
227
self->alpha = 0x100;
228
self->updateRange.x = TO_FIXED(128);
229
self->updateRange.y = TO_FIXED(128);
230
231
self->scale.x = 0x200;
232
self->scale.y = 0x200;
233
RSDK.SetSpriteAnimation(Water->aniFrames, 7, &self->animator, true, 0);
234
235
self->state = Water_State_Countdown;
236
self->stateDraw = Water_Draw_Countdown;
237
break;
238
239
default: break;
240
}
241
}
242
}
243
244
void Water_StageLoad(void)
245
{
246
Water->active = ACTIVE_ALWAYS;
247
248
Water->aniFrames = RSDK.LoadSpriteAnimation("Global/Water.bin", SCOPE_STAGE);
249
250
Water->waterLevel = 0x7FFFFFFF;
251
Water->newWaterLevel = Water->waterLevel;
252
Water->targetWaterLevel = Water->waterLevel;
253
254
Water->disableWaterSplash = false;
255
256
Water->hitboxPlayerBubble.left = -2;
257
Water->hitboxPlayerBubble.top = -2;
258
Water->hitboxPlayerBubble.right = 2;
259
Water->hitboxPlayerBubble.bottom = 2;
260
261
Water->hitboxPoint.left = -1;
262
Water->hitboxPoint.top = -1;
263
Water->hitboxPoint.right = 1;
264
Water->hitboxPoint.bottom = 1;
265
266
Water->sfxSplash = RSDK.GetSfx("Stage/Splash.wav");
267
Water->sfxBreathe = RSDK.GetSfx("Stage/Breathe.wav");
268
Water->sfxWarning = RSDK.GetSfx("Stage/Warning.wav");
269
Water->sfxDrown = RSDK.GetSfx("Stage/Drown.wav");
270
Water->sfxDrownAlert = RSDK.GetSfx("Stage/DrownAlert.wav");
271
Water->sfxSkim = RSDK.GetSfx("HCZ/Skim.wav");
272
273
if (RSDK.CheckSceneFolder("HCZ")) {
274
Water->wakeFrames = RSDK.LoadSpriteAnimation("HCZ/Wake.bin", SCOPE_STAGE);
275
Water->bigBubbleFrames = RSDK.LoadSpriteAnimation("HCZ/BigBubble.bin", SCOPE_STAGE);
276
277
Water->sfxWaterLevelL = RSDK.GetSfx("HCZ/WaterLevel_L.wav");
278
Water->sfxWaterLevelR = RSDK.GetSfx("HCZ/WaterLevel_R.wav");
279
Water->sfxDNAGrab = RSDK.GetSfx("CPZ/DNAGrab.wav");
280
Water->sfxDNABurst = RSDK.GetSfx("CPZ/DNABurst.wav");
281
282
RSDK.SetSpriteAnimation(Water->wakeFrames, 0, &Water->wakeAnimator, true, 0);
283
}
284
}
285
286
void Water_DrawHook_ApplyWaterPalette(void)
287
{
288
RSDKScreenInfo *screen = &ScreenInfo[SceneInfo->currentScreenID];
289
int32 waterDrawPos = CLAMP(FROM_FIXED(Water->waterLevel) - screen->position.y, 0, screen->size.y);
290
291
RSDK.SetActivePalette(Water->waterPalette, waterDrawPos, screen->size.y);
292
ScreenInfo[SceneInfo->currentScreenID].waterDrawPos = waterDrawPos;
293
}
294
295
void Water_DrawHook_RemoveWaterPalette(void) { RSDK.SetActivePalette(0, 0, ScreenInfo[SceneInfo->currentScreenID].size.y); }
296
297
void Water_SetupTagLink(void)
298
{
299
RSDK_THIS(Water);
300
301
self->taggedButton = NULL;
302
if (self->buttonTag > 0) {
303
if (Button) {
304
foreach_all(Button, button)
305
{
306
if (button->tag == self->buttonTag) {
307
self->taggedButton = button;
308
foreach_break;
309
}
310
}
311
}
312
313
if (PullChain && !self->taggedButton) {
314
foreach_all(PullChain, chain)
315
{
316
if (chain->tag == self->buttonTag) {
317
self->taggedButton = (EntityButton *)chain;
318
foreach_break;
319
}
320
}
321
}
322
323
if (self->taggedButton) {
324
if (self->updateRange.x < TO_FIXED(128) + abs(self->position.x - self->taggedButton->position.x))
325
self->updateRange.x = TO_FIXED(128) + abs(self->position.x - self->taggedButton->position.x);
326
327
if (self->updateRange.y < TO_FIXED(128) + abs(self->position.y - self->taggedButton->position.y))
328
self->updateRange.y = TO_FIXED(128) + abs(self->position.y - self->taggedButton->position.y);
329
}
330
}
331
}
332
333
void Water_SpawnBubble(EntityPlayer *player, int32 id)
334
{
335
if (Water->constBubbleTimer[id] <= 0) {
336
Water->constBubbleTimer[id] = 60;
337
if (RSDK.Rand(0, 3) == 1)
338
Water->randBubbleTimer[id] = RSDK.Rand(0, 16) + 8;
339
else
340
Water->randBubbleTimer[id] = 0x200;
341
}
342
else {
343
Water->constBubbleTimer[id]--;
344
if (Water->randBubbleTimer[id] <= 0)
345
Water->randBubbleTimer[id] = 0x200;
346
else
347
return;
348
}
349
350
EntityWater *bubble = CREATE_ENTITY(Water, INT_TO_VOID(WATER_BUBBLE), player->position.x, player->position.y);
351
if (player->direction) {
352
bubble->position.x -= TO_FIXED(6);
353
bubble->angle = 0x100;
354
}
355
else {
356
bubble->position.x += TO_FIXED(6);
357
}
358
359
bubble->childPtr = player;
360
bubble->bubbleX = bubble->position.x;
361
bubble->velocity.y = -0x8800;
362
bubble->drawGroup = player->drawGroup + 1;
363
}
364
365
void Water_SpawnCountDownBubble(EntityPlayer *player, int32 id, uint8 bubbleID)
366
{
367
EntityWater *bubble = CREATE_ENTITY(Water, INT_TO_VOID(WATER_COUNTDOWN), player->position.x, player->position.y);
368
if (player->direction) {
369
bubble->position.x -= TO_FIXED(6);
370
bubble->angle = 0x100;
371
}
372
else {
373
bubble->position.x += TO_FIXED(6);
374
}
375
bubble->bubbleX = bubble->position.x;
376
bubble->velocity.y = -0x8800;
377
bubble->childPtr = player;
378
bubble->countdownID = bubbleID;
379
bubble->drawGroup = Zone->playerDrawGroup[1] + 1;
380
}
381
382
void Water_State_Water(void)
383
{
384
RSDK_THIS(Water);
385
386
RSDK.ProcessAnimation(&self->animator);
387
388
if (self->type == WATER_WATERLEVEL)
389
Water->waterLevel = (self->size.x * RSDK.Sin512(2 * Zone->timer)) + Water->newWaterLevel;
390
391
for (int32 playerID = 0; playerID < Player->playerCount; ++playerID) {
392
EntityPlayer *player = RSDK_GET_ENTITY(playerID, Player);
393
394
if (player->state == Player_State_FlyToPlayer && player->abilityPtrs[0]) {
395
player->position.x = ((Entity *)player->abilityPtrs[0])->position.x;
396
player->position.y = ((Entity *)player->abilityPtrs[0])->position.y;
397
}
398
399
Water->wakePosX[playerID] = 0;
400
401
bool32 canEnterWater = true;
402
if (!Player_CheckValidState(player) || player->state == Player_State_TransportTube) {
403
if (player->state != Player_State_FlyToPlayer)
404
canEnterWater = false;
405
}
406
407
if (canEnterWater) {
408
EntityWater *currentPool = NULL;
409
uint16 underwater = 0;
410
foreach_active(Water, pool)
411
{
412
if (pool->type == WATER_POOL) {
413
if (RSDK.CheckObjectCollisionTouchBox(pool, &pool->hitbox, player, &Water->hitboxPoint)) {
414
currentPool = pool;
415
pool->childPtr = player;
416
underwater = RSDK.GetEntitySlot(pool);
417
}
418
else if (pool->childPtr == player) {
419
pool->childPtr = NULL;
420
if (!currentPool)
421
currentPool = pool;
422
}
423
}
424
}
425
426
if (player->position.y > Water->waterLevel)
427
underwater = true;
428
429
if (!Player_CheckValidState(player)) {
430
if (player->state != Player_State_FlyToPlayer)
431
underwater = false;
432
}
433
434
int32 waterID = 0;
435
#if MANIA_USE_PLUS
436
if (!player->isGhost)
437
#endif
438
waterID = underwater;
439
440
if (!waterID) {
441
if (player->underwater) {
442
EntityWater *waterSection = NULL;
443
if (player->underwater > 1)
444
waterSection = RSDK_GET_ENTITY(player->underwater, Water);
445
446
player->underwater = false;
447
Player_UpdatePhysicsState(player);
448
if (player->velocity.y && (!Current || !((1 << RSDK.GetEntitySlot(player)) & Current->activePlayers))
449
&& (Player_CheckValidState(player) || player->state == Player_State_FlyToPlayer)) {
450
if (!Water->disableWaterSplash) {
451
if (waterSection) {
452
EntityWater *splash = CREATE_ENTITY(Water, INT_TO_VOID(WATER_SPLASH), player->position.x,
453
waterSection->position.y - (waterSection->size.y >> 1));
454
splash->childPtr = waterSection;
455
splash->drawGroup = player->drawGroup;
456
}
457
else {
458
CREATE_ENTITY(Water, INT_TO_VOID(WATER_SPLASH), player->position.x, Water->waterLevel);
459
}
460
461
RSDK.PlaySfx(Water->sfxSplash, false, 0xFF);
462
}
463
464
if (player->velocity.y >= -TO_FIXED(4)) {
465
player->velocity.y <<= 1;
466
if (player->velocity.y < -TO_FIXED(16))
467
player->velocity.y = -TO_FIXED(16);
468
}
469
}
470
}
471
else {
472
// if we're not underwater already but we would otherwise be
473
if (abs(player->groundVel) >= 0x78000) {
474
Hitbox *playerHitbox = Player_GetHitbox(player);
475
if (abs(player->position.y + (playerHitbox->bottom << 16) - Water->waterLevel) <= 0x40000 && player->groundedStore) {
476
Water->wakePosX[playerID] = player->position.x;
477
Water->wakeDir[playerID] = player->groundVel < 0;
478
if (!player->onGround) {
479
player->onGround = true;
480
player->position.y = Water->waterLevel - (playerHitbox->bottom << 16);
481
}
482
}
483
}
484
}
485
486
if (!player->sidekick && player->drownTimer >= 1080) {
487
player->drownTimer = 0;
488
Music_JingleFadeOut(TRACK_DROWNING, false);
489
}
490
}
491
else {
492
bool32 notUnderwater = player->underwater == 0;
493
player->underwater = waterID;
494
495
if (notUnderwater) {
496
Player_UpdatePhysicsState(player);
497
if (player->velocity.y && (!Current || !((1 << RSDK.GetEntitySlot(player)) & Current->activePlayers))) {
498
if (!Water->disableWaterSplash) {
499
if (currentPool) {
500
EntityWater *splash = CREATE_ENTITY(Water, INT_TO_VOID(WATER_SPLASH), player->position.x,
501
currentPool->position.y - (currentPool->size.x >> 1));
502
splash->drawGroup = player->drawGroup;
503
splash->childPtr = currentPool;
504
}
505
else {
506
CREATE_ENTITY(Water, INT_TO_VOID(WATER_SPLASH), player->position.x, Water->waterLevel);
507
}
508
509
RSDK.PlaySfx(Water->sfxSplash, false, 255);
510
}
511
512
player->velocity.y >>= 2;
513
}
514
515
player->velocity.x >>= 1;
516
if (player->collisionMode == CMODE_FLOOR)
517
player->groundVel >>= 1;
518
519
player->drownTimer = 0;
520
Water->constBubbleTimer[playerID] = 52;
521
Water->unused1[playerID] = 0;
522
}
523
else {
524
if (player->invincibleTimer <= 0) {
525
if (player->shield == SHIELD_FIRE) {
526
// No cool smoke fx in mania, just a loss of shield :(
527
player->shield = SHIELD_NONE;
528
destroyEntity(RSDK_GET_ENTITY(Player->playerCount + RSDK.GetEntitySlot(player), Shield));
529
}
530
531
if (player->shield == SHIELD_LIGHTNING) {
532
// No cool electric flash fx in mania, just a loss of shield :(
533
player->shield = SHIELD_NONE;
534
destroyEntity(RSDK_GET_ENTITY(Player->playerCount + RSDK.GetEntitySlot(player), Shield));
535
}
536
}
537
538
if (player->shield != SHIELD_BUBBLE) {
539
Water_SpawnBubble(player, playerID);
540
541
bool32 playAlertSfx = false;
542
switch (++player->drownTimer) {
543
default: break;
544
545
case 360:
546
case 660:
547
case 960:
548
if (!player->sidekick)
549
RSDK.PlaySfx(Water->sfxWarning, false, 0xFF);
550
break;
551
552
case 1080:
553
#if MANIA_USE_PLUS
554
if (globals->gameMode != MODE_COMPETITION && globals->gameMode != MODE_ENCORE) {
555
#else
556
if (globals->gameMode != MODE_COMPETITION) {
557
#endif
558
if (!player->sidekick)
559
Music_PlayJingle(TRACK_DROWNING);
560
}
561
562
Water_SpawnCountDownBubble(player, playerID, 5);
563
playAlertSfx = true;
564
break;
565
566
case 1140: playAlertSfx = true; break;
567
568
case 1200:
569
Water_SpawnCountDownBubble(player, playerID, 4);
570
playAlertSfx = true;
571
break;
572
573
case 1260: playAlertSfx = true; break;
574
575
case 1320:
576
Water_SpawnCountDownBubble(player, playerID, 3);
577
playAlertSfx = true;
578
break;
579
580
case 1380: playAlertSfx = true; break;
581
582
case 1440:
583
Water_SpawnCountDownBubble(player, playerID, 2);
584
playAlertSfx = true;
585
break;
586
587
case 1500: playAlertSfx = true; break;
588
589
case 1560:
590
Water_SpawnCountDownBubble(player, playerID, 1);
591
playAlertSfx = true;
592
break;
593
594
case 1620: playAlertSfx = true; break;
595
596
case 1680:
597
Water_SpawnCountDownBubble(player, playerID, 0);
598
playAlertSfx = true;
599
break;
600
601
case 1740: playAlertSfx = true; break;
602
603
case 1800:
604
player->deathType = PLAYER_DEATH_DROWN;
605
if (!pool)
606
player->drawGroup = Zone->playerDrawGroup[1];
607
playAlertSfx = true;
608
break;
609
}
610
611
if (playAlertSfx) {
612
if (
613
#if MANIA_USE_PLUS
614
globals->gameMode == MODE_ENCORE ||
615
#endif
616
globals->gameMode == MODE_COMPETITION)
617
RSDK.PlaySfx(Water->sfxDrownAlert, false, 255);
618
}
619
}
620
}
621
}
622
}
623
624
if (self->state == Player_State_FlyToPlayer && player->abilityPtrs[0]) {
625
self->position.x = player->position.x;
626
self->position.y = player->position.y;
627
}
628
}
629
}
630
631
void Water_State_Pool(void)
632
{
633
// nothing, its handled by the water level type
634
}
635
636
void Water_State_Splash(void)
637
{
638
RSDK_THIS(Water);
639
640
EntityWater *water = self->childPtr;
641
if (water) {
642
if (water != (EntityWater *)1)
643
self->position.y = water->position.y - (water->size.y >> 1);
644
}
645
else {
646
self->position.y = Water->waterLevel;
647
}
648
649
RSDK.ProcessAnimation(&self->animator);
650
651
if (self->animator.frameID == self->animator.frameCount - 1)
652
destroyEntity(self);
653
}
654
655
void Water_HandleBubbleMovement(void)
656
{
657
RSDK_THIS(Water);
658
659
if (self->bubbleVelocity.x || self->bubbleVelocity.y) {
660
self->position.x += self->bubbleVelocity.x;
661
self->position.y += self->bubbleVelocity.y;
662
}
663
else {
664
self->position.x += self->velocity.x;
665
self->position.y += self->velocity.y;
666
}
667
668
self->position.x += self->bubbleOffset.x;
669
self->position.y += self->bubbleOffset.y;
670
671
int32 anim = self->animator.animationID;
672
if ((anim == 3 && self->animator.frameID < 12) || anim == 4 || anim == 5 || (anim == 7 && !self->activePlayers)) {
673
self->position.x = (RSDK.Sin512(self->angle) << 9) + self->bubbleX;
674
self->angle = (self->angle + 4) & 0x1FF;
675
}
676
677
if (self->position.y < Water->waterLevel) {
678
bool32 inWater = false;
679
foreach_active(Water, pool)
680
{
681
if (pool->type == WATER_POOL && RSDK.CheckObjectCollisionTouchBox(pool, &pool->hitbox, self, &Water->hitboxPoint))
682
inWater = true;
683
}
684
685
if (!inWater) {
686
if (self->animator.animationID == 3 && self->animator.frameID > 12) {
687
RSDK.SetSpriteAnimation(Water->aniFrames, 6, &self->animator, false, 0);
688
self->velocity.y = 0;
689
}
690
else if (self->animator.animationID == 7) {
691
Water_PopBigBubble(self, false);
692
}
693
else {
694
destroyEntity(self);
695
}
696
}
697
}
698
}
699
700
void Water_PopBigBubble(EntityWater *self, bool32 jumpedOut)
701
{
702
if (self->timer <= 0) {
703
RSDK.SetSpriteAnimation(Water->aniFrames, 6, &self->animator, true, 0);
704
self->velocity.x = 0;
705
self->velocity.y = 0;
706
707
RSDK.PlaySfx(Water->sfxDNABurst, false, 0xFF);
708
709
foreach_active(Player, player)
710
{
711
int32 playerID = RSDK.GetEntitySlot(player);
712
if ((1 << playerID) & self->activePlayers) {
713
if (jumpedOut) {
714
RSDK.SetSpriteAnimation(player->aniFrames, ANI_JUMP, &player->animator, true, 0);
715
}
716
else {
717
if (player->state != Player_State_Static || player->animator.animationID != ANI_BUBBLE) {
718
EntityShield *shield = RSDK_GET_ENTITY(Player->playerCount + playerID, Shield);
719
if (shield)
720
shield->visible = true;
721
self->activePlayers &= ~(1 << playerID);
722
}
723
else {
724
RSDK.SetSpriteAnimation(player->aniFrames, ANI_AIR_WALK, &player->animator, true, 0);
725
EntityShield *shield = RSDK_GET_ENTITY(Player->playerCount + playerID, Shield);
726
if (shield)
727
shield->visible = true;
728
self->activePlayers &= ~(1 << playerID);
729
}
730
}
731
732
player->state = Player_State_Air;
733
}
734
}
735
}
736
}
737
738
void Water_State_Bubble(void)
739
{
740
RSDK_THIS(Water);
741
742
EntityPlayer *playerPtr = (EntityPlayer *)self->childPtr;
743
744
if (self->animator.animationID == 6 && self->animator.frameID == self->animator.frameCount - 1)
745
destroyEntity(self);
746
747
if (playerPtr && playerPtr->state == Player_State_Bubble && self->animator.frameID < 3)
748
self->bubbleX += 0x40000;
749
750
if (self->speed != -1) {
751
if (self->speed) {
752
self->bubbleX += self->velocity.x;
753
self->velocity.x += self->speed;
754
}
755
756
Water_HandleBubbleMovement();
757
758
if (self->tileCollisions != TILECOLLISION_NONE) {
759
if (RSDK.ObjectTileCollision(self, Zone->collisionLayers, CMODE_FLOOR, 0, 0, TO_FIXED(16), false)) {
760
while (RSDK.ObjectTileCollision(self, Zone->collisionLayers, CMODE_FLOOR, 0, 0, TO_FIXED(16), 0)) self->position.y -= TO_FIXED(1);
761
}
762
else {
763
while (RSDK.ObjectTileCollision(self, Zone->collisionLayers, CMODE_ROOF, 0, 0, -TO_FIXED(16), false)
764
&& RSDK.ObjectTileCollision(self, Zone->collisionLayers, CMODE_ROOF, 0, 0, -TO_FIXED(16), false)) {
765
self->position.y += TO_FIXED(1);
766
}
767
}
768
}
769
}
770
771
RSDK.ProcessAnimation(&self->animator);
772
773
if (self->animator.frameID >= 13 || self->animator.animationID == 7) {
774
if (self->isBigBubble) {
775
if (self->bigBubbleTimer == 16) {
776
RSDK.SetSpriteAnimation(Water->bigBubbleFrames, 7, &self->animator, true, 0);
777
self->scale.x = 0x180;
778
self->scale.y = 0x180;
779
}
780
781
if (self->bigBubbleTimer <= 0) {
782
self->activePlayers = 0;
783
self->releasedPlayers = 0;
784
self->state = Water_State_BigBubble;
785
}
786
else {
787
self->bigBubbleTimer--;
788
}
789
}
790
else {
791
foreach_active(Player, player)
792
{
793
if (Player_CheckValidState(player) || player->state == Player_State_FlyToPlayer) {
794
if (player->shield != SHIELD_BUBBLE && player->underwater && !Water_GetPlayerBubble(player)
795
&& player->position.x >= self->position.x - TO_FIXED(16) && player->position.x <= self->position.x + TO_FIXED(16)) {
796
797
bool32 inWater = false;
798
if (player->animator.animationID == ANI_FAN) {
799
if (player->position.y >= self->position.y - TO_FIXED(16))
800
inWater = (player->position.y <= self->position.y + TO_FIXED(16));
801
}
802
else {
803
if (player->position.y > self->position.y)
804
inWater = (player->position.y <= self->position.y + TO_FIXED(16));
805
}
806
807
if (inWater) {
808
bool32 inBubble = false;
809
if (!(self->bubbleFlags & 1) && player->sidekick) {
810
self->bubbleFlags |= 1;
811
inBubble = true;
812
}
813
814
if (!player->sidekick || inBubble) {
815
if (!inBubble) {
816
self->state = Water_State_BubbleBreathed;
817
self->countdownID = 0;
818
self->velocity.y = 0;
819
self->childPtr = player;
820
}
821
822
if (player->state != Current_PlayerState_Right && player->state != Current_PlayerState_Left
823
&& player->state != Current_PlayerState_Up && player->state != Current_PlayerState_Down) {
824
player->velocity.x = 0;
825
player->velocity.y = 0;
826
player->groundVel = 0;
827
bool32 canBreathe = true;
828
829
int32 anim = player->animator.animationID;
830
if (player->characterID == ID_TAILS) {
831
canBreathe = anim != ANI_FLY && anim != ANI_FLY_TIRED && anim != ANI_FLY_LIFT && anim != ANI_SWIM
832
&& anim != ANI_SWIM_LIFT;
833
}
834
else if (player->characterID == ID_KNUCKLES) {
835
canBreathe = anim != ANI_LEDGE_PULL_UP && anim != ANI_GLIDE && anim != ANI_GLIDE_SLIDE
836
&& anim != ANI_CLIMB_IDLE && anim != ANI_CLIMB_UP && anim != ANI_CLIMB_DOWN;
837
}
838
839
if (canBreathe && (anim != ANI_FAN && anim != ANI_CLING)) {
840
RSDK.SetSpriteAnimation(player->aniFrames, ANI_BREATHE, &player->animator, false, 0);
841
842
if (!player->sidekick)
843
self->playerInBubble = true;
844
}
845
846
if (player->state == Player_State_FlyCarried) {
847
player->state = Player_State_Air;
848
RSDK_GET_ENTITY(SLOT_PLAYER2, Player)->flyCarryTimer = 30;
849
}
850
#if MANIA_USE_PLUS
851
else if (player->state == Player_State_MightyHammerDrop) {
852
player->state = Player_State_Air;
853
}
854
#endif
855
}
856
857
player->drownTimer = 0;
858
if (!player->sidekick)
859
Music_JingleFadeOut(TRACK_DROWNING, false);
860
861
RSDK.PlaySfx(Water->sfxBreathe, false, 255);
862
}
863
}
864
}
865
}
866
}
867
}
868
}
869
870
if (self->speed != -1) {
871
if (!RSDK.CheckOnScreen(self, &self->updateRange))
872
destroyEntity(self);
873
}
874
}
875
876
void Water_State_BubbleBreathed(void)
877
{
878
RSDK_THIS(Water);
879
880
EntityPlayer *player = (EntityPlayer *)self->childPtr;
881
if (player->state == Player_State_Hurt || !Player_CheckValidState(player))
882
self->playerInBubble = false;
883
884
if (self->speed) {
885
self->position.x += self->velocity.x;
886
self->velocity.x += self->speed;
887
}
888
889
RSDK.ProcessAnimation(&self->animator);
890
891
self->scale.x -= 0x18;
892
self->scale.y -= 0x18;
893
if (self->scale.x > 0) {
894
if (self->playerInBubble)
895
RSDK.SetSpriteAnimation(player->aniFrames, ANI_BREATHE, &player->animator, false, 0);
896
}
897
else {
898
self->scale.x = 0;
899
self->scale.y = 0;
900
901
if (self->playerInBubble)
902
RSDK.SetSpriteAnimation(player->aniFrames, ANI_WALK, &player->animator, false, 0);
903
904
destroyEntity(self);
905
}
906
}
907
908
EntityWater *Water_GetPlayerBubble(EntityPlayer *player)
909
{
910
int32 playerID = RSDK.GetEntitySlot(player);
911
912
foreach_active(Water, water)
913
{
914
if (water->state == Water_State_BigBubble && ((1 << playerID) & water->activePlayers)) {
915
foreach_return water;
916
}
917
}
918
return NULL;
919
}
920
921
void Water_State_BigBubble(void)
922
{
923
RSDK_THIS(Water);
924
925
if (self->animator.animationID == 6 && self->animator.frameID == self->animator.frameCount - 1)
926
destroyEntity(self);
927
928
Water_HandleBubbleMovement();
929
self->countdownID = self->animator.frameID;
930
931
RSDK.ProcessAnimation(&self->animator);
932
933
if (self->animator.frameID == self->countdownID || self->animator.animationID == 7) {
934
if (self->scale.x < 0x200) {
935
self->scale.y += 8;
936
self->scale.x += 8;
937
}
938
}
939
else {
940
self->scale.x = 0x180;
941
self->scale.y = 0x180;
942
}
943
944
if (self->animator.animationID != 6) {
945
foreach_active(Player, player)
946
{
947
int32 playerID = RSDK.GetEntitySlot(player);
948
if (!Player_CheckValidState(player) || !player->underwater) {
949
continue;
950
}
951
952
if (!(self->activePlayers & (1 << playerID)) && !((1 << playerID) & self->releasedPlayers)) {
953
if (globals->gameMode == MODE_COMPETITION && self->activePlayers)
954
continue;
955
956
if (Player_CheckCollisionTouch(player, self, &Water->hitboxPlayerBubble) && !Water_GetPlayerBubble(player)) {
957
RSDK.SetSpriteAnimation(player->aniFrames, ANI_BUBBLE, &player->animator, true, 0);
958
player->state = Player_State_Static;
959
EntityShield *shield = RSDK_GET_ENTITY(Player->playerCount + RSDK.GetEntitySlot(player), Shield);
960
if (shield)
961
shield->visible = false;
962
963
player->drownTimer = 0;
964
if (!player->sidekick)
965
Music_JingleFadeOut(TRACK_DROWNING, false);
966
967
RSDK.PlaySfx(Water->sfxDNAGrab, false, 255);
968
self->activePlayers |= 1 << playerID;
969
self->releasedPlayers |= 1 << playerID;
970
if (RSDK.GetEntitySlot(self) >= RESERVE_ENTITY_COUNT) {
971
int32 id = SLOT_BIGBUBBLE_P1;
972
for (; id < SLOT_BIGBUBBLE_P1 + PLAYER_COUNT; ++id) {
973
if (RSDK_GET_ENTITY_GEN(id)->classID == TYPE_BLANK)
974
break;
975
}
976
977
if (id >= 0 && id < SLOT_BIGBUBBLE_P1 + PLAYER_COUNT) {
978
RSDK.AddDrawListRef(self->drawGroup, id);
979
RSDK.CopyEntity(RSDK_GET_ENTITY(id, Water), self, true);
980
foreach_return;
981
}
982
}
983
}
984
}
985
986
if ((1 << playerID) & self->activePlayers) {
987
if (self->timer > 0) {
988
RSDK.SetSpriteAnimation(player->aniFrames, ANI_BUBBLE, &player->animator, false, 0);
989
player->state = Player_State_Static;
990
}
991
992
player->drownTimer = 0;
993
player->position.x = self->position.x;
994
player->position.y = self->position.y;
995
player->velocity.x = self->velocity.x;
996
player->velocity.y = self->velocity.y;
997
if (player->sidekick) {
998
if (player->left)
999
player->direction = FLIP_X;
1000
else if (player->right)
1001
player->direction = FLIP_NONE;
1002
1003
if (player->jumpPress) {
1004
RSDK.SetSpriteAnimation(player->aniFrames, ANI_JUMP, &player->animator, true, 0);
1005
player->state = Player_State_Air;
1006
self->activePlayers &= ~(1 << playerID);
1007
self->releasedPlayers |= 1 << playerID;
1008
}
1009
}
1010
else {
1011
self->drawGroup = player->drawGroup;
1012
self->collisionPlane = player->collisionPlane;
1013
if (player->state != Player_State_Static && player->animator.animationID != ANI_BUBBLE) {
1014
Water_PopBigBubble(self, false);
1015
foreach_return;
1016
}
1017
1018
int32 xVel = 0;
1019
if (player->left) {
1020
player->direction = FLIP_X;
1021
xVel = -0x11000;
1022
}
1023
else if (player->right) {
1024
player->direction = FLIP_NONE;
1025
xVel = 0x11000;
1026
}
1027
1028
int32 yVel = -0x8800;
1029
if (player->down)
1030
yVel = -0x4400;
1031
else if (player->up)
1032
yVel = -0x11000;
1033
1034
if (xVel - self->velocity.x)
1035
self->velocity.x += (2 * ((xVel - self->velocity.x) > 0) - 1) << 11;
1036
1037
if (yVel - self->velocity.y)
1038
self->velocity.y += (2 * ((yVel - self->velocity.y) > 0) - 1) << 11;
1039
1040
if (player->jumpPress) {
1041
Water_PopBigBubble(self, true);
1042
foreach_return;
1043
}
1044
}
1045
}
1046
1047
if ((1 << playerID) & self->releasedPlayers) {
1048
if (!Player_CheckCollisionTouch(player, self, &Water->hitboxPlayerBubble))
1049
self->releasedPlayers &= ~(1 << playerID);
1050
}
1051
}
1052
1053
if (self->timer > 0)
1054
self->timer--;
1055
1056
Hitbox hitboxSpike;
1057
hitboxSpike.left = -20;
1058
hitboxSpike.top = -20;
1059
hitboxSpike.right = 20;
1060
hitboxSpike.bottom = 20;
1061
1062
foreach_active(Spikes, spikes)
1063
{
1064
if (RSDK.CheckObjectCollisionTouchBox(self, &hitboxSpike, spikes, &spikes->hitbox)) {
1065
Water_PopBigBubble(self, false);
1066
foreach_return;
1067
}
1068
}
1069
1070
if (RSDK.ObjectTileCollision(self, Zone->collisionLayers, CMODE_ROOF, 0, 0, -TO_FIXED(24), true)
1071
|| RSDK.ObjectTileCollision(self, Zone->collisionLayers, CMODE_RWALL, 0, -TO_FIXED(24), 0, true)
1072
|| RSDK.ObjectTileCollision(self, Zone->collisionLayers, CMODE_LWALL, 0, TO_FIXED(24), 0, true)) {
1073
Water_PopBigBubble(self, false);
1074
}
1075
}
1076
}
1077
1078
void Water_State_BtnBigBubble(void)
1079
{
1080
RSDK_THIS(Water);
1081
1082
if (self->taggedButton) {
1083
if (self->timer <= 0) {
1084
Hitbox hitbox;
1085
hitbox.left = -32;
1086
hitbox.top = -32;
1087
hitbox.right = 32;
1088
hitbox.bottom = 32;
1089
1090
if (self->taggedButton->currentlyActive) {
1091
foreach_active(Player, player)
1092
{
1093
if (!player->sidekick && Player_CheckCollisionTouch(player, self, &hitbox)) {
1094
if (player->animator.animationID != ANI_BUBBLE) {
1095
EntityWater *bigBubble = CREATE_ENTITY(Water, INT_TO_VOID(WATER_BUBBLE), self->position.x, self->position.y);
1096
RSDK.SetSpriteAnimation(Water->bigBubbleFrames, 7, &bigBubble->animator, true, 0);
1097
1098
bigBubble->state = Water_State_BigBubble;
1099
bigBubble->isBigBubble = true;
1100
bigBubble->bigBubbleTimer = 0;
1101
bigBubble->velocity.y = -0x8800;
1102
bigBubble->childPtr = 0;
1103
bigBubble->bubbleX = bigBubble->position.x;
1104
bigBubble->timer = 0x1E;
1105
bigBubble->scale.x = 0x180;
1106
bigBubble->scale.y = 0x180;
1107
self->timer = 60;
1108
}
1109
}
1110
}
1111
}
1112
}
1113
else {
1114
self->timer--;
1115
}
1116
}
1117
}
1118
1119
void Water_State_Bubbler(void)
1120
{
1121
RSDK_THIS(Water);
1122
1123
self->visible = false;
1124
if (self->position.y > Water->waterLevel)
1125
self->visible = true;
1126
1127
foreach_active(Water, pool)
1128
{
1129
if (pool->type == WATER_POOL && RSDK.CheckObjectCollisionTouchBox(pool, &pool->hitbox, self, &Water->hitboxPoint))
1130
self->visible = true;
1131
}
1132
1133
if (self->visible && RSDK.CheckOnScreen(self, &self->updateRange)) {
1134
if (--self->countdownID < 0) {
1135
if (!self->bubbleFlags) {
1136
self->bubbleFlags = 1;
1137
int8 rand = RSDK.Rand(0, 0x10000);
1138
self->bubbleType1 = rand % 6;
1139
self->bubbleType2 = rand & 12;
1140
1141
if (!self->dudsRemaining--) {
1142
self->bubbleFlags |= 2;
1143
self->dudsRemaining = self->numDuds;
1144
}
1145
}
1146
1147
self->countdownID = RSDK.Rand(0, 32);
1148
1149
EntityWater *bubble = CREATE_ENTITY(Water, INT_TO_VOID(WATER_BUBBLE), self->position.x, self->position.y - 0x20000);
1150
int32 bubbleSize = Water->bubbleSizes[self->bubbleType1 + self->bubbleType2];
1151
bubble->animator.loopIndex = bubbleSize;
1152
bubble->animator.frameCount = bubbleSize + 1;
1153
if (self->type == WATER_BIG_BUBBLER) {
1154
bubble->isBigBubble = true;
1155
bubble->bigBubbleTimer = 16;
1156
}
1157
bubble->position.x += RSDK.Rand(-8, 9) << 16;
1158
bubble->velocity.y = -0x8800;
1159
bubble->angle = 2 * RSDK.Rand(0, 256);
1160
1161
bubble->childPtr = NULL;
1162
bubble->bubbleX = bubble->position.x;
1163
if (self->bubbleFlags & 2 && (!RSDK.Rand(0, 4) || !self->bubbleType1) && !(self->bubbleFlags & 4)) {
1164
RSDK.SetSpriteAnimation(Water->aniFrames, 3, &bubble->animator, false, 0);
1165
bubble->isPermanent = true;
1166
self->bubbleFlags |= 4;
1167
}
1168
1169
if (--self->bubbleType1 < 0) {
1170
self->bubbleFlags = 0;
1171
self->countdownID += RSDK.Rand(0, 128) + 128;
1172
}
1173
}
1174
}
1175
1176
RSDK.ProcessAnimation(&self->animator);
1177
}
1178
1179
void Water_State_Countdown(void)
1180
{
1181
RSDK_THIS(Water);
1182
1183
EntityPlayer *player = (EntityPlayer *)self->childPtr;
1184
1185
if (player->animator.animationID == ANI_FAN) {
1186
self->bubbleX += player->velocity.x;
1187
self->position.y += player->velocity.y;
1188
}
1189
1190
RSDK.ProcessAnimation(&self->animator);
1191
1192
bool32 isActive = false;
1193
if (self->position.y >= Water->waterLevel) {
1194
isActive = true;
1195
}
1196
else {
1197
foreach_active(Water, pool)
1198
{
1199
if (pool->type == WATER_POOL && RSDK.CheckObjectCollisionTouchBox(pool, &pool->hitbox, self, &Water->hitboxPoint)) {
1200
isActive = true;
1201
}
1202
}
1203
}
1204
1205
if ((self->animator.animationID != 7 || self->animator.frameID != self->animator.frameCount - 1) && isActive) {
1206
self->position.y += self->velocity.y;
1207
self->position.x = (RSDK.Sin512(self->angle) << 9) + self->bubbleX;
1208
self->angle = (self->angle + 4) & 0x1FF;
1209
}
1210
else {
1211
RSDK.SetSpriteAnimation(Water->aniFrames, self->countdownID + 8, &self->animator, true, 0);
1212
1213
if (player->camera) {
1214
self->size.x = (self->position.x & 0xFFFF0000) - (player->camera->position.x & 0xFFFF0000);
1215
self->size.y = (self->position.y & 0xFFFF0000) - (player->camera->position.y & 0xFFFF0000);
1216
}
1217
else {
1218
self->size.x = (self->position.x & 0xFFFF0000) - (player->position.x & 0xFFFF0000);
1219
self->size.y = (self->position.y & 0xFFFF0000) - (player->position.y & 0xFFFF0000);
1220
}
1221
1222
self->state = Water_State_CountdownFollow;
1223
}
1224
}
1225
1226
void Water_State_CountdownFollow(void)
1227
{
1228
RSDK_THIS(Water);
1229
1230
RSDK.ProcessAnimation(&self->animator);
1231
1232
if (self->angle >= 0x280) {
1233
self->scale.x -= 8;
1234
self->scale.y -= 8;
1235
if (self->scale.x <= 0)
1236
destroyEntity(self);
1237
}
1238
else {
1239
self->scale.x = (RSDK.Sin256(self->angle) >> 1) + 0x200;
1240
self->scale.y = (RSDK.Sin256(self->angle + 0x80) >> 1) + 0x200;
1241
1242
self->angle += 6;
1243
}
1244
}
1245
1246
void Water_State_HeightTrigger(void)
1247
{
1248
RSDK_THIS(Water);
1249
1250
bool32 activated = false;
1251
if (self->taggedButton) {
1252
EntityButton *button = self->taggedButton;
1253
if (button->currentlyActive) {
1254
Water->moveWaterLevel = true;
1255
activated = true;
1256
}
1257
}
1258
1259
int32 px = self->position.x + 1;
1260
if (!activated) {
1261
foreach_active(Player, player)
1262
{
1263
if (!RSDK.GetEntitySlot(player)) {
1264
if (abs(self->position.x - player->position.x) < self->updateRange.x) {
1265
if (abs(self->position.y - player->position.y) < self->updateRange.y) {
1266
activated = true;
1267
px = player->position.x;
1268
}
1269
}
1270
}
1271
}
1272
}
1273
1274
if (activated) {
1275
if (self->taggedButton)
1276
Water->targetWaterLevel = self->position.y;
1277
else if (px <= self->position.x)
1278
Water->targetWaterLevel = self->height.x;
1279
else
1280
Water->targetWaterLevel = self->height.y;
1281
1282
Water->waterMoveSpeed = self->speed << 15;
1283
if (self->destroyOnTrigger)
1284
destroyEntity(self);
1285
}
1286
}
1287
1288
void Water_Draw_Water(void)
1289
{
1290
RSDK_THIS(Water);
1291
RSDKScreenInfo *screen = &ScreenInfo[SceneInfo->currentScreenID];
1292
1293
Vector2 drawPos;
1294
drawPos.x = ((screen->position.x & 0xFFFFFFC0) + 32) << 16;
1295
drawPos.y = Water->waterLevel;
1296
for (int32 i = (screen->size.x >> 6) + 2; i > 0; --i) {
1297
RSDK.DrawSprite(&self->animator, &drawPos, false);
1298
drawPos.x += TO_FIXED(64);
1299
}
1300
1301
self->drawFX |= FX_FLIP;
1302
for (int32 i = 0; i < PLAYER_COUNT; ++i) {
1303
if (Water->wakePosX[i] > 0) {
1304
self->direction = Water->wakeDir[i];
1305
drawPos.x = Water->wakePosX[i];
1306
RSDK.DrawSprite(&Water->wakeAnimator, &drawPos, false);
1307
}
1308
}
1309
1310
self->drawFX &= ~FX_FLIP;
1311
}
1312
1313
void Water_Draw_Pool(void)
1314
{
1315
RSDK_THIS(Water);
1316
1317
RSDK.DrawRect(self->position.x - (self->size.x >> 1), self->position.y - (self->size.y >> 1), self->size.x, self->size.y,
1318
(self->r << 16) | (self->g << 8) | self->b, 0x100, INK_SUB, false);
1319
}
1320
1321
void Water_Draw_Splash(void)
1322
{
1323
RSDK_THIS(Water);
1324
1325
RSDK.DrawSprite(&self->animator, NULL, false);
1326
}
1327
1328
void Water_Draw_Countdown(void)
1329
{
1330
Vector2 drawPos;
1331
1332
RSDK_THIS(Water);
1333
if (self->state == Water_State_Countdown) {
1334
drawPos.x = self->position.x;
1335
drawPos.y = self->position.y;
1336
}
1337
else {
1338
EntityPlayer *player = (EntityPlayer *)self->childPtr;
1339
if (player->camera) {
1340
drawPos.x = player->camera->position.x + self->size.x;
1341
drawPos.y = player->camera->position.y + self->size.y;
1342
}
1343
else {
1344
drawPos.x = player->position.x + self->size.x;
1345
drawPos.y = player->position.y + self->size.y;
1346
}
1347
}
1348
1349
RSDK.DrawSprite(&self->animator, &drawPos, false);
1350
}
1351
1352
void Water_Draw_Bubbler(void)
1353
{
1354
RSDK_THIS(Water);
1355
1356
RSDK.DrawSprite(&self->animator, NULL, false);
1357
}
1358
1359
void Water_Draw_Bubble(void)
1360
{
1361
RSDK_THIS(Water);
1362
1363
RSDK.DrawSprite(&self->animator, NULL, false);
1364
}
1365
1366
#if GAME_INCLUDE_EDITOR
1367
void Water_EditorDraw(void)
1368
{
1369
RSDK_THIS(Water);
1370
1371
self->updateRange.x = TO_FIXED(128);
1372
self->updateRange.y = TO_FIXED(128);
1373
1374
self->inkEffect = INK_NONE;
1375
switch (self->type) {
1376
case WATER_WATERLEVEL:
1377
self->inkEffect = INK_ADD;
1378
self->alpha = RSDK.CheckSceneFolder("AIZ") ? 0x60 : 0xE0;
1379
RSDK.SetSpriteAnimation(Water->aniFrames, 0, &self->animator, true, 0);
1380
1381
Water->waterLevel = self->position.y;
1382
1383
Vector2 drawPos;
1384
drawPos.x = (((self->position.x >> 16) & 0xFFFFFFC0) + 32) << 16;
1385
drawPos.y = self->position.y;
1386
for (int32 i = (WIDE_SCR_XSIZE >> 6) + 2; i > 0; --i) {
1387
RSDK.DrawSprite(&self->animator, &drawPos, false);
1388
drawPos.x += TO_FIXED(64);
1389
}
1390
break;
1391
1392
case WATER_POOL:
1393
self->drawFX = FX_FLIP;
1394
self->updateRange.x = self->size.x >> 1;
1395
self->updateRange.y = self->size.y >> 1;
1396
1397
self->inkEffect = INK_BLEND;
1398
RSDK.DrawRect(self->position.x - (self->size.x >> 1), self->position.y - (self->size.y >> 1), self->size.x, self->size.y,
1399
(self->r << 16) | (self->g << 8) | self->b, 0x20, INK_ALPHA, false);
1400
if (showGizmos()) {
1401
self->inkEffect = INK_NONE;
1402
DrawHelpers_DrawRectOutline(self->position.x, self->position.y, self->size.x, self->size.y, 0xFFFF00);
1403
}
1404
break;
1405
1406
case WATER_BUBBLER:
1407
case WATER_BIG_BUBBLER:
1408
self->inkEffect = INK_ADD;
1409
self->alpha = 0x100;
1410
RSDK.SetSpriteAnimation(Water->aniFrames, 2, &self->animator, true, 0);
1411
1412
Water_Draw_Bubbler();
1413
break;
1414
1415
case WATER_HEIGHT_TRIGGER:
1416
self->active = ACTIVE_BOUNDS;
1417
self->visible = false;
1418
1419
RSDK.SetSpriteAnimation(Water->aniFrames, 0, &self->animator, true, 0);
1420
RSDK.DrawSprite(&self->animator, NULL, false);
1421
1422
if (showGizmos()) {
1423
Water_SetupTagLink();
1424
1425
RSDK_DRAWING_OVERLAY(true);
1426
if (self->taggedButton) {
1427
DrawHelpers_DrawArrow(self->taggedButton->position.x, self->taggedButton->position.y, self->position.x, self->position.y,
1428
0xFFFF00, INK_NONE, 0xFF);
1429
}
1430
RSDK_DRAWING_OVERLAY(false);
1431
}
1432
break;
1433
1434
case WATER_BTN_BIG_BUBBLE:
1435
self->drawFX = FX_SCALE;
1436
self->inkEffect = INK_ADD;
1437
self->alpha = 0x100;
1438
self->scale.x = 0x200;
1439
self->scale.y = 0x200;
1440
RSDK.SetSpriteAnimation(Water->bigBubbleFrames, 7, &self->animator, true, 0);
1441
1442
Water_Draw_Bubble();
1443
1444
if (showGizmos()) {
1445
Water_SetupTagLink();
1446
1447
RSDK_DRAWING_OVERLAY(true);
1448
if (self->taggedButton) {
1449
DrawHelpers_DrawArrow(self->taggedButton->position.x, self->taggedButton->position.y, self->position.x, self->position.y,
1450
0xFFFF00, INK_NONE, 0xFF);
1451
}
1452
RSDK_DRAWING_OVERLAY(false);
1453
}
1454
break;
1455
1456
default: break;
1457
}
1458
}
1459
1460
void Water_EditorLoad(void)
1461
{
1462
Water->aniFrames = RSDK.LoadSpriteAnimation("Global/Water.bin", SCOPE_STAGE);
1463
if (RSDK.CheckSceneFolder("HCZ")) {
1464
Water->wakeFrames = RSDK.LoadSpriteAnimation("HCZ/Wake.bin", SCOPE_STAGE);
1465
Water->bigBubbleFrames = RSDK.LoadSpriteAnimation("HCZ/BigBubble.bin", SCOPE_STAGE);
1466
RSDK.SetSpriteAnimation(Water->wakeFrames, 0, &Water->wakeAnimator, true, 0);
1467
}
1468
1469
RSDK_ACTIVE_VAR(Water, type);
1470
RSDK_ENUM_VAR("Water Level", WATER_WATERLEVEL);
1471
RSDK_ENUM_VAR("Pool", WATER_POOL);
1472
RSDK_ENUM_VAR("Bubbler", WATER_BUBBLER);
1473
RSDK_ENUM_VAR("Height Trigger", WATER_HEIGHT_TRIGGER);
1474
RSDK_ENUM_VAR("Big Bubbler", WATER_BIG_BUBBLER);
1475
RSDK_ENUM_VAR("Btn Big Bubble", WATER_BTN_BIG_BUBBLE);
1476
1477
RSDK_ACTIVE_VAR(Water, priority);
1478
RSDK_ENUM_VAR("Lowest", WATER_PRIORITY_LOWEST);
1479
RSDK_ENUM_VAR("Low", WATER_PRIORITY_LOW);
1480
RSDK_ENUM_VAR("High", WATER_PRIORITY_HIGH);
1481
RSDK_ENUM_VAR("Highest", WATER_PRIORITY_HIGHEST);
1482
}
1483
#endif
1484
1485
void Water_Serialize(void)
1486
{
1487
RSDK_EDITABLE_VAR(Water, VAR_ENUM, type);
1488
RSDK_EDITABLE_VAR(Water, VAR_UINT8, numDuds);
1489
RSDK_EDITABLE_VAR(Water, VAR_VECTOR2, size);
1490
RSDK_EDITABLE_VAR(Water, VAR_VECTOR2, height);
1491
RSDK_EDITABLE_VAR(Water, VAR_ENUM, speed);
1492
RSDK_EDITABLE_VAR(Water, VAR_ENUM, buttonTag);
1493
RSDK_EDITABLE_VAR(Water, VAR_UINT8, r);
1494
RSDK_EDITABLE_VAR(Water, VAR_UINT8, g);
1495
RSDK_EDITABLE_VAR(Water, VAR_UINT8, b);
1496
RSDK_EDITABLE_VAR(Water, VAR_UINT8, priority);
1497
RSDK_EDITABLE_VAR(Water, VAR_BOOL, destroyOnTrigger);
1498
}
1499
1500