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