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