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