1 
2 #include "../stdai.h"
3 #include "sym.fdh"
4 
5 
INITFUNC(AIRoutines)6 INITFUNC(AIRoutines)
7 {
8 	ONTICK(OBJ_NULL, ai_null);
9 	ONTICK(OBJ_HVTRIGGER, ai_hvtrigger);
10 
11 	ONTICK(OBJ_XP, ai_xp);
12 	ONTICK(OBJ_HEART, ai_powerup);
13 	ONTICK(OBJ_HEART3, ai_powerup);
14 	ONTICK(OBJ_MISSILE, ai_powerup);
15 	ONTICK(OBJ_MISSILE3, ai_powerup);
16 
17 	ONTICK(OBJ_HIDDEN_POWERUP, ai_hidden_powerup);
18 
19 	ONTICK(OBJ_DOOR, ai_door);
20 	ONTICK(OBJ_LARGEDOOR, ai_largedoor);
21 
22 	ONTICK(OBJ_SAVE_POINT, ai_save_point);
23 	ONTICK(OBJ_RECHARGE, ai_recharge);
24 
25 	ONTICK(OBJ_CHEST_CLOSED, ai_chest_closed);
26 	ONTICK(OBJ_CHEST_OPEN, ai_chest_open);
27 
28 	ONTICK(OBJ_TELEPORTER, ai_teleporter);
29 	ONTICK(OBJ_TELEPORTER_LIGHTS, ai_animate2);
30 
31 	ONTICK(OBJ_COMPUTER, ai_animate4);
32 	ONTICK(OBJ_TERMINAL, ai_terminal);
33 
34 	ONTICK(OBJ_LIFE_CAPSULE, ai_animate4);
35 	ONTICK(OBJ_XP_CAPSULE, ai_xp_capsule);
36 
37 	ONTICK(OBJ_SPRINKLER, ai_sprinkler);
38 	ONTICK(OBJ_WATER_DROPLET, ai_water_droplet);
39 	ONTICK(OBJ_LAVA_DROPLET, ai_water_droplet);
40 	ONTICK(OBJ_DROPLET_SPAWNER, ai_droplet_spawner);
41 
42 	ONTICK(OBJ_FAN_UP, ai_fan_vert);
43 	ONTICK(OBJ_FAN_DOWN, ai_fan_vert);
44 	ONTICK(OBJ_FAN_LEFT, ai_fan_hoz);
45 	ONTICK(OBJ_FAN_RIGHT, ai_fan_hoz);
46 	ONTICK(OBJ_FAN_DROPLET, ai_fan_droplet);
47 
48 	ONTICK(OBJ_PRESS, ai_press);
49 	ONTICK(OBJ_HIDDEN_SPARKLE, ai_animate4);
50 	ONTICK(OBJ_LIGHTNING, ai_lightning);
51 
52 	ONTICK(OBJ_STRAINING, ai_straining);
53 
54 	ONTICK(OBJ_BUBBLE_SPAWNER, ai_bubble_spawner);
55 
56 	ONTICK(OBJ_CHINFISH, ai_chinfish);
57 	ONTICK(OBJ_FIREPLACE, ai_fireplace);
58 
59 	ONTICK(OBJ_SMOKE_DROPPER, ai_smoke_dropper);
60 
61 	ONSPAWN(OBJ_SPIKE_SMALL, onspawn_spike_small);
62 
63 	ONTICK(OBJ_SCROLL_CONTROLLER, ai_scroll_controller);
64 	ONTICK(OBJ_QUAKE, ai_quake);
65 }
66 
67 /*
68 void c------------------------------() {}
69 */
70 
71 // ai routine for OBJ_NULL
ai_null(Object * o)72 void ai_null(Object *o)
73 {
74 	if (o->state == 0)
75 	{
76 		o->state = 1;
77 
78 		// FLAG_FACES_RIGHT is causes the object to drop down one tile when actually
79 		// seen in the game. I think this was used to make map editing easier in
80 		// places where things were getting really crouded with entities.
81 		if (o->dir == RIGHT)
82 		{
83 			o->y += (TILE_H << CSF);
84 
85 			// precedence hack for Boulder Chamber
86 			if (game.curmap == STAGE_BOULDER_CHAMBER)
87 			{
88 				Object *boulder = Objects::FindByType(OBJ_BOULDER);
89 				if (boulder) o->PushBehind(boulder);
90 			}
91 		}
92 	}
93 }
94 
95 // H/V Trigger
96 //
97 // By default, triggers on vertical axis.
98 // If set to face right, it triggers on horizontal axis instead.
99 //
100 // If FLAG_SCRIPTONTOUCH is set, projects an invisible horizontal or vertical "Beam"
101 // which is blocked by solid bricks, and only by intersecting the beam
102 // can the player trip the trigger.
103 //
ai_hvtrigger(Object * o)104 void ai_hvtrigger(Object *o)
105 {
106 	// init: find bounding box within which we will trigger
107 	if (o->state == 0)
108 	{
109 		o->state = 1;
110 		o->hvt.is_horizontal = (o->dir == LEFT);
111 
112 		// start with a trigger box around object which
113 		// encloses only the object's tile itself.
114 		o->hvt.x1 = o->x;
115 		o->hvt.y1 = o->y;
116 		o->hvt.x2 = o->x + ((TILE_W - 1) << CSF);
117 		o->hvt.y2 = o->y + ((TILE_H - 1) << CSF);
118 
119 		// now expand the trigger box as appropriate
120 		if (o->flags & FLAG_SCRIPTONTOUCH)
121 		{
122 			o->flags &= ~FLAG_SCRIPTONTOUCH;
123 			hv_project_beam(o);
124 		}
125 		else if (o->hvt.is_horizontal)
126 		{
127 			o->hvt.x1 = 0;
128 			o->hvt.x2 = (map.xsize * TILE_W) << CSF;
129 		}
130 		else
131 		{
132 			o->hvt.y1 = 0;
133 			o->hvt.y2 = (map.ysize * TILE_H) << CSF;
134 		}
135 	}
136 
137 	int px = player->CenterX();
138 	if (px > o->hvt.x2) return;
139 	if (px < o->hvt.x1) return;
140 
141 	int py = player->CenterY();
142 	if (py > o->hvt.y2) return;
143 	if (py < o->hvt.y1) return;
144 
145 	// ok then, we can trigger, except for:
146 	if (GetCurrentScript() == -1 &&		// no override other scripts
147 		game.switchstage.mapno == -1)	// no repeat exec after <TRA
148 	{
149 #ifdef DEBUG
150 		NX_LOG("HVTrigger %04d (%08x) activated", o->id2, o);
151 #endif
152 		StartScript(o->id2);
153 	}
154 }
155 
156 // project the Option 1 beam and set the hvtrigger's y1/y2 or x1/x2
hv_project_beam(Object * o)157 static void hv_project_beam(Object *o)
158 {
159 int tilex = (o->x >> CSF) / TILE_W;
160 int tiley = (o->y >> CSF) / TILE_H;
161 int x, y, t;
162 
163 	if (!o->hvt.is_horizontal)
164 	{
165 		for(y=tiley;y>=0;y--)
166 		{
167 			t = map.tiles[tilex][y];
168 			if (tileattr[t] & TA_SOLID) { y++; break; }
169 		}
170 
171 		o->hvt.y1 = (y * TILE_H) << CSF;
172 
173 		for(y=tiley;y<map.ysize;y++)
174 		{
175 			t = map.tiles[tilex][y];
176 			if (tileattr[t] & TA_SOLID) { y--; break; }
177 		}
178 
179 		o->hvt.y2 = ((y * TILE_H) + (TILE_H - 1)) << CSF;
180 	}
181 	else
182 	{
183 		for(x=tilex;x>=0;x--)
184 		{
185 			t = map.tiles[x][tiley];
186 			if (tileattr[t] & TA_SOLID) { x++; break; }
187 		}
188 
189 		o->hvt.x1 = (x * TILE_W) << CSF;
190 
191 		for(x=tilex;x<map.xsize;x++)
192 		{
193 			t = map.tiles[x][tiley];
194 			if (tileattr[t] & TA_SOLID) { x--; break; }
195 		}
196 
197 		o->hvt.x2 = ((x * TILE_W) + (TILE_W - 1)) << CSF;
198 	}
199 }
200 
201 
202 /*
203 void c------------------------------() {}
204 */
205 
206 
ai_xp(Object * o)207 void ai_xp(Object *o)
208 {
209 	if (o->state == 0)
210 	{
211 		o->yinertia = random(-400, 0);
212 		o->state = 1;
213 	}
214 
215 	if (Handle_Falling_Left(o))
216 	{	// left-falling bouncing
217 		if (map.scrolltype == BK_FASTLEFT_LAYERS)	// as opposed to Ironhead (BK_FASTLEFT)
218 		{
219 			if (o->blockl)
220 			{
221 				if (o->onscreen || pdistly((SCREEN_HEIGHT - (SCREEN_HEIGHT / 3)) << CSF))
222 					sound(SND_XP_BOUNCE);
223 
224 				o->xinertia = 0x100;
225 				o->yinertia *= 2;
226 				o->yinertia /= 3;
227 			}
228 
229 			if (o->blocku || o->blockd)
230 			{
231 				o->yinertia = -o->yinertia;
232 			}
233 		}
234 	}
235 	else
236 	{	// normal bouncing
237 		if (o->blockd)
238 		{
239 			// disappear if we were spawned embedded in ground
240 			// added for XP spawned by sandcrocs
241 			if (o->blocku || (o->blockl && o->blockr))
242 			{
243 				o->Delete();
244 				return;
245 			}
246 
247 			if (o->onscreen || pdistlx((SCREEN_WIDTH - (SCREEN_WIDTH / 3)) << CSF))
248 				sound(SND_XP_BOUNCE);
249 
250 			o->yinertia = -0x280;
251 			o->xinertia *= 2;
252 			o->xinertia /= 3;
253 		}
254 		else
255 		{
256 			o->yinertia += 42;
257 		}
258 
259 		if (o->blockl || o->blockr)
260 		{
261 			o->xinertia = -o->xinertia;
262 		}
263 	}
264 
265 
266 	if (++o->animtimer >= 3)
267 	{
268 		o->animtimer = 0;
269 		if (++o->frame > 5) o->frame = 0;
270 	}
271 
272 	if (++o->timer > 0x1f4)
273 	{
274 		o->Delete();
275 		return;
276 	}
277 	else if (o->timer > 0x1f2)
278 	{	// twinkle before disappearing
279 		o->frame = 0;
280 		o->invisible = 0;
281 		return;
282 	}
283 	else if (o->timer > 0x190)
284 	{
285 		o->invisible = (o->timer & 2);
286 	}
287 
288 	// let player get it!
289 	if (hitdetect(o, player))
290 	{
291 		switch(o->sprite)
292 		{
293 			case SPR_XP_SMALL: AddXP(XP_SMALL_AMT); break;
294 			case SPR_XP_MED: AddXP(XP_MED_AMT); break;
295 			case SPR_XP_LARGE: AddXP(XP_LARGE_AMT); break;
296 		}
297 
298 		o->Delete();
299 	}
300 }
301 
302 // Hearts and Missiles
ai_powerup(Object * o)303 void ai_powerup(Object *o)
304 {
305 	// if o->state == 0, then was present in map; not dropped by an enemy...lasts forever
306 	if (o->state > 0)
307 	{
308 		Handle_Falling_Left(o);
309 
310 		switch(o->state)
311 		{
312 			case 1:			// animating
313 			case 101:		// animating (in left-fall mode)
314 				if (++o->timer >= 256) { o->timer = 0; o->state++; }
315 							   else break;
316 			case 2:			// start blinking--we're about to go away!!
317 			case 102:		// blinking (in left-fall mode)
318 				if (++o->timer > 48)
319 				{
320 					effect(o->CenterX()-(1<<CSF), o->CenterY()-(1<<CSF), EFFECT_BONUSFLASH);
321 					o->Delete();
322 					return;
323 				}
324 
325 				o->invisible = (o->timer2 & 2);
326 				o->timer2++;
327 			break;
328 		}
329 
330 		// animation/frame selection
331 		if (++o->animtimer > 2)
332 		{
333 			o->animtimer = 0;
334 			o->frame ^= 1;
335 		}
336 	}
337 	else if (!o->state)
338 	{	// adjust position of map-spawned missiles
339 		if (o->type == OBJ_MISSILE)
340 		{
341 			o->x += (3 << CSF);
342 			o->y += (4 << CSF);
343 		}
344 		o->state = -1;
345 	}
346 
347 	// hand over the powerup if player touches it
348 	if (hitdetect(o, player))
349 	{
350 		switch(o->type)
351 		{
352 			case OBJ_HEART:  sound(SND_HEALTH_REFILL); AddHealth(2); break;
353 			case OBJ_HEART3: sound(SND_HEALTH_REFILL); AddHealth(6); break;
354 
355 			case OBJ_MISSILE:
356 			case OBJ_MISSILE3:
357 			{
358 				int amt = (o->type == OBJ_MISSILE3) ? 3 : 1;
359 				int wpn = (player->weapons[WPN_SUPER_MISSILE].hasWeapon) ? \
360 						WPN_SUPER_MISSILE : WPN_MISSILE;
361 
362 				sound(SND_GET_MISSILE);
363 				AddAmmo(wpn, amt);
364 			}
365 			break;
366 		}
367 
368 		o->Delete();
369 	}
370 }
371 
372 
373 
Handle_Falling_Left(Object * o)374 bool Handle_Falling_Left(Object *o)
375 {
376 	if (map.scrolltype == BK_FASTLEFT || \
377 		map.scrolltype == BK_FASTLEFT_LAYERS)
378 	{
379 		if (o->state < 100)			// initilize
380 		{
381 			o->state += 100;
382 			o->yinertia = random(-0x20, 0x20);
383 			o->xinertia = random(127, 256);
384 			//o->nxflags |= NXFLAG_FOLLOW_SLOPE;
385 		}
386 
387 		o->xinertia -= 0x08;
388 		if (o->xinertia < -0x600) o->xinertia = -0x600;
389 
390 		if (map.scrolltype == BK_FASTLEFT)
391 		{
392 			if (o->x < ((5 * TILE_W) << CSF)) o->Delete();		// went off screen in IronH
393 		}
394 
395 		if (o->blockl && o->xinertia <= 0) o->xinertia = 0x40;
396 		if (o->blocku && o->yinertia <= 0) o->yinertia = 0x40;
397 		if (o->blockd && o->yinertia >= 0) o->yinertia = -0x40;
398 
399 		return 1;
400 	}
401 
402 	return 0;
403 }
404 
405 
406 // hidden heart/missile
ai_hidden_powerup(Object * o)407 void ai_hidden_powerup(Object *o)
408 {
409 	if (o->hp < 990)
410 	{
411 		SmokeBoomUp(o);
412 		sound(SND_EXPL_SMALL);
413 
414 		o->ChangeType((o->dir == LEFT) ? OBJ_HEART : OBJ_MISSILE);
415 		if (o->type == OBJ_HEART)
416 		{
417 			o->x += (3 << CSF);
418 			o->y += (4 << CSF);
419 		}
420 	}
421 }
422 
423 
ai_xp_capsule(Object * o)424 void ai_xp_capsule(Object *o)
425 {
426 	ANIMATE(4, 0, 1);
427 
428 	if (o->hp < 100)
429 	{
430 		o->SpawnXP(o->id1);
431 		SmokeClouds(o, 8, 2, 2);
432 		sound(SND_FUNNY_EXPLODE);
433 
434 		o->Delete();
435 	}
436 }
437 
438 
439 /*
440 void c------------------------------() {}
441 */
442 
ai_save_point(Object * o)443 void ai_save_point(Object *o)
444 {
445 	if (o->state == 0)
446 	{
447 		smoke_if_bonus_item(o);
448 		o->flags |= FLAG_SCRIPTONACTIVATE;	// needed for SP after Sisters fight
449 		o->state = 1;
450 	}
451 
452 	ai_animate3(o);
453 
454 	o->yinertia += 0x40;
455 	LIMITY(0x5ff);
456 }
457 
458 
ai_recharge(Object * o)459 void ai_recharge(Object *o)
460 {
461 	switch(o->state)
462 	{
463 		case 0:
464 		{
465 			smoke_if_bonus_item(o);
466 			o->state = 1;
467 		}
468 		case 1:		// flickery animation
469 		{
470 			int x = random(0, 30);
471 
472 			if (x < 10) o->state = 2;
473 			else if (x < 25) o->state = 3;
474 			else o->state = 4;
475 
476 			o->timer = random(16, 64);
477 			o->animtimer = 0;
478 		}
479 		break;
480 
481 		case 2: o->frame = 0; break;				// solid on
482 
483 		case 3:										// flickery
484 			o->animtimer++;
485 			o->frame = (o->animtimer & 1);
486 		break;
487 
488 		case 4: o->frame = 1; break;				// solid off
489 	}
490 
491 	if (--o->timer <= 0)
492 		o->state = 1;
493 
494 	o->yinertia += 0x40;
495 	LIMITY(0x5ff);
496 }
497 
498 
ai_chest_closed(Object * o)499 void ai_chest_closed(Object *o)
500 {
501 	if (o->state == 0)
502 	{
503 		smoke_if_bonus_item(o);
504 		o->flags |= FLAG_SCRIPTONACTIVATE;	// isn't always set
505 		o->state = 1;
506 
507 		// stop motion of chest spawned from Kulala
508 		if (settings->emulate_bugs)
509 			o->xinertia = 0;
510 	}
511 
512 	// gleaming animation
513 	if (++o->timer < 3) o->frame = 1;
514 	else if (o->timer < 6) o->frame = 2;
515 	else
516 	{
517 		o->frame = 0;
518 		if (o->timer >= 80) o->timer = 0;
519 	}
520 
521 	// need this for the big jelly that drops a chest in Weed
522 	if (o->blockd)
523 		o->xinertia = 0;
524 
525 	// fall
526 	o->yinertia += 0x40;
527 	LIMITY(0x5ff);
528 }
529 
ai_chest_open(Object * o)530 void ai_chest_open(Object *o)
531 {
532 	o->flags |= FLAG_SCRIPTONACTIVATE;
533 }
534 
535 // save points, recharges, and chests make smoke and jump in air for a moment
536 // when spawned as a bonus item (e.g. after a boss fight). That they should do
537 // this is indicated by the script setting it's direction to RIGHT.
smoke_if_bonus_item(Object * o)538 static void smoke_if_bonus_item(Object *o)
539 {
540 	if (o->dir == RIGHT)
541 	{
542 		SmokeClouds(o, 4, 8, 8);
543 		o->yinertia = -0x200;
544 	}
545 }
546 
547 /*
548 void c------------------------------() {}
549 */
550 
ai_lightning(Object * o)551 void ai_lightning(Object *o)
552 {
553 	switch(o->state)
554 	{
555 		case 0:
556 		{
557 			o->invisible = true;
558 			o->state = 1;
559 
560 			if (o->dir == RIGHT)
561 				flashscreen.Start();
562 		}
563 		case 1:
564 		{
565 			if (++o->timer > 10)
566 			{
567 				o->state = 2;
568 				o->invisible = false;
569 				sound(SND_LIGHTNING_STRIKE);
570 			}
571 		}
572 		break;
573 
574 		case 2:
575 		{
576 			if (++o->animtimer > 2)
577 			{
578 				o->animtimer = 0;
579 
580 				if (o->frame >= 3)
581 				{
582 					for(int i=0;i<5;i++)
583 						SmokePuff(o->ActionPointX(), o->ActionPointY());
584 
585 					effect(o->ActionPointX(), o->ActionPointY(), EFFECT_BOOMFLASH);
586 					o->Delete();
587 				}
588 				else
589 				{
590 					if (++o->frame == 2)
591 						o->damage = 10;
592 				}
593 			}
594 		}
595 		break;
596 	}
597 }
598 
ai_teleporter(Object * o)599 void ai_teleporter(Object *o)
600 {
601 	if (o->state==0)
602 	{
603 		o->frame = 0;
604 	}
605 	else
606 	{
607 		o->frame ^= 1;
608 	}
609 }
610 
ai_door(Object * o)611 void ai_door(Object *o)
612 {
613 	switch(o->state)
614 	{
615 		case 0:
616 			o->frame = (o->dir == RIGHT) ? 1:0;
617 		break;
618 
619 		case 1:		// being banged on
620 			o->shaketime = 20;
621 			o->state = 0;
622 		break;
623 	}
624 }
625 
ai_largedoor(Object * o)626 void ai_largedoor(Object *o)
627 {
628 	switch(o->state)
629 	{
630 		case 0:
631 			o->state = 1;
632 			if (o->dir==RIGHT)
633 			{
634 				//o->frame = 1;
635 				o->x -= (TILE_W << CSF);
636 			}
637 			o->xmark = o->x;
638 		break;
639 
640 		case 10:	// door opens into frame
641 			o->state = 11;
642 			o->timer = 0;
643 			o->flags |= FLAG_IGNORE_SOLID;
644 			o->clip_enable = 1;
645 		case 11:
646 		{
647 			int px;
648 			o->timer++;
649 			if ((o->timer & 7)==0)
650 			{
651 				sound(SND_QUAKE);
652 			}
653 
654 			px = (o->timer >> 3);
655 			if (o->dir==LEFT)
656 			{
657 				o->x = o->xmark - (px << CSF);
658 				o->clipx1 = px;
659 			}
660 			else
661 			{
662 				o->x = o->xmark + (px << CSF);
663 				o->clipx2 = (16 - px);
664 			}
665 
666 			if (o->timer==104) o->Delete();
667 		}
668 	}
669 }
670 
671 
ai_press(Object * o)672 void ai_press(Object *o)
673 {
674 	switch(o->state)
675 	{
676 		case 0:
677 		{
678 			if (!o->blockd)
679 			{
680 				o->state = 10;
681 				o->frame = 1;
682 			}
683 		}
684 		break;
685 
686 		case 10:		// fall
687 		{
688 			if (o->frame < 2)
689 				ANIMATE_FWD(2);
690 
691 			o->yinertia += 0x20;
692 			LIMITY(0x5ff);
693 
694 			if (o->y < player->y)
695 			{
696 				o->flags &= ~FLAG_SOLID_BRICK;
697 				o->damage = 127;
698 			}
699 			else
700 			{
701 				o->flags |= FLAG_SOLID_BRICK;
702 				o->damage = 0;
703 			}
704 
705 			if (o->blockd)
706 			{
707 				SmokeSide(o, 4, DOWN);
708 				quake(10);
709 
710 				o->state = 11;
711 				o->frame = 0;
712 				o->damage = 0;
713 				o->flags |= FLAG_SOLID_BRICK;
714 			}
715 		}
716 		break;
717 	}
718 }
719 
ai_terminal(Object * o)720 void ai_terminal(Object *o)
721 {
722 	switch(o->state)
723 	{
724 		case 0:
725 		case 1:
726 			o->frame = 0;
727 			if (pdistlx(8<<CSF) && pdistly2(16<<CSF, 8<<CSF))
728 			{
729 				sound(SND_COMPUTER_BEEP);
730 				o->frame = 1;
731 				o->state = 10;
732 			}
733 		break;
734 
735 		case 10:
736 			ANIMATE(0, 1, 2);
737 		break;
738 	}
739 }
740 
741 
742 /*
743 void c------------------------------() {}
744 */
745 
746 #define FAN_BLOW_FORCE		0x88
747 
ai_fan_vert(Object * o)748 void ai_fan_vert(Object *o)
749 {
750 	if (o->dir == LEFT)
751 	{	// fan off
752 		o->frame = 0;
753 		return;
754 	}
755 
756 	int blowdir = (o->type == OBJ_FAN_UP) ? UP : DOWN;
757 	ANIMATE(0, 0, 2);
758 
759 	// spawn droplet effects
760 	if (pdistlx(SCREEN_WIDTH << CSF) && pdistly(SCREEN_HEIGHT << CSF))
761 	{
762 		if (!random(0, 5))
763 		{
764 			int x = o->x + (random(4, 12) << CSF);
765 			int y = (blowdir == DOWN) ? o->Bottom() : o->y;
766 
767 			Object *drop = CreateObject(x, y, OBJ_FAN_DROPLET);
768 			drop->dir = blowdir;
769 		}
770 	}
771 
772 	// blow player
773 	if (pdistlx(8 << CSF) && pdistly(96 << CSF))
774 	{
775 		if (blowdir == UP && player->y < o->y)
776 			player->yinertia -= FAN_BLOW_FORCE;
777 
778 		if (blowdir == DOWN && player->y > o->Bottom())
779 			player->yinertia += FAN_BLOW_FORCE;
780 	}
781 }
782 
ai_fan_hoz(Object * o)783 void ai_fan_hoz(Object *o)
784 {
785 	if (o->dir == LEFT)
786 	{	// fan off
787 		o->frame = 0;
788 		return;
789 	}
790 
791 	int blowdir = (o->type == OBJ_FAN_LEFT) ? LEFT : RIGHT;
792 	ANIMATE(0, 0, 2);
793 
794 	// spawn droplet effects
795 	if (pdistlx(SCREEN_WIDTH << CSF) && pdistly(SCREEN_HEIGHT << CSF))
796 	{
797 		if (!random(0, 5))
798 		{
799 			int x = (blowdir == LEFT) ? o->x : o->Right();
800 			int y = o->y + (random(4, 12) << CSF);
801 
802 			Object *drop = CreateObject(x, y, OBJ_FAN_DROPLET);
803 			drop->dir = blowdir;
804 		}
805 	}
806 
807 	// blow player
808 	if (pdistlx(96 << CSF) && pdistly(8 << CSF))
809 	{
810 		if (blowdir == LEFT && player->x < o->x)
811 			player->xinertia -= FAN_BLOW_FORCE;
812 
813 		if (blowdir == RIGHT && player->x > o->Right())
814 			player->xinertia += FAN_BLOW_FORCE;
815 	}
816 }
817 
818 // the visible moving air current from fans
ai_fan_droplet(Object * o)819 void ai_fan_droplet(Object *o)
820 {
821 	switch(o->state)
822 	{
823 		case 0:
824 			o->state = 1;
825 			o->frame = random(0, 2);
826 
827 			o->xinertia = o->yinertia = 0;
828 
829 			switch(o->dir)
830 			{
831 				case LEFT: o->xinertia = -1; break;
832 				case RIGHT: o->xinertia = 1; break;
833 				case UP: o->yinertia = -1; break;
834 				case DOWN: o->yinertia = 1; break;
835 			}
836 
837 			o->dir = RIGHT;		// so frame is correct
838 
839 			o->xinertia *= random((2 << CSF), (4 << CSF));
840 			o->yinertia *= random((2 << CSF), (4 << CSF));
841 		case 1:
842 			ANIMATE_FWD(6);
843 			if (o->frame > 4) o->Delete();
844 		break;
845 	}
846 }
847 
848 /*
849 void c------------------------------() {}
850 */
851 
852 // Yamashita Farm and Plantation water-droplet emitter
853 // throws out oodles of water splash droplets
ai_sprinkler(Object * o)854 void ai_sprinkler(Object *o)
855 {
856 	if (o->dir == RIGHT) return;
857 
858 	if (++o->animtimer & 1)
859 		o->frame ^= 1;
860 
861 	if (pdistlx(0x28000) && pdistly(0x1E000))
862 	{
863 		Object *drop;
864 
865 		drop = CreateObject(o->CenterX() + (1<<CSF), \
866 							o->CenterY() + (1<<CSF), \
867 							OBJ_WATER_DROPLET);
868 
869 		drop->xinertia = random(-(2 << CSF), (2 << CSF));
870 		drop->yinertia = random(-(3 << CSF), 384);
871 	}
872 }
873 
874 
875 // generates small splash water droplets
ai_droplet_spawner(Object * o)876 void ai_droplet_spawner(Object *o)
877 {
878 	if (pdistlx(SCREEN_WIDTH << CSF) && pdistly(SCREEN_HEIGHT << CSF))
879 	{
880 		if (!random(0, 80))
881 		{
882 			CreateObject(o->x + (random(2, (TILE_W - 2)) << CSF), o->y, OBJ_WATER_DROPLET);
883 		}
884 	}
885 }
886 
887 
888 // small flying water droplet from splashes or sprinklers
ai_water_droplet(Object * o)889 void ai_water_droplet(Object *o)
890 {
891 static const Point waterattrpoint[] = { 0, -3 };
892 
893 	o->flags &= ~FLAG_IGNORE_SOLID;
894 
895 	o->yinertia += 0x20;
896 	if (o->yinertia > 0x5ff) o->yinertia = 0x5ff;
897 
898 	o->frame = random(0, 4);
899 
900 	if (++o->timer > 10)
901 	{
902 		if (o->blockl || o->blockr || o->blockd || \
903 			(o->GetAttributes(waterattrpoint, 1, NULL) & TA_WATER))
904 		{
905 			o->Delete();
906 		}
907 	}
908 }
909 
910 
911 /*
912 void c------------------------------() {}
913 */
914 
ai_bubble_spawner(Object * o)915 void ai_bubble_spawner(Object *o)
916 {
917 Object *ko;
918 
919 	if (o->dir == LEFT)
920 	{	// spawn smoke (broken motorcycle in Grass)
921 		if (!random(0, 40))
922 		{
923 			ko = CreateObject(o->x + (random(-20, 20) << CSF), o->y, OBJ_SMOKE_CLOUD);
924 			ko->xinertia = 0x100;
925 			ko->yinertia = -0x200;
926 		}
927 	}
928 	else
929 	{	// spawn "bubbles" (IronH battle)
930 		ko = CreateObject(o->x + (random(-160, 160) << CSF), \
931 						  o->y + (random(-128, 128) << CSF), \
932 						  OBJ_FAN_DROPLET);
933 
934 		ko->dir = RIGHT;
935 		ko->xinertia = 0x100;
936 	}
937 }
938 
ai_chinfish(Object * o)939 void ai_chinfish(Object *o)
940 {
941 	switch(o->state)
942 	{
943 		case 0:
944 			o->state = 1;
945 			o->xmark = o->x;
946 			o->ymark = o->y;
947 			o->yinertia = 0x88;
948 		case 1:
949 			o->yinertia += (o->y > o->ymark) ? -8:8;
950 			LIMITY(0x100);
951 			ANIMATE(4, 0, 1);
952 			if (o->shaketime) o->frame = 2;
953 		break;
954 	}
955 }
956 
957 
ai_fireplace(Object * o)958 void ai_fireplace(Object *o)
959 {
960 	switch(o->state)
961 	{
962 		case 0:		// burn
963 			o->frame = 0;
964 			o->state = 1;
965 			o->invisible = 0;
966 		case 1:
967 			ai_animate4(o);
968 		break;
969 
970 		case 10:	// extinguished by Jellyfish Juice
971 			o->state = 11;
972 			effect(o->CenterX(), o->CenterY(), EFFECT_BOOMFLASH);
973 			SmokeClouds(o, 8, 16, 16);
974 		case 11:
975 			o->invisible = 1;
976 		break;
977 	}
978 }
979 
980 // straining effect from Boulder Chamber
ai_straining(Object * o)981 void ai_straining(Object *o)
982 {
983 	switch(o->state)
984 	{
985 		case 0:
986 		{
987 			if (o->dir == LEFT)
988 			{	// curly's straining
989 				o->x += (14 << CSF);
990 				o->y -= (18 << CSF);
991 			}
992 			else
993 			{	// player's straining
994 				o->x = player->x - (6 << CSF);
995 				o->y = player->y - (2 << CSF);
996 			}
997 
998 			o->state = 1;
999 		}
1000 		case 1:
1001 		{
1002 			if (++o->animtimer > 8)
1003 			{
1004 				o->animtimer = 0;
1005 				o->frame ^= 1;
1006 
1007 				if (++o->timer2 >= 8)
1008 					o->Delete();
1009 			}
1010 		}
1011 		break;
1012 	}
1013 }
1014 
1015 // used by CMP to delay smoke created during a PRI until the PRI is released.
1016 // See the description in tsc.cpp.
ai_smoke_dropper(Object * o)1017 void ai_smoke_dropper(Object *o)
1018 {
1019 	SmokeXY(o->x, o->y, o->timer2, TILE_W/2, TILE_H/2);
1020 	o->Delete();
1021 }
1022 
1023 /*
1024 void c------------------------------() {}
1025 */
1026 
1027 // this object is used in a few places, such as during the Red Demon fight (last cave),
1028 // and during some of the end sequences. It seems to be primarily involved with providing
1029 // "extra" map scrolling modes. Generally you'll <FON on it, then set the mode you desire.
ai_scroll_controller(Object * o)1030 void ai_scroll_controller(Object *o)
1031 {
1032 	//debug("scrollctrl: state %d; dp %d; linked %08x", o->state, o->dirparam, o->linkedobject);
1033 
1034 	switch(o->state)
1035 	{
1036 		// stay above player's head. This is used during the "mad run" Balcony2 stage;
1037 		// you'll notice there is not normal scrolling during this part.
1038 		case 10:
1039 		{
1040 			o->x = player->x;
1041 			o->y = player->y - (32 << CSF);
1042 		}
1043 		break;
1044 
1045 		// pan in the specified direction. used when you get the good ending
1046 		// to pan over all the scenes from the island just before it crashes.
1047 		case 20:
1048 		{
1049 			switch(o->dir)
1050 			{
1051 				case LEFT:	o->x -= (2 << CSF); break;
1052 				case UP:	o->y -= (2 << CSF); break;
1053 				case RIGHT: o->x += (2 << CSF); break;
1054 				case DOWN:	o->y += (2 << CSF); break;
1055 			}
1056 
1057 			// player is invisible during this part. dragging him along is
1058 			// what makes all the monsters, falling spikes etc react.
1059 			player->x = o->x;
1060 			player->y = o->y;
1061 		}
1062 		break;
1063 
1064 		// stay below player.
1065 		case 30:
1066 		{
1067 			o->x = player->x;
1068 			o->y = player->y + (80 << CSF);
1069 		}
1070 		break;
1071 
1072 		// stay mid-way between player and the specified object.
1073 		// used during the Red Demon fight in Last Cave (hidden).
1074 		case 100:
1075 		{
1076 			o->state = 101;
1077 
1078 			if (o->dirparam == 0)
1079 			{
1080 				o->linkedobject = game.stageboss.object;
1081 
1082 				if (!o->linkedobject)
1083 				{
1084 					NX_ERR("sctrl: no stageboss object!\n");
1085 					o->Delete();
1086 				}
1087 			}
1088 			else
1089 			{
1090 				o->linkedobject = FindObjectByID2(o->dirparam);
1091 
1092 				if (o->linkedobject)
1093 				{
1094 					NX_LOG("sctrl: successfully linked to object %08x\n", o->linkedobject);
1095 				}
1096 				else
1097 				{
1098 					NX_ERR("sctrl: failed to link to id2 %d: object not found\n", o->id2);
1099 					o->Delete();
1100 				}
1101 			}
1102 		}
1103 		case 101:
1104 		{
1105 			if (o->linkedobject)
1106 			{
1107 				o->x = (player->x + o->linkedobject->x) / 2;
1108 				o->y = (player->y + o->linkedobject->y) / 2;
1109 			}
1110 		}
1111 		break;
1112 	}
1113 }
1114 
1115 // makes a perpetual quake. used during end-run and Hell.
ai_quake(Object * o)1116 void ai_quake(Object *o)
1117 {
1118 	if (!settings->no_quake_in_hell || \
1119 		(game.curmap != STAGE_HELL1 && \
1120 		 game.curmap != STAGE_HELL2 && \
1121 		 game.curmap != STAGE_HELL3 && \
1122 		 game.curmap != STAGE_HELL4 && \
1123 		 game.curmap != STAGE_HELL42 && \
1124 		 game.curmap != STAGE_STATUE_CHAMBER && \
1125 		 game.curmap != STAGE_CORRIDOR && \
1126 		 game.curmap != STAGE_SEAL_CHAMBER))
1127 	{
1128 		game.quaketime = 10;
1129 	}
1130 }
1131 
1132 /*
1133 void c------------------------------() {}
1134 */
1135 
ai_generic_angled_shot(Object * o)1136 void ai_generic_angled_shot(Object *o)
1137 {
1138 	// enemies can set the shot's ttl by setting an initial timer value
1139 	// after they spawn us. Or, they can leave it at zero and we
1140 	// will set a default.
1141 	if (o->state == 0)
1142 	{
1143 		o->state = 1;
1144 
1145 		if (o->timer == 0)
1146 			o->timer = 200;
1147 	}
1148 
1149 	if (o->sprite == SPR_GAUDI_FLYING_SHOT)
1150 	{
1151 		ANIMATE(0, 0, sprites[o->sprite].nframes - 1);
1152 	}
1153 	else
1154 	{
1155 		ANIMATE(2, 0, sprites[o->sprite].nframes - 1);
1156 	}
1157 
1158 	if (o->blockl && o->xinertia < 0) goto del;
1159 	if (o->blockr && o->xinertia > 0) goto del;
1160 	if (o->blocku && o->yinertia < 0) goto del;
1161 	if (o->blockd && o->yinertia > 0) goto del;
1162 
1163 	if (--o->timer < 0)
1164 	{
1165 		del: ;
1166 
1167 		effect(o->CenterX(), o->CenterY(), EFFECT_FISHY);
1168 		o->Delete();
1169 	}
1170 }
1171 
1172 /*
1173 void c------------------------------() {}
1174 */
1175 
1176 
onspawn_spike_small(Object * o)1177 void onspawn_spike_small(Object *o)
1178 {
1179 	o->frame = o->id2;
1180 
1181 	// hack to remove 2 spikes in First Cave which are
1182 	// extraneous and invisible because they are embedded
1183 	// in the wall, but due to slight engine differences
1184 	// you can still sometimes get hurt by them in our engine.
1185 	int tile = map.tiles[(o->CenterX() >> CSF) / TILE_W][(o->CenterY() >> CSF) / TILE_H];
1186 	if (tileattr[tile] & TA_SOLID)
1187 	{
1188 #ifdef DEBUG
1189 		NX_LOG("onspawn_spike_small: spike %08x embedded in wall, deleting", o);
1190 #endif
1191 		o->Delete();
1192 	}
1193 }
1194 
1195 
1196 
1197 
1198