1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "common/random.h"
24
25 #include "hdb/hdb.h"
26 #include "hdb/ai.h"
27 #include "hdb/ai-player.h"
28 #include "hdb/gfx.h"
29 #include "hdb/lua-script.h"
30 #include "hdb/map.h"
31 #include "hdb/mpc.h"
32 #include "hdb/sound.h"
33 #include "hdb/window.h"
34
35 namespace HDB {
36
37 //-------------------------------------------------------------------
38 //
39 // OMNIBOT : This guy moves on a path and if he sees the player
40 // directly ahead, he will shoot at him
41 //
42 //-------------------------------------------------------------------
43
aiOmniBotInit(AIEntity * e)44 void aiOmniBotInit(AIEntity *e) {
45 if (e->value1 == 1)
46 e->aiAction = aiOmniBotMove;
47 else if (g_hdb->_ai->findPath(e))
48 e->aiAction = aiOmniBotAction;
49 }
50
aiOmniBotInit2(AIEntity * e)51 void aiOmniBotInit2(AIEntity *e) {
52 e->standdownGfx[0] = e->movedownGfx[0];
53 e->standupGfx[0] = e->movedownGfx[0];
54 e->standleftGfx[0] = e->moveleftGfx[0];
55 e->standrightGfx[0] = e->moverightGfx[0];
56 e->standdownFrames = e->standupFrames = e->standleftFrames = e->standrightFrames = 1;
57 e->draw = g_hdb->_ai->getStandFrameDir(e);
58 }
59
aiOmniBotMove(AIEntity * e)60 void aiOmniBotMove(AIEntity *e) {
61 if (e->goalX)
62 g_hdb->_ai->animateEntity(e);
63 else
64 g_hdb->_ai->animEntFrames(e);
65 }
66
aiOmniBotAction(AIEntity * e)67 void aiOmniBotAction(AIEntity *e) {
68 AIEntity *p = g_hdb->_ai->getPlayer();
69 if (e->goalX) {
70 if (!e->sequence) {
71 g_hdb->_ai->animateEntity(e);
72 // Is the Player collding?
73 if (hitPlayer(e->x, e->y) && (p->level == e->level)) {
74 g_hdb->_ai->killPlayer(DEATH_FRIED);
75 return;
76 }
77
78 // Shoot player ?
79 if (onEvenTile(e->x, e->y) && g_hdb->getActionMode()) {
80 int xv = 0, yv = 0, result;
81 bool shoot = false;
82
83 // FIXME: Is reloading Player required here?
84 p = g_hdb->_ai->getPlayer();
85
86 // On same level/screen?
87 if ((e->level != p->level) || g_hdb->_ai->playerDead() || !e->onScreen)
88 return;
89
90 // Is Player in Line of Sight?
91 switch (e->dir) {
92 case DIR_UP:
93 if (p->x == e->x && p->y < e->y) {
94 shoot = true;
95 yv = -1;
96 }
97 break;
98 case DIR_DOWN:
99 if (p->x == e->x && p->y > e->y) {
100 shoot = true;
101 yv = 1;
102 }
103 break;
104 case DIR_LEFT:
105 if (p->x < e->x && p->y == e->y) {
106 shoot = true;
107 xv = -1;
108 }
109 break;
110 case DIR_RIGHT:
111 if (p->x > e->x && p->y == e->y) {
112 shoot = true;
113 xv = 1;
114 }
115 break;
116 case DIR_NONE:
117 default:
118 break;
119 }
120
121 // If shoot = true, take the shot
122 // (1) Check we're not shooting into a solid tile
123 // (2) Check we're not shooting into an Entity unless it's the player
124 AIEntity *hit = g_hdb->_ai->legalMoveOverWater(e->tileX + xv, e->tileY + yv, e->level, &result);
125 if (shoot && !hit && result) {
126 AIEntity *omni = g_hdb->_ai->spawn(AI_OMNIBOT_MISSILE, e->dir, e->tileX + xv, e->tileY + yv, nullptr, nullptr, nullptr, DIR_NONE, e->level, 0, 0, 1);
127 omni->xVel = xv * kPlayerMoveSpeed * 2;
128 omni->yVel = yv * kPlayerMoveSpeed * 2;
129 if (g_hdb->_map->onScreen(e->tileX, e->tileY))
130 g_hdb->_sound->playSound(SND_OMNIBOT_FIRE);
131 if (!g_hdb->getActionMode()) {
132 omni->xVel >>= 1;
133 omni->yVel >>= 1;
134 }
135 e->sequence = 16;
136 }
137 }
138 }
139 } else {
140 g_hdb->_ai->findPath(e);
141 if (e->onScreen)
142 g_hdb->_sound->playSound(SND_OMNIBOT_AMBIENT);
143 }
144
145 if (e->sequence)
146 e->sequence--;
147 }
148
149 //-------------------------------------------------------------------
150 //
151 // OMNIBOT MISSILE : Used by the FOURFIRER and OMNIBOT, this deadly
152 // missile flies through the air, killing anything it hits
153 //
154 //-------------------------------------------------------------------
155
aiOmniBotMissileInit(AIEntity * e)156 void aiOmniBotMissileInit(AIEntity *e) {
157 e->state = STATE_MOVEDOWN;
158 e->aiAction = aiOmniBotMissileAction;
159 }
160
aiOmniBotMissileInit2(AIEntity * e)161 void aiOmniBotMissileInit2(AIEntity *e) {
162 for (int i = 0; i < e->movedownFrames; i++)
163 e->moveleftGfx[i] = e->moverightGfx[i] = e->moveupGfx[i] = e->movedownGfx[i];
164
165 e->moveleftFrames = e->moverightFrames = e->moveupFrames = e->movedownFrames;
166 e->draw = e->movedownGfx[0];
167 }
168
aiOmniBotMissileAction(AIEntity * e)169 void aiOmniBotMissileAction(AIEntity *e) {
170 AIEntity *p = g_hdb->_ai->getPlayer();
171
172 g_hdb->_ai->animEntFrames(e);
173 e->x += e->xVel;
174 e->y += e->yVel;
175 e->tileX = e->x / kTileWidth;
176 e->tileY = e->y / kTileHeight;
177
178 // Did we hit a solid wall?
179 int result;
180 AIEntity *hit = g_hdb->_ai->legalMoveOverWaterIgnore(e->tileX, e->tileY, e->level, &result, e);
181
182 if (hit || !result) {
183 g_hdb->_ai->addAnimateTarget(e->x, e->y, 0, 3, ANIM_FAST, false, false, "steam_puff_sit");
184 g_hdb->_ai->removeEntity(e);
185 }
186
187 // On Even tiles, check for hitting player
188 if (onEvenTile(e->x, e->y))
189 if (hitPlayer(e->x, e->y) && (p->level == e->level)) {
190 g_hdb->_ai->killPlayer(DEATH_NORMAL);
191 g_hdb->_ai->addAnimateTarget(e->x, e->y, 0, 3, ANIM_FAST, false, false, "steam_puff_sit");
192 g_hdb->_ai->removeEntity(e);
193 }
194 }
195
196 //-------------------------------------------------------------------
197 //
198 // TURNBOT : Moves straight ahead until it hits a wall, then turns
199 // right and continues.
200 //
201 //-------------------------------------------------------------------
202
aiTurnBotInit(AIEntity * e)203 void aiTurnBotInit(AIEntity *e) {
204 e->aiAction = aiTurnBotAction;
205 }
206
aiTurnBotInit2(AIEntity * e)207 void aiTurnBotInit2(AIEntity *e) {
208 e->draw = g_hdb->_ai->getStandFrameDir(e);
209 }
210
aiTurnBotChoose(AIEntity * e)211 void aiTurnBotChoose(AIEntity *e) {
212 static const int xvAhead[5] = { 9, 0, 0, -1, 1 };
213 static const int yvAhead[5] = { 9, -1, 1, 0, 0 };
214 static const AIDir turnRight[5] = { DIR_NONE, DIR_RIGHT, DIR_LEFT, DIR_UP, DIR_DOWN };
215 static const AIState dirState[5] = { STATE_NONE, STATE_MOVEUP, STATE_MOVEDOWN, STATE_MOVELEFT, STATE_MOVERIGHT };
216
217 int xv = xvAhead[e->dir];
218 int yv = yvAhead[e->dir];
219 if (g_hdb->_map->getMapBGTileFlags(e->tileX + xv, e->tileY + yv) & (kFlagSolid | kFlagWater)) {
220 e->xVel = e->yVel = 0;
221 e->animFrame = 0;
222 e->animDelay = e->animCycle;
223 e->dir = turnRight[e->dir];
224 e->state = dirState[e->dir];
225 } else {
226 e->xVel = xv * kPlayerMoveSpeed;
227 e->yVel = yv * kPlayerMoveSpeed;
228 if (!g_hdb->getActionMode()) {
229 e->xVel >>= 1;
230 e->yVel >>= 1;
231 }
232 e->goalX = e->tileX + xv;
233 e->goalY = e->tileY + yv;
234 e->state = dirState[e->dir];
235 if (e->dir == DIR_DOWN)
236 e->animFrame = 3;
237 }
238 }
239
aiTurnBotAction(AIEntity * e)240 void aiTurnBotAction(AIEntity *e) {
241 if (e->goalX)
242 g_hdb->_ai->animateEntity(e);
243 else {
244 aiTurnBotChoose(e);
245 g_hdb->_ai->animateEntity(e);
246 if (e->onScreen)
247 g_hdb->_sound->playSound(SND_TURNBOT_TURN);
248 }
249
250 if (e->onScreen && onEvenTile(e->x, e->y) && g_hdb->_ai->checkPlayerCollision(e->x, e->y, 0) && !g_hdb->_ai->playerDead())
251 g_hdb->_ai->killPlayer(DEATH_NORMAL);
252 }
253
254 //-------------------------------------------------------------------
255 //
256 // SHOCKBOT : Moves on a path, electrifying all tiles surrounding it
257 // that are METAL. Will pause when changing directions.
258 //
259 //-------------------------------------------------------------------
260
aiShockBotInit(AIEntity * e)261 void aiShockBotInit(AIEntity *e) {
262 g_hdb->_ai->findPath(e);
263 e->aiAction = aiShockBotAction;
264 e->animCycle = 0;
265 e->sequence = 0;
266 e->aiDraw = aiShockBotShock;
267 }
268
aiShockBotInit2(AIEntity * e)269 void aiShockBotInit2(AIEntity *e) {
270 e->standupFrames = e->standdownFrames = e->standleftFrames = e->standrightFrames =
271 e->moveupFrames = e->moverightFrames = e->moveleftFrames = e->movedownFrames;
272
273 for (int i = 0; i < e->movedownFrames; i++)
274 e->standupGfx[i] = e->standleftGfx[i] = e->standrightGfx[i] = e->standdownGfx[i] = e->moveupGfx[i] = e->moveleftGfx[i] = e->moverightGfx[i] = e->movedownGfx[i];
275
276 e->draw = g_hdb->_ai->getStandFrameDir(e);
277 }
278
aiShockBotAction(AIEntity * e)279 void aiShockBotAction(AIEntity *e) {
280 if (e->goalX) {
281 if (!e->sequence) {
282 if (hitPlayer(e->x, e->y))
283 g_hdb->_ai->killPlayer(DEATH_SHOCKED);
284 g_hdb->_ai->animateEntity(e);
285 } else
286 g_hdb->_ai->animEntFrames(e);
287 } else {
288 g_hdb->_ai->findPath(e);
289 e->sequence = 20;
290 g_hdb->_ai->animEntFrames(e);
291 if (e->onScreen)
292 g_hdb->_sound->playSound(SND_SHOCKBOT_AMBIENT);
293
294 }
295
296 if (e->sequence)
297 e->sequence--;
298 }
299
aiShockBotShock(AIEntity * e,int mx,int my)300 void aiShockBotShock(AIEntity *e, int mx, int my) {
301 static const int offX[8] = { -1, 0, 1, 1, 1, 0, -1, -1 };
302 static const int offY[8] = { -1, -1, -1, 0, 1, 1, 1, 0 };
303
304 // Only on a exact tile boundary do we change the shocked tiles
305 // Start at top left and go around
306 if (g_hdb->_map->getMapBGTileFlags(e->tileX, e->tileY) & kFlagMetal)
307 e->special1Gfx[e->animFrame]->drawMasked(e->tileX * kTileWidth - mx, e->tileY * kTileHeight - my);
308
309 for (int i = 0; i < 8; i++) {
310 uint32 flags = g_hdb->_map->getMapBGTileFlags(e->tileX + offX[i], e->tileY + offY[i]);
311 if (flags & kFlagMetal) {
312 // Is the shocking tile onScreen?
313 if (g_hdb->_map->checkXYOnScreen((e->tileX + offX[i]) * kTileWidth, (e->tileY + offY[i]) * kTileHeight)) {
314 // Draw shocking tile animation
315 e->special1Gfx[e->animFrame]->drawMasked((e->tileX + offX[i])*kTileWidth - mx, (e->tileY + offY[i])*kTileHeight - my);
316 // Did the player get fried?
317 // Check every 4 frames
318 if (e->onScreen && !e->animFrame && g_hdb->_ai->checkPlayerTileCollision(e->tileX + offX[i], e->tileY + offY[i]) && !g_hdb->_ai->playerDead()) {
319 g_hdb->_ai->killPlayer(DEATH_SHOCKED);
320 return;
321 }
322 if (!e->animFrame && g_hdb->_map->boomBarrelExist(e->tileX + offX[i], e->tileY + offY[i])) {
323 AIEntity *e2 = g_hdb->_ai->findEntityType(AI_BOOMBARREL, e->tileX + offX[i], e->tileY + offY[i]);
324 aiBarrelExplode(e2);
325 }
326 }
327 }
328 }
329 }
330
331 //-------------------------------------------------------------------
332 //
333 // RIGHTBOT
334 //
335 // Rules: Follows the right-hand wall. That's it!
336 //
337 //-------------------------------------------------------------------
338
aiRightBotInit(AIEntity * e)339 void aiRightBotInit(AIEntity *e) {
340 e->moveSpeed = kPlayerMoveSpeed;
341 if (!g_hdb->getActionMode())
342 e->moveSpeed >>= 1;
343 e->aiAction = aiRightBotAction;
344 }
345
aiRightBotInit2(AIEntity * e)346 void aiRightBotInit2(AIEntity *e) {
347 switch (e->dir) {
348 case DIR_UP:
349 e->draw = e->moveupGfx[0];
350 e->state = STATE_MOVEUP;
351 break;
352 case DIR_DOWN:
353 e->draw = e->movedownGfx[0];
354 e->state = STATE_MOVEDOWN;
355 break;
356 case DIR_LEFT:
357 e->draw = e->moveleftGfx[0];
358 e->state = STATE_MOVELEFT;
359 break;
360 case DIR_RIGHT:
361 e->draw = e->moverightGfx[0];
362 e->state = STATE_MOVERIGHT;
363 break;
364 case DIR_NONE:
365 default:
366 break;
367 }
368 }
369
aiRightBotFindGoal(AIEntity * e)370 void aiRightBotFindGoal(AIEntity *e) {
371 static const int xvAhead[5] = { 9, 0, 0,-1, 1 };
372 static const int yvAhead[5] = { 9,-1, 1, 0, 0 };
373 static const int xvAToR[5] = { 9, 1,-1,-1, 1 };
374 static const int yvAToR[5] = { 9,-1, 1,-1, 1 };
375 static const int xvToR[5] = { 9, 1,-1, 0, 0 };
376 static const int yvToR[5] = { 9, 0, 0,-1, 1 };
377 static const int xvToL[5] = { 9,-1, 1, 0, 0 };
378 static const int yvToL[5] = { 9, 0, 0, 1,-1 };
379
380 AIEntity *p = g_hdb->_ai->getPlayer();
381 int rotate = 0;
382
383 int xv, yv;
384 int bg, bg2, bg3;
385 AIEntity *e1, *e2, *e3;
386 int sx, sy;
387
388 do {
389 xv = xvAhead[e->dir]; // Search Ahead
390 yv = yvAhead[e->dir];
391 int xv2 = xvAToR[e->dir]; // Search Ahead and to the Right
392 int yv2 = yvAToR[e->dir];
393 int xv3 = xvToR[e->dir]; // Search to the Right
394 int yv3 = yvToR[e->dir];
395
396 // Search until we hit a wall...or empty space to our right (and forward)
397 bool hit = false;
398 sx = e->tileX;
399 sy = e->tileY;
400
401 while (!hit) {
402 bg = g_hdb->_map->getMapBGTileFlags(sx + xv, sy + yv) & (kFlagSolid | kFlagWater | kFlagSlime | kFlagSpecial);
403 e1 = g_hdb->_ai->findEntity(sx + xv, sy + yv);
404 if (e1 && e1 == p)
405 e1 = nullptr;
406 bg2 = g_hdb->_map->getMapBGTileFlags(sx + xv2, sy + yv2) & (kFlagSolid | kFlagWater | kFlagSlime | kFlagSpecial);
407 e2 = g_hdb->_ai->findEntity(sx + xv2, sy + yv2);
408 if (e2 && e2 == p)
409 e2 = nullptr;
410 bg3 = g_hdb->_map->getMapBGTileFlags(sx + xv3, sy + yv3) & (kFlagSolid | kFlagWater | kFlagSlime | kFlagSpecial);
411 e3 = g_hdb->_ai->findEntity(sx + xv3, sy + yv3);
412 if (e3 && e3 == p)
413 e3 = nullptr;
414
415 // Okay to move forward?
416 if ((!bg && !e1) && (bg2 || e2 || bg3 || e3)) {
417 sx += xv;
418 sy += yv;
419 rotate = 0;
420 } else
421 hit = true;
422 }
423
424 // Are we stuck in a corner?
425 if (sx == e->tileX && sy == e->tileY) {
426 sx = e->tileX;
427 sy = e->tileY;
428 rotate += 1;
429
430 // Need to check for turning RIGHT when we're in a corner
431 xv = xvToL[e->dir];
432 yv = yvToL[e->dir];
433
434 // Check Tile flags to our left and right
435 bg = g_hdb->_map->getMapBGTileFlags(sx + xv, sy + yv) & (kFlagSolid | kFlagWater | kFlagSlime | kFlagSpecial);
436 e1 = g_hdb->_ai->findEntity(sx + xv, sy + yv);
437 bg2 = g_hdb->_map->getMapBGTileFlags(sx + xv3, sy + yv3) & (kFlagSolid | kFlagWater | kFlagSlime | kFlagSpecial);
438 e2 = g_hdb->_ai->findEntity(sx + xv3, sy + yv3);
439 if (e1 && e1->type == AI_GUY)
440 e1 = nullptr;
441 if (e2 && e2->type == AI_GUY)
442 e2 = nullptr;
443
444 // Is tile to the right clear?
445 // Is tile to the left clear?
446 // If neither, go backwards
447 if (!bg2 && !e2) {
448 switch (e->dir) {
449 case DIR_UP:
450 e->dir = DIR_RIGHT;
451 break;
452 case DIR_DOWN:
453 e->dir = DIR_LEFT;
454 break;
455 case DIR_LEFT:
456 e->dir = DIR_UP;
457 break;
458 case DIR_RIGHT:
459 e->dir = DIR_DOWN;
460 break;
461 case DIR_NONE:
462 default:
463 break;
464 }
465 } else if (!bg && !e1) {
466 switch (e->dir) {
467 case DIR_UP:
468 e->dir = DIR_LEFT;
469 break;
470 case DIR_DOWN:
471 e->dir = DIR_RIGHT;
472 break;
473 case DIR_LEFT:
474 e->dir = DIR_DOWN;
475 break;
476 case DIR_RIGHT:
477 e->dir = DIR_UP;
478 break;
479 case DIR_NONE:
480 default:
481 break;
482 }
483 } else {
484 switch (e->dir) {
485 case DIR_UP:
486 e->dir = DIR_DOWN;
487 yv = 1;
488 xv = 0;
489 break;
490 case DIR_DOWN:
491 e->dir = DIR_UP;
492 yv = -1;
493 xv = 0;
494 break;
495 case DIR_LEFT:
496 e->dir = DIR_RIGHT;
497 yv = 0;
498 xv = 1;
499 break;
500 case DIR_RIGHT:
501 e->dir = DIR_LEFT;
502 yv = 0;
503 xv = -1;
504 break;
505 case DIR_NONE:
506 default:
507 break;
508 }
509 sx += xv;
510 sy += yv;
511 rotate = 4;
512 }
513 }
514 } while (rotate >= 1 && rotate < 4);
515
516 switch (e->dir) {
517 case DIR_UP:
518 e->state = STATE_MOVEUP;
519 break;
520 case DIR_DOWN:
521 e->state = STATE_MOVEDOWN;
522 break;
523 case DIR_LEFT:
524 e->state = STATE_MOVELEFT;
525 break;
526 case DIR_RIGHT:
527 e->state = STATE_MOVERIGHT;
528 break;
529 case DIR_NONE:
530 default:
531 break;
532 }
533
534 e->goalX = sx;
535 e->goalY = sy;
536 e->xVel = xv * e->moveSpeed;
537 e->yVel = yv * e->moveSpeed;
538 if (e->onScreen)
539 g_hdb->_sound->playSound(SND_RIGHTBOT_TURN);
540 }
541
aiRightBotAction(AIEntity * e)542 void aiRightBotAction(AIEntity *e) {
543 AIEntity *p = g_hdb->_ai->getPlayer();
544
545 if (e->goalX) {
546 if (e->onScreen && g_hdb->_ai->checkPlayerCollision(e->x, e->y, 0) && p->state != STATE_DEAD && p->level == e->level && !g_hdb->_ai->playerDead())
547 g_hdb->_ai->killPlayer(DEATH_NORMAL);
548 g_hdb->_ai->animateEntity(e);
549 } else {
550 aiRightBotFindGoal(e);
551 g_hdb->_ai->animEntFrames(e);
552 }
553 }
554
555 //-------------------------------------------------------------------
556 //
557 // PUSHBOT : Very simple, this guy goes forward and pushes anything in his
558 // path all the way until it can't go any further. Then, he turns 180
559 // degress and comes back until he can't go any further. Then... he
560 // turns 180 degrees and does it all over again!
561 //
562 //-------------------------------------------------------------------
563
aiPushBotInit(AIEntity * e)564 void aiPushBotInit(AIEntity *e) {
565 if (e->value1 != 1)
566 e->aiAction = aiPushBotAction;
567 }
568
aiPushBotInit2(AIEntity * e)569 void aiPushBotInit2(AIEntity *e) {
570 e->draw = g_hdb->_ai->getStandFrameDir(e);
571 }
572
aiPushBotAction(AIEntity * e)573 void aiPushBotAction(AIEntity *e) {
574 static const AIState moveState[5] = { STATE_NONE, STATE_MOVEUP, STATE_MOVEDOWN, STATE_MOVELEFT, STATE_MOVERIGHT };
575 static const int xvAhead[5] = { 9, 0, 0,-1, 1 };
576 static const int yvAhead[5] = { 9,-1, 1, 0, 0 };
577 static const AIDir oneEighty[5] = { DIR_NONE, DIR_DOWN, DIR_UP, DIR_RIGHT, DIR_LEFT };
578
579 AIEntity *e1 = nullptr;
580
581 if (e->goalX) {
582 g_hdb->_ai->animateEntity(e);
583 if (hitPlayer(e->x, e->y))
584 g_hdb->_ai->killPlayer(DEATH_NORMAL);
585 } else {
586 if (hitPlayer(e->x, e->y))
587 g_hdb->_ai->killPlayer(DEATH_NORMAL);
588
589 // Where to go next
590 int nx = e->tileX + xvAhead[e->dir];
591 int ny = e->tileY + yvAhead[e->dir];
592
593 int result;
594 e1 = g_hdb->_ai->legalMove(nx, ny, e->level, &result);
595
596 // Push something
597 // Turn Around
598 // Move Forward
599 if (e1 && onEvenTile(e1->x, e1->y) && (e1->type == AI_LIGHTBARREL || e1->type == AI_HEAVYBARREL || e1->type == AI_BOOMBARREL || e1->type == AI_CRATE)) {
600 // Actually going over a floating crate?
601 if (e1 && (e1->state == STATE_FLOATING || e1->state == STATE_MELTED)) {
602 e->state = moveState[e->dir];
603 g_hdb->_ai->setEntityGoal(e, nx, ny);
604 g_hdb->_ai->animateEntity(e);
605 return;
606 }
607
608 int nx2 = nx + xvAhead[e->dir];
609 int ny2 = ny + yvAhead[e->dir];
610
611 uint32 bgFlags = g_hdb->_map->getMapBGTileFlags(nx2, ny2);
612 uint32 fgFlags = g_hdb->_map->getMapFGTileFlags(nx2, ny2);
613 AIEntity *e2 = g_hdb->_ai->findEntity(nx2, ny2);
614 result = (e->level == 1) ? (bgFlags & kFlagSolid) : !(fgFlags & kFlagGrating) && (bgFlags & kFlagSolid);
615
616 // If we're going to push something onto a floating thing, that's ok
617 if (e2 && (e2->state == STATE_FLOATING || e2->state == STATE_MELTED))
618 e2 = nullptr;
619
620 // If no walls in front & no entities
621 if (!result && !e2 && e1->state != STATE_EXPLODING) {
622 e->state = moveState[e->dir];
623 g_hdb->_ai->setEntityGoal(e, nx, ny);
624
625 e1->dir = e->dir;
626 e1->state = e->state;
627 e1->moveSpeed = e->moveSpeed;
628 g_hdb->_ai->setEntityGoal(e1, nx2, ny2);
629 switch (e1->type) {
630 case AI_CRATE:
631 g_hdb->_sound->playSound(SND_CRATE_SLIDE);
632 break;
633 case AI_HEAVYBARREL:
634 case AI_BOOMBARREL:
635 g_hdb->_sound->playSound(SND_HEAVY_SLIDE);
636 break;
637 case AI_LIGHTBARREL:
638 g_hdb->_sound->playSound(SND_LIGHT_SLIDE);
639 break;
640 default:
641 break;
642 }
643 } else {
644 if (e->onScreen)
645 g_hdb->_sound->playSound(SND_PUSHBOT_STRAIN);
646 e->dir = oneEighty[e->dir];
647 e->state = moveState[e->dir];
648 nx = e->tileX + xvAhead[e->dir];
649 ny = e->tileY + yvAhead[e->dir];
650 e1 = g_hdb->_ai->legalMove(nx, ny, e->level, &result);
651 if (!e1 && result)
652 g_hdb->_ai->setEntityGoal(e, nx, ny);
653 }
654 } else if (!result || (e1 && !onEvenTile(e1->x, e1->y))) {
655 e->dir = oneEighty[e->dir];
656 e->state = moveState[e->dir];
657 nx = e->tileX + xvAhead[e->dir];
658 ny = e->tileY + yvAhead[e->dir];
659 e1 = g_hdb->_ai->legalMove(nx, ny, e->level, &result);
660 if (!e1 && result)
661 g_hdb->_ai->setEntityGoal(e, nx, ny);
662 } else {
663 e->state = moveState[e->dir];
664 g_hdb->_ai->setEntityGoal(e, nx, ny);
665 }
666 g_hdb->_ai->animateEntity(e);
667 }
668 }
669
670 //-------------------------------------------------------------------
671 //
672 // RAILRIDER : crazy green goopy dude -- he gives you rides on his
673 // special track!
674 //
675 //-------------------------------------------------------------------
676
aiRailRiderInit(AIEntity * e)677 void aiRailRiderInit(AIEntity *e) {
678 if (e->type == AI_RAILRIDER_ON) {
679 // On the tracks already - spawn RED arrow
680 g_hdb->_ai->addToPathList(e->tileX, e->tileY, 0, e->dir);
681 e->state = STATE_STANDUP;
682 e->aiAction = aiRailRiderOnAction;
683 e->aiUse = aiRailRiderOnUse;
684 } else {
685 e->state = STATE_STANDDOWN;
686 e->sequence = 0;
687 e->aiAction = aiRailRiderAction;
688 e->aiUse = aiRailRiderUse;
689 }
690 e->moveSpeed = kPlayerMoveSpeed;
691 }
692
aiRailRiderInit2(AIEntity * e)693 void aiRailRiderInit2(AIEntity *e) {
694 e->draw = e->standdownGfx[0];
695 }
696
697 // Talking to RailRider off track
aiRailRiderUse(AIEntity * e)698 void aiRailRiderUse(AIEntity *e) {
699 e->sequence = 1;
700 }
701
aiRailRiderAction(AIEntity * e)702 void aiRailRiderAction(AIEntity *e) {
703 switch (e->sequence) {
704 // Waiting for Dialog to goaway
705 case 1:
706 // Dialog gone?
707 if (!g_hdb->_window->dialogActive()) {
708 e->sequence = 2;
709 switch (e->dir) {
710 case DIR_UP:
711 e->xVel = 0;
712 e->yVel = -1;
713 break;
714 case DIR_DOWN:
715 e->xVel = 0;
716 e->yVel = 1;
717 break;
718 case DIR_LEFT:
719 e->xVel = -1;
720 e->yVel = 0;
721 break;
722 case DIR_RIGHT:
723 e->xVel = 1;
724 e->yVel = 0;
725 break;
726 case DIR_NONE:
727 default:
728 break;
729 }
730 }
731 break;
732 // Walking over to track
733 case 2:
734 e->x += e->xVel;
735 e->y += e->yVel;
736 if (onEvenTile(e->x, e->y)) {
737 ArrowPath *arrowPath;
738 e->tileX = e->x / kTileWidth;
739 e->tileY = e->y / kTileHeight;
740 e->sequence = 3; // Wait for use
741 e->type = AI_RAILRIDER_ON;
742 e->state = STATE_STANDUP;
743 e->aiAction = aiRailRiderOnAction;
744 e->aiUse = aiRailRiderOnUse;
745 arrowPath = g_hdb->_ai->findArrowPath(e->tileX, e->tileY);
746
747 if (arrowPath == nullptr)
748 return;
749
750 e->dir = arrowPath->dir;
751 e->value1 = 0; // Not in a tunnel
752 }
753 break;
754 default:
755 break;
756 }
757
758 // Cycle through animation frames
759 if (e->animDelay-- > 0)
760 return;
761 e->animDelay = e->animCycle;
762 e->animFrame++;
763 if (e->animFrame == e->standdownFrames)
764 e->animFrame = 0;
765
766 e->draw = e->standdownGfx[e->animFrame];
767 }
768
769 // Talking to RailRider on track
aiRailRiderOnUse(AIEntity * e)770 void aiRailRiderOnUse(AIEntity *e) {
771 AIEntity *p = g_hdb->_ai->getPlayer();
772
773 if (p->tileX == e->tileX) {
774 if (p->tileY > e->tileY)
775 g_hdb->_ai->setEntityGoal(p, p->tileX, p->tileY - 1);
776 else
777 g_hdb->_ai->setEntityGoal(p, p->tileX, p->tileY + 1);
778 } else if (p->tileX > e->tileX)
779 g_hdb->_ai->setEntityGoal(p, p->tileX - 1, p->tileY);
780 else
781 g_hdb->_ai->setEntityGoal(p, p->tileX + 1, p->tileY);
782
783 e->sequence = -1; // Waiting for player to board
784 }
785
aiRailRiderOnAction(AIEntity * e)786 void aiRailRiderOnAction(AIEntity *e) {
787 static const int xv[5] = { 9, 0, 0, -1, 1 };
788 static const int yv[5] = { 9, -1, 1, 0, 0 };
789
790 AIEntity*p = g_hdb->_ai->getPlayer();
791
792 switch (e->sequence) {
793 // Player is boarding
794 case -1:
795 if (!p->goalX)
796 e->sequence = 1; // Boarded yet?
797 // fallthrough
798 // Cycle Animation Frames
799 case 3:
800 if (e->animDelay-- > 0)
801 return;
802 e->animDelay = e->animCycle;
803 e->animFrame++;
804 if (e->animFrame == e->standupFrames)
805 e->animFrame = 0;
806
807 e->draw = e->standupGfx[e->animFrame];
808 break;
809 // Player is in - lock him
810 case 1:
811 g_hdb->_ai->setPlayerInvisible(true);
812 g_hdb->_ai->setPlayerLock(true);
813 g_hdb->_ai->setEntityGoal(e, e->tileX + xv[e->dir], e->tileY + yv[e->dir]);
814 g_hdb->_sound->playSound(SND_RAILRIDER_TASTE);
815 e->sequence = 2;
816 e->value1 = 0;
817 // fallthrough
818
819 // New RailRider gfx
820 // Move the RailRider
821 case 2: {
822 // Done moving to next spot?
823 if (!e->goalX) {
824 ArrowPath *arrowPath = g_hdb->_ai->findArrowPath(e->tileX, e->tileY);
825 if (arrowPath) {
826 // Stop Arrow?
827 if (!arrowPath->type) {
828 HereT *h;
829 e->sequence = 4; // Get Player off RailRider - RIGHT SIDE ONLY
830 p->tileX = e->tileX;
831 p->tileY = e->tileY;
832 p->x = e->x;
833 p->y = e->y;
834 // Try to find a HERE icon to either side of the track and go there
835 switch (e->dir) {
836 case DIR_UP:
837 h = g_hdb->_ai->findHere(e->tileX - 1, e->tileY);
838 if (h)
839 g_hdb->_ai->setEntityGoal(p, e->tileX - 1, e->tileY);
840 else
841 g_hdb->_ai->setEntityGoal(p, e->tileX + 1, e->tileY);
842 break;
843 case DIR_DOWN:
844 h = g_hdb->_ai->findHere(e->tileX + 1, e->tileY);
845 if (h)
846 g_hdb->_ai->setEntityGoal(p, e->tileX + 1, e->tileY);
847 else
848 g_hdb->_ai->setEntityGoal(p, e->tileX - 1, e->tileY);
849 break;
850 case DIR_LEFT:
851 h = g_hdb->_ai->findHere(e->tileX, e->tileY + 1);
852 if (h)
853 g_hdb->_ai->setEntityGoal(p, e->tileX, e->tileY + 1);
854 else
855 g_hdb->_ai->setEntityGoal(p, e->tileX, e->tileY - 1);
856 break;
857 case DIR_RIGHT:
858 h = g_hdb->_ai->findHere(e->tileX, e->tileY - 1);
859 if (h)
860 g_hdb->_ai->setEntityGoal(p, e->tileX, e->tileY - 1);
861 else
862 g_hdb->_ai->setEntityGoal(p, e->tileX, e->tileY + 1);
863 break;
864 case DIR_NONE:
865 default:
866 break;
867 }
868 g_hdb->_ai->setPlayerInvisible(false);
869 g_hdb->_sound->playSound(SND_RAILRIDER_EXIT);
870 } else if (arrowPath->type == 1) {
871 e->dir = arrowPath->dir;
872 g_hdb->_ai->setEntityGoal(e, e->tileX + xv[e->dir], e->tileY + yv[e->dir]);
873 }
874 } else
875 g_hdb->_ai->setEntityGoal(e, e->tileX + xv[e->dir], e->tileY + yv[e->dir]);
876
877 g_hdb->_sound->playSound(SND_RAILRIDER_ONTRACK);
878 }
879
880 p->tileX = e->tileX;
881 p->tileY = e->tileY;
882 p->x = e->x;
883 p->y = e->y;
884 g_hdb->_ai->animateEntity(e);
885 switch (e->dir) {
886 case DIR_UP:
887 e->draw = e->moveupGfx[0];
888 break;
889 case DIR_DOWN:
890 e->draw = e->movedownGfx[0];
891 break;
892 case DIR_LEFT:
893 e->draw = e->moveleftGfx[0];
894 break;
895 case DIR_RIGHT:
896 e->draw = e->moverightGfx[0];
897 break;
898 case DIR_NONE:
899 default:
900 break;
901 }
902 g_hdb->_map->centerMapXY(e->x + 16, e->y + 16);
903
904 SingleTele t;
905 // Did we hit a tunnel entrance?
906 if (onEvenTile(e->x, e->y) && g_hdb->_ai->findTeleporterDest(e->tileX, e->tileY, &t) && !e->value1 && !e->dir2) {
907 // Set tunnel destination
908 e->value1 = t.x;
909 e->value2 = t.y;
910 e->dir2 = (AIDir)(t.x + t.y); // Flag for coming out of tunnel
911 }
912
913 // Are we going through a tunnel?
914 if (e->value1) {
915 // Reach the End?
916 // If not, don't draw RailRider
917 if (onEvenTile(e->x, e->y) && e->tileX == e->value1 && e->tileY == e->value2)
918 e->value1 = 0;
919 else
920 e->draw = nullptr;
921 } else if (e->dir2 && e->dir2 != (AIDir)(e->tileX + e->tileY))
922 e->dir2 = DIR_NONE;
923 break;
924 }
925 // Waiting for Player to move to Dest
926 case 4:
927 if (!p->goalX) {
928 g_hdb->_ai->setPlayerLock(false);
929 e->sequence = 3; // Wait for Use
930 }
931
932 // Cycle Animation frames
933 if (e->animDelay-- > 0)
934 return;
935
936 e->animDelay = e->animCycle;
937 e->animFrame++;
938 if (e->animFrame == e->standupFrames)
939 e->animFrame = 0;
940
941 e->draw = e->standupGfx[e->animFrame];
942 break;
943 default:
944 break;
945 }
946 }
947
948 //-------------------------------------------------------------------
949 //
950 // MAINTBOT : This little fella likes to cause trouble! He just jubs
951 // around the map and looks for stuff to press. Touch him and you die.
952 //
953 //-------------------------------------------------------------------
954
aiMaintBotInit(AIEntity * e)955 void aiMaintBotInit(AIEntity *e) {
956 // value1 field determines whether the "MMM!" sound plays
957 // 1 means NO
958 e->int1 = e->value1;
959 e->aiAction = aiMaintBotAction;
960 e->value1 = 0;
961 g_hdb->_ai->findPath(e);
962 }
963
aiMaintBotInit2(AIEntity * e)964 void aiMaintBotInit2(AIEntity *e) {
965 e->draw = g_hdb->_ai->getStandFrameDir(e);
966 }
967
aiMaintBotAction(AIEntity * e)968 void aiMaintBotAction(AIEntity *e) {
969 static const AIState useState[5] = {STATE_NONE, STATE_USEUP, STATE_USEDOWN, STATE_USELEFT, STATE_USERIGHT};
970 static const AIState standState[5] = {STATE_NONE, STATE_STANDUP, STATE_STANDDOWN, STATE_STANDLEFT, STATE_STANDRIGHT};
971 static const int xvAhead[5] = {9, 0, 0,-1, 1};
972 static const int yvAhead[5] = {9,-1, 1, 0, 0};
973 static const int whistles[3] = {SND_MBOT_WHISTLE1, SND_MBOT_WHISTLE2, SND_MBOT_WHISTLE3};
974 static const AIDir lookRight[5] = {DIR_NONE, DIR_RIGHT, DIR_LEFT, DIR_UP, DIR_DOWN};
975 static const AIDir lookLeft[5] = {DIR_NONE, DIR_LEFT, DIR_RIGHT, DIR_DOWN, DIR_UP};
976 static const AIDir dirList[5] = {DIR_NONE, DIR_UP, DIR_DOWN, DIR_LEFT, DIR_RIGHT};
977
978 // Waiting at an arrow (or hit by player)?
979 if (e->sequence) {
980 e->sequence--;
981 g_hdb->_ai->animEntFrames(e);
982
983 // Use Something here
984 if (!e->value2)
985 switch (e->sequence) {
986 case 50:
987 if (e->onScreen && !e->int1 && !g_hdb->isDemo()) {
988 if (g_hdb->_rnd->getRandomNumber(1))
989 g_hdb->_sound->playSound(SND_MBOT_HMMM2);
990 else
991 g_hdb->_sound->playSound(SND_MBOT_HMMM);
992 }
993 break;
994 // Need to USE the object
995 case 30: {
996 e->state = useState[e->dir];
997 int nx = e->tileX + xvAhead[e->dir];
998 int ny = e->tileY + yvAhead[e->dir];
999 AIEntity *it = g_hdb->_ai->findEntity(nx, ny);
1000 if (it) {
1001 if (e->onScreen)
1002 e->value1 = 1;
1003 g_hdb->useEntity(it);
1004 break;
1005 }
1006 // Did the MaintBot use an Action Tile?
1007 if (g_hdb->_ai->checkActionList(e, nx, ny, true)) {
1008 if (e->onScreen)
1009 e->value1 = 1;
1010 break;
1011 }
1012 // Did the MaintBot use an AutoAction Tile?
1013 if (g_hdb->_ai->checkAutoList(e, nx, ny)) {
1014 if (e->onScreen)
1015 e->value1 = 1;
1016 break;
1017 }
1018 // Did the MaintBot use a LUA Tile?
1019 if (g_hdb->_ai->checkLuaList(e, nx, ny)) {
1020 if (e->onScreen)
1021 e->value1 = 1;
1022 break;
1023 }
1024 break;
1025 }
1026 // Play a sound if we used something
1027 case 25:
1028 e->value1 = 0;
1029 break;
1030 // Change to Standing frames
1031 case 20:
1032 e->state = standState[e->dir];
1033 break;
1034 // All done - find a new path
1035 case 0:
1036 e->dir = e->dir2;
1037 g_hdb->_ai->findPath(e);
1038 g_hdb->_ai->animateEntity(e);
1039 break;
1040 default:
1041 break;
1042 }
1043 // Deciding where to go at 4-way
1044 else {
1045 switch (e->sequence) {
1046 // HMM
1047 case 50:
1048 if (e->onScreen && !e->int1 && !g_hdb->isDemo())
1049 g_hdb->_sound->playSound(SND_MBOT_HMMM);
1050 break;
1051 // Look Right
1052 case 40:
1053 e->dir = lookRight[e->dir2];
1054 e->state = standState[e->dir];
1055 break;
1056 // Look Left
1057 case 30:
1058 e->dir = lookLeft[e->dir];
1059 e->state = standState[e->dir];
1060 break;
1061 // HMM2
1062 case 25:
1063 if (e->onScreen && !e->int1 && !g_hdb->isDemo())
1064 g_hdb->_sound->playSound(SND_MBOT_HMMM2);
1065 break;
1066 // Decide direction and GO
1067 case 0:
1068 {
1069 int dir = (g_hdb->_rnd->getRandomNumber(3)) + 1;
1070 e->dir = dirList[dir];
1071 g_hdb->_ai->findPath(e);
1072 if (e->onScreen && !g_hdb->isDemo())
1073 g_hdb->_sound->playSound(whistles[g_hdb->_rnd->getRandomNumber(2)]);
1074 }
1075 break;
1076 default:
1077 break;
1078 }
1079 }
1080 return;
1081 }
1082
1083 // Moving already, keep going
1084 if (e->goalX) {
1085 g_hdb->_ai->animateEntity(e);
1086 if (hitPlayer(e->x, e->y)) {
1087 g_hdb->_ai->killPlayer(DEATH_GRABBED);
1088 if (!g_hdb->isDemo())
1089 g_hdb->_sound->playSound(SND_MBOT_DEATH);
1090 }
1091 } else {
1092 // Check if there's an arrow UNDER the bot, and if its RED
1093 // If so, turn in that direction and use something
1094 ArrowPath *ar = g_hdb->_ai->findArrowPath(e->tileX, e->tileY);
1095 if (ar) {
1096 // STOP Arrow
1097 // GO Arrow
1098 // 4-way Arrow
1099 if (!ar->type) {
1100 e->dir2 = e->dir; // dir2 holds the last direction we were travelling in
1101 e->dir = ar->dir;
1102 e->sequence = 64; // sequence is the timer of events
1103 e->state = standState[e->dir];
1104 e->value2 = 0;
1105 return;
1106 } else if (ar->type == 1) {
1107 g_hdb->_ai->findPath(e);
1108 if (!g_hdb->isDemo())
1109 g_hdb->_sound->playSound(whistles[g_hdb->_rnd->getRandomNumber(2)]);
1110 } else {
1111 e->sequence = 64;
1112 e->dir2 = e->dir;
1113 e->value2 = 1;
1114 return;
1115 }
1116 }
1117 g_hdb->_ai->animateEntity(e);
1118 }
1119 }
1120
1121 //-------------------------------------------------------------------
1122 //
1123 // FOURFIRER : This bot turns and fires in the direction it's facing,
1124 // but only if the player is visible
1125 //
1126 //-------------------------------------------------------------------
1127
aiFourFirerInit(AIEntity * e)1128 void aiFourFirerInit(AIEntity *e) {
1129 e->value1 = 0;
1130 e->aiAction = aiFourFirerAction;
1131 }
1132
aiFourFirerInit2(AIEntity * e)1133 void aiFourFirerInit2(AIEntity *e) {
1134 e->draw = g_hdb->_ai->getStandFrameDir(e);
1135 }
1136
aiFourFirerAction(AIEntity * e)1137 void aiFourFirerAction(AIEntity *e) {
1138 static const AIState state[5] = {STATE_NONE, STATE_STANDUP, STATE_STANDDOWN, STATE_STANDLEFT, STATE_STANDRIGHT};
1139 static const AIDir turn[5] = {DIR_NONE, DIR_RIGHT, DIR_LEFT, DIR_UP, DIR_DOWN};
1140
1141 AIEntity *p = g_hdb->_ai->getPlayer();
1142 // Time to turn right?
1143 if (!e->value1) {
1144 e->dir = turn[e->dir];
1145 e->state = state[e->dir];
1146 e->value1 = 16;
1147 if (e->onScreen)
1148 g_hdb->_sound->playSound(SND_FOURFIRE_TURN);
1149 }
1150 e->value1--;
1151
1152 // Waiting before firing again?
1153 if (e->sequence) {
1154 e->sequence--;
1155 return;
1156 }
1157
1158 g_hdb->_ai->animEntFrames(e);
1159
1160 // Can we see the player on the same level?
1161 if ((e->level != p->level) || g_hdb->_ai->playerDead() || !e->onScreen)
1162 return;
1163
1164 // Check player direction
1165 bool shoot = false;
1166 int xv = 0;
1167 int yv = 0;
1168
1169 switch (e->dir) {
1170 case DIR_UP:
1171 if (p->x == e->x && p->y < e->y) {
1172 shoot = true;
1173 yv = -1; }
1174 break;
1175 case DIR_DOWN:
1176 if (p->x == e->x && p->y > e->y) {
1177 shoot = true;
1178 yv = 1;
1179 }
1180 break;
1181 case DIR_LEFT:
1182 if (p->y == e->y && p->x < e->x) {
1183 shoot = true;
1184 xv = -1;
1185 }
1186 break;
1187 case DIR_RIGHT:
1188 if (p->y == e->y && p->x > e->x) {
1189 shoot = true;
1190 xv = 1;
1191 }
1192 break;
1193 case DIR_NONE:
1194 default:
1195 break;
1196 }
1197
1198 // Shoot if needed
1199 // Make sure not shooting into solid tile
1200 // Make sure if shooting at entity it is the player
1201 int result;
1202 AIEntity *hit = g_hdb->_ai->legalMoveOverWater(e->tileX + xv, e->tileY + yv, e->level, &result);
1203 if (hit && hit->type == AI_GUY)
1204 hit = nullptr;
1205
1206 if (shoot && !hit && result) {
1207 AIEntity *fire = g_hdb->_ai->spawn(AI_OMNIBOT_MISSILE, e->dir, e->tileX + xv, e->tileY + yv, nullptr, nullptr, nullptr, DIR_NONE, e->level, 0, 0, 1);
1208 if (g_hdb->_map->onScreen(e->tileX, e->tileY))
1209 g_hdb->_sound->playSound(SND_FOUR_FIRE);
1210 fire->xVel = xv * kPlayerMoveSpeed * 2;
1211 fire->yVel = yv * kPlayerMoveSpeed * 2;
1212 if (!g_hdb->getActionMode()) {
1213 fire->xVel >>= 1;
1214 fire->yVel >>= 1;
1215 }
1216 e->sequence = 16;
1217 if (hitPlayer(fire->tileX*kTileWidth, fire->tileY*kTileHeight))
1218 g_hdb->_ai->killPlayer(DEATH_FRIED);
1219 }
1220 }
1221
1222 //-------------------------------------------------------------------
1223 //
1224 // DEADEYE : Crazy attack dog with Chompie(tm) sounds! Will sit in one spot
1225 // looking around, then run in a random direction and distance. If, while
1226 // scanning, Deadeye sees the player, he goes nuts and attacks!
1227 //
1228 //-------------------------------------------------------------------
1229
aiDeadEyeInit(AIEntity * e)1230 void aiDeadEyeInit(AIEntity *e) {
1231 e->sequence = 64;
1232 e->blinkFrames = e->goalX = 0;
1233 if (e->value1 == 1)
1234 e->aiAction = aiDeadEyeWalkInPlace;
1235 else
1236 e->aiAction = aiDeadEyeAction;
1237 }
1238
aiDeadEyeInit2(AIEntity * e)1239 void aiDeadEyeInit2(AIEntity *e) {
1240 e->draw = g_hdb->_ai->getStandFrameDir(e);
1241 }
1242
aiDeadEyeWalkInPlace(AIEntity * e)1243 void aiDeadEyeWalkInPlace(AIEntity *e) {
1244 static const AIState state[5] = {STATE_NONE, STATE_MOVEUP, STATE_MOVEDOWN, STATE_MOVELEFT, STATE_MOVERIGHT};
1245
1246 e->sequence--;
1247
1248 switch (e->sequence) {
1249 case 50:
1250 case 40:
1251 case 30:
1252 case 20:
1253 case 10:
1254 {
1255 int rnd = g_hdb->_rnd->getRandomNumber(3) + 1;
1256 e->dir = (AIDir)rnd;
1257 e->state = state[rnd];
1258 if (e->onScreen) {
1259 if (e->sequence == 50)
1260 g_hdb->_sound->playSound(SND_DEADEYE_AMB01);
1261 else if (e->sequence == 10)
1262 g_hdb->_sound->playSound(SND_DEADEYE_AMB02);
1263 }
1264 }
1265 break;
1266 case 0:
1267 e->sequence = 64;
1268 break;
1269 default:
1270 break;
1271 }
1272 g_hdb->_ai->animEntFrames(e);
1273 }
1274
aiDeadEyeAction(AIEntity * e)1275 void aiDeadEyeAction(AIEntity *e) {
1276 static const AIState state[5] = {STATE_NONE, STATE_MOVEUP, STATE_MOVEDOWN, STATE_MOVELEFT, STATE_MOVERIGHT};
1277 static const int xvAhead[5] = {9, 0, 0, -1, 1};
1278 static const int yvAhead[5] = {9, -1, 1, 0, 0};
1279
1280 if (e->sequence) {
1281 e->sequence--;
1282
1283 if (e->blinkFrames) // Between attacks timer
1284 e->blinkFrames--;
1285
1286 // Is player visible to us?
1287 AIEntity *p = g_hdb->_ai->getPlayer();
1288 if (e->onScreen && p->level == e->level && !e->blinkFrames) {
1289 bool nuts = false;
1290 switch (e->dir) {
1291 case DIR_UP:
1292 if (p->tileX == e->tileX && p->tileY < e->tileY)
1293 nuts = true;
1294 break;
1295 case DIR_DOWN:
1296 if (p->tileX == e->tileX && p->tileY > e->tileY)
1297 nuts = true;
1298 break;
1299 case DIR_LEFT:
1300 if (p->tileY == e->tileY && p->tileX < e->tileX)
1301 nuts = true;
1302 break;
1303 case DIR_RIGHT:
1304 if (p->tileY == e->tileY && p->tileX > e->tileX)
1305 nuts = true;
1306 break;
1307 case DIR_NONE:
1308 default:
1309 break;
1310 }
1311
1312 // Did we see the player (and we're done moving)?
1313 if (nuts && e->aiAction != aiDeadEyeWalkInPlace) {
1314 e->sequence = 0;
1315 e->blinkFrames = 20;
1316
1317 int xv = xvAhead[e->dir];
1318 int yv = yvAhead[e->dir];
1319 int newX = e->tileX + xv;
1320 int newY = e->tileY + yv;
1321
1322 bool okToMove = false;
1323 bool done = false;
1324 do {
1325 int result;
1326 AIEntity *hit = g_hdb->_ai->legalMove(newX, newY, e->level, &result);
1327 if (hit && hit->type == AI_GUY)
1328 hit = nullptr;
1329 if (result && !hit) {
1330 okToMove = true;
1331 newX += xv;
1332 newY += yv;
1333 if (newX == p->tileX && newY == p->tileY)
1334 done = true;
1335 } else {
1336 newX -= xv;
1337 newY -= yv;
1338 done = true;
1339 }
1340 } while (!done);
1341
1342 // If we can move in the direction of the player, set our goal at him
1343 if (okToMove) {
1344 e->moveSpeed = kPlayerMoveSpeed << 1;
1345 g_hdb->_ai->setEntityGoal(e, newX, newY);
1346 (p->tileX & 1) ? g_hdb->_sound->playSound(SND_DEADEYE_ATTACK01) : g_hdb->_sound->playSound(SND_DEADEYE_ATTACK02);
1347 }
1348 g_hdb->_ai->animateEntity(e);
1349 return;
1350 }
1351 }
1352
1353 switch (e->sequence) {
1354 // Look around
1355 case 50:
1356 case 40:
1357 case 30:
1358 case 20:
1359 case 10:
1360 {
1361 int dir = g_hdb->_rnd->getRandomNumber(3) + 1;
1362 e->dir = (AIDir)dir;
1363 e->state = state[dir];
1364
1365 if (e->onScreen) {
1366 if (e->sequence == 50)
1367 g_hdb->_sound->playSound(SND_DEADEYE_AMB01);
1368 else if (e->sequence == 10)
1369 g_hdb->_sound->playSound(SND_DEADEYE_AMB02);
1370 }
1371 }
1372 break;
1373 case 0:
1374 {
1375 // Pick a random direction and random number of tiles in that direction
1376 int dir = g_hdb->_rnd->getRandomNumber(3) + 1;
1377 int walk = g_hdb->_rnd->getRandomNumber(4) + 1;
1378
1379 e->dir = (AIDir)dir;
1380 e->state = state[dir];
1381
1382 int xv = xvAhead[dir] * walk;
1383 if (e->tileX + xv < 1)
1384 xv = 1 - e->tileX;
1385 if (e->tileX + xv > g_hdb->_map->_width)
1386 xv = g_hdb->_map->_width - e->tileX - 1;
1387
1388 int yv = yvAhead[dir] * walk;
1389 if (e->tileY + yv < 1)
1390 yv = 1 - e->tileY;
1391 if (e->tileY + yv > g_hdb->_map->_height)
1392 yv = g_hdb->_map->_height - e->tileY - 1;
1393
1394 e->value1 = xvAhead[dir];
1395 e->value2 = yvAhead[dir];
1396 e->moveSpeed = kPlayerMoveSpeed;
1397 int result;
1398 AIEntity *hit = g_hdb->_ai->legalMove(e->tileX + xvAhead[e->dir], e->tileY + yvAhead[e->dir], e->level, &result);
1399 if (hit && hit->type == AI_GUY)
1400 hit = nullptr;
1401
1402 if (!hit && result)
1403 g_hdb->_ai->setEntityGoal(e, e->tileX + xv, e->tileY + yv);
1404 }
1405 break;
1406 default:
1407 break;
1408 }
1409 g_hdb->_ai->animEntFrames(e);
1410 return;
1411 }
1412
1413 // In the process of moving around
1414 if (e->goalX) {
1415 // Hit the player
1416 if (hitPlayer(e->x, e->y)) {
1417 g_hdb->_ai->killPlayer(DEATH_GRABBED);
1418 return;
1419 }
1420 // Did we run into a wall, entity, water, slime etc?
1421 // If so, Pick new direction
1422 if (onEvenTile(e->x, e->y)) {
1423 int result;
1424 AIEntity *hit = g_hdb->_ai->legalMove(e->tileX + e->value1, e->tileY + e->value2, e->level, &result);
1425 if (hit && hit->type == AI_GUY)
1426 hit = nullptr;
1427 if (!result || hit) {
1428 g_hdb->_ai->stopEntity(e);
1429 e->state = STATE_MOVEDOWN;
1430 e->sequence = 64;
1431 return;
1432 }
1433 }
1434 g_hdb->_ai->animateEntity(e);
1435 } else
1436 // If not, start looking around
1437 e->sequence = 64;
1438 }
1439
1440 //-------------------------------------------------------------------
1441 //
1442 // LASER
1443 //
1444 //-------------------------------------------------------------------
1445
aiLaserInit(AIEntity * e)1446 void aiLaserInit(AIEntity *e) {
1447 e->aiDraw = aiLaserDraw;
1448 // start & end of laser beam
1449 e->value1 = e->value2 = 0;
1450 }
1451
aiLaserInit2(AIEntity * e)1452 void aiLaserInit2(AIEntity *e) {
1453 e->draw = g_hdb->_ai->getStandFrameDir(e);
1454 if (!g_hdb->_ai->_gfxLaserbeamUD[0]) {
1455 char name[64];
1456 for (int i = 0; i < 4; i++) {
1457 sprintf(name, FORCEFIELD_UD"0%d", i + 1);
1458 g_hdb->_ai->_gfxLaserbeamUD[i] = g_hdb->_gfx->loadTile(name);
1459 sprintf(name, FORCESPLASH_TOP"0%d", i + 1);
1460 g_hdb->_ai->_gfxLaserbeamUDTop[i] = g_hdb->_gfx->loadTile(name);
1461 sprintf(name, FORCESPLASH_BTM"0%d", i + 1);
1462 g_hdb->_ai->_gfxLaserbeamUDBottom[i] = g_hdb->_gfx->loadTile(name);
1463 sprintf(name, FORCEFIELD_LR"0%d", i + 1);
1464 g_hdb->_ai->_gfxLaserbeamLR[i] = g_hdb->_gfx->loadTile(name);
1465 sprintf(name, FORCESPLASH_LEFT"0%d", i + 1);
1466 g_hdb->_ai->_gfxLaserbeamLRLeft[i] = g_hdb->_gfx->loadTile(name);
1467 sprintf(name, FORCESPLASH_RIGHT"0%d", i + 1);
1468 g_hdb->_ai->_gfxLaserbeamLRRight[i] = g_hdb->_gfx->loadTile(name);
1469 }
1470 }
1471 }
1472
aiLaserAction(AIEntity * e)1473 void aiLaserAction(AIEntity *e) {
1474 static const int xva[] = {9, 0, 0,-1, 1};
1475 static const int yva[] = {9,-1, 1, 0, 0};
1476
1477 AIEntity *hit = e;
1478 int moveOK = 0;
1479 int moveCount = 0;
1480
1481 do {
1482 int nx = hit->tileX;
1483 int ny = hit->tileY;
1484
1485 if (hit->type != AI_DIVERTER) {
1486 hit->int1 = xva[hit->dir];
1487 hit->int2 = yva[hit->dir];
1488
1489 if (hit->dir == DIR_UP || hit->dir == DIR_DOWN)
1490 hit->value1 = ny;
1491 else
1492 hit->value1 = nx;
1493 } else {
1494 // diverter is on y-plane?
1495 if (hit->tileX == e->tileX) {
1496 hit->value1 = nx;
1497 hit->int2 = 0;
1498 switch (hit->dir2) {
1499 case DIR_UP:
1500 hit->int1 = 1;
1501 break;
1502 case DIR_DOWN:
1503 hit->int1 = -1;
1504 break;
1505 case DIR_LEFT:
1506 hit->int1 = -1;
1507 break;
1508 case DIR_RIGHT:
1509 hit->int1 = 1;
1510 break;
1511 case DIR_NONE:
1512 default:
1513 break;
1514 }
1515 } else {
1516 // diverter is on x-plane
1517 hit->value1 = ny;
1518 hit->int1 = 0;
1519 switch (hit->dir2) {
1520 case DIR_UP:
1521 hit->int2 = 1;
1522 break;
1523 case DIR_DOWN:
1524 hit->int2 = 1;
1525 break;
1526 case DIR_LEFT:
1527 hit->int2 = -1;
1528 break;
1529 case DIR_RIGHT:
1530 hit->int2 = -1;
1531 break;
1532 case DIR_NONE:
1533 default:
1534 break;
1535 }
1536 }
1537 }
1538 e = hit;
1539
1540 //
1541 // scan for all legal moves
1542 //
1543 do {
1544 nx += e->int1;
1545 ny += e->int2;
1546 hit = g_hdb->_ai->legalMoveOverWater(nx, ny, e->level, &moveOK);
1547 g_hdb->_map->setLaserBeam(nx, ny, 1);
1548 if (hit && hit->type != AI_LASERBEAM) {
1549 //
1550 // hit player = death
1551 //
1552 if (hit == g_hdb->_ai->getPlayer() && onEvenTile(hit->x, hit->y) && !g_hdb->_ai->playerDead())
1553 g_hdb->_ai->killPlayer(DEATH_FRIED);
1554 else if (hit->type == AI_BOOMBARREL && hit->state != STATE_EXPLODING && onEvenTile(hit->x, hit->y)) {
1555 // hit BOOM BARREL = explodes
1556 aiBarrelExplode(hit);
1557 aiBarrelBlowup(hit, nx, ny);
1558 } else if (hit->type == AI_LIGHTBARREL || hit->type == AI_HEAVYBARREL || hit->type == AI_CRATE) {
1559 // hit LIGHT/HEAVY BARREL = blocking
1560 moveOK = 0;
1561 } else if (hit->type == AI_DIVERTER) {
1562 // hit a diverter?
1563 moveOK = 0;
1564 } else if (onEvenTile(hit->x, hit->y) && hit != g_hdb->_ai->getPlayer()) {
1565 switch (hit->type) {
1566 // cannot kill Vortexians!
1567 case AI_VORTEXIAN:
1568 continue;
1569
1570 case AI_BOOMBARREL:
1571 if (hit->state == STATE_EXPLODING)
1572 continue;
1573 break;
1574 case AI_LASER:
1575 g_hdb->_ai->_laserRescan = true;
1576 break;
1577 case ITEM_KEYCARD_WHITE:
1578 case ITEM_KEYCARD_BLUE:
1579 case ITEM_KEYCARD_RED:
1580 case ITEM_KEYCARD_GREEN:
1581 case ITEM_KEYCARD_PURPLE:
1582 case ITEM_KEYCARD_BLACK:
1583 case ITEM_CABKEY:
1584 g_hdb->_window->centerTextOut("CARD DESTROYED!", 306, 5 * 60);
1585 g_hdb->_sound->playSound(SND_QUEST_FAILED);
1586 break;
1587 default:
1588 break;
1589 }
1590 g_hdb->_ai->removeEntity(hit);
1591 g_hdb->_ai->addAnimateTarget(nx * kTileWidth,
1592 ny * kTileHeight, 0, 3, ANIM_NORMAL, false, false, GROUP_EXPLOSION_BOOM_SIT);
1593 g_hdb->_sound->playSound(SND_BARREL_EXPLODE);
1594 }
1595 }
1596 } while (moveOK);
1597
1598 if (e->int2) {
1599 e->value2 = ny;
1600 // check for hitting the BACK of a Diverter. It stops the laser.
1601 if (hit && hit->type == AI_DIVERTER) {
1602 if (e->int2 < 0 && hit->state != STATE_DIVERTER_BL && hit->state != STATE_DIVERTER_BR)
1603 hit = nullptr;
1604 else if (e->int2 > 0 && hit->state != STATE_DIVERTER_TL && hit->state != STATE_DIVERTER_TR)
1605 hit = nullptr;
1606 }
1607 } else {
1608 e->value2 = nx;
1609 // check for hitting the BACK of a Diverter. It stops the laser.
1610 if (hit && hit->type == AI_DIVERTER) {
1611 if (e->int1 < 0 && hit->state != STATE_DIVERTER_BR && hit->state != STATE_DIVERTER_TR)
1612 hit = nullptr;
1613 else if (e->int1 > 0 && hit->state != STATE_DIVERTER_TL && hit->state != STATE_DIVERTER_BL)
1614 hit = nullptr;
1615 }
1616 }
1617
1618 moveCount++;
1619
1620 // It is possible to set a configuration which leads to a closed loop.
1621 // Thus, we're breaking it here
1622 if (moveCount > 1000)
1623 hit = nullptr;
1624 } while (hit && hit->type == AI_DIVERTER);
1625 }
1626
aiLaserDraw(AIEntity * e,int mx,int my)1627 void aiLaserDraw(AIEntity *e, int mx, int my) {
1628 int i;
1629 int frame = e->movedownFrames & 3;
1630 int onScreen = 0;
1631
1632 switch (e->dir) {
1633 case DIR_UP:
1634 {
1635 for (i = e->value1 - 1; i > e->value2; i--)
1636 onScreen += g_hdb->_ai->_gfxLaserbeamUD[frame]->drawMasked(e->x - mx, i * kTileWidth - my);
1637 onScreen += g_hdb->_ai->_gfxLaserbeamUDBottom[frame & 3]->drawMasked(e->x - mx, i * kTileWidth - my);
1638 if (onScreen) {
1639 g_hdb->_sound->playSoundEx(SND_LASER_LOOP, kLaserChannel, true);
1640 g_hdb->_ai->_laserOnScreen = true;
1641 }
1642 }
1643 break;
1644 case DIR_DOWN:
1645 {
1646 for (i = e->value1 + 1; i < e->value2; i++)
1647 onScreen += g_hdb->_ai->_gfxLaserbeamUD[frame]->drawMasked(e->x - mx, i * kTileWidth - my);
1648 onScreen += g_hdb->_ai->_gfxLaserbeamUDBottom[frame]->drawMasked(e->x - mx, i * kTileWidth - my);
1649 if (onScreen) {
1650 g_hdb->_sound->playSoundEx(SND_LASER_LOOP, kLaserChannel, true);
1651 g_hdb->_ai->_laserOnScreen = true;
1652 }
1653 }
1654 break;
1655 case DIR_LEFT:
1656 {
1657 for (i = e->value1 - 1; i > e->value2; i--)
1658 onScreen += g_hdb->_ai->_gfxLaserbeamLR[frame]->drawMasked(i * kTileWidth - mx, e->y - my);
1659 onScreen += g_hdb->_ai->_gfxLaserbeamLRRight[frame]->drawMasked(i * kTileWidth - mx, e->y - my);
1660 if (onScreen) {
1661 g_hdb->_sound->playSoundEx(SND_LASER_LOOP, kLaserChannel, true);
1662 g_hdb->_ai->_laserOnScreen = true;
1663 }
1664 }
1665 break;
1666 case DIR_RIGHT:
1667 {
1668 for (i = e->value1 + 1; i < e->value2; i++)
1669 onScreen += g_hdb->_ai->_gfxLaserbeamLR[frame]->drawMasked(i * kTileWidth - mx, e->y - my);
1670 onScreen += g_hdb->_ai->_gfxLaserbeamLRLeft[frame]->drawMasked(i * kTileWidth - mx, e->y - my);
1671 if (onScreen) {
1672 g_hdb->_sound->playSoundEx(SND_LASER_LOOP, kLaserChannel, true);
1673 g_hdb->_ai->_laserOnScreen = true;
1674 }
1675 }
1676 break;
1677 case DIR_NONE:
1678 default:
1679 break;
1680 }
1681 e->movedownFrames++;
1682 }
1683
1684 //-------------------------------------------------------------------
1685 //
1686 // DIVERTER
1687 //
1688 //-------------------------------------------------------------------
1689
aiDiverterInit(AIEntity * e)1690 void aiDiverterInit(AIEntity *e) {
1691 e->aiDraw = aiDiverterDraw;
1692 e->aiAction = aiDiverterAction;
1693 e->moveSpeed = kPlayerMoveSpeed << 1;
1694 e->dir2 = e->dir;
1695 }
1696
aiDiverterInit2(AIEntity * e)1697 void aiDiverterInit2(AIEntity *e) {
1698 e->movedownGfx[0] = e->standdownGfx[0];
1699 e->moveupGfx[0] = e->standupGfx[0];
1700 e->moveleftGfx[0] = e->standleftGfx[0];
1701 e->moverightGfx[0] = e->standrightGfx[0];
1702 e->movedownFrames =
1703 e->moveupFrames =
1704 e->moveleftFrames =
1705 e->moverightFrames = 1;
1706
1707 // this is to handle loadgames...
1708 AIDir d = e->dir2;
1709 if (e->dir2 == DIR_NONE)
1710 d = e->dir;
1711 switch (d) {
1712 case DIR_DOWN:
1713 e->state = STATE_DIVERTER_BL;
1714 e->draw = e->standdownGfx[0];
1715 break;
1716 case DIR_UP:
1717 e->state = STATE_DIVERTER_BR;
1718 e->draw = e->standupGfx[0];
1719 break;
1720 case DIR_LEFT:
1721 e->state = STATE_DIVERTER_TL;
1722 e->draw = e->standleftGfx[0];
1723 break;
1724 case DIR_RIGHT:
1725 e->state = STATE_DIVERTER_TR;
1726 e->draw = e->standrightGfx[0];
1727 break;
1728 case DIR_NONE:
1729 default:
1730 break;
1731 }
1732
1733 g_hdb->_ai->_laserRescan = true;
1734 }
1735
aiDiverterAction(AIEntity * e)1736 void aiDiverterAction(AIEntity *e) {
1737 if (e->goalX) {
1738 g_hdb->_ai->animateEntity(e);
1739 g_hdb->_ai->_laserRescan = true;
1740
1741 // have to reset the state because we might have been moved...
1742 switch (e->dir2) {
1743 case DIR_DOWN:
1744 e->state = STATE_DIVERTER_BL;
1745 e->draw = e->standdownGfx[0];
1746 break;
1747 case DIR_UP:
1748 e->state = STATE_DIVERTER_BR;
1749 e->draw = e->standupGfx[0];
1750 break;
1751 case DIR_LEFT:
1752 e->state = STATE_DIVERTER_TL;
1753 e->draw = e->standleftGfx[0];
1754 break;
1755 case DIR_RIGHT:
1756 e->state = STATE_DIVERTER_TR;
1757 e->draw = e->standrightGfx[0];
1758 break;
1759 case DIR_NONE:
1760 default:
1761 break;
1762 }
1763 }
1764 }
1765
aiDiverterDraw(AIEntity * e,int mx,int my)1766 void aiDiverterDraw(AIEntity *e, int mx, int my) {
1767 if (!e->value1 && !e->value2)
1768 return;
1769
1770 int frame = e->movedownFrames & 3;
1771 int onScreen = 0;
1772 int i;
1773 switch (e->dir2) {
1774 case DIR_UP:
1775 if (e->tileY == e->value1 && e->int2) { // going down or right?
1776 for (i = e->value1 + 1; i < e->value2; i++)
1777 onScreen += g_hdb->_ai->_gfxLaserbeamUD[frame]->drawMasked(e->x - mx, i * kTileHeight - my);
1778 onScreen += g_hdb->_ai->_gfxLaserbeamUDTop[frame]->drawMasked(e->x - mx, i * kTileHeight - my);
1779 if (onScreen) {
1780 g_hdb->_sound->playSoundEx(SND_LASER_LOOP, kLaserChannel, true);
1781 g_hdb->_ai->_laserOnScreen = true;
1782 }
1783 } else {
1784 for (i = e->value1 + 1; i < e->value2; i++)
1785 onScreen += g_hdb->_ai->_gfxLaserbeamLR[frame]->drawMasked(i * kTileWidth - mx, e->y - my);
1786 onScreen += g_hdb->_ai->_gfxLaserbeamLRLeft[frame]->drawMasked(i * kTileWidth - mx, e->y - my);
1787 if (onScreen) {
1788 g_hdb->_sound->playSoundEx(SND_LASER_LOOP, kLaserChannel, true);
1789 g_hdb->_ai->_laserOnScreen = true;
1790 }
1791 }
1792 break;
1793 case DIR_DOWN:
1794 if (e->tileY == e->value1 && e->int2) { // going down or left?
1795 for (i = e->value1 + 1; i < e->value2; i++)
1796 onScreen += g_hdb->_ai->_gfxLaserbeamUD[frame]->drawMasked(e->x - mx, i * kTileHeight - my);
1797 onScreen += g_hdb->_ai->_gfxLaserbeamUDTop[frame]->drawMasked(e->x - mx, i * kTileHeight - my);
1798 if (onScreen) {
1799 g_hdb->_sound->playSoundEx(SND_LASER_LOOP, kLaserChannel, true);
1800 g_hdb->_ai->_laserOnScreen = true;
1801 }
1802 } else {
1803 for (i = e->value1 - 1; i > e->value2; i--)
1804 onScreen += g_hdb->_ai->_gfxLaserbeamLR[frame]->drawMasked(i * kTileWidth - mx, e->y - my);
1805 onScreen += g_hdb->_ai->_gfxLaserbeamLRRight[frame]->drawMasked(i * kTileWidth - mx, e->y - my);
1806 if (onScreen) {
1807 g_hdb->_sound->playSoundEx(SND_LASER_LOOP, kLaserChannel, true);
1808 g_hdb->_ai->_laserOnScreen = true;
1809 }
1810 }
1811 break;
1812 case DIR_LEFT:
1813 if (e->tileY == e->value1 && e->int2) { // going up or left?
1814 for (i = e->value1 - 1; i > e->value2; i--)
1815 onScreen += g_hdb->_ai->_gfxLaserbeamUD[frame]->drawMasked(e->x - mx, i * kTileHeight - my);
1816 onScreen += g_hdb->_ai->_gfxLaserbeamUDBottom[frame]->drawMasked(e->x - mx, i * kTileHeight - my);
1817 if (onScreen) {
1818 g_hdb->_sound->playSoundEx(SND_LASER_LOOP, kLaserChannel, true);
1819 g_hdb->_ai->_laserOnScreen = true;
1820 }
1821 } else {
1822 for (i = e->value1 - 1; i > e->value2; i--)
1823 onScreen += g_hdb->_ai->_gfxLaserbeamLR[frame]->drawMasked(i * kTileWidth - mx, e->y - my);
1824 onScreen += g_hdb->_ai->_gfxLaserbeamLRRight[frame]->drawMasked(i * kTileWidth - mx, e->y - my);
1825 if (onScreen) {
1826 g_hdb->_sound->playSoundEx(SND_LASER_LOOP, kLaserChannel, true);
1827 g_hdb->_ai->_laserOnScreen = true;
1828 }
1829 }
1830 break;
1831 case DIR_RIGHT:
1832 if (e->tileY == e->value1 && e->int2) { // going up or right?
1833 for (i = e->value1 - 1; i > e->value2; i--)
1834 onScreen += g_hdb->_ai->_gfxLaserbeamUD[frame]->drawMasked(e->x - mx, i * kTileHeight - my);
1835 onScreen += g_hdb->_ai->_gfxLaserbeamUDBottom[frame]->drawMasked(e->x - mx, i * kTileHeight - my);
1836 if (onScreen) {
1837 g_hdb->_sound->playSoundEx(SND_LASER_LOOP, kLaserChannel, true);
1838 g_hdb->_ai->_laserOnScreen = true;
1839 }
1840 } else {
1841 for (i = e->value1 + 1; i < e->value2; i++)
1842 onScreen += g_hdb->_ai->_gfxLaserbeamLR[frame]->drawMasked(i * kTileWidth - mx, e->y - my);
1843 onScreen += g_hdb->_ai->_gfxLaserbeamLRLeft[frame]->drawMasked(i * kTileWidth - mx, e->y - my);
1844 if (onScreen) {
1845 g_hdb->_sound->playSoundEx(SND_LASER_LOOP, kLaserChannel, true);
1846 g_hdb->_ai->_laserOnScreen = true;
1847 }
1848 }
1849 break;
1850 case DIR_NONE:
1851 default:
1852 break;
1853 }
1854 e->movedownFrames++;
1855 }
1856
1857 //-------------------------------------------------------------------
1858 //
1859 // MEERKAT : nutty little groundhog dude that will bite Guy if he's on
1860 // his mound. That blows 1-5 gems outta Guy!
1861 //
1862 //-------------------------------------------------------------------
1863
aiMeerkatInit(AIEntity * e)1864 void aiMeerkatInit(AIEntity *e) {
1865 e->state = STATE_NONE;
1866 e->sequence = 0;
1867 if (e->value1 == 1) {
1868 e->aiAction = aiMeerkatLookAround;
1869 e->state = STATE_MEER_LOOK;
1870 } else
1871 e->aiAction = aiMeerkatAction;
1872 }
1873
aiMeerkatInit2(AIEntity * e)1874 void aiMeerkatInit2(AIEntity *e) {
1875 // hidden at the start!
1876 e->draw = nullptr;
1877
1878 // make the looking around cycle better...
1879 e->movedownGfx[3] = e->movedownGfx[1];
1880 e->movedownFrames++;
1881 }
1882
aiMeerkatDraw(AIEntity * e,int mx,int my)1883 void aiMeerkatDraw(AIEntity *e, int mx, int my) {
1884 char word[3];
1885 g_hdb->_window->getGemGfx()->drawMasked(e->value1 - mx, e->value2 - my, 255 - e->blinkFrames * 16);
1886 g_hdb->_gfx->setCursor(e->value1 + 12 - mx, e->value2 - 8 - my);
1887 word[2] = 0;
1888 if (!e->special1Frames) {
1889 word[0] = '0';
1890 word[1] = 0;
1891 } else {
1892 word[0] = '-';
1893 word[1] = '0' + e->special1Frames;
1894 }
1895 g_hdb->_gfx->drawText(word);
1896 }
1897
aiMeerkatAction(AIEntity * e)1898 void aiMeerkatAction(AIEntity *e) {
1899 static const int gem_xv[] = { 0, 0,-2,-3,-4,-4,-3,-2,-2,-2,-2,-1,-1, 100};
1900 static const int gem_yv[] = {-6,-5,-4,-3,-2,-1, 0, 0, 1, 2, 3, 4, 5, 100};
1901
1902 AIEntity *p = g_hdb->_ai->getPlayer();
1903
1904 switch (e->sequence) {
1905 // waiting to see the player
1906 case 0:
1907 if ((abs(p->tileX - e->tileX) <= 1 && p->tileY == e->tileY) ||
1908 (abs(p->tileY - e->tileY) <= 1 && p->tileX == e->tileX)) {
1909 e->sequence = 1;
1910 e->state = STATE_MEER_MOVE;
1911 e->animFrame = 0;
1912 e->animCycle = 1;
1913 e->animDelay = e->animCycle;
1914 if (e->onScreen)
1915 g_hdb->_sound->playSound(SND_MEERKAT_WARNING);
1916 }
1917 break;
1918 // time to show the mound for a sec...
1919 case 1:
1920 g_hdb->_ai->animateEntity(e);
1921 if (!e->animFrame && e->animDelay == e->animCycle)
1922 e->sequence++;
1923 if (e->sequence == 2) {
1924 e->state = STATE_MEER_APPEAR;
1925 e->animFrame = 0;
1926 e->animDelay = e->animCycle;
1927 if (e->onScreen)
1928 g_hdb->_sound->playSound(SND_MEERKAT_APPEAR);
1929 }
1930 break;
1931
1932 // pop outta the dirt!
1933 case 2:
1934 g_hdb->_ai->animateEntity(e);
1935 // done w/sequence?
1936 if (!e->animFrame && e->animDelay == e->animCycle) {
1937 e->sequence++;
1938 e->state = STATE_MEER_LOOK;
1939 e->animFrame = 0;
1940 e->animCycle = 2;
1941 e->animDelay = e->animCycle;
1942 }
1943 break;
1944
1945 // looking around...... time to bite the player!?
1946 case 3:
1947 case 4:
1948 g_hdb->_ai->animateEntity(e);
1949 if (!e->animFrame && e->animDelay == e->animCycle) {
1950 e->sequence++;
1951 if (e->sequence == 5)
1952 e->state = STATE_MEER_DISAPPEAR;
1953 }
1954 if (g_hdb->_ai->checkPlayerTileCollision(e->tileX, e->tileY)) {
1955 e->state = STATE_MEER_BITE;
1956 e->sequence = 6;
1957 e->animFrame = 0;
1958 e->animDelay = e->animCycle;
1959 if (e->onScreen)
1960 g_hdb->_sound->playSound(SND_MEERKAT_BITE);
1961 }
1962 break;
1963
1964 // going back underground!
1965 case 5:
1966 g_hdb->_ai->animateEntity(e);
1967 if (!e->animFrame && e->animDelay == e->animCycle) {
1968 e->sequence = 0;
1969 e->state = STATE_NONE;
1970 e->draw = nullptr;
1971 }
1972 break;
1973
1974 // biting the player right now!
1975 case 6:
1976 g_hdb->_ai->animateEntity(e);
1977 // hit the player?
1978 if (g_hdb->_ai->checkPlayerTileCollision(e->tileX, e->tileY)) {
1979 g_hdb->_ai->stopEntity(p);
1980 g_hdb->_ai->setPlayerLock(true);
1981 e->sequence = 7;
1982 p->moveSpeed <<= 1;
1983 if (g_hdb->_ai->findEntity(p->tileX, p->tileY + 1))
1984 g_hdb->_ai->setEntityGoal(p, p->tileX, p->tileY - 1);
1985 else
1986 g_hdb->_ai->setEntityGoal(p, p->tileX, p->tileY + 1);
1987 e->aiDraw = aiMeerkatDraw;
1988 e->value1 = e->x;
1989 e->value2 = e->y;
1990 e->blinkFrames = 0; // index into movement table...
1991
1992 // figure # of gems to take
1993 e->special1Frames = g_hdb->_rnd->getRandomNumber(4) + 1;
1994 int amt = g_hdb->_ai->getGemAmount();
1995 if (amt - e->special1Frames < 0)
1996 e->special1Frames = amt;
1997
1998 // if we're in Puzzle Mode and there's no gems left, give one back
1999 if (!g_hdb->getActionMode() && !(e->special1Frames - amt) && e->special1Frames)
2000 e->special1Frames--;
2001
2002 amt -= e->special1Frames;
2003 g_hdb->_ai->setGemAmount(amt);
2004 }
2005 // go back to looking?
2006 if (!e->animFrame && e->animDelay == e->animCycle) {
2007 e->sequence = 3;
2008 e->state = STATE_MEER_LOOK;
2009 e->animFrame = 0;
2010 e->animDelay = e->animCycle;
2011 }
2012 break;
2013
2014 // waiting for player to blast backward
2015 case 7:
2016 g_hdb->_ai->animateEntity(e);
2017 if (!p->goalX) {
2018 p->moveSpeed = kPlayerMoveSpeed;
2019 g_hdb->_ai->setPlayerLock(false);
2020 e->sequence = 5;
2021 e->state = STATE_MEER_DISAPPEAR;
2022 e->animFrame = 0;
2023 e->animDelay = e->animCycle;
2024 }
2025 break;
2026
2027 default:
2028 break;
2029 }
2030
2031 // blasting a gem outta Guy?
2032 if (e->value1) {
2033 if (gem_xv[e->blinkFrames] == 100) {
2034 e->value1 = 0;
2035 e->aiDraw = nullptr;
2036 return;
2037 }
2038 e->value1 += gem_xv[e->blinkFrames];
2039 e->value2 += gem_yv[e->blinkFrames];
2040 e->blinkFrames++;
2041 }
2042 }
2043
aiMeerkatLookAround(AIEntity * e)2044 void aiMeerkatLookAround(AIEntity *e) {
2045 g_hdb->_ai->animEntFrames(e);
2046 }
2047
2048 //-------------------------------------------------------------------
2049 //
2050 // FATFROG : Just sits in place and blasts out his tongue if you're
2051 // within range.
2052 //
2053 //-------------------------------------------------------------------
2054
aiFatFrogInit(AIEntity * e)2055 void aiFatFrogInit(AIEntity *e) {
2056 e->aiAction = aiFatFrogAction;
2057 }
2058
aiFatFrogInit2(AIEntity * e)2059 void aiFatFrogInit2(AIEntity *e) {
2060 e->draw = g_hdb->_ai->getStandFrameDir(e);
2061 // load tongue tiles
2062 switch (e->dir) {
2063 case DIR_DOWN:
2064 if (!g_hdb->_ai->_tileFroglickMiddleUD) {
2065 g_hdb->_ai->_tileFroglickMiddleUD = g_hdb->_gfx->loadTile(TILE_FFTONGUE_UD_MIDDLE);
2066 g_hdb->_ai->_tileFroglickWiggleUD[0] = g_hdb->_gfx->loadTile(TILE_FFTONGUE_UD_WIGGLE_L);
2067 g_hdb->_ai->_tileFroglickWiggleUD[1] = g_hdb->_gfx->loadTile(TILE_FFTONGUE_UD_WIGGLE_M);
2068 g_hdb->_ai->_tileFroglickWiggleUD[2] = g_hdb->_gfx->loadTile(TILE_FFTONGUE_UD_WIGGLE_R);
2069 }
2070 e->state = STATE_STANDDOWN;
2071 break;
2072 case DIR_LEFT:
2073 if (!g_hdb->_ai->_tileFroglickMiddleLR)
2074 g_hdb->_ai->_tileFroglickMiddleLR = g_hdb->_gfx->loadTile(TILE_FFTONGUE_LR_MIDDLE);
2075
2076 if (!g_hdb->_ai->_tileFroglickWiggleLeft[0]) {
2077 g_hdb->_ai->_tileFroglickWiggleLeft[0] = g_hdb->_gfx->loadTile(TILE_FFTONGUE_L_WIGGLE_U);
2078 g_hdb->_ai->_tileFroglickWiggleLeft[1] = g_hdb->_gfx->loadTile(TILE_FFTONGUE_L_WIGGLE_M);
2079 g_hdb->_ai->_tileFroglickWiggleLeft[2] = g_hdb->_gfx->loadTile(TILE_FFTONGUE_L_WIGGLE_D);
2080 }
2081 e->state = STATE_STANDLEFT;
2082 break;
2083 case DIR_RIGHT:
2084 if (!g_hdb->_ai->_tileFroglickMiddleLR)
2085 g_hdb->_ai->_tileFroglickMiddleLR = g_hdb->_gfx->loadTile(TILE_FFTONGUE_LR_MIDDLE);
2086
2087 if (!g_hdb->_ai->_tileFroglickWiggleRight[0]) {
2088 g_hdb->_ai->_tileFroglickWiggleRight[0] = g_hdb->_gfx->loadTile(TILE_FFTONGUE_R_WIGGLE_U);
2089 g_hdb->_ai->_tileFroglickWiggleRight[1] = g_hdb->_gfx->loadTile(TILE_FFTONGUE_R_WIGGLE_M);
2090 g_hdb->_ai->_tileFroglickWiggleRight[2] = g_hdb->_gfx->loadTile(TILE_FFTONGUE_R_WIGGLE_D);
2091 }
2092 e->state = STATE_STANDRIGHT;
2093 break;
2094 default:
2095 break;
2096 }
2097 }
2098
aiFatFrogAction(AIEntity * e)2099 void aiFatFrogAction(AIEntity *e) {
2100 AIEntity *p = g_hdb->_ai->getPlayer();
2101
2102 switch (e->state) {
2103 //-------------------------------------------------------------------
2104 // WAITING TO ATTACK
2105 //-------------------------------------------------------------------
2106 case STATE_STANDDOWN:
2107 e->draw = e->standdownGfx[e->animFrame];
2108 // is player within 2 tiles below fatfrog?
2109 if (p->tileX == e->tileX && p->tileY - e->tileY < 3 && p->tileY > e->tileY) {
2110 e->state = STATE_LICKDOWN;
2111 e->animDelay = e->animCycle << 2;
2112 e->animFrame = 0;
2113 }
2114 // cycle animation frames
2115 if (e->animDelay-- > 0)
2116 return;
2117 e->animDelay = e->animCycle << 2;
2118 e->animFrame++;
2119 if (e->animFrame == e->standdownFrames)
2120 e->animFrame = 0;
2121 if (!g_hdb->_rnd->getRandomNumber(29) && e->onScreen)
2122 g_hdb->_sound->playSound(SND_FROG_RIBBIT1);
2123 break;
2124
2125 case STATE_STANDLEFT:
2126 e->draw = e->standleftGfx[e->animFrame];
2127 // is player within 2 tiles below fatfrog?
2128 if (p->tileY == e->tileY && e->tileX - p->tileX < 3 && p->tileX < e->tileX) {
2129 e->state = STATE_LICKLEFT;
2130 e->animDelay = e->animCycle << 2;
2131 e->animFrame = 0;
2132 }
2133 // cycle animation frames
2134 if (e->animDelay-- > 0)
2135 return;
2136 e->animDelay = e->animCycle << 2;
2137 e->animFrame++;
2138 if (e->animFrame == e->standleftFrames)
2139 e->animFrame = 0;
2140 if (!g_hdb->_rnd->getRandomNumber(29) && e->onScreen)
2141 g_hdb->_sound->playSound(SND_FROG_RIBBIT2);
2142 break;
2143
2144 case STATE_STANDRIGHT:
2145 e->draw = e->standrightGfx[e->animFrame];
2146 // is player within 2 tiles below fatfrog?
2147 if (p->tileY == e->tileY && p->tileX - e->tileX < 3 && p->tileX > e->tileX) {
2148 e->state = STATE_LICKRIGHT;
2149 e->animDelay = e->animCycle << 2;
2150 e->animFrame = 0;
2151 }
2152 // cycle animation frames
2153 if (e->animDelay-- > 0)
2154 return;
2155 e->animDelay = e->animCycle << 2;
2156 e->animFrame++;
2157 if (e->animFrame == e->standrightFrames)
2158 e->animFrame = 0;
2159 if (!g_hdb->_rnd->getRandomNumber(29) && e->onScreen)
2160 g_hdb->_sound->playSound(SND_FROG_RIBBIT2);
2161 break;
2162
2163 //-------------------------------------------------------------------
2164 // LICK ATTACK
2165 //-------------------------------------------------------------------
2166 case STATE_LICKDOWN:
2167 e->draw = e->movedownGfx[e->animFrame];
2168 // ready to start licking?
2169 if (e->animFrame == e->movedownFrames - 1 && !e->value1) {
2170 e->value1 = 1;
2171 e->aiDraw = aiFatFrogTongueDraw;
2172 g_hdb->_sound->playSound(SND_FROG_LICK);
2173 } else if (e->animFrame == e->movedownFrames - 1 && e->value1) {
2174 // animate licking
2175
2176 // check player death
2177 if (((p->tileX == e->tileX && p->tileY == e->tileY + 1) || // in front of frog + 1 tile!?
2178 (e->value1 > 3 && p->tileX == e->tileX && p->tileY < e->tileY + 3)) && // in front of frog + 2 tiles!?
2179 g_hdb->_ai->playerDead() == false)
2180 g_hdb->_ai->killPlayer(DEATH_NORMAL);
2181
2182 e->value1++;
2183 if (e->value1 == 14) {
2184 e->animFrame = e->value1 = 0;
2185 e->aiDraw = nullptr;
2186 e->state = STATE_STANDDOWN;
2187 }
2188 } else {
2189 // animate pre-licking
2190 // cycle animation frames
2191 if (e->animDelay-- > 0)
2192 return;
2193 e->animDelay = e->animCycle;
2194 e->animFrame++;
2195 }
2196 break;
2197
2198 case STATE_LICKLEFT:
2199 e->draw = e->moveleftGfx[e->animFrame];
2200 // ready to start licking?
2201 if (e->animFrame == e->moveleftFrames - 1 && !e->value1) {
2202 e->value1 = 1;
2203 e->aiDraw = aiFatFrogTongueDraw;
2204 g_hdb->_sound->playSound(SND_FROG_LICK);
2205 } else if (e->animFrame == e->moveleftFrames - 1 && e->value1) {
2206 // animate licking
2207
2208 // check player death
2209 if (((p->tileY == e->tileY && p->tileX == e->tileX - 1) || // in front of frog + 1 tile!?
2210 (e->value1 > 3 && p->tileY == e->tileY && p->tileX > e->tileX - 3)) && // in front of frog + 2 tiles!?
2211 g_hdb->_ai->playerDead() == false)
2212 g_hdb->_ai->killPlayer(DEATH_NORMAL);
2213
2214 e->value1++;
2215 if (e->value1 == 14) {
2216 e->animFrame = e->value1 = 0;
2217 e->aiDraw = nullptr;
2218 e->state = STATE_STANDLEFT;
2219 }
2220 } else {
2221 // animate pre-licking
2222 // cycle animation frames
2223 if (e->animDelay-- > 0)
2224 return;
2225 e->animDelay = e->animCycle;
2226 e->animFrame++;
2227 }
2228 break;
2229
2230 case STATE_LICKRIGHT:
2231 e->draw = e->moverightGfx[e->animFrame];
2232 // ready to start licking?
2233 if (e->animFrame == e->moverightFrames - 1 && !e->value1) {
2234 e->value1 = 1;
2235 e->aiDraw = aiFatFrogTongueDraw;
2236 g_hdb->_sound->playSound(SND_FROG_LICK);
2237 } else if (e->animFrame == e->moverightFrames - 1 && e->value1) {
2238 // animate licking
2239 // check player death
2240 //
2241 if (((p->tileY == e->tileY && p->tileX == e->tileX + 1) || // in front of frog + 1 tile!?
2242 (e->value1 > 3 && p->tileY == e->tileY && p->tileX < e->tileX + 3)) && // in front of frog + 2 tiles!?
2243 g_hdb->_ai->playerDead() == false)
2244 g_hdb->_ai->killPlayer(DEATH_NORMAL);
2245
2246 e->value1++;
2247 if (e->value1 == 14) {
2248 e->animFrame = e->value1 = 0;
2249 e->aiDraw = nullptr;
2250 e->state = STATE_STANDRIGHT;
2251 }
2252 } else {
2253 // animate pre-licking
2254 // cycle animation frames
2255 if (e->animDelay-- > 0)
2256 return;
2257 e->animDelay = e->animCycle;
2258 e->animFrame++;
2259 }
2260 break;
2261 default:
2262 // no op
2263 break;
2264 }
2265 }
2266
aiFatFrogTongueDraw(AIEntity * e,int mx,int my)2267 void aiFatFrogTongueDraw(AIEntity *e, int mx, int my) {
2268 int nx, ny;
2269
2270 switch (e->state) {
2271 case STATE_LICKDOWN:
2272 switch (e->value1) {
2273 case 1:
2274 case 2:
2275 case 3:
2276 case 13:
2277 case 14:
2278 nx = e->x;
2279 ny = e->y + 32;
2280 g_hdb->_ai->_tileFroglickWiggleUD[1]->drawMasked(nx - mx, ny - my);
2281 break;
2282 case 4:
2283 case 7:
2284 case 10:
2285 nx = e->x;
2286 ny = e->y + 32;
2287 g_hdb->_ai->_tileFroglickMiddleUD->drawMasked(nx - mx, ny - my);
2288 g_hdb->_ai->_tileFroglickWiggleUD[0]->drawMasked(nx - mx, ny + 32 - my);
2289 break;
2290 case 5:
2291 case 8:
2292 case 11:
2293 nx = e->x;
2294 ny = e->y + 32;
2295 g_hdb->_ai->_tileFroglickMiddleUD->drawMasked(nx - mx, ny - my);
2296 g_hdb->_ai->_tileFroglickWiggleUD[1]->drawMasked(nx - mx, ny + 32 - my);
2297 break;
2298 case 6:
2299 case 9:
2300 case 12:
2301 nx = e->x;
2302 ny = e->y + 32;
2303 g_hdb->_ai->_tileFroglickMiddleUD->drawMasked(nx - mx, ny - my);
2304 g_hdb->_ai->_tileFroglickWiggleUD[2]->drawMasked(nx - mx, ny + 32 - my);
2305 break;
2306 default:
2307 break;
2308 }
2309 break;
2310
2311 case STATE_LICKLEFT:
2312 switch (e->value1) {
2313 case 1:
2314 case 2:
2315 case 3:
2316 case 13:
2317 case 14:
2318 nx = e->x - 32;
2319 ny = e->y;
2320 g_hdb->_ai->_tileFroglickWiggleLeft[1]->drawMasked(nx - mx, ny - my);
2321 break;
2322 case 4:
2323 case 7:
2324 case 10:
2325 nx = e->x - 32;
2326 ny = e->y;
2327 g_hdb->_ai->_tileFroglickMiddleLR->drawMasked(nx - mx, ny - my);
2328 g_hdb->_ai->_tileFroglickWiggleLeft[0]->drawMasked(nx - 32 - mx, ny - my);
2329 break;
2330 case 5:
2331 case 8:
2332 case 11:
2333 nx = e->x - 32;
2334 ny = e->y;
2335 g_hdb->_ai->_tileFroglickMiddleLR->drawMasked(nx - mx, ny - my);
2336 g_hdb->_ai->_tileFroglickWiggleLeft[1]->drawMasked(nx - 32 - mx, ny - my);
2337 break;
2338 case 6:
2339 case 9:
2340 case 12:
2341 nx = e->x - 32;
2342 ny = e->y;
2343 g_hdb->_ai->_tileFroglickMiddleLR->drawMasked(nx - mx, ny - my);
2344 g_hdb->_ai->_tileFroglickWiggleLeft[2]->drawMasked(nx - 32 - mx, ny - my);
2345 break;
2346 default:
2347 break;
2348 }
2349 break;
2350
2351 case STATE_LICKRIGHT:
2352 switch (e->value1) {
2353 case 1:
2354 case 2:
2355 case 3:
2356 case 13:
2357 case 14:
2358 nx = e->x + 32;
2359 ny = e->y;
2360 g_hdb->_ai->_tileFroglickWiggleRight[1]->drawMasked(nx - 32 - mx, ny - my);
2361 break;
2362 case 4:
2363 case 7:
2364 case 10:
2365 nx = e->x + 32;
2366 ny = e->y;
2367 g_hdb->_ai->_tileFroglickMiddleLR->drawMasked(nx - mx, ny - my);
2368 g_hdb->_ai->_tileFroglickWiggleRight[0]->drawMasked(nx + 32 - mx, ny - my);
2369 break;
2370 case 5:
2371 case 8:
2372 case 11:
2373 nx = e->x + 32;
2374 ny = e->y;
2375 g_hdb->_ai->_tileFroglickMiddleLR->drawMasked(nx - mx, ny - my);
2376 g_hdb->_ai->_tileFroglickWiggleRight[1]->drawMasked(nx + 32 - mx, ny - my);
2377 break;
2378 case 6:
2379 case 9:
2380 case 12:
2381 nx = e->x + 32;
2382 ny = e->y;
2383 g_hdb->_ai->_tileFroglickMiddleLR->drawMasked(nx - mx, ny - my);
2384 g_hdb->_ai->_tileFroglickWiggleRight[2]->drawMasked(nx + 32 - mx, ny - my);
2385 break;
2386 default:
2387 break;
2388 }
2389 break;
2390 default:
2391 break;
2392 }
2393 }
2394
2395 //-------------------------------------------------------------------
2396 //
2397 // GOODFAIRY
2398 //
2399 //-------------------------------------------------------------------
2400
aiGoodFairyInit(AIEntity * e)2401 void aiGoodFairyInit(AIEntity *e) {
2402 e->aiAction = aiGoodFairyAction;
2403 e->sequence = 20;
2404 e->blinkFrames = e->goalX = 0;
2405 }
2406
aiGoodFairyInit2(AIEntity * e)2407 void aiGoodFairyInit2(AIEntity *e) {
2408 e->draw = g_hdb->_ai->getStandFrameDir(e);
2409 }
2410
aiGoodFairyAction(AIEntity * e)2411 void aiGoodFairyAction(AIEntity *e) {
2412 static const AIState state[5] = {STATE_NONE, STATE_MOVEUP, STATE_MOVEDOWN, STATE_MOVELEFT, STATE_MOVERIGHT};
2413 static const int xvAhead[5] = {9, 0, 0,-1, 1};
2414 static const int yvAhead[5] = {9,-1, 1, 0, 0};
2415
2416 int result;
2417
2418 if (e->sequence) {
2419 e->sequence--;
2420
2421 // look around...
2422 switch (e->sequence) {
2423 case 19:
2424 e->state = STATE_MOVEDOWN;
2425 break;
2426 case 0:
2427 {
2428 // Create a GEM?
2429 if (g_hdb->_rnd->getRandomNumber(99) > 98) {
2430 // spawn a gem in a random direction
2431 int d = g_hdb->_rnd->getRandomNumber(3) + 1;
2432 int xv = xvAhead[d];
2433 int yv = yvAhead[d];
2434
2435 e->sequence = 30;
2436 e->state = STATE_MOVEDOWN;
2437 // is something there already?
2438 if ((g_hdb->_ai->findEntityType(AI_CRATE, e->tileX + xv, e->tileY + yv) != nullptr) ||
2439 (g_hdb->_ai->findEntityType(AI_LIGHTBARREL, e->tileX + xv, e->tileY + yv) != nullptr))
2440 return;
2441 int spawnOK;
2442 AIEntity *hit = g_hdb->_ai->legalMove(e->tileX + xv, e->tileY + yv, e->level, &spawnOK);
2443 uint32 bg_flags = g_hdb->_map->getMapBGTileFlags(e->tileX + xv, e->tileY + yv);
2444 if (hit || !spawnOK || (bg_flags & kFlagSpecial))
2445 return;
2446
2447 g_hdb->_ai->spawn(ITEM_GEM_WHITE, e->dir, e->tileX + xv, e->tileY + yv, nullptr, nullptr, nullptr, DIR_NONE, e->level, 0, 0, 1);
2448 g_hdb->_ai->addAnimateTarget(e->x + xv * kTileWidth, e->y + yv * kTileHeight, 0, 3, ANIM_NORMAL, false, false, GEM_FLASH);
2449 if (e->onScreen) {
2450 g_hdb->_sound->playSound(SND_GET_GEM);
2451 g_hdb->_sound->playSound(SND_GOOD_FAERIE_SPELL);
2452 }
2453 return;
2454 }
2455
2456 int tries = 4;
2457 do {
2458 // pick a random direction, then a random # of tiles in that direction
2459 AIDir d = (AIDir)(g_hdb->_rnd->getRandomNumber(3) + 1);
2460 int walk = g_hdb->_rnd->getRandomNumber(4) + 1;
2461 AIEntity *p = g_hdb->_ai->getPlayer();
2462
2463 // if player is within 3 tiles, move closer
2464 if (abs(p->tileX - e->tileX) < 3 && abs(p->tileY - e->tileY) < 3) {
2465 if (abs(p->tileX - e->tileX) > abs(p->tileY - e->tileY)) {
2466 testx:
2467 if (p->tileX != e->tileX) {
2468 if (p->tileX < e->tileX)
2469 d = DIR_LEFT;
2470 else
2471 d = DIR_RIGHT;
2472 } else if (p->tileY != e->tileY)
2473 goto testy;
2474 } else {
2475 testy:
2476 if (p->tileY != e->tileY) {
2477 if (p->tileY <= e->tileY)
2478 d = DIR_UP;
2479 else
2480 d = DIR_DOWN;
2481 } else if (p->tileX != e->tileX)
2482 goto testx;
2483 }
2484 }
2485
2486 // special case: if player is exactly 2 tiles away, move out of the way
2487 if (abs(p->tileX - e->tileX) == 2 && p->tileY == e->tileY) {
2488 int move_ok;
2489 d = DIR_UP;
2490 AIEntity *h = g_hdb->_ai->legalMoveOverWater(e->tileX, e->tileY - 1, e->level, &move_ok);
2491 if (h || !move_ok)
2492 d = DIR_DOWN;
2493 } else if (abs(p->tileY - e->tileY) == 2 && p->tileX == e->tileX) {
2494 int move_ok;
2495 d = DIR_LEFT;
2496 AIEntity *h = g_hdb->_ai->legalMoveOverWater(e->tileX - 1, e->tileY, e->level, &move_ok);
2497 if (h || !move_ok)
2498 d = DIR_RIGHT;
2499 }
2500
2501 e->dir = d;
2502 int tmpDir = (int)d;
2503 e->state = state[tmpDir];
2504 int xv = xvAhead[tmpDir] * walk;
2505 if (e->tileX + xv < 1)
2506 xv = -e->tileX + 1;
2507 if (e->tileX + xv > g_hdb->_map->_width)
2508 xv = g_hdb->_map->_width - e->tileX - 1;
2509
2510 int yv = yvAhead[d] * walk;
2511 if (e->tileY + yv < 1)
2512 yv = -e->tileY + 1;
2513 if (e->tileY + yv > g_hdb->_map->_height)
2514 yv = g_hdb->_map->_height - e->tileY - 1;
2515
2516 e->value1 = xvAhead[d];
2517 e->value2 = yvAhead[d];
2518 e->moveSpeed = kPlayerMoveSpeed;
2519
2520 // make sure we can move over water & white gems, but not fg_hdb->_ai->y blockers and solids
2521 AIEntity *hit = g_hdb->_ai->legalMoveOverWater(e->tileX + e->value1, e->tileY + e->value2, e->level, &result);
2522 if (hit && ((hit->type == ITEM_GEM_WHITE) || (hit->type == AI_GUY)))
2523 hit = nullptr;
2524 uint32 bg_flags = g_hdb->_map->getMapBGTileFlags(e->tileX + e->value1, e->tileY + e->value2);
2525 if (result && !hit && !(bg_flags & kFlagSpecial)) {
2526 g_hdb->_ai->setEntityGoal(e, e->tileX + xv, e->tileY + yv);
2527 if (e->onScreen && !g_hdb->_rnd->getRandomNumber(29))
2528 g_hdb->_sound->playSound(SND_GOOD_FAERIE_AMBIENT);
2529 g_hdb->_ai->animateEntity(e);
2530 return;
2531 }
2532 tries--; // don't lock the system if the fg_hdb->_ai->y is cornered
2533 } while (!result && tries);
2534
2535 // couldn't find a place to move so just sit here for a sec & try agg_hdb->_ai->
2536 e->dir = DIR_NONE;
2537 e->state = STATE_MOVEDOWN;
2538 e->sequence = 1;
2539 e->value1 = e->value2 = e->xVel = e->yVel = 0;
2540 }
2541 break;
2542 default:
2543 break;
2544 }
2545 g_hdb->_ai->animEntFrames(e);
2546 return;
2547 }
2548
2549 // in the process of moving around...
2550 if (e->goalX) {
2551 // did we run into a wall, entity, water, slime etc?
2552 // if so, pick a new direction!
2553 if (onEvenTile(e->x, e->y)) {
2554 // did we hit a Fg_hdb->_ai->YSTONE??? if so - teleport the thing at the other end to here!
2555 int index = g_hdb->_ai->checkFairystones(e->tileX, e->tileY);
2556 if (index >= 0) {
2557 int sx, sy;
2558 g_hdb->_ai->getFairystonesSrc(index, &sx, &sy);
2559 AIEntity *hit = g_hdb->_ai->findEntity(sx, sy);
2560 if (hit && (hit != g_hdb->_ai->getPlayer())) {
2561 hit->tileX = e->tileX;
2562 hit->tileY = e->tileY;
2563 hit->x = hit->tileX * kTileWidth;
2564 hit->y = hit->tileY * kTileHeight;
2565 hit->goalX = hit->goalY = 0;
2566 g_hdb->_ai->addAnimateTarget(e->x, e->y, 0, 7, ANIM_NORMAL, false, false, TELEPORT_FLASH);
2567 g_hdb->_ai->addAnimateTarget(sx * kTileWidth, sy * kTileHeight, 0, 7, ANIM_NORMAL, false, false, TELEPORT_FLASH);
2568 if (e->onScreen)
2569 g_hdb->_sound->playSound(SND_TELEPORT);
2570 if (hit->onScreen)
2571 g_hdb->_sound->playSound(SND_TELEPORT);
2572 }
2573 }
2574
2575 // see if we're about to move to a bad spot, which means:
2576 // (1) we're gonna hit a solid wall; ok to move over water/slime
2577 // (2) ok to move thru white gems
2578 // (3) cannot move thru SPECIAL flagged tiles (fg_hdb->_ai->y blockers)
2579 AIEntity *hit = g_hdb->_ai->legalMoveOverWater(e->tileX + e->value1, e->tileY + e->value2, e->level, &result);
2580 uint32 bg_flags = g_hdb->_map->getMapBGTileFlags(e->tileX + e->value1, e->tileY + e->value2);
2581 if (!result || (hit && hit->type != ITEM_GEM_WHITE && hit->type != AI_GUY) || (bg_flags & kFlagSpecial)) {
2582 g_hdb->_ai->stopEntity(e);
2583 e->value1 = e->value2 = 0;
2584 e->state = STATE_MOVEDOWN;
2585 e->sequence = 20;
2586 return;
2587 }
2588 }
2589 g_hdb->_ai->animateEntity(e);
2590 } else {
2591 // if not, start looking around!
2592 e->sequence = 20;
2593 }
2594 }
2595
2596 //-------------------------------------------------------------------
2597 //
2598 // BADFAIRY
2599 //
2600 //-------------------------------------------------------------------
2601
aiBadFairyInit(AIEntity * e)2602 void aiBadFairyInit(AIEntity *e) {
2603 e->aiAction = aiBadFairyAction;
2604 e->sequence = 20;
2605 e->blinkFrames = e->goalX = 0;
2606 }
2607
aiBadFairyInit2(AIEntity * e)2608 void aiBadFairyInit2(AIEntity *e) {
2609 e->draw = g_hdb->_ai->getStandFrameDir(e);
2610 }
2611
aiBadFairyAction(AIEntity * e)2612 void aiBadFairyAction(AIEntity *e) {
2613 static const AIState state[5] = {STATE_NONE, STATE_MOVEUP, STATE_MOVEDOWN, STATE_MOVELEFT, STATE_MOVERIGHT};
2614 static const AIDir opposite[5] = {DIR_NONE, DIR_DOWN, DIR_UP, DIR_RIGHT, DIR_LEFT};
2615 static const int xvAhead[5] = {9, 0, 0,-1, 1};
2616 static const int yvAhead[5] = {9,-1, 1, 0, 0};
2617
2618 if (e->sequence) {
2619 e->sequence--;
2620
2621 // look around...
2622 switch (e->sequence) {
2623 case 19:
2624 e->state = STATE_MOVEDOWN;
2625 break;
2626 case 0:
2627 {
2628 // Create a GATE PUDDLE?
2629 if (e->onScreen && (g_hdb->_rnd->getRandomNumber(99) > 90) && g_hdb->getActionMode() && (g_hdb->_ai->getGatePuddles() < kMaxGatePuddles)) {
2630 if (e->onScreen)
2631 g_hdb->_sound->playSound(SND_BADFAIRY_SPELL);
2632
2633 e->sequence = 30;
2634 e->state = STATE_MOVEUP;
2635 g_hdb->_ai->spawn(AI_GATEPUDDLE, opposite[e->dir], e->tileX, e->tileY, nullptr, nullptr, nullptr, DIR_NONE, e->level, 0, 0, 1);
2636 g_hdb->_ai->addAnimateTarget(e->x, e->y, 0, 7, ANIM_NORMAL, false, false, TELEPORT_FLASH);
2637 g_hdb->_ai->addGatePuddle(1);
2638 if (e->onScreen)
2639 g_hdb->_sound->playSound(SND_GATEPUDDLE_SPAWN);
2640 return;
2641 }
2642
2643 int tries = 4;
2644 int result;
2645 do {
2646 // pick a random direction, then a random # of tiles in that direction
2647 int d = g_hdb->_rnd->getRandomNumber(3) + 1;
2648 int walk = g_hdb->_rnd->getRandomNumber(4) + 1;
2649 AIEntity *p = g_hdb->_ai->getPlayer();
2650
2651 e->dir = (AIDir)d;
2652 e->state = state[d];
2653 int xv = xvAhead[d] * walk;
2654 if (e->tileX + xv < 1)
2655 xv = -e->tileX + 1;
2656 if (e->tileX + xv > g_hdb->_map->_width)
2657 xv = g_hdb->_map->_width - e->tileX - 1;
2658
2659 int yv = yvAhead[d] * walk;
2660 if (e->tileY + yv < 1)
2661 yv = -e->tileY + 1;
2662 if (e->tileY + yv > g_hdb->_map->_height)
2663 yv = g_hdb->_map->_height - e->tileY - 1;
2664
2665 e->value1 = xvAhead[d];
2666 e->value2 = yvAhead[d];
2667 e->moveSpeed = kPlayerMoveSpeed;
2668 if (!g_hdb->getActionMode())
2669 e->moveSpeed >>= 1;
2670
2671 AIEntity *hit = g_hdb->_ai->legalMoveOverWater(e->tileX + e->value1, e->tileY + e->value2, e->level, &result);
2672 uint32 bg_flags = g_hdb->_map->getMapBGTileFlags(e->tileX + e->value1, e->tileY + e->value2);
2673 if (hit == p && !g_hdb->_ai->playerDead()) {
2674 g_hdb->_ai->killPlayer(DEATH_FRIED);
2675 hit = nullptr;
2676 }
2677
2678 if (!hit && result && !(bg_flags & kFlagSpecial)) {
2679 g_hdb->_ai->setEntityGoal(e, e->tileX + xv, e->tileY + yv);
2680 g_hdb->_ai->animateEntity(e);
2681 if (e->onScreen && !g_hdb->_rnd->getRandomNumber(19))
2682 g_hdb->_sound->playSound(SND_BADFAIRY_AMBIENT);
2683 return;
2684 }
2685 tries--; // don't lock the system if the player gets the fairy cornered
2686 } while (!result && tries);
2687
2688 // couldn't find a place to move so just sit here for a sec & try again
2689 e->dir = DIR_NONE;
2690 e->state = STATE_MOVEDOWN;
2691 e->sequence = 1;
2692 e->value1 = e->value2 = e->xVel = e->yVel = 0;
2693 }
2694 break;
2695 default:
2696 break;
2697 }
2698 g_hdb->_ai->animEntFrames(e);
2699 return;
2700 }
2701
2702 // in the process of moving around...
2703 if (e->goalX) {
2704 // hit the player?
2705 if (hitPlayer(e->x, e->y)) {
2706 g_hdb->_ai->killPlayer(DEATH_FRIED);
2707 g_hdb->_sound->playSound(SND_MBOT_DEATH);
2708 return;
2709 }
2710
2711 // did we run into a wall, entity, water, slime etc?
2712 // if so, pick a new direction!
2713 if (onEvenTile(e->x, e->y)) {
2714 int result;
2715 AIEntity *hit = g_hdb->_ai->legalMoveOverWater(e->tileX + e->value1, e->tileY + e->value2, e->level, &result);
2716 uint32 bg_flags = g_hdb->_map->getMapBGTileFlags(e->tileX + e->value1, e->tileY + e->value2);
2717 if (!result || (hit && hit->type != AI_GUY) || (bg_flags & kFlagSpecial)) {
2718 g_hdb->_ai->stopEntity(e);
2719 e->state = STATE_MOVEDOWN;
2720 e->sequence = 20;
2721 return;
2722 }
2723 }
2724 g_hdb->_ai->animateEntity(e);
2725 } else {
2726 // if not, start looking around!
2727 e->sequence = 20;
2728 }
2729 }
2730
2731 //-------------------------------------------------------------------
2732 //
2733 // BADFAIRY's GATE PUDDLE!
2734 //
2735 //-------------------------------------------------------------------
2736
aiGatePuddleInit(AIEntity * e)2737 void aiGatePuddleInit(AIEntity *e) {
2738 e->aiAction = aiGatePuddleAction;
2739 e->value1 = 50;
2740 }
2741
aiGatePuddleInit2(AIEntity * e)2742 void aiGatePuddleInit2(AIEntity *e) {
2743 }
2744
aiGatePuddleAction(AIEntity * e)2745 void aiGatePuddleAction(AIEntity *e) {
2746 static const int xva[5] = {9, 0, 0,-1, 1};
2747 static const int yva[5] = {9,-1, 1, 0, 0};
2748
2749 AIEntity *p = g_hdb->_ai->getPlayer();
2750
2751 if (e->goalX) {
2752 g_hdb->_ai->animateEntity(e);
2753 if (hitPlayer(e->x, e->y)) {
2754 for (int i = 0; i < kMaxTeleporters; i++) {
2755 if (g_hdb->_ai->_teleporters[i].anim1 == 2) { // PANIC ZONE?
2756 p->tileX = g_hdb->_ai->_teleporters[i].x1;
2757 p->tileY = g_hdb->_ai->_teleporters[i].y1;
2758 p->x = p->tileX * kTileWidth;
2759 p->y = p->tileY * kTileHeight;
2760 p->xVel = p->yVel = 0;
2761 p->goalX = p->goalY = 0;
2762 p->animFrame = 0;
2763 p->drawXOff = p->drawYOff = 0;
2764 p->dir = g_hdb->_ai->_teleporters[i].dir1;
2765 p->level = g_hdb->_ai->_teleporters[i].level1;
2766 g_hdb->_ai->addAnimateTarget(p->x, p->y, 0, 7, ANIM_NORMAL, false, false, TELEPORT_FLASH);
2767 g_hdb->_sound->playSound(SND_TELEPORT);
2768 g_hdb->_ai->clearWaypoints();
2769 g_hdb->_window->startPanicZone();
2770 g_hdb->_map->centerMapXY(p->x + 16, p->y + 16);
2771 switch (p->dir) {
2772 case DIR_UP:
2773 g_hdb->_ai->setEntityGoal(p, p->tileX, p->tileY - 1);
2774 break;
2775 case DIR_DOWN:
2776 g_hdb->_ai->setEntityGoal(p, p->tileX, p->tileY + 1);
2777 break;
2778 case DIR_LEFT:
2779 g_hdb->_ai->setEntityGoal(p, p->tileX - 1, p->tileY);
2780 break;
2781 case DIR_RIGHT:
2782 g_hdb->_ai->setEntityGoal(p, p->tileX + 1, p->tileY);
2783 break;
2784 case DIR_NONE:
2785 default:
2786 break;
2787 }
2788 g_hdb->_ai->_playerEmerging = true;
2789 break;
2790 } else if (g_hdb->_ai->_teleporters[i].anim2 == 2) { // PANIC ZONE?
2791 p->tileX = g_hdb->_ai->_teleporters[i].x2;
2792 p->tileY = g_hdb->_ai->_teleporters[i].y2;
2793 p->x = p->tileX * kTileWidth;
2794 p->y = p->tileY * kTileHeight;
2795 p->xVel = p->yVel = 0;
2796 p->goalX = p->goalY = 0;
2797 p->animFrame = 0;
2798 p->drawXOff = p->drawYOff = 0;
2799 p->dir = g_hdb->_ai->_teleporters[i].dir2;
2800 p->level = g_hdb->_ai->_teleporters[i].level2;
2801 g_hdb->_ai->addAnimateTarget(p->x, p->y, 0, 7, ANIM_NORMAL, false, false, TELEPORT_FLASH);
2802 g_hdb->_sound->playSound(SND_TELEPORT);
2803 g_hdb->_ai->clearWaypoints();
2804 g_hdb->_window->startPanicZone();
2805 g_hdb->_map->centerMapXY(p->x + 16, p->y + 16);
2806 switch (p->dir) {
2807 case DIR_UP:
2808 g_hdb->_ai->setEntityGoal(p, p->tileX, p->tileY - 1);
2809 break;
2810 case DIR_DOWN:
2811 g_hdb->_ai->setEntityGoal(p, p->tileX, p->tileY + 1);
2812 break;
2813 case DIR_LEFT:
2814 g_hdb->_ai->setEntityGoal(p, p->tileX - 1, p->tileY);
2815 break;
2816 case DIR_RIGHT:
2817 g_hdb->_ai->setEntityGoal(p, p->tileX + 1, p->tileY);
2818 break;
2819 case DIR_NONE:
2820 default:
2821 break;
2822 }
2823 g_hdb->_ai->_playerEmerging = true;
2824 break;
2825 }
2826 }
2827 }
2828 } else {
2829 int rnd = g_hdb->_rnd->getRandomNumber(3) + 1;
2830 e->dir = (AIDir)rnd;
2831 int nx = e->tileX + xva[rnd];
2832 int ny = e->tileY + yva[rnd];
2833
2834 int move_ok;
2835 AIEntity *hit = g_hdb->_ai->legalMoveOverWater(nx, ny, e->level, &move_ok);
2836 if (hit == p)
2837 hit = nullptr;
2838
2839 if (!hit && move_ok) {
2840 uint32 bg_flags = g_hdb->_map->getMapBGTileFlags(nx, ny);
2841 // Gate Puddles can't go over METAL!!! It's in their genes...
2842 if (!(bg_flags & kFlagMetal)) {
2843 if (e->onScreen)
2844 g_hdb->_sound->playSound(SND_GATEPUDDLE_AMBIENT);
2845
2846 g_hdb->_ai->setEntityGoal(e, nx, ny);
2847 e->state = STATE_MOVEDOWN;
2848 g_hdb->_ai->animateEntity(e);
2849 }
2850 }
2851
2852 // can only move 50 spaces or collisions
2853 e->value1--;
2854 if (!e->value1) {
2855 g_hdb->_ai->addGatePuddle(-1);
2856 g_hdb->_ai->addAnimateTarget(e->x, e->y, 0, 7, ANIM_NORMAL, false, false, TELEPORT_FLASH);
2857 if (e->onScreen)
2858 g_hdb->_sound->playSound(SND_GATEPUDDLE_DISSIPATE);
2859 g_hdb->_ai->removeEntity(e);
2860 }
2861 }
2862 }
2863
2864 //-------------------------------------------------------------------
2865 //
2866 // ICEPUFF : Little icy dude peeks out of the ground and pops up and
2867 // throws a snowball at you if he sees you....then he blasts back
2868 // into the snow and hides for a while....
2869 //
2870 // Variables used specially:
2871 // value1, value2 : x,y of snowball
2872 // dir2 : direction of snowball. DIR_NONE = no snowball
2873 // sequence : timer for peeking
2874 //
2875 //-------------------------------------------------------------------
2876
aiIcePuffSnowballInit(AIEntity * e)2877 void aiIcePuffSnowballInit(AIEntity *e) {
2878 // which direction are we throwing in? Load the graphic if we need to
2879 switch (e->dir) {
2880 case DIR_DOWN:
2881 e->value1 = e->x + 12;
2882 e->value2 = e->y + 32;
2883 break;
2884 case DIR_LEFT:
2885 e->value1 = e->x - 4;
2886 e->value2 = e->y + 16;
2887 break;
2888 case DIR_RIGHT:
2889 e->value1 = e->x + 32;
2890 e->value2 = e->y + 16;
2891 break;
2892 default:
2893 break;
2894 }
2895 e->aiDraw = aiIcePuffSnowballDraw;
2896 }
2897
aiIcePuffSnowballAction(AIEntity * e)2898 void aiIcePuffSnowballAction(AIEntity *e) {
2899 // check for hit BEFORE moving so snowball is closer to object
2900 // NOTE: Need to do logic in this draw routine just in case the ICEPUFF gets stunned!
2901 int result;
2902 AIEntity *hit = g_hdb->_ai->legalMoveOverWater(e->value1 / kTileWidth, e->value2 / kTileHeight, e->level, &result);
2903 if (hit && hit->type == AI_GUY && !g_hdb->_ai->playerDead()) {
2904 g_hdb->_ai->killPlayer(DEATH_NORMAL);
2905 g_hdb->_ai->addAnimateTarget(hit->x, hit->y, 0, 3, ANIM_NORMAL, false, false, GROUP_WATER_SPLASH_SIT);
2906 result = 0; // fall thru...
2907 }
2908
2909 // hit something solid - kill the snowball
2910 if (!result) {
2911 e->dir2 = DIR_NONE;
2912 e->aiDraw = nullptr;
2913 return;
2914 }
2915
2916 int speed = kPlayerMoveSpeed;
2917 if (!g_hdb->getActionMode())
2918 speed >>= 1;
2919
2920 switch (e->dir2) {
2921 case DIR_DOWN:
2922 e->value2 += speed;
2923 break;
2924 case DIR_LEFT:
2925 e->value1 -= speed;
2926 break;
2927 case DIR_RIGHT:
2928 e->value1 += speed;
2929 break;
2930 default:
2931 break;
2932 }
2933 }
2934
aiIcePuffSnowballDraw(AIEntity * e,int mx,int my)2935 void aiIcePuffSnowballDraw(AIEntity *e, int mx, int my) {
2936 // did we throw a snowball? make it move!
2937 if (e->dir2 != DIR_NONE)
2938 aiIcePuffSnowballAction(e);
2939
2940 switch (e->dir2) {
2941 case DIR_DOWN:
2942 if (!g_hdb->_ai->_icepSnowballGfxDown)
2943 g_hdb->_ai->_icepSnowballGfxDown = g_hdb->_gfx->loadPic(ICEPUFF_SNOWBALL_DOWN);
2944 g_hdb->_ai->_icepSnowballGfxDown->drawMasked(e->value1 - mx, e->value2 - my);
2945 break;
2946 case DIR_LEFT:
2947 if (!g_hdb->_ai->_icepSnowballGfxLeft)
2948 g_hdb->_ai->_icepSnowballGfxLeft = g_hdb->_gfx->loadPic(ICEPUFF_SNOWBALL_LEFT);
2949 g_hdb->_ai->_icepSnowballGfxLeft->drawMasked(e->value1 - mx, e->value2 - my);
2950 break;
2951 case DIR_RIGHT:
2952 if (!g_hdb->_ai->_icepSnowballGfxRight)
2953 g_hdb->_ai->_icepSnowballGfxRight = g_hdb->_gfx->loadPic(ICEPUFF_SNOWBALL_RIGHT);
2954 g_hdb->_ai->_icepSnowballGfxRight->drawMasked(e->value1 - mx, e->value2 - my);
2955 break;
2956 default:
2957 break;
2958 }
2959 }
2960
aiIcePuffInit(AIEntity * e)2961 void aiIcePuffInit(AIEntity *e) {
2962 // PEEK - but no head up yet
2963 e->sequence = 30; // timed sequence for peeking
2964 e->state = STATE_ICEP_PEEK; // start in PEEK mode
2965 e->dir2 = DIR_NONE; // no snowball out
2966 e->aiAction = aiIcePuffAction;
2967 }
2968
aiIcePuffInit2(AIEntity * e)2969 void aiIcePuffInit2(AIEntity *e) {
2970 // empty frame
2971 e->draw = e->blinkGfx[3];
2972 }
2973
aiIcePuffAction(AIEntity * e)2974 void aiIcePuffAction(AIEntity *e) {
2975 AIEntity *p = g_hdb->_ai->getPlayer();
2976
2977 switch (e->state) {
2978 case STATE_ICEP_PEEK:
2979 e->sequence--;
2980 switch (e->sequence) {
2981 case 20: // underground
2982 e->draw = e->blinkGfx[0];
2983 break;
2984 case 16: // peek - looking
2985 e->draw = e->blinkGfx[1];
2986 break;
2987 case 12: // peek - blinking
2988 e->draw = e->blinkGfx[2];
2989 break;
2990 case 8: // peek - looking
2991 e->draw = e->blinkGfx[1];
2992 break;
2993 case 4: // peek - looking
2994 e->draw = e->blinkGfx[0];
2995 break;
2996 case 3:
2997 if (e->onScreen && !g_hdb->_rnd->getRandomNumber(5))
2998 g_hdb->_sound->playSound(SND_ICEPUFF_WARNING);
2999 break;
3000 case 0:
3001 // underground
3002 e->draw = e->blinkGfx[3];
3003 e->sequence = 30;
3004 break;
3005 default:
3006 break;
3007 }
3008
3009 // can we see the player? (and no snowball is out)
3010 if (e->sequence <= 20 && !g_hdb->_ai->playerDead() && e->onScreen) {
3011 if (p->tileX == e->tileX && p->tileY > e->tileY && e->dir2 == DIR_NONE) {
3012 e->dir = DIR_DOWN;
3013 e->state = STATE_ICEP_APPEAR;
3014 e->animFrame = 0;
3015 if (e->onScreen)
3016 g_hdb->_sound->playSound(SND_ICEPUFF_APPEAR);
3017 } else if (p->tileY == e->tileY && e->dir2 == DIR_NONE) {
3018 p->tileX < e->tileX ? e->dir = DIR_LEFT : e->dir = DIR_RIGHT;
3019 e->state = STATE_ICEP_APPEAR;
3020 e->animFrame = 0;
3021 if (e->onScreen)
3022 g_hdb->_sound->playSound(SND_ICEPUFF_APPEAR);
3023 }
3024 }
3025 break;
3026
3027 case STATE_ICEP_APPEAR:
3028 e->draw = e->standupGfx[e->animFrame];
3029
3030 // cycle animation frames
3031 if (e->animDelay-- > 0)
3032 return;
3033 e->animDelay = e->animCycle;
3034
3035 e->animFrame++;
3036 if (e->animFrame == e->standupFrames) {
3037 e->animFrame = 0;
3038 switch (e->dir) {
3039 case DIR_DOWN:
3040 e->state = STATE_ICEP_THROWDOWN;
3041 g_hdb->_sound->playSound(SND_ICEPUFF_THROW);
3042 break;
3043 case DIR_LEFT:
3044 e->state = STATE_ICEP_THROWLEFT;
3045 g_hdb->_sound->playSound(SND_ICEPUFF_THROW);
3046 break;
3047 case DIR_RIGHT:
3048 e->state = STATE_ICEP_THROWRIGHT;
3049 g_hdb->_sound->playSound(SND_ICEPUFF_THROW);
3050 break;
3051 default:
3052 break;
3053 }
3054 }
3055 break;
3056
3057 case STATE_ICEP_THROWDOWN:
3058 e->draw = e->standdownGfx[e->animFrame];
3059
3060 // cycle animation frames
3061 if (e->animDelay-- > 0)
3062 return;
3063 e->animDelay = e->animCycle;
3064
3065 e->animFrame++;
3066 if (e->animFrame == e->standdownFrames && e->state != STATE_ICEP_DISAPPEAR) {
3067 // dir2 = direction snowball is moving
3068 e->dir2 = e->dir;
3069 // throw it!
3070 aiIcePuffSnowballInit(e);
3071 e->animFrame = 0;
3072 e->state = STATE_ICEP_DISAPPEAR;
3073 } else if (e->animFrame == e->special1Frames) {
3074 e->state = STATE_ICEP_PEEK;
3075 e->draw = e->blinkGfx[3];
3076 e->sequence = g_hdb->_rnd->getRandomNumber(99) + 30;
3077 }
3078 break;
3079
3080 case STATE_ICEP_THROWLEFT:
3081 e->draw = e->standleftGfx[e->animFrame];
3082
3083 // cycle animation frames
3084 if (e->animDelay-- > 0)
3085 return;
3086 e->animDelay = e->animCycle;
3087
3088 e->animFrame++;
3089 if (e->animFrame == e->standdownFrames && e->state != STATE_ICEP_DISAPPEAR) {
3090 // dir2 = direction snowball is moving
3091 e->dir2 = e->dir;
3092 // throw it!
3093 aiIcePuffSnowballInit(e);
3094 e->animFrame = 0;
3095 e->state = STATE_ICEP_DISAPPEAR;
3096 } else if (e->animFrame == e->special1Frames) {
3097 e->state = STATE_ICEP_PEEK;
3098 e->draw = e->blinkGfx[3];
3099 e->sequence = g_hdb->_rnd->getRandomNumber(99) + 30;
3100 }
3101 break;
3102
3103 case STATE_ICEP_THROWRIGHT:
3104 e->draw = e->standrightGfx[e->animFrame];
3105
3106 // cycle animation frames
3107 if (e->animDelay-- > 0)
3108 return;
3109 e->animDelay = e->animCycle;
3110
3111 e->animFrame++;
3112 if (e->animFrame == e->standdownFrames && e->state != STATE_ICEP_DISAPPEAR) {
3113 // dir2 = direction snowball is moving
3114 e->dir2 = e->dir;
3115 // throw it!
3116 aiIcePuffSnowballInit(e);
3117 e->animFrame = 0;
3118 e->state = STATE_ICEP_DISAPPEAR;
3119 } else if (e->animFrame == e->special1Frames) {
3120 e->state = STATE_ICEP_PEEK;
3121 e->draw = e->blinkGfx[3];
3122 e->sequence = g_hdb->_rnd->getRandomNumber(99) + 30;
3123 }
3124 break;
3125
3126 case STATE_ICEP_DISAPPEAR:
3127 e->draw = e->special1Gfx[e->animFrame];
3128 default:
3129 break;
3130 }
3131 }
3132
3133 //-------------------------------------------------------------------
3134 //
3135 // BUZZFLY : Simply flies around on paths.... kills you if you touch him.
3136 // He pauses at corners, too.
3137 //
3138 //-------------------------------------------------------------------
3139
aiBuzzflyInit(AIEntity * e)3140 void aiBuzzflyInit(AIEntity *e) {
3141 e->aiAction = aiBuzzflyAction;
3142 e->sequence = 0;
3143
3144 g_hdb->_ai->findPath(e);
3145 }
3146
aiBuzzflyInit2(AIEntity * e)3147 void aiBuzzflyInit2(AIEntity *e) {
3148 e->draw = g_hdb->_ai->getStandFrameDir(e);
3149 for (int i = 0; i < e->movedownFrames; i++) {
3150 e->standdownGfx[i] = e->movedownGfx[i];
3151 e->standupGfx[i] = e->moveupGfx[i];
3152 e->standleftGfx[i] = e->moveleftGfx[i];
3153 e->standrightGfx[i] = e->moverightGfx[i];
3154 }
3155 e->standdownFrames = e->movedownFrames;
3156 e->standupFrames = e->moveupFrames;
3157 e->standleftFrames = e->moveleftFrames;
3158 e->standrightFrames = e->moverightFrames;
3159 }
3160
aiBuzzflyAction(AIEntity * e)3161 void aiBuzzflyAction(AIEntity *e) {
3162 if (!e->goalX) {
3163 switch (e->sequence) {
3164 case 0:
3165 case 1:
3166 case 2:
3167 case 3:
3168 case 4:
3169 if (!e->animFrame && e->animDelay == e->animCycle)
3170 e->sequence++;
3171
3172 e->draw = e->standdownGfx[e->animFrame];
3173
3174 // cycle animation frames
3175 if (e->animDelay-- > 0)
3176 return;
3177 e->animDelay = e->animCycle;
3178 e->animFrame++;
3179 if (e->animFrame == e->standdownFrames)
3180 e->animFrame = 0;
3181
3182 break;
3183
3184 case 5:
3185 g_hdb->_ai->findPath(e);
3186 if (e->onScreen)
3187 g_hdb->_sound->playSound(SND_BUZZFLY_FLY);
3188 e->sequence = 0;
3189 break;
3190
3191 default:
3192 break;
3193 }
3194 } else {
3195 g_hdb->_ai->animateEntity(e);
3196 if (g_hdb->_ai->checkPlayerCollision(e->x, e->y, 6) && !g_hdb->_ai->playerDead()) {
3197 g_hdb->_sound->playSound(SND_BUZZFLY_STING);
3198 g_hdb->_ai->killPlayer(DEATH_GRABBED);
3199 }
3200 }
3201 }
3202
3203 //-------------------------------------------------------------------
3204 //
3205 // DRAGON
3206 //
3207 //-------------------------------------------------------------------
3208
aiDragonInit(AIEntity * e)3209 void aiDragonInit(AIEntity *e) {
3210 e->state = STATE_STANDDOWN;
3211 e->sequence = 0; // 0 = sleeping
3212 e->aiAction = aiDragonAction;
3213 e->aiDraw = aiDragonDraw;
3214 e->animCycle = 10; // time between flaps
3215
3216 // need to save the dragon's coords and type in the blocking entity for gem-hit-blocking detection
3217 AIEntity *block = spawnBlocking(e->tileX - 1, e->tileY, e->level);
3218 block->value1 = (int)AI_DRAGON;
3219 sprintf(block->luaFuncUse, "%03d%03d", e->tileX, e->tileY);
3220 block = spawnBlocking(e->tileX + 1, e->tileY, e->level);
3221 block->value1 = (int)AI_DRAGON;
3222 sprintf(block->luaFuncUse, "%03d%03d", e->tileX, e->tileY);
3223 block = spawnBlocking(e->tileX - 1, e->tileY - 1, e->level);
3224 block->value1 = (int)AI_DRAGON;
3225 sprintf(block->luaFuncUse, "%03d%03d", e->tileX, e->tileY);
3226 block = spawnBlocking(e->tileX + 1, e->tileY - 1, e->level);
3227 block->value1 = (int)AI_DRAGON;
3228 sprintf(block->luaFuncUse, "%03d%03d", e->tileX, e->tileY);
3229 block = spawnBlocking(e->tileX - 1, e->tileY - 2, e->level);
3230 block->value1 = (int)AI_DRAGON;
3231 sprintf(block->luaFuncUse, "%03d%03d", e->tileX, e->tileY);
3232 block = spawnBlocking(e->tileX + 1, e->tileY - 2, e->level);
3233 block->value1 = (int)AI_DRAGON;
3234 sprintf(block->luaFuncUse, "%03d%03d", e->tileX, e->tileY);
3235 }
3236
aiDragonInit2(AIEntity * e)3237 void aiDragonInit2(AIEntity *e) {
3238 e->draw = nullptr;
3239 if (!g_hdb->_ai->_gfxDragonAsleep) {
3240 g_hdb->_ai->_gfxDragonAsleep = g_hdb->_gfx->loadPic(DRAGON_ASLEEP);
3241 g_hdb->_ai->_gfxDragonFlap[0] = g_hdb->_gfx->loadPic(DRAGON_FLAP1);
3242 g_hdb->_ai->_gfxDragonFlap[1] = g_hdb->_gfx->loadPic(DRAGON_FLAP2);
3243 g_hdb->_ai->_gfxDragonBreathe[0] = g_hdb->_gfx->loadPic(DRAGON_BREATHE_START);
3244 g_hdb->_ai->_gfxDragonBreathe[1] = g_hdb->_gfx->loadPic(DRAGON_BREATHING_1);
3245 g_hdb->_ai->_gfxDragonBreathe[2] = g_hdb->_gfx->loadPic(DRAGON_BREATHING_2);
3246 }
3247 }
3248
aiDragonWake(AIEntity * e)3249 void aiDragonWake(AIEntity *e) {
3250 // woke up, start flapping and breathing!
3251 e->sequence = 1;
3252 e->animFrame = 0;
3253 e->animDelay = e->animCycle;
3254 }
3255
aiDragonUse(AIEntity * e)3256 void aiDragonUse(AIEntity *e) {
3257 aiDragonWake(e);
3258 }
3259
aiDragonAction(AIEntity * e)3260 void aiDragonAction(AIEntity *e) {
3261 AIEntity *p = g_hdb->_ai->getPlayer();
3262
3263 switch (e->sequence) {
3264 // Sleeping, waiting for the player to wake him up
3265 case 0:
3266 if (e->onScreen &&
3267 p->tileX >= e->tileX - 1 &&
3268 p->tileX <= e->tileX + 1 &&
3269 p->tileY <= e->tileY + 1 &&
3270 p->tileY >= e->tileY - 3) {
3271 if ((p->state >= STATE_ATK_CLUB_UP &&
3272 p->state <= STATE_ATK_SLUG_RIGHT) || g_hdb->_window->inPanicZone()) {
3273 aiDragonWake(e);
3274 if (e->onScreen)
3275 g_hdb->_sound->playSound(SND_DRAGON_WAKE);
3276 }
3277 }
3278 break;
3279
3280 // Woke up - flapping wings 3 times!
3281 case 1:
3282 e->animDelay--;
3283
3284 if (e->animDelay < 1) {
3285 if (e->onScreen)
3286 g_hdb->_sound->playSound(SND_DRAGON_WAKE);
3287 e->animDelay = e->animCycle;
3288 e->animFrame++;
3289 if (e->animFrame >= 8) {
3290 e->animFrame = 0;
3291 e->sequence = 2;
3292 e->animCycle = 2;
3293 }
3294 }
3295 break;
3296
3297 // Start breathing fire!
3298 case 2:
3299 e->animDelay--;
3300
3301 if (e->onScreen)
3302 g_hdb->_sound->playSound(SND_DRAGON_BREATHEFIRE);
3303 if (e->animDelay < 1) {
3304 e->animDelay = e->animCycle;
3305 e->animFrame++;
3306 if (e->animFrame >= 1) {
3307 e->animFrame = 0;
3308 e->sequence = 3;
3309 e->animCycle = 2; // time between flaps
3310 }
3311 }
3312
3313 break;
3314
3315 // Breathing fire!
3316 case 3:
3317 {
3318 if (hitPlayer(e->x, e->y + 32)) {
3319 g_hdb->_ai->killPlayer(DEATH_FRIED);
3320 return;
3321 }
3322
3323 // whatever entity is in front of the dragon is gettin' USED!
3324 AIEntity *hit = g_hdb->_ai->findEntity(e->tileX, e->tileY + 1);
3325 if (hit) {
3326 switch (hit->type) {
3327 case AI_CHICKEN:
3328 g_hdb->_ai->addAnimateTarget(hit->tileX * kTileWidth, hit->tileY * kTileHeight, 0, 2, ANIM_NORMAL, false, false, GROUP_ENT_CHICKEN_DIE);
3329 g_hdb->_sound->playSound(SND_CHICKEN_DEATH);
3330 g_hdb->_ai->removeEntity(hit);
3331 e->sequence = 4;
3332 break;
3333 case AI_MAGIC_EGG:
3334 case AI_ICE_BLOCK:
3335 aiMagicEggUse(hit);
3336 break;
3337 default:
3338 if (hit->aiUse)
3339 hit->aiUse(hit);
3340 if (hit->luaFuncUse[0])
3341 g_hdb->_lua->callFunction(hit->luaFuncUse, 0);
3342 }
3343 }
3344
3345 e->animDelay--;
3346
3347 if (e->animDelay < 1) {
3348 if (e->onScreen && !(e->animFrame & 7))
3349 g_hdb->_sound->playSound(SND_DRAGON_BREATHEFIRE);
3350
3351 e->animDelay = e->animCycle;
3352 e->animFrame++;
3353 if (e->animFrame >= 30) {
3354 e->animFrame = 0;
3355 e->sequence = 4;
3356 e->animCycle = 10; // time between flaps
3357 }
3358 }
3359 }
3360
3361 break;
3362
3363 // Done burning - flapping wings 3 times
3364 case 4:
3365 e->animDelay--;
3366
3367 if (e->animDelay < 1) {
3368 e->animDelay = e->animCycle;
3369 e->animFrame++;
3370 if (e->animFrame >= 8) {
3371 e->animFrame = 0;
3372 e->sequence = 0;
3373 if (e->onScreen)
3374 g_hdb->_sound->playSound(SND_DRAGON_FALLASLEEP);
3375 }
3376 }
3377 break;
3378
3379 default:
3380 break;
3381 }
3382 }
3383
aiDragonDraw(AIEntity * e,int mx,int my)3384 void aiDragonDraw(AIEntity *e, int mx, int my) {
3385 switch (e->sequence) {
3386 // sleeping
3387 case 0:
3388 g_hdb->_ai->_gfxDragonAsleep->drawMasked(e->x - 32 - mx, e->y - 96 - my);
3389 break;
3390 // flapping 3 times
3391 case 1:
3392 g_hdb->_ai->_gfxDragonFlap[e->animFrame & 1]->drawMasked(e->x - 32 - mx, e->y - 96 - my);
3393 break;
3394 // start breathing (very short)
3395 case 2:
3396 g_hdb->_ai->_gfxDragonBreathe[0]->drawMasked(e->x - 32 - mx, e->y - 96 - my);
3397 break;
3398 // breathing
3399 case 3:
3400 g_hdb->_ai->_gfxDragonBreathe[(e->animFrame & 1) + 1]->drawMasked(e->x - 32 - mx, e->y - 96 - my);
3401 break;
3402 // flapping 3 times
3403 case 4:
3404 g_hdb->_ai->_gfxDragonBreathe[e->animFrame & 1]->drawMasked(e->x - 32 - mx, e->y - 96 - my);
3405 break;
3406 default:
3407 break;
3408 }
3409 }
3410
3411 } // End of Namespace
3412