1 #include "core.h"
2
3 #include "../../ObjManager.h"
4 #include "../../autogen/sprites.h"
5 #include "../../caret.h"
6 #include "../../common/misc.h"
7 #include "../../Utils/Logger.h"
8 #include "../../game.h"
9 #include "../../graphics/Renderer.h"
10 #include "../../map.h"
11 #include "../../player.h"
12 #include "../../sound/SoundManager.h"
13 #include "../../trig.h"
14 #include "../ai.h"
15 #include "../almond/almond.h"
16 #include "../stdai.h"
17 #include "../sym/smoke.h"
18
19 using namespace NXE::Graphics;
20
21 /* ------------------------------------------------------------------------------------------ */
22 /* CODE FOR THE CORE BOSS (ALMOND)
23 */
24 /* ------------------------------------------------------------------------------------------ */
25
26 // these are the meanings of the various pieces inside pieces[] array
27 #define MC1 0 // minicores 1 through 5
28 #define MC2 1
29 #define MC3 2
30 #define MC4 3
31 #define MC5 4
32 #define CFRONT 5 // front half of the core body
33 #define CBACK 6 // back half of the core body
34
35 // states for the core
36 #define CORE_SLEEP 10
37 #define CORE_CLOSED 200
38 #define CORE_OPEN 210
39 #define CORE_GUST 220
40
41 // and the states for the minicores
42 #define MC_SLEEP 0
43 #define MC_THRUST 10
44 #define MC_CHARGE_FIRE 20
45 #define MC_FIRE 30
46 #define MC_FIRED 40
47 #define MC_RETREAT 50
48
49 // makes the core open his mouth and handles flashing red when hit
50 #define OPEN_MOUTH \
51 { \
52 RunOpenMouth(); \
53 }
54
55 // makes the core close his mouth
56 #define CLOSE_MOUTH \
57 { \
58 pieces[CFRONT]->frame = 2; \
59 pieces[CBACK]->frame = 0; \
60 }
61
INITFUNC(AIRoutines)62 INITFUNC(AIRoutines)
63 {
64 ONTICK(OBJ_MINICORE, ai_minicore);
65 ONTICK(OBJ_MINICORE_SHOT, ai_minicore_shot);
66
67 AFTERMOVE(OBJ_CORE_BACK, ai_core_back);
68 AFTERMOVE(OBJ_CORE_FRONT, ai_core_front);
69
70 ONTICK(OBJ_CORE_GHOSTIE, ai_core_ghostie);
71 ONTICK(OBJ_CORE_BLAST, ai_core_blast);
72 }
73
74 /*
75 void c------------------------------() {}
76 */
77
CreateMinicore(Object * core)78 static Object *CreateMinicore(Object *core)
79 {
80 Object *o;
81
82 o = CreateObject(0, 0, OBJ_MINICORE);
83 o->linkedobject = core;
84 o->flags = (FLAG_SHOOTABLE | FLAG_INVULNERABLE | FLAG_IGNORE_SOLID);
85 o->hp = 1000;
86 o->state = MC_SLEEP;
87
88 return o;
89 }
90
91 // called at the entry to the Core room.
92 // initilize all the pieces of the Core boss.
OnMapEntry(void)93 void CoreBoss::OnMapEntry(void)
94 {
95 LOG_DEBUG("CoreBoss::OnMapEntry");
96
97 o = CreateObject(0, 0, OBJ_CORE_CONTROLLER);
98 game.stageboss.object = o;
99
100 o->state = 10;
101
102 o->flags = (FLAG_SHOW_FLOATTEXT | FLAG_IGNORE_SOLID | FLAG_SCRIPTONDEATH);
103 o->id2 = 1000;
104
105 o->x = (1207 * CSFI);
106 o->y = (212 * CSFI);
107 o->xinertia = o->yinertia = 0;
108 o->hp = 650;
109
110 o->sprite = SPR_CORESHOOTMARKER;
111
112 // spawn all the pieces in the correct z-order
113 pieces[3] = CreateMinicore(o);
114 pieces[4] = CreateMinicore(o);
115 pieces[CFRONT] = CreateObject(0, 0, OBJ_CORE_FRONT);
116 pieces[CBACK] = CreateObject(0, 0, OBJ_CORE_BACK);
117 pieces[0] = CreateMinicore(o);
118 pieces[1] = CreateMinicore(o);
119 pieces[2] = CreateMinicore(o);
120
121 // set up the front piece
122 pieces[CFRONT]->sprite = SPR_CORE_FRONT;
123 pieces[CFRONT]->state = CORE_SLEEP;
124 pieces[CFRONT]->linkedobject = o;
125 pieces[CFRONT]->flags |= (FLAG_IGNORE_SOLID | FLAG_INVULNERABLE);
126 pieces[CFRONT]->frame = 2; // mouth closed
127
128 // set up our back piece
129 pieces[CBACK]->sprite = SPR_CORE_BACK;
130 pieces[CBACK]->state = CORE_SLEEP;
131 pieces[CBACK]->linkedobject = o;
132 pieces[CBACK]->flags |= (FLAG_IGNORE_SOLID | FLAG_INVULNERABLE);
133 pieces[CBACK]->frame = 0;
134
135 // set the positions of all the minicores
136 pieces[0]->x = (o->x - 0x1000);
137 pieces[0]->y = (o->y - 0x8000);
138
139 pieces[1]->x = (o->x + 0x2000);
140 pieces[1]->y = o->y;
141
142 pieces[2]->x = (o->x - 0x1000);
143 pieces[2]->y = (o->y + 0x8000);
144
145 pieces[3]->x = (o->x - 0x6000);
146 pieces[3]->y = (o->y + 0x4000);
147
148 pieces[4]->x = (o->x - 0x6000);
149 pieces[4]->y = (o->y - 0x4000);
150
151 this->hittimer = 0;
152 }
153
OnMapExit()154 void CoreBoss::OnMapExit()
155 {
156 // ensure we are called no longer
157 game.stageboss.object = NULL;
158 o = NULL;
159 }
160
161 /*
162 void c------------------------------() {}
163 */
164
Run()165 void CoreBoss::Run()
166 {
167 bool do_thrust = false;
168 int i;
169
170 if (!o)
171 return;
172
173 // LOG_DEBUG("state = {}", o->state);
174
175 switch (o->state)
176 {
177 case CORE_SLEEP:
178 break; // core is asleep
179
180 // Core's mouth is closed.
181 // Core targets player point but does not update it during the state.
182 // This is also the state set via BOA to awaken the core.
183 case CORE_CLOSED:
184 {
185 o->state = CORE_CLOSED + 1;
186 o->timer = 0;
187
188 StopWaterStream();
189 o->xmark = player->x;
190 o->ymark = player->y;
191 }
192 case CORE_CLOSED + 1:
193 {
194 // open mouth after 400 ticks
195 if (o->timer > 400)
196 {
197 if (++o->timer2 > 3)
198 { // every 3rd time do gusting left and big core blasts
199 o->timer2 = 0;
200 o->state = CORE_GUST;
201 }
202 else
203 {
204 o->state = CORE_OPEN;
205 }
206
207 do_thrust = true;
208 }
209 }
210 break;
211
212 // Core's mouth is open.
213 // Core moves towards player, and updates the position throughout
214 // the state (is "aggressive" about seeking him).
215 // Core fires ghosties, and curly targets it.
216 case CORE_OPEN:
217 {
218 o->state = CORE_OPEN + 1;
219 o->timer = 0;
220 // gonna open mouth, so save the current HP so we'll
221 // know how much damage we've taken this time.
222 o->savedhp = o->hp;
223 }
224 case CORE_OPEN + 1:
225 {
226 o->xmark = player->x;
227 o->ymark = player->y;
228
229 // must call constantly for red-flashing when hit
230 OPEN_MOUTH;
231
232 // hint curly to target us
233 if ((o->timer % 64) == 1)
234 {
235 o->CurlyTargetHere();
236 }
237
238 // spawn ghosties
239 if (o->timer < 200)
240 {
241 if ((o->timer % 20) == 0)
242 {
243 CreateObject(o->x + (random(-48, -16) * CSFI), o->y + (random(-64, 64) * CSFI), OBJ_CORE_GHOSTIE);
244 }
245 }
246
247 // close mouth when 400 ticks have passed or we've taken more than 200 damage
248 if (o->timer > 400 || (o->savedhp - o->hp) >= 200)
249 {
250 o->state = CORE_CLOSED;
251 CLOSE_MOUTH;
252 do_thrust = true;
253 }
254 }
255 break;
256
257 case CORE_GUST:
258 {
259 o->state = CORE_GUST + 1;
260 o->timer = 0;
261
262 StartWaterStream();
263 }
264 case CORE_GUST + 1:
265 {
266 // spawn water droplet effects and push player
267 Object *droplet = CreateObject(player->x + ((random(-50, 150) * CSFI) * 2),
268 player->y + (random(-160, 160) * CSFI), OBJ_FAN_DROPLET);
269 droplet->dir = LEFT;
270 player->xinertia -= 0x20;
271
272 OPEN_MOUTH;
273
274 // spawn the big white blasts
275 if (o->timer == 300 || o->timer == 350 || o->timer == 400)
276 {
277 EmFireAngledShot(pieces[CFRONT], OBJ_CORE_BLAST, 0, 3 * CSFI);
278 NXE::Sound::SoundManager::getInstance()->playSfx(NXE::Sound::SFX::SND_LIGHTNING_STRIKE);
279 }
280
281 if (o->timer > 400)
282 {
283 o->state = CORE_CLOSED;
284 CLOSE_MOUTH;
285 do_thrust = true;
286 }
287 }
288 break;
289
290 case 500: // defeated!!
291 {
292 StopWaterStream();
293 map.wlforcestate = WL_CALM;
294
295 o->state = 501;
296 o->timer = 0;
297 o->xinertia = o->yinertia = 0;
298 game.curlytarget.timeleft = 0;
299
300 CLOSE_MOUTH;
301
302 game.quaketime = 20;
303 SmokeXY(pieces[CBACK]->x, pieces[CBACK]->CenterY(), 20, 128, 64);
304
305 // tell all the MC's to retreat
306 for (i = 0; i < 5; i++)
307 {
308 pieces[i]->flags &= ~(FLAG_SHOOTABLE & FLAG_INVULNERABLE);
309 pieces[i]->state = MC_RETREAT;
310 }
311 }
312 case 501:
313 {
314 o->timer++;
315 if ((o->timer & 0x0f) != 0)
316 {
317 SmokeXY(pieces[CBACK]->x, pieces[CBACK]->CenterY(), 1, 64, 32);
318 }
319
320 if (o->timer & 2)
321 o->x -= (1 * CSFI);
322 else
323 o->x += (1 * CSFI);
324
325 #define CORE_DEATH_TARGET_X 0x7a000
326 #define CORE_DEATH_TARGET_Y 0x16000
327 o->xinertia += (o->x > CORE_DEATH_TARGET_X) ? -0x80 : 0x80;
328 o->yinertia += (o->y > CORE_DEATH_TARGET_Y) ? -0x80 : 0x80;
329 }
330 break;
331
332 case 600: // teleported away by Misery
333 {
334 o->xinertia = 0;
335 o->yinertia = 0;
336 o->state++;
337 // NXE::Sound::SoundManager::getInstance()->playSfx(NXE::Sound::SFX::SND_TELEPORT);
338
339 pieces[CFRONT]->clip_enable = pieces[CBACK]->clip_enable = 1;
340 o->timer = Renderer::getInstance()->sprites.sprites[pieces[CFRONT]->sprite].h;
341 }
342 case 601:
343 {
344 pieces[CFRONT]->display_xoff = pieces[CBACK]->display_xoff = random(-8, 8);
345
346 pieces[CFRONT]->clipy2 = o->timer;
347 pieces[CBACK]->clipy2 = o->timer;
348
349 if (--o->timer < 0)
350 {
351 pieces[CFRONT]->invisible = true;
352 pieces[CBACK]->invisible = true;
353
354 // restore status bars
355 game.stageboss.object = NULL;
356 game.bossbar.object = NULL;
357 o->Delete();
358 o = NULL;
359 return;
360 }
361 }
362 break;
363 }
364
365 if (do_thrust)
366 {
367 // tell all the minicores to jump to a new position
368 for (i = 0; i < 5; i++)
369 {
370 pieces[i]->state = MC_THRUST;
371 }
372
373 quake(20);
374 NXE::Sound::SoundManager::getInstance()->playSfx(NXE::Sound::SFX::SND_CORE_THRUST);
375 }
376
377 // fire the minicores in any awake non-dead state
378 if (o->state >= CORE_CLOSED && o->state < 500)
379 {
380 o->timer++;
381
382 // fire off each minicore sequentially...
383 switch (o->timer)
384 {
385 case 80 + 0:
386 pieces[0]->state = MC_CHARGE_FIRE;
387 break;
388 case 80 + 30:
389 pieces[1]->state = MC_CHARGE_FIRE;
390 break;
391 case 80 + 60:
392 pieces[2]->state = MC_CHARGE_FIRE;
393 break;
394 case 80 + 90:
395 pieces[3]->state = MC_CHARGE_FIRE;
396 break;
397 case 80 + 120:
398 pieces[4]->state = MC_CHARGE_FIRE;
399 break;
400 }
401
402 // move main core towards a spot in front of target
403 o->xinertia += (o->x > (o->xmark + (160 * CSFI))) ? -4 : 4;
404 o->yinertia += (o->y > o->ymark - (o->Height() / 2)) ? -4 : 4;
405 }
406
407 // set up our shootable status--you never actually hit the core (CFRONT),
408 // but if it's mouth is open, make us, the invisible controller object, shootable.
409 if (pieces[CFRONT]->frame == 2)
410 {
411 o->flags &= ~FLAG_SHOOTABLE;
412 pieces[CFRONT]->flags |= FLAG_INVULNERABLE;
413 }
414 else
415 {
416 o->flags |= FLAG_SHOOTABLE;
417 pieces[CFRONT]->flags &= ~FLAG_INVULNERABLE;
418 }
419
420 LIMITX(0x80);
421 LIMITY(0x80);
422 }
423
424 /*
425 void c------------------------------() {}
426 */
427
RunOpenMouth()428 void CoreBoss::RunOpenMouth()
429 {
430 // flash red when struck, else stay in Mouth Open frame
431 pieces[CFRONT]->frame = 0;
432 pieces[CBACK]->frame = 0;
433
434 if (o->shaketime)
435 {
436 this->hittimer++;
437 if (this->hittimer & 2)
438 {
439 pieces[CFRONT]->frame = 1;
440 pieces[CBACK]->frame = 1;
441 }
442 }
443 else
444 {
445 this->hittimer = 0;
446 }
447 }
448
StartWaterStream(void)449 void CoreBoss::StartWaterStream(void)
450 {
451 // bring the water up if it's not already up, but don't keep it up
452 // if it's already been up on it's own because that's not fair
453 if (map.wlstate == WL_DOWN)
454 map.wlforcestate = WL_UP;
455
456 game.quaketime = 100;
457 NXE::Sound::SoundManager::getInstance()->startStreamSound(1000);
458 }
459
StopWaterStream(void)460 void CoreBoss::StopWaterStream(void)
461 {
462 // bring the water down again if it's not already
463 if (map.wlstate == WL_UP)
464 map.wlforcestate = WL_CYCLE;
465
466 NXE::Sound::SoundManager::getInstance()->stopLoopSfx();
467 }
468
469 /*
470 void c------------------------------() {}
471 */
472
473 // the front (mouth) piece of the main core
ai_core_front(Object * o)474 void ai_core_front(Object *o)
475 {
476 Object *core = o->linkedobject;
477 if (!core)
478 {
479 o->Delete();
480 return;
481 }
482
483 o->x = core->x - 0x4800;
484 o->y = core->y - 0x5e00;
485 }
486
487 // the back (unanimated) piece of the main core
ai_core_back(Object * o)488 void ai_core_back(Object *o)
489 {
490 Object *core = o->linkedobject;
491 if (!core)
492 {
493 o->Delete();
494 return;
495 }
496
497 o->x = core->x + (0x5800 - (8 * CSFI));
498 o->y = core->y - 0x5e00;
499 }
500
501 /*
502 void c------------------------------() {}
503 */
504
ai_minicore(Object * o)505 void ai_minicore(Object *o)
506 {
507 Object *core = o->linkedobject;
508 if (!core)
509 {
510 o->Delete();
511 return;
512 }
513
514 switch (o->state)
515 {
516 case MC_SLEEP: // idle & mouth closed
517 o->frame = 2;
518 o->xmark = o->x;
519 o->ymark = o->y;
520 break;
521
522 case MC_THRUST: // thrust (move to random new pos)
523 o->state = MC_THRUST + 1;
524 o->frame = 2;
525 o->timer = 0;
526 o->xmark = core->x + (random(-128, 32) * CSFI);
527 o->ymark = core->y + (random(-64, 64) * CSFI);
528 case MC_THRUST + 1:
529 if (++o->timer > 50)
530 {
531 o->frame = 0;
532 }
533 break;
534
535 case MC_CHARGE_FIRE: // charging for fire
536 o->state = MC_CHARGE_FIRE + 1;
537 o->timer = 0;
538 case MC_CHARGE_FIRE + 1: // flash blue
539 o->timer++;
540 o->frame = ((o->timer >> 1) & 1);
541 if (o->timer > 20)
542 {
543 o->state = MC_FIRE;
544 }
545 break;
546
547 case MC_FIRE: // firing
548 o->state = MC_FIRE + 1;
549 o->frame = 2; // close mouth again
550 o->timer = 0;
551 o->xmark = o->x + (random(24, 48) * CSFI);
552 o->ymark = o->y + (random(-4, 4) * CSFI);
553 case MC_FIRE + 1:
554 if (++o->timer > 50)
555 {
556 o->state = MC_FIRED;
557 o->frame = 0;
558 }
559 else if (o->timer == 1 || o->timer == 3)
560 {
561 // fire at player at speed (2 * CSFI) with 2 degrees of variance
562 EmFireAngledShot(o, OBJ_MINICORE_SHOT, 2, 2 * CSFI);
563 NXE::Sound::SoundManager::getInstance()->playSfx(NXE::Sound::SFX::SND_EM_FIRE);
564 }
565 break;
566
567 case MC_RETREAT: // defeated!
568 o->state = MC_RETREAT + 1;
569 o->frame = 2;
570 o->xinertia = o->yinertia = 0;
571 case MC_RETREAT + 1: // retreat back into the abyss
572 o->xinertia += 0x20;
573 if (o->x > ((map.xsize * TILE_W) * CSFI) + 0x4000)
574 {
575 o->Delete();
576 }
577 break;
578 }
579
580 if (o->state < MC_RETREAT)
581 {
582 // jump back when shot
583 if (o->shaketime)
584 {
585 o->xmark += 0x400;
586 }
587
588 o->x += (o->xmark - o->x) / 16;
589 o->y += (o->ymark - o->y) / 16;
590 }
591
592 // don't let them kill us
593 o->hp = 1000;
594
595 // invincible when mouth is closed
596 if (o->frame != 2)
597 o->flags &= ~FLAG_INVULNERABLE;
598 else
599 o->flags |= FLAG_INVULNERABLE;
600 }
601
ai_minicore_shot(Object * o)602 void ai_minicore_shot(Object *o)
603 {
604 if (++o->timer2 > 150)
605 {
606 effect(o->CenterX(), o->CenterY(), EFFECT_FISHY);
607 o->Delete();
608 }
609
610 ai_animate2(o);
611 }
612 // shutter made noise when opening
613 // curly looks up at no 4
614
615 /*
616 void c------------------------------() {}
617 */
618
ai_core_ghostie(Object * o)619 void ai_core_ghostie(Object *o)
620 {
621 char hit = 0;
622
623 if (o->xinertia > 0 && o->blockr)
624 hit = 1;
625 if (o->xinertia < 0 && o->blockl)
626 hit = 1;
627 if (o->yinertia > 0 && o->blockd)
628 hit = 1;
629 if (o->yinertia < 0 && o->blocku)
630 hit = 1;
631
632 o->xinertia -= 0x20;
633 LIMITX(0x400);
634
635 if (hit)
636 {
637 effect(o->CenterX(), o->CenterY(), EFFECT_FISHY);
638 o->Delete();
639 }
640
641 ai_animate2(o);
642 }
643
ai_core_blast(Object * o)644 void ai_core_blast(Object *o)
645 {
646 if (++o->timer > 200)
647 o->Delete();
648 ANIMATE(2, 0, 1);
649 }
650