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