1 /*
2 * PROPRIETARY INFORMATION. This software is proprietary to POWDER
3 * Development, and is not to be reproduced, transmitted, or disclosed
4 * in any way without written permission.
5 *
6 * Produced by: Jeff Lait
7 *
8 * POWDER Development
9 *
10 * NAME: map.cpp ( POWDER Library, C++ )
11 *
12 * COMMENTS:
13 * Routines to handle the map data structure.
14 *
15 * Note that code relevant to the creation of levels is in build.cpp.
16 */
17
18 #include <stdio.h>
19 #include <math.h>
20 #include "mygba.h"
21 #include "map.h"
22 #include "msg.h"
23 #include "gfxengine.h"
24 #include "rand.h"
25 #include "assert.h"
26 #include "creature.h"
27 #include "item.h"
28 #include "itemstack.h"
29 #include "sramstream.h"
30 #include "piety.h"
31 #include "victory.h"
32 #include "ptrlist.h"
33
34 MAP *glbCurLevel = 0;
35 int glbMapCount = 0;
36
37 u16 *MAP::ourItemMap = 0;
38 u16 *MAP::ourMobMap = 0;
39 // Temporary map used to mark if stuff are walls or not.
40 u8 *MAP::ourWallMap = 0;
41 // Used while building our levels.
42 u16 *MAP::ourDistanceMap = 0;
43 int *MAP::ourCells = 0;
44 int *MAP::ourCutOuts = 0;
45
46 // Used for smelling..
47 u16 *MAP::ourAvatarSmell = 0;
48 u16 MAP::ourAvatarSmellWatermark = 64;
49
50 MOBREF *MAP::ourMobRefMap = 0;
51 ITEM **MAP::ourItemPtrMap = 0;
52
53 MOBREF *MAP::ourMobGuiltyMap = 0;
54
55 bool MAP::ourIsLoading = false;
56
57 s8 MAP::ourWindDX = 0;
58 s8 MAP::ourWindDY = 0;
59 int MAP::ourWindCounter = 0;
60
61 bool MAP::ourMapDirty = true;
62
63 // Marks if we have a pending level change.
64 MAP *MAP::ourDelayedLevelChange = 0;
65
66 u32 glbBlockingTiles[32] =
67 { 0, 0, 0, 0,
68 0, 0, 0, 0,
69 0, 0, 0, 0,
70 0, 0, 0, 0,
71
72 0, 0, 0, 0,
73 0, 0, 0, 0,
74 0, 0, 0, 0,
75 0, 0, 0, 0 };
76
77 u32 glbWalkTiles1[32] =
78 { 0, 0, 0, 0,
79 0, 0, 0, 0,
80 0, 0, 0, 0,
81 0, 0, 0, 0,
82
83 0, 0, 0, 0,
84 0, 0, 0, 0,
85 0, 0, 0, 0,
86 0, 0, 0, 0 };
87
88 u32 glbWalkTiles2[32] =
89 { 0, 0, 0, 0,
90 0, 0, 0, 0,
91 0, 0, 0, 0,
92 0, 0, 0, 0,
93
94 0, 0, 0, 0,
95 0, 0, 0, 0,
96 0, 0, 0, 0,
97 0, 0, 0, 0 };
98
99
100 static int
sign(int d)101 sign(int d)
102 {
103 if (d == 0)
104 return 0;
105 else if (d < 0)
106 return -1;
107 else
108 return 1;
109 }
110
MAP(int level,BRANCH_NAMES branch)111 MAP::MAP(int level, BRANCH_NAMES branch)
112 {
113 glbMapCount++;
114 mySquares = new u8[MAP_WIDTH * MAP_HEIGHT];
115 myFlags = new u8[MAP_WIDTH * MAP_HEIGHT];
116
117 // Default abilities for all levels.
118 myGlobalFlags = 0;
119 myGlobalFlags |= MAPFLAG_DIG;
120 myGlobalFlags |= MAPFLAG_NEWMOBS;
121
122 if (!ourItemMap)
123 ourItemMap = new u16[MAP_WIDTH * MAP_HEIGHT];
124 if (!ourMobMap)
125 ourMobMap = new u16[MAP_WIDTH * MAP_HEIGHT];
126 if (!ourWallMap)
127 ourWallMap = new u8[MAP_WIDTH * MAP_HEIGHT];
128 if (!ourDistanceMap)
129 ourDistanceMap = new u16[MAP_WIDTH * MAP_HEIGHT];
130 if (!ourCells)
131 ourCells = new int[1024];
132 if (!ourCutOuts)
133 ourCutOuts = new int[512];
134
135 if (!ourAvatarSmell)
136 ourAvatarSmell = new u16[MAP_WIDTH * MAP_HEIGHT];
137
138 if (!ourMobRefMap)
139 ourMobRefMap = new MOBREF[MAP_WIDTH * MAP_HEIGHT];
140 if (!ourItemPtrMap)
141 ourItemPtrMap = new ITEM*[MAP_WIDTH * MAP_HEIGHT];
142
143 if (!ourMobGuiltyMap)
144 ourMobGuiltyMap = new MOBREF[MAP_WIDTH * MAP_HEIGHT];
145
146 myMobCount = 0;
147 myItemCount = 0;
148
149 myMapDown = 0;
150 myMapUp = 0;
151 myMapBranch = 0;
152 myItemHead = 0;
153 myMobHead = 0;
154 myMobSafeNext = 0;
155
156 mySignList = 0;
157
158 clear();
159
160 myDepth = level;
161 myBranch = branch;
162
163 myFOVX = myFOVY = -1;
164 }
165
~MAP()166 MAP::~MAP()
167 {
168 delete [] mySquares;
169 delete [] myFlags;
170
171 delete mySignList;
172
173 glbMapCount--;
174 }
175
176 void
deleteAllMaps()177 MAP::deleteAllMaps()
178 {
179 MOB *mob;
180 ITEM *item;
181
182 // Clear out our mob refs.
183 if (this == glbCurLevel)
184 {
185 int x, y;
186
187 for (y = 0; y < MAP_HEIGHT; y++)
188 for (x = 0; x < MAP_WIDTH; x++)
189 {
190 ourMobRefMap[(y) * MAP_WIDTH + x].setMob(0);
191 ourMobGuiltyMap[(y) * MAP_WIDTH + x].setMob(0);
192 }
193 }
194
195 for (mob = getMobHead(); mob; mob = getMobHead())
196 {
197 myMobHead = mob->getNext();
198 mob->setNext(0);
199 delete mob;
200 }
201
202 for (item = getItemHead(); item; item = getItemHead())
203 {
204 myItemHead = item->getNext();
205 item->setNext(0);
206 delete item;
207 }
208
209 if (myMapUp)
210 {
211 myMapUp->myMapDown = 0;
212 myMapUp->deleteAllMaps();
213 }
214
215 if (myMapDown)
216 {
217 myMapDown->myMapUp = 0;
218 myMapDown->deleteAllMaps();
219 }
220
221 if (myMapBranch)
222 {
223 // Break the branch.
224 myMapBranch->myMapBranch = 0;
225 myMapBranch->deleteAllMaps();
226 }
227
228 myMapUp = myMapDown = myMapBranch = 0;
229
230 delete this;
231
232 changeCurrentLevel(0);
233 }
234
235 bool
findPath(int sx,int sy,int ex,int ey,MOVE_NAMES movetype,bool allowmob,bool allowdoor,int & dx,int & dy)236 MAP::findPath(int sx, int sy,
237 int ex, int ey,
238 MOVE_NAMES movetype,
239 bool allowmob,
240 bool allowdoor,
241 int &dx, int &dy)
242 {
243 int x, y, iterations;
244 u32 mask, val, endmask, diff;
245 u32 *curtile, *lasttile, *tmp;
246
247 UT_ASSERT(sx >= 0 && sx < 32);
248 UT_ASSERT(sy >= 0 && sy < 32);
249 UT_ASSERT(ex >= 0 && ex < 32);
250 UT_ASSERT(ey >= 0 && ey < 32);
251
252 if (sx == ex && sy == ey)
253 {
254 // Already there, trivial.
255 dx = dy = 0;
256 return true;
257 }
258
259 // Initialize the blocking tiles.
260 for (y = 0; y < 32; y++)
261 {
262 val = 0;
263 mask = 1;
264 for (x = 0; x < 32; x++)
265 {
266 if (canMove(x, y, movetype, allowmob, allowdoor))
267 val |= mask;
268 mask <<= 1;
269 }
270 glbBlockingTiles[y] = val;
271 }
272
273 // Initialize the current array to the destination location.
274 memset(glbWalkTiles1, 0, sizeof(u32) * 32);
275 glbWalkTiles1[ey] = (1 << ex);
276
277 // Now, walk until we get a match...
278 curtile = glbWalkTiles1;
279 lasttile = glbWalkTiles2;
280
281 endmask = (1 << sx);
282
283 // We explicitly allow walking to the end square. If allowmob
284 // is false, for example, we may falsely report we can't get to the
285 // mob.
286 glbBlockingTiles[sy] |= endmask;
287
288 iterations = 0;
289 while (1)
290 {
291 // Write from curtile into lasttile the effect of one step.
292 for (y = 0; y < 32; y++)
293 {
294 // Include effects of stepping left and right.
295 lasttile[y] = curtile[y] | (curtile[y] << 1) | (curtile[y] >> 1);
296 }
297
298 // Now, merge in vertical steps.
299 for (y = 1; y < 31; y++)
300 {
301 lasttile[y] |= curtile[y-1] | curtile[y+1];
302 }
303
304 // Special cases.
305 lasttile[0] |= curtile[1];
306 lasttile[31] |= curtile[30];
307
308 // Now, clear out the relevant mask. At the same time,
309 // we track if we have any differences. If there are no
310 // differences, we can't reach our destination.
311 diff = 0;
312 for (y = 0; y < 32; y++)
313 {
314 lasttile[y] &= glbBlockingTiles[y];
315 diff |= lasttile[y] ^ curtile[y];
316 }
317
318 if (!diff)
319 {
320 #if 0
321 BUF buf;
322 buf.sprintf(buf, "Same effect at %d. ", iterations);
323 msg_report(buf);
324 #endif
325 return false;
326 }
327
328 // Swap last & cur.
329 tmp = curtile;
330 curtile = lasttile;
331 lasttile = tmp;
332
333 // If the curtile now has the end bit set, we are done.
334 if (curtile[sy] & endmask)
335 break;
336
337 iterations++;
338 }
339
340 if ((0))
341 {
342 BUF buf;
343 buf.sprintf("Found end at %d. ", iterations);
344 msg_report(buf);
345 }
346
347 // We have successfully built a path to the start. To determine
348 // the best path, we check the previous step. All the neighbouring
349 // squares of the previous step are, by definition, closer. Further,
350 // at least one must be set.
351 int numdir, dir, dirlist[4];
352
353 numdir = 0;
354 for (dir = 0; dir < 4; dir++)
355 {
356 getDirection(dir, dx, dy);
357 x = sx + dx;
358 y = sy + dy;
359 if (x >= 0 && x < 32 && y >= 0 && y < 32)
360 {
361 if (lasttile[y] & (1 << x))
362 {
363 dirlist[numdir++] = dir;
364 }
365 }
366 }
367
368 if (!numdir)
369 {
370 UT_ASSERT(!"No direction found?");
371 return false;
372 }
373
374 // Pick a direction at random...
375 dir = rand_choice(numdir);
376 dir = dirlist[dir];
377
378 getDirection(dir, dx, dy);
379 return true;
380 }
381
382 void
updateAvatarSmell(int x,int y,int smell)383 MAP::updateAvatarSmell(int x, int y, int smell)
384 {
385 int cursmell;
386
387 UT_ASSERT(x >= 0 && x < MAP_WIDTH);
388 UT_ASSERT(y >= 0 && y < MAP_HEIGHT);
389
390 // Move to top of current watermark.
391 smell += ourAvatarSmellWatermark;
392
393 // Get current smell at this square.
394 cursmell = ourAvatarSmell[y * MAP_WIDTH + x];
395
396 // Update if better...
397 if (cursmell < smell || cursmell > ourAvatarSmellWatermark)
398 ourAvatarSmell[y * MAP_WIDTH + x] = smell;
399 }
400
401 int
getAvatarSmell(int x,int y) const402 MAP::getAvatarSmell(int x, int y) const
403 {
404 u16 usmell; // You smell
405 int ismell; // I smell
406 // char wiismell; // We smell.
407
408 usmell = ourAvatarSmell[y * MAP_WIDTH + x];
409 // If we are farther than the watermark, it is zero.
410 if (usmell > ourAvatarSmellWatermark)
411 return 0;
412 // Find negative smell. Lower is less smelly, but is centered
413 // with 0 being closest.
414 ismell = (int) usmell - (int)ourAvatarSmellWatermark;
415 // Normalize.
416 ismell += 65535;
417
418 return ismell;
419 }
420
421 bool
getAvatarSmellGradient(MOB * mob,int x,int y,int & dx,int & dy) const422 MAP::getAvatarSmellGradient(MOB *mob, int x, int y, int &dx, int &dy) const
423 {
424 int maxsmell = 0, smell;
425 int ddx, ddy;
426
427 FORALL_8DIR(ddx, ddy)
428 {
429 if (mob && mob->canMove(x+ddx, y+ddy, true))
430 {
431 // Possible smelly square to move to.
432 smell = getAvatarSmell(x+ddx, y+ddy);
433 if (smell > maxsmell)
434 {
435 dx = ddx;
436 dy = ddy;
437 maxsmell = smell;
438 }
439 }
440 }
441
442 // See if we got a good direction...
443 if (maxsmell)
444 return true;
445 return false;
446 }
447
448 void
populate()449 MAP::populate()
450 {
451 int desiredcount;
452 MOB *mob;
453 int x, y;
454 int fails;
455 int i, n;
456
457 desiredcount = getDesiredMobCount();
458
459 // desiredcount = 100;
460
461 fails = 0;
462 while (myMobCount < desiredcount && (fails < 20))
463 {
464 // No worry about FOV here.
465 if (!createAndPlaceNPC(false))
466 fails++;
467 }
468
469 // Create a dimensional portal on the correct level
470 if (myDepth == 20 && branchName() == BRANCH_MAIN)
471 {
472 int tries = 20;
473 while (tries--)
474 {
475 if (findRandomLoc(x, y, MOVE_STD_SWIM, true, false, false, false, false, false))
476 {
477 // Make sure this isn't an invulnerable square.
478 SQUARE_NAMES square;
479
480 square = getTile(x, y);
481 if (!glb_squaredefs[square].invulnerable)
482 {
483 setTile(x, y, SQUARE_DIMDOOR);
484 // Dimensional door glows!
485 setFlag(x, y, SQUAREFLAG_LIT);
486 break;
487 }
488 }
489 }
490 UT_ASSERT(tries);
491 }
492
493 // Check if we are a special level..
494 if (myDepth == 25 && branchName() == BRANCH_MAIN)
495 {
496 // Add the daemon...
497 mob = MOB::create(MOB_BAEZLBUB);
498 if (findRandomLoc(x, y, mob->getMoveType(), false,
499 mob->getSize() >= SIZE_GARGANTUAN,
500 false, false, true, true))
501 {
502 if (!mob->move(x, y, true))
503 {
504 UT_ASSERT(!"COOL MOB INSTA KILLED");
505 }
506 else
507 {
508 registerMob(mob);
509 // We want this to be a global broadcast.
510 msg_report("You sense your doom approaching! ");
511 }
512 }
513 else
514 {
515 UT_ASSERT(!"Couldn't place cool mob!");
516 delete mob;
517 }
518 }
519
520 // Populate the floor of the dungeon.
521 n = 10 + rand_choice(myDepth);
522
523 // Do not put anything on the wilderness map.
524 if (!allowItemGeneration())
525 n = 0;
526 for (i = 0; i < n; i++)
527 {
528 ITEM *item;
529 // Don't generate items in nomob locations such as star fields.
530 if (findRandomLoc(x, y, MOVE_WALK, true, false, false, false, false, true))
531 {
532 item = ITEM::createRandom();
533 #if 0
534 item = ITEM::create(ITEM_BOULDER);
535 #endif
536 acquireItem(item, x, y, 0);
537 }
538 }
539 }
540
541 bool
createAndPlaceNPC(bool avoidfov)542 MAP::createAndPlaceNPC(bool avoidfov)
543 {
544 MOB *mob;
545 int x, y;
546 bool avoidfire = true, avoidacid = true;
547
548 mob = MOB::createNPC(getThreatLevel());
549 if (rand_chance(1))
550 mob->makeUnique();
551
552 if (mob->hasIntrinsic(INTRINSIC_RESISTFIRE) &&
553 !mob->hasIntrinsic(INTRINSIC_VULNFIRE))
554 avoidfire = false;
555
556 if ((mob->getMoveType() & MOVE_FLY))
557 avoidacid = false;
558
559 if (mob->hasIntrinsic(INTRINSIC_RESISTACID) &&
560 !mob->hasIntrinsic(INTRINSIC_VULNACID))
561 avoidacid = false;
562
563 // We don't want the new dude to show up in the FOV.
564 if (findRandomLoc(x, y, mob->getMoveType(), false,
565 mob->getSize() >= SIZE_GARGANTUAN,
566 avoidfov, avoidfire, avoidacid, true))
567 {
568 // Do an interlevel move to skip trap checking.
569 // Still check for move death.
570 if (mob->move(x, y, true))
571 {
572 registerMob(mob);
573 return true;
574 }
575 }
576 // Failed to place the mob, ignore.
577 delete mob;
578 return false;
579 }
580
581 void
refresh(bool preserve_ray)582 MAP::refresh(bool preserve_ray)
583 {
584 int x, y, tile, mobtile;
585 MOB *mob;
586 ITEM *item;
587 bool avatarblind = false;
588 bool avatarinvis = false;
589 bool avatarinpit = false;
590 bool avatarintree = false;
591 bool avatarsubmerged = false;
592 bool forcelit;
593
594 memset(ourItemMap, TILE_VOID, sizeof(u16) * MAP_HEIGHT * MAP_WIDTH);
595 memset(ourMobMap, TILE_VOID, sizeof(u16) * MAP_HEIGHT * MAP_WIDTH);
596
597 // Cycle through all MOBs...
598 MOB *avatar = MOB::getAvatar();
599 int ax = 0, ay = 0;
600
601 if (avatar)
602 {
603 avatarblind = avatar->hasIntrinsic(INTRINSIC_BLIND);
604 avatarinpit = avatar->hasIntrinsic(INTRINSIC_INPIT);
605 avatarintree = avatar->hasIntrinsic(INTRINSIC_INTREE);
606 avatarsubmerged = avatar->hasIntrinsic(INTRINSIC_SUBMERGED);
607 ax = avatar->getX();
608 ay = avatar->getY();
609 }
610
611 // Cycle through all items...
612 for (item = getItemHead(); item; item = item->getNext())
613 {
614 int ix, iy;
615
616 ix = item->getX();
617 iy = item->getY();
618
619 // Items that are below grade (in pits, or under water)
620 // an only be seen by avatars that are below grade,
621 // in pits or underwater.
622 if (item->isBelowGrade())
623 {
624 if (avatar)
625 {
626 // One's in a pit are only seen by avatars on that
627 // square.
628 if (avatarinpit)
629 {
630 if (ix != ax || iy != ay)
631 {
632 // Can't see, not in our own pit.
633 continue;
634 }
635 }
636 // If the avatar is underwater, can see all below
637 // grade stuff (if it has LOS, of course :>)
638 else if (!avatarsubmerged)
639 {
640 // Not in a pit, not underwater.
641 continue;
642 }
643 }
644 }
645 else
646 {
647 if (avatarinpit || avatarsubmerged)
648 {
649 // Can't see anything above ground if we are
650 // underground.
651 continue;
652 }
653
654 // Can't see anything under tree canopy when in trees.
655 if (avatarintree && glb_squaredefs[getTile(ix,iy)].isforest)
656 continue;
657 }
658
659 // Figure out rejection criteria from expensive to cheap...
660 if (item->isMapped())
661 {
662 // Trivially true.
663 ourItemMap[iy * MAP_WIDTH + ix] = item->getTile();
664 continue;
665 }
666 if (!getFlag(ix, iy, SQUAREFLAG_MAPPED))
667 {
668 // Trivially false...
669 continue;
670 }
671 // Now, check if ix, iy is visible to us. Our square is always
672 // visible, even if blind. Otherwise, if we can see, we use
673 // LOS.
674 if (ix == ax && iy == ay)
675 {
676 item->markMapped();
677 ourItemMap[iy * MAP_WIDTH + ix] = item->getTile();
678 continue;
679 }
680 // Check if avatar is blind, that is trivial no see.
681 if (avatarblind)
682 continue;
683
684 // Check if square is lit. If not, can't see.
685 // We can always feel around to neighbouring squares if it
686 // is merely dark.
687 forcelit = false;
688 if ((ix - ax >= -1) && (ix - ax <= 1) &&
689 (iy - ay >= -1) && (iy - ay <= 1))
690 forcelit = true;
691
692 if (!forcelit && !isLit(ix, iy))
693 continue;
694
695 // Finally, check LOS. Have LOS, can see.
696 UT_ASSERT(ix >= 0 && ix < MAP_WIDTH);
697 UT_ASSERT(iy >= 0 && iy < MAP_HEIGHT);
698 if (hasLOS(ax, ay, ix, iy))
699 {
700 item->markMapped();
701 ourItemMap[iy * MAP_WIDTH + ix] = item->getTile();
702 }
703 }
704
705 for (mob = getMobHead(); mob; mob = mob->getNext())
706 {
707 if (avatar ? avatar->canSense(mob)
708 : true)
709 {
710 if (avatar && avatar->getSenseType(mob) == SENSE_WARN)
711 {
712 // MOB tile gets changed to a ?
713 ourMobMap[mob->getY() * MAP_WIDTH + mob->getX()] =
714 TILE_UNKNOWN;
715 if (mob->getSize() >= SIZE_GARGANTUAN
716 && mob->getX() < MAP_WIDTH - 1
717 && mob->getY() < MAP_HEIGHT - 1)
718 {
719 ourMobMap[(mob->getY()+1) * MAP_WIDTH + mob->getX()] =
720 TILE_UNKNOWN;
721 ourMobMap[(mob->getY()+1) * MAP_WIDTH + mob->getX()+1] =
722 TILE_UNKNOWN;
723 ourMobMap[mob->getY() * MAP_WIDTH + mob->getX()+1] =
724 TILE_UNKNOWN;
725 }
726 }
727 else
728 {
729 ourMobMap[mob->getY() * MAP_WIDTH + mob->getX()] =
730 mob->getTile();
731 if (mob->getSize() >= SIZE_GARGANTUAN
732 && mob->getX() < MAP_WIDTH - 1
733 && mob->getY() < MAP_HEIGHT - 1)
734 {
735 ourMobMap[(mob->getY()+1) * MAP_WIDTH + mob->getX()] =
736 mob->getTileLL();
737 ourMobMap[(mob->getY()+1) * MAP_WIDTH + mob->getX()+1] =
738 mob->getTileLR();
739 ourMobMap[mob->getY() * MAP_WIDTH + mob->getX()+1] =
740 mob->getTileUR();
741 }
742 }
743 }
744 else if (mob->isAvatar())
745 {
746 // Can't see oneself!
747 avatarinvis = true;
748 }
749 }
750
751 for (y = 0; y < MAP_HEIGHT; y++)
752 {
753 for (x = 0; x < MAP_WIDTH; x++)
754 {
755 if (getFlag(x, y, SQUAREFLAG_MAPPED))
756 tile = glb_squaredefs[mySquares[y * MAP_WIDTH + x]].tile;
757 else
758 tile = TILE_VOID;
759
760 mobtile = TILE_VOID;
761 if (ourItemMap[y * MAP_WIDTH + x] != TILE_VOID)
762 mobtile = ourItemMap[y * MAP_WIDTH + x];
763 if (ourMobMap[y * MAP_WIDTH + x] != TILE_VOID)
764 mobtile = ourMobMap[y * MAP_WIDTH + x];
765
766 // Clear out underneath the mob if opaque tiles are on.
767 if (mobtile != TILE_VOID && glbOpaqueTiles)
768 tile = TILE_VOID;
769
770 gfx_settile(x, y, tile);
771 gfx_setmoblayer(x, y, mobtile);
772
773 // See if we force the tile as lit due to avatar being
774 // dead or this being next to avatar.
775 forcelit = false;
776 if (!avatar)
777 forcelit = true;
778 else if (!avatarblind)
779 {
780 int diffx, diffy;
781 diffx = ax - x;
782 diffy = ay - y;
783 if (diffx >= -1 && diffx <= 1 &&
784 diffy >= -1 && diffy <= 1)
785 {
786 forcelit = true;
787 }
788 }
789
790 // If the avatar is blind, everything is shadow.
791 if (!avatarblind && hasFOV(x, y) && (forcelit || isLit(x, y)))
792 {
793 // It isn't shadow. It might be a smokey square though.
794 if (getFlag(x, y, SQUAREFLAG_SMOKE))
795 {
796 SMOKE_NAMES smoke;
797
798 smoke = getSmoke(x, y);
799
800 tile = glb_smokedefs[smoke].tile;
801 }
802 else
803 tile = TILE_VOID;
804 }
805 else
806 tile = TILE_SHADOW;
807
808 // Exception: If this is a mob, we want to reveal how
809 // we saw the mob.
810 // We have to apply this to all mobs. If a mob is in a lit
811 // area but we only sense with hearing, we want to put
812 // the hearing overlay on.
813 if ((tile == TILE_SHADOW || tile == TILE_VOID) &&
814 ourMobMap[y * MAP_WIDTH + x] != TILE_VOID)
815 {
816 if (avatar)
817 {
818 switch (avatar->getSenseType(getMob(x, y)))
819 {
820 case SENSE_SIGHT:
821 {
822 MOB *mob = getMob(x, y);
823
824 tile = TILE_VOID;
825 if (mob)
826 {
827 // If the creature is burning, draw special
828 if (mob->hasIntrinsic(INTRINSIC_AFLAME))
829 {
830 tile = TILE_AFLAME;
831 }
832 else if (mob->hasIntrinsic(INTRINSIC_TAME) &&
833 mob->isSlave(MOB::getAvatar()))
834 {
835 tile = TILE_TAMEMOB;
836 }
837 else if (mob->hasIntrinsic(INTRINSIC_UNIQUE))
838 {
839 tile = TILE_UNIQUEMOB;
840 }
841 else if (mob->hasIntrinsic(INTRINSIC_ASLEEP))
842 {
843 tile = TILE_ASLEEP;
844 }
845 }
846 break;
847 }
848
849 case SENSE_NONE:
850 // Should not happen.
851 // Maybe black tile?
852 tile = TILE_SHADOW;
853 break;
854
855 case SENSE_HEAR:
856 tile = TILE_SHADOW_HEAR;
857 break;
858
859 case SENSE_WARN:
860 tile = TILE_SHADOW_ESP;
861 break;
862
863 case SENSE_ESP:
864 tile = TILE_SHADOW_ESP;
865 break;
866
867 case NUM_SENSES:
868 UT_ASSERT(!"Invalid sense!");
869 break;
870 }
871 }
872 }
873
874 // Exception: If this is the avatar's location, and the
875 // avatar isn't displayed due to being invisible, draw
876 // the invisible avatar overlay tile.
877 if (avatarinvis)
878 {
879 if (x == ax && y == ay)
880 {
881 tile = TILE_INVISIBLEAVATAR;
882 }
883 }
884
885 // Exception: If there is no mob here, but there is
886 // an item which is an artifact, flag as such.
887 if (tile == TILE_VOID && mobtile != TILE_VOID &&
888 ourMobMap[y * MAP_WIDTH + x] == TILE_VOID)
889 {
890 // Returns topmost item quickly.
891 item = getItem(x, y);
892 if (item && item->isArtifact())
893 {
894 // Okay, a bit misnamed now.
895 tile = TILE_UNIQUEMOB;
896 }
897 }
898
899 // If preserve ray is turned on, we check the current
900 // overlay tile and see if we have manually updated
901 // it into a ray tile. If so, we don't want to
902 // overwrite it.
903 // Alternatively, we may want to preserve anything that
904 // is *not* one of the standard shadow tiles?
905 if (preserve_ray)
906 {
907 int raytile;
908
909 raytile = gfx_getoverlay(x, y);
910
911 switch (raytile)
912 {
913 case TILE_RAYSLASH:
914 case TILE_RAYPIPE:
915 case TILE_RAYDASH:
916 case TILE_RAYBACKSLASH:
917 // Preseve the tile
918 tile = raytile;
919 break;
920 default:
921 // Leave tile unchanged.
922 break;
923 }
924 }
925
926 gfx_setoverlay(x, y, tile);
927 }
928 }
929 }
930
931 MOB *
getGuiltyMob(int x,int y) const932 MAP::getGuiltyMob(int x, int y) const
933 {
934 if (this != glbCurLevel)
935 return 0;
936
937 return ourMobGuiltyMap[y*MAP_WIDTH+x].getMob();
938 }
939
940 void
setGuiltyMob(int x,int y,MOB * mob)941 MAP::setGuiltyMob(int x, int y, MOB *mob)
942 {
943 if (this != glbCurLevel)
944 return;
945
946 ourMobGuiltyMap[y*MAP_WIDTH+x].setMob(mob);
947 }
948
949 void
moveMob(MOB * mob,int ox,int oy,int nx,int ny)950 MAP::moveMob(MOB *mob, int ox, int oy, int nx, int ny)
951 {
952 int dx, dy;
953
954 // Note that the mob may not be in its old position, it may
955 // have already been overwritten.
956 // Note also that the old position may be invalid.
957
958 if (this != glbCurLevel)
959 return;
960
961 if (ox >= 0 && ox < MAP_WIDTH &&
962 oy >= 0 && oy < MAP_HEIGHT)
963 {
964 // Clear old spot.
965 if (ourMobRefMap[oy * MAP_WIDTH + ox].getMob() == mob)
966 ourMobRefMap[oy * MAP_WIDTH + ox].setMob(0);
967
968 if (mob->getSize() >= SIZE_GARGANTUAN)
969 {
970 for (dx = 0; dx < 2; dx++)
971 {
972 for (dy = 0; dy < 2; dy++)
973 {
974 if (ourMobRefMap[(oy + dy) * MAP_WIDTH + (ox+dx)].getMob() == mob)
975 ourMobRefMap[(oy + dy) * MAP_WIDTH + (ox+dx)].setMob(0);
976 }
977 }
978 }
979 }
980
981 if (nx >= 0 && nx < MAP_WIDTH &&
982 ny >= 0 && ny < MAP_HEIGHT)
983 {
984 // Mark new spot.
985 ourMobRefMap[ny * MAP_WIDTH + nx].setMob(mob);
986
987 if (mob->getSize() >= SIZE_GARGANTUAN)
988 {
989 for (dx = 0; dx < 2; dx++)
990 {
991 for (dy = 0; dy < 2; dy++)
992 {
993 ourMobRefMap[(ny + dy) * MAP_WIDTH + (nx+dx)].setMob(mob);
994 }
995 }
996 }
997 }
998 }
999
1000
1001 void
wallCrush(MOB * mob)1002 MAP::wallCrush(MOB *mob)
1003 {
1004 UT_ASSERT(mob != 0);
1005 if (!mob)
1006 return;
1007
1008 if (mob->getSize() < SIZE_GARGANTUAN)
1009 return;
1010
1011 int dx, dy, x, y;
1012 MOB *movemob;
1013 x = mob->getX();
1014 y = mob->getY();
1015 for (dy = 0; dy < 2; dy++)
1016 {
1017 for (dx = 0; dx < 2; dx++)
1018 {
1019 if (!canMove(x+dx, y+dy, MOVE_STD_FLY))
1020 {
1021 if (glb_squaredefs[getTile(x+dx, y+dy)].invulnerable)
1022 {
1023 // Don't destroy invulernable walls.
1024 mob->formatAndReport("%U <crush> the wall, but the wall withstands the impact!");
1025 }
1026 else
1027 {
1028 mob->formatAndReport("%U <crush> the walls!");
1029 setTile(x+dx, y+dy, SQUARE_CORRIDOR);
1030 }
1031 }
1032 movemob = getMobPrecise(x+dx, y+dy);
1033 if (movemob && (movemob != mob))
1034 {
1035 movemob->formatAndReport("%U <be> pushed out of the way.");
1036 int nx, ny;
1037 nx = movemob->getX();
1038 ny = movemob->getY();
1039 if (findCloseTile(nx, ny, movemob->getMoveType(), false))
1040 {
1041 movemob->move(nx, ny);
1042 }
1043 else
1044 {
1045 movemob->formatAndReport("But there is no where to move!");
1046 }
1047 }
1048 }
1049 }
1050 }
1051
1052 void
registerMob(MOB * mob)1053 MAP::registerMob(MOB *mob)
1054 {
1055 mob->setNext(myMobHead);
1056 mob->setDLevel(myDepth);
1057 myMobHead = mob;
1058 myMobCount++;
1059
1060 if (glbCurLevel == this)
1061 {
1062 ourMobRefMap[mob->getY() * MAP_WIDTH + mob->getX()].setMob(mob);
1063 if (mob->getSize() >= SIZE_GARGANTUAN)
1064 {
1065 int dx, dy;
1066 for (dx = 0; dx < 2; dx++)
1067 for (dy = 0; dy < 2; dy++)
1068 ourMobRefMap[(mob->getY() + dy) * MAP_WIDTH + mob->getX() + dx].setMob(mob);
1069 }
1070 }
1071 }
1072
1073 void
unregisterMob(MOB * mob)1074 MAP::unregisterMob(MOB *mob)
1075 {
1076 // TODO: If this is deadly, a double linked list would get us
1077 // O(1) removal...
1078 MOB *cur;
1079
1080 mob->setDLevel(-1);
1081 mob->clearReferences();
1082
1083 if (glbCurLevel == this)
1084 {
1085 ourMobRefMap[mob->getY() * MAP_WIDTH + mob->getX()].setMob(0);
1086 if (mob->getSize() >= SIZE_GARGANTUAN)
1087 {
1088 ourMobRefMap[(mob->getY()+1) * MAP_WIDTH + mob->getX()].setMob(0);
1089 ourMobRefMap[(mob->getY()+1) * MAP_WIDTH + mob->getX()+1].setMob(0);
1090 ourMobRefMap[mob->getY() * MAP_WIDTH + mob->getX()+1].setMob(0);
1091 }
1092 }
1093
1094 // Ensure any safe mob loops don't crash.
1095 if (myMobSafeNext == mob)
1096 myMobSafeNext = mob->getNext();
1097
1098 for (cur = getMobHead(); cur; cur = cur->getNext())
1099 {
1100 if (cur != mob)
1101 cur->alertMobGone(mob);
1102 }
1103
1104 if (mob == myMobHead)
1105 {
1106 myMobHead = myMobHead->getNext();
1107 mob->setNext(0);
1108 myMobCount--;
1109 return;
1110 }
1111 for (cur = myMobHead; cur->getNext(); cur = cur->getNext())
1112 {
1113 if (mob == cur->getNext())
1114 {
1115 cur->setNext(mob->getNext());
1116 mob->setNext(0);
1117 myMobCount--;
1118 return;
1119 }
1120 }
1121 UT_ASSERT(!"Removal of non-present mob");
1122 }
1123
1124 MOB *
getMob(int x,int y) const1125 MAP::getMob(int x, int y) const
1126 {
1127 MOB *cur;
1128
1129 if (this == glbCurLevel)
1130 {
1131 if (x < 0 || x >= MAP_WIDTH ||
1132 y < 0 || y >= MAP_HEIGHT)
1133 return 0;
1134
1135 return ourMobRefMap[y * MAP_WIDTH + x].getMob();
1136 }
1137
1138 for (cur = getMobHead(); cur; cur = cur->getNext())
1139 {
1140 if (cur->getX() == x && cur->getY() == y)
1141 return cur;
1142 if (cur->getSize() >= SIZE_GARGANTUAN)
1143 {
1144 if ( (cur->getX()+1 == x || cur->getX() == x) &&
1145 (cur->getY()+1 == y || cur->getY() == y) )
1146 {
1147 return cur;
1148 }
1149 }
1150 }
1151 return 0;
1152 }
1153
1154 MOB *
getMobPrecise(int x,int y) const1155 MAP::getMobPrecise(int x, int y) const
1156 {
1157 MOB *cur;
1158
1159 for (cur = getMobHead(); cur; cur = cur->getNext())
1160 {
1161 if (cur->getX() == x && cur->getY() == y)
1162 return cur;
1163 }
1164 return 0;
1165 }
1166
1167 void
chaseMobOnStaircase(SQUARE_NAMES destsquare,int srcx,int srcy,MOB * chasee,MAP * nextmap)1168 MAP::chaseMobOnStaircase(SQUARE_NAMES destsquare,
1169 int srcx, int srcy,
1170 MOB *chasee, MAP *nextmap)
1171 {
1172 MOB *safenext, *chaser;
1173 int range;
1174 bool follow;
1175 int x, y;
1176
1177 for (chaser = getMobHead(); chaser; chaser = safenext)
1178 {
1179 safenext = chaser->getNext();
1180
1181 range = MAX(abs(chaser->getX() - srcx),
1182 abs(chaser->getY() - srcy));
1183
1184 follow = false;
1185
1186 if (range <= 1)
1187 {
1188 // Within range, this dude can make it to the staircase.
1189 // However, he may not be interested.
1190
1191 // Avatar is never moved implicitly.
1192 if (chaser->isAvatar())
1193 continue;
1194
1195 // Double square mobs won't find correct spots on otherside
1196 // of the stair case, so don't chase for now.
1197 if (chaser->getSize() >= SIZE_GARGANTUAN)
1198 {
1199 continue;
1200 }
1201
1202 if (chaser->isSlave(chasee))
1203 {
1204 // Our owner went downstairs, we follow
1205 follow = true;
1206 }
1207
1208 if (chaser->getAITarget() == chasee)
1209 {
1210 // Someone we want to kill went downstiars, follow.
1211 follow = true;
1212 }
1213 }
1214
1215 if (follow)
1216 {
1217 if (nextmap->findTile(destsquare, x, y) &&
1218 nextmap->findCloseTile(x, y, chaser->getMoveType()))
1219 {
1220 // We have a valid destination, unlink and move!
1221 unregisterMob(chaser);
1222 chaser->move(x, y, true);
1223 nextmap->registerMob(chaser);
1224 }
1225 }
1226 }
1227 }
1228
1229 bool
findRandomLoc(int & ox,int & oy,int movetype,bool allowmob,bool doublesize,bool avoidfov,bool avoidfire,bool avoidacid,bool avoidnomob)1230 MAP::findRandomLoc(int &ox, int &oy, int movetype, bool allowmob, bool doublesize, bool avoidfov, bool avoidfire, bool avoidacid, bool avoidnomob)
1231 {
1232 // TODO: This is 2k we could save.
1233 static u16 validlist[MAP_WIDTH * MAP_HEIGHT];
1234 int numvalid = 0;
1235 int x, y, tile, i;
1236 ITEM *item;
1237
1238 for (y = 0; y < MAP_HEIGHT; y++)
1239 {
1240 for (x = 0; x < MAP_WIDTH; x++)
1241 {
1242 // Don't allow spots with FOV set if this is enabled.
1243 if (avoidfov && hasFOV(x, y))
1244 continue;
1245
1246 if (avoidnomob && getFlag(x, y, SQUAREFLAG_NOMOB))
1247 continue;
1248
1249 // Check if this tile is valid.
1250 tile = getTile(x, y);
1251
1252 if (avoidfire && tile == SQUARE_LAVA)
1253 continue;
1254 if (avoidacid && tile == SQUARE_ACID)
1255 continue;
1256
1257 if (glb_squaredefs[tile].movetype & movetype)
1258 {
1259 // Check if it has a mob...
1260 if (allowmob || !getMob(x, y))
1261 {
1262 // Check to see if there is an immovable item.
1263 item = getItem(x, y);
1264 if (item && !item->isPassable())
1265 continue;
1266
1267 if (doublesize)
1268 {
1269 if (x < MAP_WIDTH - 1 &&
1270 y < MAP_HEIGHT - 1)
1271 {
1272 if ((glb_squaredefs[getTile(x+1,y)].movetype & movetype) &&
1273 (glb_squaredefs[getTile(x+1,y+1)].movetype & movetype) &&
1274 (glb_squaredefs[getTile(x,y+1)].movetype & movetype))
1275 {
1276 if (allowmob ||
1277 (!getMob(x+1,y) &&
1278 !getMob(x+1,y+1) &&
1279 !getMob(x,y+1)))
1280 {
1281 validlist[numvalid++] = x + y * MAP_WIDTH;
1282 }
1283 }
1284 }
1285 }
1286 else
1287 validlist[numvalid++] = x + y * MAP_WIDTH;
1288 }
1289 }
1290 }
1291 }
1292 // If valid list is empty, we have no valid squares.
1293 if (!numvalid)
1294 {
1295 // This can happen in places like the Hell Level if a lot
1296 // of creatures are spawned (as found in the stress test)
1297 //UT_ASSERT(!"Failed to find valid square");
1298 return false;
1299 }
1300
1301 // Now, we pick an entry at random...
1302 i = rand_range(0, numvalid-1);
1303
1304 ox = validlist[i] & (MAP_WIDTH-1);
1305 oy = validlist[i] / MAP_WIDTH;
1306 return true;
1307 }
1308
1309 void
moveNPCs()1310 MAP::moveNPCs()
1311 {
1312 MOB *mob;
1313
1314 for (mob = getMobHead(); mob; mob = myMobSafeNext)
1315 {
1316 myMobSafeNext = mob->getNext();
1317
1318 // What if next is killed by doAI?
1319 // - handled by using myMobSafeNext (hack)
1320 // What if mob is killed by doAI?
1321 // - ignored since is last stage of processing
1322 // What if mob is killed by heartbeat?
1323 // - handled by returning false from doHeartbeat (unrobust)
1324 if (mob->isAvatar())
1325 continue; // Ignore the PC.
1326 // Heartbeat returns true if AI can run.
1327 if (mob->doHeartbeat())
1328 if (mob->doMovePrequel())
1329 mob->doAI();
1330 }
1331 }
1332
1333 int
getThreatLevel() const1334 MAP::getThreatLevel() const
1335 {
1336 int threatlevel = myDepth;
1337
1338 if (glbStressTest && !glbMapStats)
1339 return 50;
1340
1341 // Surface world doesn't have monsters.
1342 if (!threatlevel)
1343 return threatlevel;
1344
1345 if (MOB::getAvatar())
1346 {
1347 MOB *avatar = MOB::getAvatar();
1348
1349 if (avatar->isPolymorphed())
1350 {
1351 avatar = avatar->getBaseType();
1352 if (!avatar)
1353 {
1354 UT_ASSERT(!"No base type!");
1355 avatar = MOB::getAvatar();
1356 }
1357 }
1358 threatlevel += avatar->getHitDie() + avatar->getMagicDie();
1359 }
1360
1361 return threatlevel;
1362 }
1363
1364 int
getDesiredMobCount() const1365 MAP::getDesiredMobCount() const
1366 {
1367 if (!allowMobGeneration())
1368 return 0;
1369
1370 return getThreatLevel() / 2;
1371 }
1372
1373 void
doHeartbeat()1374 MAP::doHeartbeat()
1375 {
1376 // In all cases, update our smell watermark so we can get proper
1377 // gradients.
1378 ourAvatarSmellWatermark++;
1379 if (ourAvatarSmellWatermark == 65535)
1380 {
1381 // We overflowed our watermark. We clear our map and set it
1382 // to the defualt distance of 64. I wonder if anyone will notice
1383 // that every 65k moves all creatures become confused?
1384 ourAvatarSmellWatermark = 64;
1385 memset(ourAvatarSmell, 0xff, sizeof(u16) * MAP_WIDTH * MAP_HEIGHT);
1386 }
1387
1388 // Determine if we are in a proper phase.
1389 if (!speed_isheartbeatphase())
1390 return;
1391
1392 // Determine if our number of mobs makes sense for our
1393 // threat level...
1394 int spawnchance = 0;
1395 int desiredcount;
1396
1397 // Our probability of spawning will never go to zero. It will
1398 // attempt to maintain the proper number of mobs for this threat
1399 // level. If we have less than our desired number, we spawn
1400 // at 10% (one every 10 turns). If we have ten more than that number,
1401 // we spawn at 1%. Between that is interpolated.
1402 // The desired number is threatlevel / 2, maxed at 20.
1403 desiredcount = getDesiredMobCount();
1404
1405 if (desiredcount > 20) desiredcount = 20;
1406 if (myMobCount < desiredcount)
1407 spawnchance = 10;
1408 else if (myMobCount - 10 < desiredcount)
1409 spawnchance = 10 - (desiredcount - myMobCount);
1410 else
1411 spawnchance = 1;
1412
1413 if (!desiredcount)
1414 spawnchance = 0;
1415
1416 // if (1)
1417 if (rand_chance(spawnchance))
1418 {
1419 createAndPlaceNPC(true);
1420 }
1421
1422 // Run the heartbeat on all the items...
1423 ITEM *cur, *next;
1424
1425 for (cur = getItemHead(); cur; cur = next)
1426 {
1427 next = cur->getNext();
1428
1429 if (!cur->doHeartbeat(glbCurLevel, 0))
1430 {
1431 dropItem(cur);
1432 delete cur;
1433 }
1434 }
1435 }
1436
1437 ITEM *
getItem(int x,int y) const1438 MAP::getItem(int x, int y) const
1439 {
1440 ITEM *cur, *result = 0;
1441
1442 if (this == glbCurLevel)
1443 {
1444 // Use the item map.
1445 if (x < 0 || x >= MAP_WIDTH ||
1446 y < 0 || y >= MAP_HEIGHT)
1447 return 0;
1448 return ourItemPtrMap[y * MAP_WIDTH + x];
1449 }
1450
1451 // Because the drawn item is the top item, it is also the guy we
1452 // return here.
1453 for (cur = getItemHead(); cur; cur = cur->getNext())
1454 {
1455 if (cur->getX() == x && cur->getY() == y)
1456 result = cur;
1457 }
1458 return result;
1459 }
1460
1461 ITEM *
getItem(int x,int y,bool submerged) const1462 MAP::getItem(int x, int y, bool submerged) const
1463 {
1464 ITEM *cur, *result = 0;
1465
1466 if (this == glbCurLevel)
1467 {
1468 // Use the item map.
1469 if (x < 0 || x >= MAP_WIDTH ||
1470 y < 0 || y >= MAP_HEIGHT)
1471 return 0;
1472
1473 cur = ourItemPtrMap[y * MAP_WIDTH + x];
1474 if (cur && cur->isBelowGrade())
1475 {
1476 if (submerged)
1477 result = cur;
1478 }
1479 else
1480 {
1481 if (!submerged)
1482 result = cur;
1483 }
1484
1485 // TODO: Doesn't work for water walking on top of submerged
1486 // stuff!
1487 return result;
1488 }
1489
1490 // Because the drawn item is the top item, it is also the guy we
1491 // return here.
1492 for (cur = getItemHead(); cur; cur = cur->getNext())
1493 {
1494 if (cur->getX() == x && cur->getY() == y)
1495 {
1496 if (cur->isBelowGrade())
1497 {
1498 if (submerged)
1499 result = cur;
1500 }
1501 else
1502 {
1503 if (!submerged)
1504 result = cur;
1505 }
1506 }
1507 }
1508 return result;
1509 }
1510
1511 int
getItemStack(ITEMSTACK & stack,int x,int y,int submergecheck,bool checkmapped) const1512 MAP::getItemStack(ITEMSTACK &stack, int x, int y, int submergecheck,
1513 bool checkmapped) const
1514 {
1515 int numadded = 0;
1516 ITEM *item;
1517 bool subboolean = (submergecheck ? true : false);
1518
1519 // Because the drawn item is the top item, it is also the guy we
1520 // return here.
1521 for (item = getItemHead(); item; item = item->getNext())
1522 {
1523 if (item->getX() == x && item->getY() == y)
1524 {
1525 if (submergecheck < 0 || (subboolean == item->isBelowGrade()))
1526 {
1527 if (checkmapped && !item->isMapped())
1528 continue;
1529 stack.append(item);
1530 numadded++;
1531 }
1532 }
1533 }
1534
1535 return numadded;
1536 }
1537
1538 int
getPets(MOBSTACK & stack,MOB * owner,bool onlysensable) const1539 MAP::getPets(MOBSTACK &stack, MOB *owner, bool onlysensable) const
1540 {
1541 MOB *mob;
1542
1543 stack.clear();
1544
1545 if (!owner)
1546 return 0;
1547
1548 for (mob = myMobHead; mob; mob = mob->getNext())
1549 {
1550 if (mob == owner)
1551 continue;
1552
1553 if (mob->getMaster() == owner)
1554 {
1555 // Verify we can sense the mob.
1556 if (onlysensable)
1557 {
1558 if (!owner->canSense(mob))
1559 continue;
1560 }
1561 stack.append(mob);
1562 }
1563 }
1564
1565 return stack.entries();
1566 }
1567
1568 // If this is a bottle, and if it dropped into liquid, we want
1569 // to fill it with the liquid.
1570 // We preserve the blessed/curse status of the bottle.
1571 void
fillBottle(ITEM * item,int x,int y) const1572 MAP::fillBottle(ITEM *item, int x, int y) const
1573 {
1574 int tile;
1575
1576 if (item->getDefinition() != ITEM_BOTTLE)
1577 return;
1578
1579 tile = getTile(x, y);
1580 switch (tile)
1581 {
1582 case SQUARE_WATER:
1583 {
1584 // Convert to water.
1585 item->formatAndReport("%U <fill> with water.");
1586 item->setDefinition(ITEM_WATER);
1587 break;
1588 }
1589 case SQUARE_LAVA:
1590 {
1591 // Fill with lava.
1592 item->formatAndReport("%U <be> infused with fire.");
1593 item->setDefinition(ITEM::lookupMagicItem(MAGICTYPE_POTION,
1594 POTION_GREEKFIRE));
1595 break;
1596 }
1597 case SQUARE_ACID:
1598 {
1599 // Fill with acid.
1600 item->formatAndReport("%U <fill> with acid.");
1601 item->setDefinition(ITEM::lookupMagicItem(MAGICTYPE_POTION,
1602 POTION_ACID));
1603 break;
1604 }
1605 default:
1606 // Safe to do nothing.
1607 break;
1608 }
1609 }
1610
1611 void
acquireItem(ITEM * item,int x,int y,MOB * dropper,ITEM ** newitem,bool ignoregrade)1612 MAP::acquireItem(ITEM *item, int x, int y, MOB *dropper, ITEM **newitem, bool ignoregrade)
1613 {
1614 ITEM *last, *cur, *oldtopitem;
1615 bool settopitem = false;
1616 bool falldown = false;
1617 bool dropinliquid = false;
1618 BUF buf;
1619 int tile, trap;
1620
1621 if (newitem)
1622 *newitem = item;
1623
1624 // The item should already be removed from its own list.
1625 UT_ASSERT(!item->getNext());
1626
1627 myItemCount++;
1628 oldtopitem = 0;
1629 if (this == glbCurLevel)
1630 {
1631 // Impassable items remain on the top of the heap.
1632 if (!ourItemPtrMap[y * MAP_WIDTH + x] ||
1633 ourItemPtrMap[y * MAP_WIDTH + x]->getStackOrder() <= item->getStackOrder())
1634 {
1635 settopitem = true;
1636 oldtopitem = ourItemPtrMap[y * MAP_WIDTH + x];
1637 ourItemPtrMap[y * MAP_WIDTH + x] = item;
1638 }
1639 }
1640
1641 // Determine if the item is going below grade. If so,
1642 // generate the flavour text.
1643 tile = getTile(x, y);
1644 trap = glb_squaredefs[tile].trap;
1645 switch ((TRAP_NAMES) trap)
1646 {
1647 case TRAP_HOLE:
1648 {
1649 // Item falls to the next level.
1650 item->setPos(x, y);
1651 item->markMapped(false);
1652 if (item->fallInHole(this, false, dropper))
1653 {
1654 // Clear out the boulder from our top list.
1655 if (settopitem)
1656 {
1657 ourItemPtrMap[y * MAP_WIDTH + x] = oldtopitem;
1658 }
1659 // Callers should be warned we no longer exist.
1660 if (newitem)
1661 *newitem = 0;
1662 return;
1663 }
1664 break;
1665 }
1666 case TRAP_PIT:
1667 case TRAP_SPIKEDPIT:
1668 // Item falls into a pit.
1669 falldown = true;
1670 buf = MOB::formatToString("%U <fall> into a %B1.",
1671 0, item, 0, 0,
1672 glb_trapdefs[trap].name);
1673 break;
1674 case TRAP_TELEPORT:
1675 {
1676 item->setPos(x, y);
1677
1678 if (item->randomTeleport(this, false, dropper))
1679 {
1680 // That's the end of the story. The item's new position is
1681 // handled by its own call to acquireItem.
1682 if (settopitem)
1683 {
1684 ourItemPtrMap[y * MAP_WIDTH + x] = oldtopitem;
1685 }
1686 return;
1687 }
1688 break;
1689 }
1690 case TRAP_SMOKEVENT:
1691 case TRAP_POISONVENT:
1692 case TRAP_NONE:
1693 case NUM_TRAPS:
1694 // Do nothing.
1695 break;
1696 }
1697 // Switch on floor type:
1698 switch (tile)
1699 {
1700 case SQUARE_WATER:
1701 falldown = true;
1702 dropinliquid = true;
1703 buf = MOB::formatToString("%U <fall> into the water.",
1704 0, item, 0, 0);
1705 if (item->douse())
1706 {
1707 delete item;
1708
1709 if (newitem)
1710 *newitem = 0;
1711
1712 // Our top item map is now invalid.
1713 refreshPtrMaps();
1714 return;
1715 }
1716 break;
1717 case SQUARE_ACID:
1718 {
1719 // If we can dissolve: Good bye item!
1720 item->setPos(x, y);
1721 if (item->dissolve())
1722 {
1723 delete item;
1724
1725 if (newitem)
1726 *newitem = 0;
1727
1728 // Our top item map is now invalid.
1729 refreshPtrMaps();
1730 return;
1731 }
1732 falldown = true;
1733 dropinliquid = true;
1734 buf = MOB::formatToString("%U <fall> into the acid.",
1735 0, item, 0, 0);
1736 break;
1737 }
1738 case SQUARE_LAVA:
1739 // If we can burn: Good bye item!
1740 item->setPos(x, y);
1741 if (item->ignite())
1742 {
1743 delete item;
1744
1745 if (newitem)
1746 *newitem = 0;
1747
1748 // Our top item map is now invalid.
1749 refreshPtrMaps();
1750 return;
1751 }
1752 falldown = true;
1753 dropinliquid = true;
1754 buf = MOB::formatToString("%U <fall> into the lava.",
1755 0, item, 0, 0);
1756 break;
1757 }
1758
1759 if (dropinliquid)
1760 {
1761 if (item->hasIntrinsic(INTRINSIC_WATERWALK))
1762 falldown = false;
1763 }
1764
1765 if (!ignoregrade && falldown)
1766 {
1767 // Only report the fall down message if we are not
1768 // below grade.
1769 if (!item->isBelowGrade())
1770 {
1771 item->markBelowGrade(true);
1772
1773 reportMessage(buf, x, y);
1774 }
1775
1776 if (dropinliquid)
1777 {
1778 fillBottle(item, x, y);
1779 }
1780 }
1781 else if (!ignoregrade)
1782 {
1783 item->markBelowGrade(false);
1784 }
1785
1786 // Check to see if we should merge with an existing pile.
1787 for (cur = getItemHead(); cur; cur = cur->getNext())
1788 {
1789 if (cur->getX() == x && cur->getY() == y)
1790 {
1791 if (cur->canMerge(item))
1792 {
1793 cur->mergeStack(item);
1794 delete item;
1795 if (newitem)
1796 *newitem = cur;
1797 // Account for our over estimate:
1798 myItemCount--;
1799 if (settopitem)
1800 {
1801 ourItemPtrMap[y * MAP_WIDTH + x] = oldtopitem;
1802 }
1803 return;
1804 }
1805 }
1806 }
1807
1808 // We add the item where it fits according to its sort order.
1809 last = 0;
1810 for (cur = getItemHead(); cur; cur = cur->getNext())
1811 {
1812 if (cur->getStackOrder() > item->getStackOrder())
1813 break;
1814 last = cur;
1815 }
1816
1817 // We wish to insert before cur. Note cur & last may be null.
1818 item->setNext(cur);
1819 if (!last)
1820 {
1821 // Insert at beginning.
1822 myItemHead = item;
1823 }
1824 else
1825 {
1826 // Insert before cur.
1827 last->setNext(item);
1828 UT_ASSERT(last != item);
1829 }
1830
1831 item->setPos(x, y);
1832
1833 // Now, check to see if we have to fill a pit, or similar. NOTE:
1834 // this may delete the item!
1835 fillSquare(item, dropper, newitem);
1836 }
1837
1838 ITEM *
dropItem(int x,int y)1839 MAP::dropItem(int x, int y)
1840 {
1841 ITEM *item;
1842
1843 item = getItem(x, y);
1844 if (item)
1845 dropItem(item);
1846 return item;
1847 }
1848
1849 void
dropItem(ITEM * item)1850 MAP::dropItem(ITEM *item)
1851 {
1852 ITEM *cur, *prev = 0;
1853
1854 for (cur = getItemHead(); cur; cur = cur->getNext())
1855 {
1856 if (cur == item)
1857 {
1858 break;
1859 }
1860 prev = cur;
1861 }
1862
1863 UT_ASSERT(cur && cur == item);
1864 if (!cur || cur != item)
1865 return;
1866
1867 myItemCount--;
1868 if (prev)
1869 prev->setNext(item->getNext());
1870 else
1871 myItemHead = item->getNext();
1872
1873 item->markBelowGrade(false);
1874 item->setNext(0);
1875
1876 if (this == glbCurLevel &&
1877 ourItemPtrMap[item->getY() * MAP_WIDTH + item->getX()] == cur)
1878 {
1879 // TODO: Find next on stack!
1880 refreshPtrMaps();
1881 }
1882 }
1883
1884 void
buildFOV(int ox,int oy,int radx,int rady)1885 MAP::buildFOV(int ox, int oy, int radx, int rady)
1886 {
1887 int sx, ex;
1888 int sy, ey;
1889 int x, y, r;
1890
1891 // Reset FOV stash location...
1892 myFOVX = myFOVY = -1;
1893
1894 sx = ox - radx;
1895 if (sx < 0) sx = 0;
1896 ex = ox + radx;
1897 if (ex >= MAP_WIDTH) ex = MAP_WIDTH-1;
1898
1899 sy = oy - rady;
1900 if (sy < 0) sy = 0;
1901 ey = oy + rady;
1902 if (ey >= MAP_HEIGHT) ey = MAP_HEIGHT-1;
1903
1904 for (y = 0; y < sy; y++)
1905 {
1906 for (x = 0; x < MAP_WIDTH; x++)
1907 clearFlag(x, y, SQUAREFLAG_FOV);
1908 }
1909 for (; y <= ey; y++)
1910 {
1911 for (x = 0; x < sx; x++)
1912 clearFlag(x, y, SQUAREFLAG_FOV);
1913 for (; x <= ex; x++)
1914 {
1915 if (hasLOS(ox, oy, x, y))
1916 {
1917 setFlag(x, y, SQUAREFLAG_FOV, true);
1918 // Update our distance table...
1919 r = ABS(x - ox) + ABS(y - oy);
1920 updateAvatarSmell(x, y, -r);
1921 }
1922 else
1923 clearFlag(x, y, SQUAREFLAG_FOV);
1924 }
1925 for (; x < MAP_WIDTH; x++)
1926 clearFlag(x, y, SQUAREFLAG_FOV);
1927 }
1928 for (; y < MAP_HEIGHT; y++)
1929 {
1930 for (x = 0; x < MAP_WIDTH; x++)
1931 clearFlag(x, y, SQUAREFLAG_FOV);
1932 }
1933 myFOVX = ox;
1934 myFOVY = oy;
1935 }
1936
1937 bool
hasFOV(int x,int y) const1938 MAP::hasFOV(int x, int y) const
1939 {
1940 UT_ASSERT(x >= 0 && y >= 0);
1941 UT_ASSERT(x < MAP_WIDTH && y < MAP_HEIGHT);
1942 return getFlag(x, y, SQUAREFLAG_FOV) ? true : false;
1943 }
1944
1945 bool
allowDiggingDown() const1946 MAP::allowDiggingDown() const
1947 {
1948 if (myGlobalFlags & MAPFLAG_DIG)
1949 return true;
1950 return false;
1951 }
1952
1953 bool
allowMobGeneration() const1954 MAP::allowMobGeneration() const
1955 {
1956 if (myGlobalFlags & MAPFLAG_NEWMOBS)
1957 return true;
1958 return false;
1959 }
1960
1961 bool
allowItemGeneration() const1962 MAP::allowItemGeneration() const
1963 {
1964 if (myGlobalFlags & MAPFLAG_NOITEMS)
1965 return false;
1966 return true;
1967 }
1968
1969 bool
allowTeleportation() const1970 MAP::allowTeleportation() const
1971 {
1972 if (myGlobalFlags & MAPFLAG_NOTELE)
1973 return false;
1974 return true;
1975 }
1976
1977 void
markMapped(int ox,int oy,int radx,int rady,bool uselos)1978 MAP::markMapped(int ox, int oy, int radx, int rady, bool uselos)
1979 {
1980 int sx, ex;
1981 int sy, ey;
1982 int x, y;
1983 bool forcelit;
1984
1985 sx = ox - radx;
1986 if (sx < 0) sx = 0;
1987 ex = ox + radx;
1988 if (ex >= MAP_WIDTH) ex = MAP_WIDTH-1;
1989
1990 sy = oy - rady;
1991 if (sy < 0) sy = 0;
1992 ey = oy + rady;
1993 if (ey >= MAP_HEIGHT) ey = MAP_HEIGHT-1;
1994
1995 for (y = sy; y <= ey; y++)
1996 {
1997 for (x = sx; x <= ex; x++)
1998 {
1999 forcelit = false;
2000 if (uselos)
2001 {
2002 int diffx, diffy;
2003 diffx = ox - x;
2004 diffy = oy - y;
2005 if (diffx >= -1 && diffx <= 1 && diffy >= -1 && diffy <= 1)
2006 {
2007 forcelit = true;
2008 }
2009 }
2010 if (!uselos || (hasLOS(ox, oy, x, y) && (forcelit || isLit(x, y))))
2011 setFlag(x, y, SQUAREFLAG_MAPPED);
2012 }
2013 }
2014 }
2015
2016 bool
isTransparent(int x,int y) const2017 MAP::isTransparent(int x, int y) const
2018 {
2019 SQUARE_NAMES square;
2020
2021 // Read squares definition to learn if it blocks LOS.
2022 // This is more complicated than if it blocks movement!
2023 square = getTile(x, y);
2024
2025 bool forcetrans = false;
2026
2027 // If the avatar is in a tree trees don't block sight.
2028 // This is wrong as it lets creatures see through the trees
2029 // as soon as the avatar does. However, creatures still can't
2030 // see the avatar due to the in-tree rules for visibility, so this
2031 // just affects mob/mob and mob/pet which hopefully people don't
2032 // notice. Pet/mob is retconned by the avatar directing the pets.
2033 if (square == SQUARE_FOREST || square == SQUARE_FORESTFIRE)
2034 {
2035 if (MOB::getAvatar() && MOB::getAvatar()->hasIntrinsic(INTRINSIC_INTREE))
2036 {
2037 forcetrans = true;
2038 }
2039 }
2040 if (!forcetrans && !glb_squaredefs[square].transparent)
2041 return false;
2042
2043 // Smokey squares block LOS.
2044 if (getFlag(x, y, SQUAREFLAG_SMOKE))
2045 return false;
2046
2047 return true;
2048 }
2049
2050 bool
hasLOS(int x1,int y1,int x2,int y2) const2051 MAP::hasLOS(int x1, int y1, int x2, int y2) const
2052 {
2053 // Check to see if the start, (x1,y1), is the stashed FOV location
2054 // If so, we use the prebuilt FOV table.
2055 if (x1 == myFOVX && y1 == myFOVY)
2056 {
2057 return hasFOV(x2, y2);
2058 }
2059 else if (x2 == myFOVX && y2 == myFOVY)
2060 {
2061 // While hasLOS is not symmetric, we force it to be
2062 // in the case of FOV as that avoids hockeystick
2063 // paradoxes. (And is more efficient)
2064 return hasFOV(x1, y1);
2065 }
2066
2067 int cx, cy, x, y, acx, acy;
2068
2069 cx = x2 - x1;
2070 cy = y2 - y1;
2071 acx = abs(cx);
2072 acy = abs(cy);
2073
2074 // Check for trivial LOS (and divide by zero)
2075 if (acx <= 1 && acy <= 1)
2076 return true;
2077
2078 if (acx > acy)
2079 {
2080 int dx, dy, error;
2081
2082 dx = sign(cx);
2083 dy = sign(cy);
2084
2085 error = 0;
2086 error = acy;
2087 y = y1;
2088 for (x = x1 + dx; x != x2; x += dx)
2089 {
2090 if (error >= acx)
2091 {
2092 error -= acx;
2093 y += dy;
2094 }
2095 if (!isTransparent(x, y))
2096 return false;
2097
2098 error += acy;
2099 }
2100 }
2101 else
2102 {
2103 int dx, dy, error;
2104
2105 dx = sign(cx);
2106 dy = sign(cy);
2107
2108 error = 0;
2109 error = acx;
2110 x = x1;
2111 for (y = y1 + dy; y != y2; y += dy)
2112 {
2113 if (error >= acy)
2114 {
2115 error -= acy;
2116 x += dx;
2117 }
2118 if (!isTransparent(x, y))
2119 return false;
2120
2121 error += acx;
2122 }
2123 }
2124
2125 // Successful LOS.
2126 return true;
2127 }
2128
2129 bool
hasSlowLOS(int x1,int y1,int x2,int y2) const2130 MAP::hasSlowLOS(int x1, int y1, int x2, int y2) const
2131 {
2132 int cx, cy, dx, dy, x, y, acx, acy;
2133
2134 cx = x2 - x1;
2135 cy = y2 - y1;
2136 acx = abs(cx);
2137 acy = abs(cy);
2138
2139 // Check for trivial LOS (and divide by zero)
2140 if (acx <= 1 && acy <= 1)
2141 return true;
2142
2143 if (acx > acy)
2144 {
2145 float truey;
2146 float deltay;
2147 int fy;
2148
2149 deltay = cy / (float) abs(cx);
2150 dx = sign(cx);
2151 truey = y1 + deltay;
2152
2153 for (x = x1 + dx; x != x2; x += dx)
2154 {
2155 fy = (int)floor(truey + 0.5f);
2156 if (!isTransparent(x, fy))
2157 {
2158 return false;
2159 }
2160 truey += deltay;
2161 }
2162 }
2163 else
2164 {
2165 float truex;
2166 float deltax;
2167 int fx;
2168
2169 deltax = cx / (float) abs(cy);
2170 dy = sign(cy);
2171 truex = x1 + deltax;
2172
2173 for (y = y1 + dy; y != y2; y += dy)
2174 {
2175 fx = (int)floor(truex+0.5f);
2176 if (!isTransparent(fx, y))
2177 {
2178 return false;
2179 }
2180 truex += deltax;
2181 }
2182 }
2183
2184 // Successful LOS.
2185 return true;
2186 }
2187
2188 bool
hasDrunkLOS(int x1,int y1,int x2,int y2) const2189 MAP::hasDrunkLOS(int x1, int y1, int x2, int y2) const
2190 {
2191 int x, y;
2192 float truex, truey;
2193
2194 // This does somewhat more correct LOS by drawing a straight line
2195 // between ourselves and the destintation...
2196 // You have the direct floating point LOS line between two points.
2197 // An LOS exists if EITHER of the integer solutions of said line
2198 // are valid.
2199
2200 // Trivial cases...
2201 if (x1 == x2 && y1 == y2)
2202 {
2203 return true;
2204 }
2205
2206 // Step through...
2207 x = x1;
2208 y = y1;
2209 if (abs(x2 - x1) > abs(y2 - y1))
2210 {
2211 // X major, use truey.
2212 while (x != x2)
2213 {
2214 x += sign(x2 - x1);
2215 truey = (y2 - y1) * ( (float)(x - x1) / (x2 - x1) ) + y1;
2216 if (!isTransparent(x, (int)(truey)) &&
2217 !isTransparent(x, (int)(truey)+1))
2218 {
2219 return false;
2220 }
2221 }
2222 return true;
2223 }
2224 else
2225 {
2226 // Y major, use truex.
2227 while (y != y2)
2228 {
2229 y += sign(y2 - y1);
2230 truex = (x2 - x1) * ( (float)(y - y1) / (y2 - y1) ) + x1;
2231 if (!isTransparent((int)(truex), y) &&
2232 !isTransparent((int)(truex)+1, y))
2233 {
2234 return false;
2235 }
2236 }
2237 return true;
2238 }
2239 }
2240
2241 bool
hasManhattenLOS(int x1,int y1,int x2,int y2) const2242 MAP::hasManhattenLOS(int x1, int y1, int x2, int y2) const
2243 {
2244 int dx, dy;
2245
2246 // NOTE: The dominance matrix approach won't gain us much here
2247 // as it is basically a prebuilt left/right/diagonal LUT that
2248 // is only efficient if we restrict ourselves to 4x4.
2249
2250 // A square is visible if it has a LOS to the source.
2251 // We are saying anything with walk/swim/fly is visible.
2252 // We consider any manhatten walk between origin and dest a valid
2253 // LOS.
2254
2255 // Trivial...
2256 if (x1 == x2 && y1 == y2)
2257 {
2258 return true;
2259 }
2260
2261 // Beside is trivial...
2262 if (abs(x1 - x2) == 1 && y1 == y2)
2263 return true;
2264 if (abs(y1 - y2) == 1 && x1 == x2)
2265 return true;
2266
2267 // Find the direction...
2268 dx = sign(x2 - x1);
2269 dy = sign(y2 - y1);
2270
2271 if (dx)
2272 {
2273 // Check if visible in dx direction...
2274 if (isTransparent(x1 + dx, y1))
2275 {
2276 if (hasManhattenLOS(x1 + dx, y1, x2, y2))
2277 return true;
2278 }
2279 }
2280
2281 if (dy)
2282 {
2283 // Check if visible in dy direction...
2284 if (isTransparent(x1, y1 + dy))
2285 {
2286 if (hasManhattenLOS(x1, y1 + dy, x2, y2))
2287 return true;
2288 }
2289 }
2290
2291 // Failed in all directions.
2292 return false;
2293 }
2294
2295 bool
isLit(int x,int y) const2296 MAP::isLit(int x, int y) const
2297 {
2298 if (getFlag(x, y, SQUAREFLAG_LIT) ||
2299 getFlag(x, y, SQUAREFLAG_TEMPLIT))
2300 {
2301 return true;
2302 }
2303
2304 return false;
2305 }
2306
2307 bool
isDiggable(int x,int y) const2308 MAP::isDiggable(int x, int y) const
2309 {
2310 switch (getTile(x, y))
2311 {
2312 case SQUARE_EMPTY:
2313 case SQUARE_WALL:
2314 case SQUARE_SECRETDOOR:
2315 case SQUARE_VIEWPORT:
2316 return true;
2317 }
2318
2319 ITEM *item;
2320
2321 item = glbCurLevel->getItem(x, y);
2322 if (item && item->getDefinition() == ITEM_BOULDER)
2323 {
2324 return true;
2325 }
2326
2327 return false;
2328 }
2329
2330 void
updateLights()2331 MAP::updateLights()
2332 {
2333 int x, y;
2334 MOB *mob;
2335 ITEM *item;
2336
2337 // First, clear out all dynamic lights.
2338 for (y = 0; y < MAP_HEIGHT; y++)
2339 {
2340 for (x = 0; x < MAP_WIDTH; x++)
2341 {
2342 // Set the temp lit according to the square's status
2343 int tile;
2344
2345 tile = getTile(x, y);
2346 if (glb_squaredefs[tile].islit)
2347 setFlag(x, y, SQUAREFLAG_TEMPLIT);
2348 else
2349 clearFlag(x, y, SQUAREFLAG_TEMPLIT);
2350 }
2351 }
2352
2353 // Now run through our object & mob list looking for lights...
2354 for (mob = getMobHead(); mob; mob = mob->getNext())
2355 {
2356 if (mob->isLight())
2357 {
2358 applyTempLight(mob->getX(), mob->getY(), mob->getLightRadius());
2359 }
2360 }
2361
2362 for (item = getItemHead(); item; item = item->getNext())
2363 {
2364 if (item->isLight())
2365 {
2366 applyTempLight(item->getX(), item->getY(), item->getLightRadius());
2367 }
2368 }
2369 }
2370
2371 void
applyTempLight(int x,int y,int radius)2372 MAP::applyTempLight(int x, int y, int radius)
2373 {
2374 applyFlag(SQUAREFLAG_TEMPLIT, x, y, radius, true, true);
2375 }
2376
2377 void
applyFlag(SQUAREFLAG_NAMES flag,int cx,int cy,int radius,bool uselos,bool state)2378 MAP::applyFlag(SQUAREFLAG_NAMES flag, int cx, int cy, int radius,
2379 bool uselos, bool state)
2380 {
2381 int x, y;
2382
2383 for (y = cy - radius; y <= cy + radius; y++)
2384 {
2385 if (y < 0 || y >= MAP_WIDTH)
2386 continue;
2387 for (x = cx - radius; x <= cx + radius; x++)
2388 {
2389 if (x < 0 || x >= MAP_WIDTH)
2390 continue;
2391 if (!uselos || hasLOS(cx, cy, x, y))
2392 {
2393 setFlag(x, y, flag, state);
2394 }
2395 }
2396 }
2397 }
2398
2399 bool
isSquareInLockedRoom(int x,int y) const2400 MAP::isSquareInLockedRoom(int x, int y) const
2401 {
2402 int nx, ny;
2403 bool founddoor = false;
2404
2405 // Note this room might be non square.
2406 // Thus we want do do a flood fill of flyable squares,
2407 // looking for an open door.
2408 bool *checkedsquare;
2409
2410 checkedsquare = new bool [MAP_HEIGHT * MAP_WIDTH];
2411
2412 memset(checkedsquare, 0, MAP_HEIGHT * MAP_WIDTH);
2413
2414 PTRLIST<int> stack;
2415
2416 stack.append(x + y * MAP_WIDTH);
2417 checkedsquare[x+y*MAP_WIDTH] = true;
2418
2419 while (!founddoor && stack.entries())
2420 {
2421 int idx = stack.pop(), idxtest;
2422 SQUARE_NAMES tile;
2423
2424 nx = idx & (MAP_WIDTH-1);
2425 ny = idx / MAP_HEIGHT;
2426
2427 tile = getTile(nx, ny);
2428
2429 if (!isTileWall(tile))
2430 {
2431 // an empty door is a door.
2432 if (tile == SQUARE_CORRIDOR)
2433 founddoor = true;
2434
2435 // Spread out in 4 directions.
2436 if (nx)
2437 {
2438 idxtest = idx-1;
2439 if (!checkedsquare[idxtest])
2440 {
2441 checkedsquare[idxtest] = true;
2442 stack.append(idxtest);
2443 }
2444 }
2445 if (nx < MAP_WIDTH-1)
2446 {
2447 idxtest = idx+1;
2448 if (!checkedsquare[idxtest])
2449 {
2450 checkedsquare[idxtest] = true;
2451 stack.append(idxtest);
2452 }
2453 }
2454 if (ny)
2455 {
2456 idxtest = idx-MAP_WIDTH;
2457 if (!checkedsquare[idxtest])
2458 {
2459 checkedsquare[idxtest] = true;
2460 stack.append(idxtest);
2461 }
2462 }
2463 if (ny < MAP_HEIGHT-1)
2464 {
2465 idxtest = idx+MAP_WIDTH;
2466 if (!checkedsquare[idxtest])
2467 {
2468 checkedsquare[idxtest] = true;
2469 stack.append(idxtest);
2470 }
2471 }
2472 }
2473 else
2474 {
2475 // We stop here. But if it is a door, we are done.
2476 switch (tile)
2477 {
2478 case SQUARE_DOOR:
2479 case SQUARE_BLOCKEDDOOR:
2480 case SQUARE_OPENDOOR:
2481 founddoor = true;
2482 break;
2483 default:
2484 break;
2485 }
2486 }
2487 }
2488
2489 delete [] checkedsquare;
2490 return !founddoor;
2491 }
2492
2493 bool
findTile(SQUARE_NAMES tile,int & ox,int & oy)2494 MAP::findTile(SQUARE_NAMES tile, int &ox, int &oy)
2495 {
2496 int x, y;
2497
2498 // First, we find an instance of the given tile...
2499 for (y = 0; y < MAP_HEIGHT; y++)
2500 {
2501 for (x = 0; x < MAP_WIDTH; x++)
2502 {
2503 if (getTile(x, y) == tile)
2504 {
2505 // Success.
2506 ox = x;
2507 oy = y;
2508 return true;
2509 }
2510 }
2511 }
2512
2513 // Nothing worked, leave x & y unchanged and return false...
2514 return false;
2515 }
2516
2517 bool
findCloseTile(int & x,int & y,MOVE_NAMES move,bool allowmob,int maxradius)2518 MAP::findCloseTile(int &x, int &y, MOVE_NAMES move, bool allowmob,
2519 int maxradius)
2520 {
2521 int rad = 0;
2522 int ox, oy, d;
2523
2524 ox = x;
2525 oy = y;
2526
2527 // Search in ever increasing circles until we get a hit.
2528 for (rad = 0; rad < maxradius; rad++)
2529 {
2530 for (d = -rad; d <= rad; d++)
2531 {
2532 x = ox + rad;
2533 y = oy + d;
2534 if (x >= 0 && x < MAP_WIDTH &&
2535 y >= 0 && y < MAP_HEIGHT)
2536 if (canMove(x, y, move, allowmob))
2537 return true;
2538
2539 x = ox - rad;
2540 y = oy + d;
2541 if (x >= 0 && x < MAP_WIDTH &&
2542 y >= 0 && y < MAP_HEIGHT)
2543 if (canMove(x, y, move, allowmob))
2544 return true;
2545
2546 x = ox + d;
2547 y = oy + rad;
2548 if (x >= 0 && x < MAP_WIDTH &&
2549 y >= 0 && y < MAP_HEIGHT)
2550 if (canMove(x, y, move, allowmob))
2551 return true;
2552
2553 x = ox + d;
2554 y = oy - rad;
2555 if (x >= 0 && x < MAP_WIDTH &&
2556 y >= 0 && y < MAP_HEIGHT)
2557 if (canMove(x, y, move, allowmob))
2558 return true;
2559 }
2560 }
2561
2562 // Nothing found, fail.
2563 x = ox;
2564 y = oy;
2565 return false;
2566 }
2567
2568 MAP *
peekMapUp() const2569 MAP::peekMapUp() const
2570 {
2571 return myMapUp;
2572 }
2573
2574 MAP *
getMapUp(bool create)2575 MAP::getMapUp(bool create)
2576 {
2577 if (myMapUp)
2578 return myMapUp;
2579
2580 if (!create)
2581 return 0;
2582
2583 if (myDepth == 0)
2584 return 0; // Nothing up there!
2585
2586 myMapUp = new MAP(myDepth - 1, branchName());
2587
2588 myMapUp->setMapDown(this);
2589
2590 myMapUp->build(false);
2591 myMapUp->populate();
2592
2593 return myMapUp;
2594 }
2595
2596 MAP *
peekMapDown() const2597 MAP::peekMapDown() const
2598 {
2599 return myMapDown;
2600 }
2601
2602 MAP *
getMapDown(bool create)2603 MAP::getMapDown(bool create)
2604 {
2605 if (myMapDown)
2606 return myMapDown;
2607
2608 if (!create)
2609 return 0;
2610
2611 myMapDown = new MAP(myDepth + 1, branchName());
2612
2613 myMapDown->setMapUp(this);
2614
2615 myMapDown->build(false);
2616 myMapDown->populate();
2617
2618 return myMapDown;
2619 }
2620
2621 MAP *
peekMapBranch() const2622 MAP::peekMapBranch() const
2623 {
2624 return myMapBranch;
2625 }
2626
2627 BRANCH_NAMES
offshootName() const2628 MAP::offshootName() const
2629 {
2630 // THis is way more complicated than it should be.
2631 const int tridudedepth = 20;
2632 switch (branchName())
2633 {
2634 case BRANCH_MAIN:
2635 if (getDepth() == tridudedepth)
2636 return BRANCH_TRIDUDE;
2637 case BRANCH_TRIDUDE:
2638 if (getDepth() == tridudedepth)
2639 return BRANCH_MAIN;
2640 break;
2641
2642 case BRANCH_NONE:
2643 case NUM_BRANCHS:
2644 UT_ASSERT(!"Invalid source branch name");
2645 break;
2646 }
2647 return BRANCH_NONE;
2648 }
2649
2650 BRANCH_NAMES
branchName() const2651 MAP::branchName() const
2652 {
2653 return (BRANCH_NAMES) myBranch;
2654 }
2655
2656 MAP *
getMapBranch(bool create)2657 MAP::getMapBranch(bool create)
2658 {
2659 if (myMapBranch)
2660 return myMapBranch;
2661
2662 if (!create)
2663 return 0;
2664
2665 BRANCH_NAMES branch;
2666
2667 branch = offshootName();
2668 if (branch == BRANCH_NONE)
2669 {
2670 return 0;
2671 }
2672
2673 myMapBranch = new MAP(getDepth(), branch);
2674
2675 myMapBranch->setMapBranch(this);
2676
2677 myMapBranch->build(false);
2678 myMapBranch->populate();
2679
2680 return myMapBranch;
2681 }
2682
2683
2684 bool
describeSquare(int x,int y,bool blind,bool includeself)2685 MAP::describeSquare(int x, int y, bool blind, bool includeself)
2686 {
2687 bool describe, started;
2688 BUF buf;
2689 ITEMSTACK stack;
2690 MOB *mob;
2691 const char *prefix;
2692 const char *here;
2693 bool ownsquare = false;
2694 bool checksubmerged;
2695 int tile;
2696 int i, n;
2697
2698 if (x == MOB::getAvatar()->getX() && y == MOB::getAvatar()->getY())
2699 {
2700 here = " here. ";
2701 // We also want to explicitly map this square. Mapping may
2702 // occur after walking, so this avoids getting a know nothing
2703 // if you teleport, etc.
2704 markMapped(x, y, 0, 0, false);
2705 ownsquare = true;
2706 }
2707 else
2708 here = " there. ";
2709
2710 checksubmerged = false;
2711 if (ownsquare)
2712 if (MOB::getAvatar()->hasIntrinsic(INTRINSIC_INPIT))
2713 checksubmerged = true;
2714
2715 if (MOB::getAvatar()->hasIntrinsic(INTRINSIC_SUBMERGED))
2716 checksubmerged = true;
2717
2718 describe = false;
2719 started = false;
2720
2721 // MUST check mobs first as they don't chain!
2722
2723 // Check all MOBs. Do not report the avatar as we are the one doing
2724 // the describing..
2725 mob = getMob(x, y);
2726 if (mob && !mob->isAvatar() && MOB::getAvatar()->canSense(mob))
2727 {
2728 buf.strcat(mob->getDescription());
2729 describe = false;
2730 started = true;
2731 }
2732 else if (includeself && mob && mob->isAvatar())
2733 {
2734 if (blind ||
2735 (mob->hasIntrinsic(INTRINSIC_INVISIBLE) &&
2736 !mob->hasIntrinsic(INTRINSIC_SEEINVISIBLE)))
2737 buf.strcat("You feel yourself");
2738 else
2739 buf.strcat("You see yourself");
2740 describe = true;
2741 started = true;
2742 }
2743
2744 // We do not care if the items are mapped if they are on our own
2745 // square. Indeed, the act of stepping on them will trigger them
2746 // to be mapped.
2747 getItemStack(stack, x, y, checksubmerged, !ownsquare);
2748 n = stack.entries();
2749
2750 if (ownsquare)
2751 {
2752 // Map all the items.
2753 for (i = 0; i < n; i++)
2754 {
2755 stack(i)->markMapped();
2756 }
2757 }
2758
2759 // Print out all the items. We cap at 5 items and just
2760 // report many items.
2761 for (i = 0; i < n; i++)
2762 {
2763 if (!describe)
2764 {
2765 if (!ownsquare && (!hasFOV(x, y) || !isLit(x, y) || blind))
2766 {
2767 // We are working from memory.
2768 buf.strcat("You recall ");
2769 }
2770 else if (blind)
2771 {
2772 buf.strcat("You feel ");
2773 }
2774 else
2775 {
2776 buf.strcat("You see ");
2777 }
2778 }
2779 else
2780 {
2781 buf.strcat(", ");
2782 }
2783 describe = true;
2784
2785 if (n > 5)
2786 {
2787 buf.strcat("many items");
2788 break;
2789 }
2790 buf.strcat(stack(i)->getName());
2791 }
2792
2793 // Check if the square is mapped...
2794 // If not, the only thing we could have is the MOB report or
2795 // any known items.
2796 if (!getFlag(x, y, SQUAREFLAG_MAPPED))
2797 {
2798 if (!describe)
2799 {
2800 if (!started)
2801 buf.sprintf("You know nothing of this spot. ");
2802 }
2803 else
2804 {
2805 if (blind)
2806 {
2807 buf.strcat(here);
2808 }
2809 else
2810 buf.strcat(". ");
2811 }
2812
2813 msg_report(buf);
2814 return true;
2815 }
2816
2817 // Check for special tiles...
2818 if (!describe)
2819 {
2820 if (!ownsquare && (!hasFOV(x, y) || !isLit(x, y) || blind))
2821 prefix = "You recall ";
2822 else if (blind)
2823 prefix = "You feel ";
2824 else
2825 prefix = "You see ";
2826 }
2827 else
2828 prefix = ", ";
2829
2830 SMOKE_NAMES smoke = getSmoke(x, y);
2831
2832 if (smoke != SMOKE_NONE)
2833 {
2834 buf.strcat(prefix);
2835 buf.strcat(glb_smokedefs[smoke].name);
2836 prefix = " and ";
2837 describe = true;
2838 }
2839
2840 tile = getTile(x, y);
2841 if (glb_squaredefs[tile].description)
2842 {
2843 buf.strcat(prefix);
2844 buf.strcat(glb_squaredefs[tile].description);
2845 describe = true;
2846 }
2847
2848 if (describe)
2849 {
2850 if (blind)
2851 buf.strcat(here);
2852 else
2853 buf.strcat(". ");
2854 msg_report(buf);
2855 }
2856 else if (started)
2857 {
2858 msg_report(buf);
2859 }
2860
2861 return describe || started;
2862 }
2863
2864 bool
canMove(int x,int y,MOVE_NAMES movetype,bool allowmob,bool allowdoor,bool allowitem)2865 MAP::canMove(int x, int y, MOVE_NAMES movetype,
2866 bool allowmob, bool allowdoor, bool allowitem)
2867 {
2868 SQUARE_NAMES square;
2869
2870 // Cannot move outside of the map.
2871 if (x < 0 || x >= MAP_WIDTH ||
2872 y < 0 || y >= MAP_HEIGHT)
2873 return false;
2874
2875 if (!allowmob)
2876 {
2877 if (getMob(x, y))
2878 return false;
2879 }
2880
2881 if (!allowitem)
2882 {
2883 ITEM *item;
2884
2885 item = getItem(x, y);
2886 if (item && !item->isPassable())
2887 return false;
2888 }
2889
2890 square = getTile(x, y);
2891
2892 if (allowdoor)
2893 {
2894 if (square == SQUARE_DOOR)
2895 return true;
2896 }
2897 if (!(glb_squaredefs[getTile(x, y)].movetype & movetype))
2898 {
2899 return false;
2900 }
2901 return true;
2902 }
2903
2904 bool
canRayMove(int x,int y,MOVE_NAMES movetype,bool reflect,bool * didreflect)2905 MAP::canRayMove(int x, int y, MOVE_NAMES movetype, bool reflect,
2906 bool *didreflect)
2907 {
2908 MOB *mob;
2909
2910 if (didreflect)
2911 *didreflect = false;
2912
2913 // All rays terminate at map boundaries.
2914 if (x < 0 || x >= MAP_WIDTH)
2915 return false;
2916 if (y < 0 || y >= MAP_HEIGHT)
2917 return false;
2918
2919 if (!(glb_squaredefs[getTile(x, y)].movetype & movetype))
2920 {
2921 return false;
2922 }
2923
2924 // Non-reflecting beams ignore reflection.
2925 if (!reflect)
2926 return true;
2927
2928 // Check for mobs with reflection...
2929 mob = getMob(x, y);
2930 if (mob)
2931 {
2932 if (mob->hasIntrinsic(INTRINSIC_REFLECTION))
2933 {
2934 if (didreflect)
2935 *didreflect = true;
2936 return false;
2937 }
2938 }
2939 return true;
2940 }
2941
2942 void
throwItem(int x,int y,int dx,int dy,ITEM * missile,ITEM * stack,ITEM * launcher,MOB * thrower,const ATTACK_DEF * attack,bool ricochet)2943 MAP::throwItem(int x, int y, int dx, int dy,
2944 ITEM *missile, ITEM *stack, ITEM *launcher,
2945 MOB *thrower,
2946 const ATTACK_DEF *attack,
2947 bool ricochet)
2948 {
2949 int cansee;
2950 bool didhit;
2951 MOB *mob;
2952 int range;
2953 int oldoverlay;
2954 BUF buf;
2955
2956 x += dx;
2957 y += dy;
2958
2959 range = attack->range;
2960 oldoverlay = -1;
2961
2962 didhit = false;
2963 while (canMove(x, y, MOVE_STD_FLY) && range > 0)
2964 {
2965 cansee = glbCurLevel->getFlag(x, y, SQUAREFLAG_FOV);
2966
2967 // Erase old overlay...
2968 if (oldoverlay >= 0)
2969 {
2970 gfx_setoverlay(x - dx, y - dy, oldoverlay);
2971 }
2972 // And put the item in as the new overlay.
2973 if (cansee)
2974 {
2975 oldoverlay = gfx_getoverlay(x, y);
2976 gfx_setoverlay(x, y, missile->getTile());
2977 }
2978 else
2979 oldoverlay = -1;
2980
2981 if (cansee)
2982 {
2983 gfx_sleep(6);
2984 }
2985
2986 // Determine if we have a hit...
2987 mob = getMob(x, y);
2988
2989 if (mob)
2990 {
2991 mob->receiveAttack(attack, thrower, missile,
2992 launcher,
2993 ATTACKSTYLE_THROWN, &didhit);
2994
2995 if (didhit)
2996 {
2997 range = 0;
2998 break;
2999 }
3000 }
3001
3002 range--;
3003 if (range)
3004 {
3005 // Check for ricochet
3006 if (ricochet && !canMove(x+dx, y+dy, MOVE_STD_FLY))
3007 {
3008 bool didbounce = false;
3009 int ox = x, oy = y;
3010
3011 // Diagonal test, only obvious bounce.
3012 if (dx && dy)
3013 {
3014 if (canMove(x+dx,y,MOVE_STD_FLY)
3015 && !canMove(x,y+dy,MOVE_STD_FLY))
3016 {
3017 y += dy;
3018 dy = -dy;
3019 didbounce = true;
3020 }
3021 else if (!canMove(x+dx,y,MOVE_STD_FLY)
3022 && canMove(x,y+dy,MOVE_STD_FLY))
3023 {
3024 x += dx;
3025 dx = -dx;
3026 didbounce = true;
3027 }
3028 }
3029 else if (dx)
3030 {
3031 // Horizontal, bounce into diagonal provided
3032 // a path open.
3033 if (canMove(x, y+1, MOVE_STD_FLY))
3034 {
3035 didbounce = true;
3036 dy = 1;
3037 }
3038 if (canMove(x, y-1, MOVE_STD_FLY) && (!didbounce || rand_choice(2)))
3039 {
3040 didbounce = true;
3041 dy = -1;
3042 }
3043
3044 if (didbounce)
3045 {
3046 x += dx;
3047 dx = -dx;
3048 }
3049 }
3050 else if (dy)
3051 {
3052 // Vertical, bounce into diagonal provided
3053 // a path open.
3054 if (canMove(x+1, y, MOVE_STD_FLY))
3055 {
3056 didbounce = true;
3057 dx = 1;
3058 }
3059 if (canMove(x-1, y, MOVE_STD_FLY) && (!didbounce || rand_choice(2)))
3060 {
3061 didbounce = true;
3062 dx = -1;
3063 }
3064
3065 if (didbounce)
3066 {
3067 y += dy;
3068 dy = -dy;
3069 }
3070 }
3071
3072 if (didbounce)
3073 {
3074 buf = MOB::formatToString("%U <ricochet>.", 0, missile, 0, 0);
3075 reportMessage(buf, x+dx, y+dy);
3076
3077 // Clear out the old position.
3078 if (oldoverlay >= 0)
3079 {
3080 gfx_setoverlay(ox, oy, oldoverlay);
3081 oldoverlay = -1;
3082 }
3083 }
3084 }
3085 x += dx;
3086 y += dy;
3087 }
3088 }
3089
3090 if (range)
3091 {
3092 // Stopped short, undo last step so we don't leave it in a wall.
3093 x -= dx;
3094 y -= dy;
3095 }
3096
3097 // Clean up... We always have our old position here...
3098 if (oldoverlay >= 0)
3099 {
3100 gfx_setoverlay(x, y, oldoverlay);
3101 }
3102
3103 // Always get a break at the end of a throw.
3104 if (missile->doBreak(10, thrower, 0, this, x, y))
3105 return;
3106
3107 // If we did not break it, there is a chance we ided it.
3108 if (didhit && thrower && thrower->isAvatar())
3109 {
3110 if (!missile->isKnownEnchant())
3111 {
3112 int skill, chance;
3113
3114 skill = thrower->getWeaponSkillLevel(missile, ATTACKSTYLE_THROWN);
3115 chance = 5;
3116 if (skill >= 1)
3117 chance += 5;
3118 if (skill >= 2)
3119 chance += 10;
3120
3121 // About 20 hits to figure it out with 0 skill, 10 for
3122 // one level skill, and 5 for 2 levels skill.
3123 if (rand_chance(chance))
3124 {
3125 missile->markEnchantKnown();
3126 if (stack)
3127 stack->markEnchantKnown();
3128 buf = MOB::formatToString("%U <know> %r %Iu better. ", thrower, 0, 0, missile);
3129 // This is important enough to be broadcast.
3130 msg_announce(gram_capitalize(buf));
3131 }
3132 }
3133 }
3134
3135 // Drop the item on the ground.
3136 acquireItem(missile, x, y, thrower);
3137 }
3138
3139
3140 void
fireRay(int ox,int oy,int dx,int dy,int length,MOVE_NAMES movetype,bool dobounce,bool raycallback (int x,int y,bool final,void * data),void * data)3141 MAP::fireRay(int ox, int oy, int dx, int dy, int length,
3142 MOVE_NAMES movetype, bool dobounce,
3143 bool raycallback(int x, int y, bool final, void *data),
3144 void *data)
3145 {
3146 if (length <= 0)
3147 {
3148 if (raycallback)
3149 raycallback(ox, oy, true, data);
3150 return;
3151 }
3152
3153 // Technically a bool, but deprecated for
3154 // performance.
3155 int cansee;
3156 bool validreport = true;
3157 bool didreflect;
3158
3159 UT_ASSERT(dx || dy);
3160 if (!(dx || dy))
3161 {
3162 if (raycallback)
3163 raycallback(ox, oy, true, data);
3164 return;
3165 }
3166
3167 // See if we can pass...
3168 int x, y;
3169
3170 x = ox + dx;
3171 y = oy + dy;
3172
3173 // Determine if we can see the new ray square.
3174 // This could be out of bounds so adjust appropriately.
3175 if (x < 0 || x >= MAP_WIDTH ||
3176 y < 0 || y >= MAP_HEIGHT)
3177 {
3178 // Cansee is determined by old position. This may still
3179 // be out of bounds.
3180 if (ox >= 0 && ox < MAP_WIDTH && oy >= 0 && oy < MAP_HEIGHT)
3181 {
3182 cansee = false;
3183 validreport = true;
3184 }
3185 else
3186 {
3187 cansee = false;
3188 validreport = false;
3189 }
3190 }
3191 else
3192 {
3193 cansee = getFlag(x, y, SQUAREFLAG_FOV);
3194 // We always report to ox/oy
3195 if (ox < 0 || ox >= MAP_WIDTH ||
3196 oy < 0 || oy >= MAP_HEIGHT)
3197 {
3198 validreport = false;
3199 }
3200 else
3201 validreport = cansee;
3202 }
3203
3204 if (!canRayMove(x, y, movetype, dobounce, &didreflect))
3205 {
3206 // Can't move in this direction.
3207 if (!dobounce)
3208 {
3209 // Blow up in previous place!
3210 if (raycallback)
3211 raycallback(ox, oy, true, data);
3212 return; // Ray is done.
3213 }
3214
3215 if (validreport)
3216 reportMessage("The ray bounces.", ox, oy);
3217
3218 // If it reflected due to reflection, and we have an attacker,
3219 // make the appropriate note.
3220 // TODO: This isn't currently possible :<
3221
3222 // If the ray is in one direction, we merely negate the direction
3223 // and resend from the new position. This will then rehit
3224 // ox & oy. We decrement length here, resulting in the
3225 // bounce costing one hit.
3226 if (!dx || !dy)
3227 {
3228 // Report the message..
3229 fireRay(x, y, -dx, -dy, length-1,
3230 movetype, dobounce, raycallback, data);
3231 return;
3232 }
3233
3234 // We have a complicated bounce...
3235 bool canx, cany;
3236
3237 canx = canRayMove(ox+dx, oy, movetype, dobounce);
3238 cany = canRayMove(ox, oy+dy, movetype, dobounce);
3239
3240 if ((canx && cany) || (!canx && !cany))
3241 {
3242 // We have a corner, inside or outside, so bounce straight
3243 // back.
3244 fireRay(x, y, -dx, -dy, length-1,
3245 movetype, dobounce, raycallback, data);
3246 return;
3247 }
3248
3249 // We can have a bounce. We want to move into the square we
3250 // can move to. Thus, if it is canx, we'd like to go to the
3251 // new y pos, with the old x, reversing dy. Complicated? Yep.
3252 if (canx)
3253 fireRay(ox, y, dx, -dy, length-1,
3254 movetype, dobounce, raycallback, data);
3255 else // MUST be cany...
3256 fireRay(x, oy, -dx, dy, length-1,
3257 movetype, dobounce, raycallback, data);
3258
3259 // Did something, so return...
3260 return;
3261 }
3262
3263 // We are not blocked! Stash the old overlay, drop in the ray,
3264 // call the callback, and pause. Then progress...
3265 int oldoverlay = TILE_VOID;
3266
3267 if (cansee)
3268 {
3269 oldoverlay = gfx_getoverlay(x, y);
3270
3271 if (!dx)
3272 gfx_setoverlay(x, y, TILE_RAYPIPE);
3273 else if (!dy)
3274 gfx_setoverlay(x, y, TILE_RAYDASH);
3275 else if (dx * dy > 0)
3276 gfx_setoverlay(x, y, TILE_RAYBACKSLASH);
3277 else
3278 gfx_setoverlay(x, y, TILE_RAYSLASH);
3279 }
3280
3281 if (raycallback)
3282 {
3283 if (!raycallback(x, y, false, data))
3284 {
3285 // Ray requested to end at this person.
3286 return;
3287 }
3288 }
3289
3290 // Only sleep if the avatar can see the ray!
3291 if (cansee)
3292 gfx_sleep(6);
3293
3294 // Fire to the next level...
3295 fireRay(x, y, dx, dy, length-1,
3296 movetype, dobounce, raycallback, data);
3297
3298 // Restore the screen...
3299 if (cansee)
3300 gfx_setoverlay(x, y, oldoverlay);
3301 }
3302
3303 void
fireBall(int ox,int oy,int rad,bool includeself,bool ballcallback (int x,int y,bool final,void * data),void * data)3304 MAP::fireBall(int ox, int oy, int rad, bool includeself,
3305 bool ballcallback(int x, int y, bool final, void *data),
3306 void *data)
3307 {
3308 int dx, dy, diam, x, y;
3309 int *oldoverlay;
3310 bool canbeseen = false;
3311
3312 UT_ASSERT(rad >= 0);
3313
3314 diam = rad * 2 + 1;
3315 oldoverlay = new int[diam * diam];
3316
3317 // Draw the overlay...
3318 for (dx = -rad; dx <= rad; dx++)
3319 {
3320 x = ox + dx;
3321 if (x < 0) continue;
3322 if (x >= MAP_WIDTH) continue;
3323 for (dy = -rad; dy <= rad; dy++)
3324 {
3325 y = oy + dy;
3326 if (y < 0) continue;
3327 if (y >= MAP_HEIGHT) continue;
3328
3329 oldoverlay[dx + rad + (dy + rad) * diam] = gfx_getoverlay(x, y);
3330
3331 // Only draw the ball effect if we can see it.
3332 if (!hasFOV(x, y))
3333 continue;
3334
3335 if (!dx && !dy)
3336 continue;
3337
3338 canbeseen = true;
3339
3340 if (!dx)
3341 gfx_setoverlay(x, y, TILE_RAYDASH);
3342 else if (!dy)
3343 gfx_setoverlay(x, y, TILE_RAYPIPE);
3344 else if (dx * dy > 0)
3345 gfx_setoverlay(x, y, TILE_RAYSLASH);
3346 else
3347 gfx_setoverlay(x, y, TILE_RAYBACKSLASH);
3348 }
3349 }
3350
3351 // Apply the effect.
3352 for (dx = -rad; dx <= rad; dx++)
3353 {
3354 x = ox + dx;
3355 if (x < 0) continue;
3356 if (x >= MAP_WIDTH) continue;
3357 for (dy = -rad; dy <= rad; dy++)
3358 {
3359 y = oy + dy;
3360 if (y < 0) continue;
3361 if (y >= MAP_HEIGHT) continue;
3362
3363 if (!includeself && !dx && !dy)
3364 continue;
3365
3366 if (ballcallback)
3367 ballcallback(x, y, false, data);
3368 }
3369 }
3370
3371 // Sleep for the effect to be visible.
3372 if (canbeseen)
3373 gfx_sleep(15);
3374
3375 // Restore the overlay.
3376 for (dx = -rad; dx <= rad; dx++)
3377 {
3378 x = ox + dx;
3379 if (x < 0) continue;
3380 if (x >= MAP_WIDTH) continue;
3381 for (dy = -rad; dy <= rad; dy++)
3382 {
3383 y = oy + dy;
3384 if (y < 0) continue;
3385 if (y >= MAP_HEIGHT) continue;
3386
3387 if (!dx && !dy)
3388 continue;
3389
3390 gfx_setoverlay(x, y, oldoverlay[dx + rad + (dy + rad) * diam]);
3391 }
3392 }
3393
3394 delete [] oldoverlay;
3395 }
3396
3397 bool
knockbackMob(int tx,int ty,int dx,int dy,bool infinitemass,bool ignoremob)3398 MAP::knockbackMob(int tx, int ty, int dx, int dy, bool infinitemass,
3399 bool ignoremob)
3400 {
3401 // We now want to knock back the critter. This
3402 // may also knock back ourselves if we are smaller.
3403 // Note both myself and the critter may be dead now.
3404 MOB *caster, *target;
3405
3406 caster = glbCurLevel->getMob(tx - dx, ty - dy);
3407 target = glbCurLevel->getMob(tx, ty);
3408
3409 if (infinitemass)
3410 caster = 0;
3411
3412 // If we have both a caster and a target, knockback
3413 // is possible. If either died, or they are the same
3414 // person (think Baazl'bub here) no knock back occurs.
3415 if ((infinitemass && target) ||
3416 (caster && target && (caster != target)))
3417 {
3418 MOB *knockee;
3419 MOVE_NAMES move;
3420
3421 // Determine who flies back and in what direction.
3422 // The larger critter stays put. In a tie,
3423 // it is random.
3424 // However, if only one creature can
3425 // knockback, that creature moves,
3426
3427 // Determine if target can fly back.
3428 move = target->getMoveType();
3429 // We want to allow knocking into water because
3430 // we are very cruel.
3431 move = (MOVE_NAMES) (move | MOVE_SWIM);
3432
3433 if (!glbCurLevel->canMove(tx+dx, ty+dy, move,
3434 ignoremob, false, false))
3435 {
3436 target = 0;
3437 }
3438
3439 if (caster)
3440 {
3441 // Determine if caster can knock back
3442 move = caster->getMoveType();
3443 // We want to allow knocking into water because
3444 // we are very cruel.
3445 move = (MOVE_NAMES) (move | MOVE_SWIM);
3446
3447 if (!glbCurLevel->canMove(tx-2*dx, ty-2*dy, move,
3448 ignoremob, false, false))
3449 {
3450 caster = 0;
3451 }
3452 }
3453
3454 // Choose which one will knock back, ie, the
3455 // knockee.
3456 if (target &&
3457 (!caster ||
3458 (caster->getSize() > target->getSize()) ||
3459 (caster->getSize() == target->getSize()
3460 && rand_choice(2)))
3461 )
3462 {
3463 // The target is sent flying in dx/dy.
3464 knockee = target;
3465 }
3466 else
3467 {
3468 // The caster is sent backwards.
3469 knockee = caster;
3470 dx = -dx;
3471 dy = -dy;
3472 }
3473
3474 // If knockee was false, no knockback is
3475 // possible
3476 if (knockee)
3477 {
3478 // A valid move! Yay!
3479 tx = knockee->getX() + dx;
3480 ty = knockee->getY() + dy;
3481
3482 knockee->formatAndReport("%U <be> knocked back.");
3483
3484 knockee->move(tx, ty);
3485
3486 return true;
3487 }
3488 }
3489
3490 // Failed knockback
3491 return false;
3492 }
3493
3494 void
saveGlobal(SRAMSTREAM & os)3495 MAP::saveGlobal(SRAMSTREAM &os)
3496 {
3497 int x, y;
3498
3499 os.write(ourWindCounter, 32);
3500 if (ourWindCounter)
3501 {
3502 os.write(ourWindDX, 8);
3503 os.write(ourWindDY, 8);
3504 }
3505
3506 for (y = 0; y < MAP_HEIGHT; y++)
3507 {
3508 for (x = 0; x < MAP_WIDTH; x++)
3509 {
3510 ourMobGuiltyMap[y*MAP_WIDTH+x].save(os);
3511 }
3512 }
3513 }
3514
3515 void
loadGlobal(SRAMSTREAM & is)3516 MAP::loadGlobal(SRAMSTREAM &is)
3517 {
3518 int val;
3519 int x, y;
3520
3521 is.read(val, 32);
3522 ourWindCounter = val;
3523 if (ourWindCounter)
3524 {
3525 is.read(val, 8);
3526 ourWindDX = val;
3527 is.read(val, 8);
3528 ourWindDY = val;
3529 }
3530
3531 // We may be loading globals before we load any particular map.
3532 if (!ourMobGuiltyMap)
3533 ourMobGuiltyMap = new MOBREF[MAP_WIDTH * MAP_HEIGHT];
3534 for (y = 0; y < MAP_HEIGHT; y++)
3535 {
3536 for (x = 0; x < MAP_WIDTH; x++)
3537 {
3538 ourMobGuiltyMap[y*MAP_WIDTH+x].load(is);
3539 }
3540 }
3541 }
3542
3543 void
loadBranch(SRAMSTREAM & is,bool checkbranch)3544 MAP::loadBranch(SRAMSTREAM &is, bool checkbranch)
3545 {
3546 MAP *map;
3547 int cont;
3548
3549 // Read ourself.
3550 load(is, checkbranch);
3551
3552 // Read parents.
3553 map = this;
3554 while (1)
3555 {
3556 is.uread(cont, 8);
3557 if (cont)
3558 break;
3559 map->setMapUp(new MAP(0, BRANCH_NONE));
3560 map->getMapUp()->setMapDown(map);
3561 map = map->getMapUp(false);
3562
3563 map->load(is);
3564 }
3565
3566 // And the children
3567 map = this;
3568 while (1)
3569 {
3570 is.uread(cont, 8);
3571 if (cont)
3572 break;
3573 map->setMapDown(new MAP(0, BRANCH_NONE));
3574 map->getMapDown()->setMapUp(map);
3575 map = map->getMapDown(false);
3576 map->load(is);
3577 }
3578 }
3579
3580 void
saveBranch(SRAMSTREAM & os,bool savebranch)3581 MAP::saveBranch(SRAMSTREAM &os, bool savebranch)
3582 {
3583 MAP *map;
3584
3585 save(os, savebranch);
3586
3587 for (map = getMapUp(false); map; map = map->getMapUp(false))
3588 {
3589 os.write(0, 8);
3590 map->save(os);
3591 }
3592
3593 os.write(1, 8);
3594 for (map = getMapDown(false); map; map = map->getMapDown(false))
3595 {
3596 os.write(0, 8);
3597 map->save(os);
3598 }
3599
3600 os.write(1, 8);
3601 }
3602
3603 bool
save(SRAMSTREAM & os,bool savebranch)3604 MAP::save(SRAMSTREAM &os, bool savebranch)
3605 {
3606 int x, y, tile;
3607 ITEM *item;
3608 MOB *mob;
3609 MAP *comp;
3610
3611 // While we aren't exactly loading, we don't want the build to
3612 // trigger creation.
3613 ourIsLoading = true;
3614
3615 // Save our private data...
3616 os.write(myDepth, 8);
3617 os.write(mySeed, 32);
3618 os.write(myGlobalFlags, 8);
3619 os.write(myBranch, 8);
3620
3621 mySmokeStack.save(os);
3622
3623 comp = new MAP(myDepth, branchName());
3624 rand_setseed(mySeed);
3625 comp->build(true);
3626
3627 for (y = 0; y < MAP_HEIGHT; y++)
3628 {
3629 for (x = 0; x < MAP_WIDTH; x++)
3630 {
3631 tile = getTile(x, y);
3632 tile ^= comp->getTile(x, y);
3633 os.write(tile, 8);
3634 }
3635 }
3636 for (y = 0; y < MAP_HEIGHT; y++)
3637 {
3638 for (x = 0; x < MAP_WIDTH; x++)
3639 {
3640 tile = getRawFlag(x, y);
3641 // Clear out temporary flags that we don't care about,
3642 // as we will be rebuilding them anyways.
3643 tile &= ~ (SQUAREFLAG_TEMPLIT | SQUAREFLAG_FOV);
3644 tile ^= comp->getRawFlag(x, y);
3645 os.write(tile, 8);
3646 }
3647 }
3648
3649 // Write out our signposts. While we could likely compress well
3650 // against the comparison map, the only signpost heavy level
3651 // is the tutorial we don't care about compressing.
3652 if (mySignList)
3653 mySignList->save(os);
3654 else
3655 os.write(0, 8);
3656
3657 delete comp;
3658
3659 for (mob = getMobHead(); mob; mob = mob->getNext())
3660 {
3661 // Write that there is a mob...
3662 os.write(0, 8);
3663 // And the mob data...
3664 mob->save(os);
3665 }
3666 // Write no more mobs...
3667 os.write(1, 8);
3668
3669 for (item = getItemHead(); item; item = item->getNext())
3670 {
3671 // Write that there is an item...
3672 os.write(0, 8);
3673 // And the item data
3674 item->save(os);
3675 }
3676 // Write no more items...
3677 os.write(1, 8);
3678
3679 ourIsLoading = false;
3680
3681 // Now, once we are done, check for a branch.
3682 if (savebranch)
3683 {
3684 if (getMapBranch(false))
3685 {
3686 MAP *map = getMapBranch(false);
3687 os.write(1, 8);
3688
3689 map->saveBranch(os, false);
3690 }
3691 else
3692 {
3693 os.write(0, 8);
3694 }
3695 }
3696
3697 return true;
3698 }
3699
3700 bool
load(SRAMSTREAM & is,bool checkbranch)3701 MAP::load(SRAMSTREAM &is, bool checkbranch)
3702 {
3703 int x, y, tile, val;
3704
3705 ourIsLoading = true;
3706
3707 // Load private data...
3708 is.read(val, 8);
3709 myDepth = val;
3710 is.uread(val, 32);
3711 mySeed = val;
3712 is.uread(val, 8);
3713 myGlobalFlags = val;
3714 is.uread(val, 8);
3715 myBranch = val;
3716
3717 mySmokeStack.load(is);
3718
3719 // Rebuild according to our settings...
3720 rand_setseed(mySeed);
3721 build(true);
3722
3723 for (y = 0; y < MAP_HEIGHT; y++)
3724 {
3725 for (x = 0; x < MAP_WIDTH; x++)
3726 {
3727 is.uread(tile, 8);
3728 setTile(x, y, (SQUARE_NAMES) (tile ^ getTile(x, y)));
3729 }
3730 }
3731 for (y = 0; y < MAP_HEIGHT; y++)
3732 {
3733 for (x = 0; x < MAP_WIDTH; x++)
3734 {
3735 is.uread(tile, 8);
3736 setRawFlag(x, y, tile ^ getRawFlag(x, y));
3737 }
3738 }
3739
3740 // Load the signposts.
3741 delete mySignList;
3742 mySignList = new SIGNLIST;
3743 mySignList->load(is);
3744 if (!mySignList->entries())
3745 {
3746 // Don't bother to keep empty signpost lists.
3747 delete mySignList;
3748 mySignList = 0;
3749 }
3750
3751 // Load mobs...
3752 MOB *mob;
3753 int cont;
3754 while (1)
3755 {
3756 is.uread(cont, 8);
3757 if (cont)
3758 break;
3759 mob = MOB::load(is);
3760 registerMob(mob);
3761 }
3762
3763 // Load items...
3764 ITEM *item;
3765 while (1)
3766 {
3767 is.uread(cont, 8);
3768 if (cont)
3769 break;
3770 item = ITEM::load(is);
3771 acquireItem(item, item->getX(), item->getY(), 0, 0, true);
3772 }
3773
3774 ourIsLoading = false;
3775
3776 // Conservative...
3777 ourMapDirty = true;
3778
3779 // Having finished loading ourself, we check for a branch
3780 // and load that.
3781 if (checkbranch)
3782 {
3783 is.uread(val, 8);
3784 if (val)
3785 {
3786 MAP *branch;
3787
3788 branch = new MAP(0, BRANCH_NONE);
3789 setMapBranch(branch);
3790 branch->setMapBranch(this);
3791 branch->loadBranch(is, false);
3792 }
3793 }
3794
3795 return true;
3796 }
3797
3798 bool
verifyMobLocal() const3799 MAP::verifyMobLocal() const
3800 {
3801 MOB *mob;
3802 ITEM *item;
3803 bool valid = true;
3804
3805 for (mob = getMobHead(); mob; mob = mob->getNext())
3806 {
3807 valid &= mob->verifyMob();
3808 }
3809
3810 for (item = getItemHead(); item; item = item->getNext())
3811 {
3812 valid &= item->verifyMob();
3813 }
3814
3815 return valid;
3816 }
3817
3818 bool
verifyMob() const3819 MAP::verifyMob() const
3820 {
3821 // Recurse in both directions, up and down.
3822 const MAP *map;
3823 bool valid = true;
3824
3825 for (map = this; map; map = map->peekMapUp())
3826 {
3827 valid &= map->verifyMobLocal();
3828 }
3829 for (map = peekMapDown(); map; map = map->peekMapDown())
3830 {
3831 valid &= map->verifyMobLocal();
3832 }
3833
3834 return valid;
3835 }
3836
3837 bool
verifyCounterGoneLocal(INTRINSIC_COUNTER * counter) const3838 MAP::verifyCounterGoneLocal(INTRINSIC_COUNTER *counter) const
3839 {
3840 MOB *mob;
3841 ITEM *item;
3842 bool valid = true;
3843
3844 for (mob = getMobHead(); mob; mob = mob->getNext())
3845 {
3846 valid &= mob->verifyCounterGone(counter);
3847 }
3848
3849 for (item = getItemHead(); item; item = item->getNext())
3850 {
3851 valid &= item->verifyCounterGone(counter);
3852 }
3853
3854 return valid;
3855 }
3856
3857 bool
verifyCounterGone(INTRINSIC_COUNTER * counter) const3858 MAP::verifyCounterGone(INTRINSIC_COUNTER *counter) const
3859 {
3860 // Recurse in both directions, up and down.
3861 const MAP *map;
3862 bool valid = true;
3863
3864 for (map = this; map; map = map->peekMapUp())
3865 {
3866 valid &= map->verifyCounterGoneLocal(counter);
3867 }
3868 for (map = peekMapDown(); map; map = map->peekMapDown())
3869 {
3870 valid &= map->verifyCounterGoneLocal(counter);
3871 }
3872
3873 return valid;
3874 }
3875
3876 void
digHole(int x,int y,MOB * digger)3877 MAP::digHole(int x, int y, MOB *digger)
3878 {
3879 SQUARE_NAMES tile, newtile;
3880
3881 UT_ASSERT(x >= 0 && x < MAP_WIDTH);
3882 UT_ASSERT(y >= 0 && y < MAP_HEIGHT);
3883
3884 // If we don't allow digging in this level, prohibit it.
3885 if (!allowDiggingDown())
3886 {
3887 if (digger)
3888 {
3889 digger->reportMessage("The ground on this level is to hard to dig deep!");
3890 }
3891 digPit(x, y, digger);
3892 return;
3893 }
3894
3895 // Determine type at this square...
3896 tile = getTile(x, y);
3897 switch (tile)
3898 {
3899 case SQUARE_FLOOR:
3900 case SQUARE_FLOORPIT:
3901 newtile = SQUARE_FLOORHOLE;
3902 break;
3903 case SQUARE_CORRIDOR:
3904 case SQUARE_PATHPIT:
3905 newtile = SQUARE_PATHHOLE;
3906 break;
3907 case SQUARE_FLOORHOLE:
3908 case SQUARE_PATHHOLE:
3909 {
3910 // Already a hole here!
3911 if (digger)
3912 {
3913 digger->formatAndReport("%U slightly <enlarge> the existing hole.");
3914 }
3915 return;
3916 }
3917 default:
3918 newtile = tile;
3919 break;
3920 }
3921
3922 if (newtile != tile)
3923 {
3924 // You actuallly dig...
3925 setTile(x, y, newtile);
3926 setGuiltyMob(x, y, digger);
3927 // drop the items before falling to the new level, in case avatar is
3928 // the digger
3929 dropItems(x, y, digger);
3930 if (digger)
3931 {
3932 digger->formatAndReport("%U <dig> a hole in the floor.");
3933
3934 // See if the digger should fall through...
3935 MAP *nextmap;
3936 int nx, ny;
3937 nextmap = getMapDown();
3938
3939 if (nextmap &&
3940 nextmap->isUnlocked() &&
3941 nextmap->findRandomLoc(nx, ny, digger->getMoveType(), false,
3942 digger->getSize() >= SIZE_GARGANTUAN,
3943 false, false, false, true))
3944 {
3945 digger->formatAndReport("%U <fall> through the hole in the ground!");
3946
3947 unregisterMob(digger);
3948
3949 if (!digger->move(nx, ny, true))
3950 return;
3951
3952 nextmap->registerMob(digger);
3953
3954 if (digger->isAvatar())
3955 {
3956 changeCurrentLevel(nextmap);
3957 }
3958 }
3959 else
3960 {
3961 digger->formatAndReport("%U <find> the bottom of the hole blocked.");
3962 }
3963 }
3964
3965 }
3966 else
3967 {
3968 // Can't dig here.
3969 if (digger)
3970 {
3971 digger->formatAndReport("%U <dig> in vain.");
3972 }
3973 }
3974 }
3975
3976 void
digPit(int x,int y,MOB * digger)3977 MAP::digPit(int x, int y, MOB *digger)
3978 {
3979 SQUARE_NAMES tile, newtile;
3980
3981 UT_ASSERT(x >= 0 && x < MAP_WIDTH);
3982 UT_ASSERT(y >= 0 && y < MAP_HEIGHT);
3983
3984 // Determine type at this square...
3985 tile = getTile(x, y);
3986 switch (tile)
3987 {
3988 case SQUARE_FLOOR:
3989 newtile = SQUARE_FLOORPIT;
3990 break;
3991 case SQUARE_CORRIDOR:
3992 case SQUARE_GRASS:
3993 newtile = SQUARE_PATHPIT;
3994 break;
3995 case SQUARE_FLOORPIT:
3996 case SQUARE_PATHPIT:
3997 {
3998 // Already a hole here!
3999 setGuiltyMob(x, y, digger);
4000 if (digger)
4001 {
4002 digger->formatAndReport("%U slightly <enlarge> the existing pit.");
4003
4004 // If we are in the pit, we sink with it.
4005 if (digger->getX() == x && digger->getY() == y)
4006 {
4007 digger->setIntrinsic(INTRINSIC_INPIT);
4008 }
4009
4010 // Anyone on the square will fall down again due to
4011 // the widening...
4012 dropMobs(x, y, true, digger);
4013 }
4014 return;
4015 }
4016 default:
4017 newtile = tile;
4018 break;
4019 }
4020
4021 if (newtile != tile)
4022 {
4023 // You actuallly dig...
4024 setTile(x, y, newtile);
4025 setGuiltyMob(x, y, digger);
4026 if (digger)
4027 {
4028 digger->formatAndReport("%U <dig> a pit in the floor.");
4029
4030 if (digger->getX() == x && digger->getY() == y)
4031 {
4032 digger->setIntrinsic(INTRINSIC_INPIT);
4033 }
4034 }
4035
4036 dropItems(x, y, digger);
4037 dropMobs(x, y, true, digger);
4038 }
4039 else
4040 {
4041 // Can't dig here.
4042 if (digger)
4043 {
4044 digger->formatAndReport("%U <dig> in vain.");
4045 }
4046 }
4047 }
4048
4049 bool
digSquare(int x,int y,MOB * digger)4050 MAP::digSquare(int x, int y, MOB *digger)
4051 {
4052 SQUARE_NAMES tile;
4053
4054 UT_ASSERT(x >= 0 && x < MAP_WIDTH);
4055 UT_ASSERT(y >= 0 && y < MAP_HEIGHT);
4056
4057 // Determine type at this square...
4058 tile = getTile(x, y);
4059 switch (tile)
4060 {
4061 case SQUARE_EMPTY:
4062 case SQUARE_WALL:
4063 {
4064 setTile(x, y, SQUARE_CORRIDOR);
4065 break;
4066 }
4067
4068 case SQUARE_SECRETDOOR:
4069 {
4070 reportMessage(
4071 "Stone falls away, revealing a secret door.",
4072 x, y);
4073 setTile(x, y, SQUARE_DOOR);
4074 return false;
4075 }
4076
4077 case SQUARE_VIEWPORT:
4078 {
4079 reportMessage("The glass wall shatters.", x, y);
4080
4081 setTile(x, y, SQUARE_BROKENVIEWPORT);
4082
4083 // Ends the bolt
4084 return false;
4085 }
4086
4087 case SQUARE_DOOR:
4088 case SQUARE_BLOCKEDDOOR:
4089 {
4090 // Can't tunnel through these!
4091 return false;
4092 }
4093
4094 default:
4095 break;
4096 }
4097
4098 ITEM *item;
4099
4100 item = getItem(x, y);
4101 if (item && item->getDefinition() == ITEM_BOULDER)
4102 {
4103 item->formatAndReport("%U <disintegrate>.");
4104 // Destroy the item.
4105 dropItem(item);
4106 delete item;
4107 }
4108
4109 // Can still keep tunneling.
4110 return true;
4111 }
4112
4113 void
growForest(int x,int y,MOB * grower)4114 MAP::growForest(int x, int y, MOB *grower)
4115 {
4116 SQUARE_NAMES tile;
4117
4118 UT_ASSERT(x >= 0 && x < MAP_WIDTH);
4119 UT_ASSERT(y >= 0 && y < MAP_HEIGHT);
4120
4121 // Determine type at this square...
4122 tile = getTile(x, y);
4123 if (glb_squaredefs[tile].invulnerable)
4124 {
4125 if (grower)
4126 grower->formatAndReport("The trees cannot gain a roothold and quickly collapse.");
4127 return;
4128 }
4129
4130 if (glb_squaredefs[tile].movetype == MOVE_SWIM)
4131 {
4132 if (grower)
4133 grower->formatAndReport("The trees lack ground for their roots and quickly collapse.");
4134 return;
4135 }
4136
4137 if (glb_squaredefs[tile].movetype != MOVE_WALK)
4138 {
4139 if (grower)
4140 grower->formatAndReport("The trees lack room to grow and quickly collapse.");
4141 return;
4142 }
4143
4144 // Check to see if it is already a forest
4145 if (tile == SQUARE_FOREST)
4146 {
4147 if (grower)
4148 grower->formatAndReport("The forest appears a bit denser.");
4149 return;
4150 }
4151
4152 if (tile == SQUARE_FORESTFIRE)
4153 {
4154 if (grower)
4155 grower->formatAndReport("More fuel is added to the fire.");
4156 return;
4157 }
4158
4159 // You actuallly grow...
4160 setTile(x, y, SQUARE_FOREST);
4161 setGuiltyMob(x, y, grower);
4162 }
4163
4164 void
douseSquare(int x,int y,bool holy,MOB * douser)4165 MAP::douseSquare(int x, int y, bool holy, MOB *douser)
4166 {
4167 MOB *victim = getMob(x, y);
4168 bool didanything = false;
4169 MOBREF dref;
4170
4171 dref.setMob(douser);
4172
4173 // Check to see if we upconvert smoke into acid.
4174 if (getSmoke(x, y) == SMOKE_ACID)
4175 {
4176 reportMessage("The water reacts violently with the acid.", x, y);
4177 setSmoke(x, y, SMOKE_STEAM, douser);
4178 }
4179
4180 if (victim)
4181 victim->submergeItemEffects(SQUARE_WATER);
4182
4183 // Wet everything on this square.
4184 {
4185 ITEMSTACK items;
4186 int i;
4187
4188 getItemStack(items, x, y);
4189 for (i = 0; i < items.entries(); i++)
4190 {
4191 if (items(i)->douse(dref.getMob()))
4192 {
4193 dropItem(items(i));
4194 delete items(i);
4195 }
4196 }
4197 }
4198
4199 if (victim && victim->getDefinition() == MOB_FIREELEMENTAL)
4200 {
4201 victim->receiveDamage(ATTACK_DOUSEFIREELEMENTAL,
4202 dref.getMob(), 0, 0, ATTACKSTYLE_MISC);
4203 // Create steam.
4204 glbCurLevel->setSmoke(x, y, SMOKE_STEAM, dref.getMob());
4205 didanything = true;
4206 }
4207
4208 if (!didanything && victim && victim->getMobType() == MOBTYPE_UNDEAD)
4209 {
4210 if (holy)
4211 {
4212 // Holy water burns undead.
4213 victim->receiveDamage(ATTACK_HOLYWATER,
4214 dref.getMob(), 0, 0, ATTACKSTYLE_MISC);
4215 didanything = true;
4216 }
4217 }
4218
4219 // Put out fire. Ben Shadwick actually tried this,
4220 // and failed. Shame accrues with me!
4221 if (!didanything && victim && victim->hasIntrinsic(INTRINSIC_AFLAME))
4222 {
4223 victim->formatAndReport("The water puts out %R flames.");
4224 victim->clearIntrinsic(INTRINSIC_AFLAME);
4225 didanything = true;
4226 }
4227
4228 // Hmm... Should water put out a forest fire?
4229 // If we decide not to, make sure we freezeSquare inside downPour
4230 if (glbCurLevel->getTile(x, y) == SQUARE_FORESTFIRE)
4231 {
4232 glbCurLevel->reportMessage("The water douses the forest fire.",
4233 x, y);
4234 glbCurLevel->setTile(x, y, SQUARE_FOREST);
4235 setGuiltyMob(x, y, dref.getMob());
4236 glbCurLevel->setSmoke(x, y, SMOKE_STEAM, dref.getMob());
4237 didanything = true;
4238 }
4239
4240 // If this was a lava square, steam will be generated.
4241 if (glbCurLevel->getTile(x, y) == SQUARE_LAVA)
4242 {
4243 glbCurLevel->reportMessage("The water vaporizes on contact with the lava.",
4244 x, y);
4245 glbCurLevel->setSmoke(x, y, SMOKE_STEAM, dref.getMob());
4246 didanything = true;
4247 }
4248
4249 // Soak the creature.
4250 if (victim && !didanything)
4251 {
4252 victim->formatAndReport("%U <be> soaked with water.");
4253 }
4254 }
4255
4256 void
downPour(int x,int y,MOB * pourer)4257 MAP::downPour(int x, int y, MOB *pourer)
4258 {
4259 SQUARE_NAMES tile;
4260 BUF buf;
4261 MOBREF dref;
4262
4263 UT_ASSERT(x >= 0 && x < MAP_WIDTH);
4264 UT_ASSERT(y >= 0 && y < MAP_HEIGHT);
4265
4266 dref.setMob(pourer);
4267
4268 // Directly apply water to the square, this will damage fire eles,
4269 // etc.
4270 douseSquare(x, y, false, dref.getMob());
4271
4272 // Determine type at this square...
4273 tile = getTile(x, y);
4274
4275 if (tile == SQUARE_LAVA)
4276 {
4277 // While we don't let normal water extinguish lava, this is
4278 // a lot of water.
4279 freezeSquare(x, y, dref.getMob());
4280 }
4281
4282 switch ((TRAP_NAMES) glb_squaredefs[tile].trap)
4283 {
4284 case TRAP_TELEPORT:
4285 {
4286 int tx, ty;
4287
4288 reportMessage("Water disappears into the teleporter.", x, y);
4289 if (findRandomLoc(tx, ty, MOVE_STD_FLY,
4290 true, false, true, true, true, false))
4291 {
4292 // We prohibit landing on a teleporter as I'm paranoid
4293 // of infinite recursion
4294 if (glb_squaredefs[getTile(tx,ty)].trap != TRAP_TELEPORT)
4295 {
4296 downPour(tx, ty, dref.getMob());
4297 }
4298 }
4299 break;
4300 }
4301 case TRAP_HOLE:
4302 reportMessage("Water flows down the hole.", x, y);
4303 // TODO downpour on next level!
4304 break;
4305 case TRAP_PIT:
4306 case TRAP_SPIKEDPIT:
4307 floodSquare(SQUARE_WATER, x, y, dref.getMob());
4308 break;
4309 case TRAP_SMOKEVENT:
4310 case TRAP_POISONVENT:
4311 case TRAP_NONE:
4312 case NUM_TRAPS:
4313 // Do nothing.
4314 break;
4315 }
4316
4317 }
4318
4319 void
floodSquare(SQUARE_NAMES newtile,int x,int y,MOB * flooder)4320 MAP::floodSquare(SQUARE_NAMES newtile, int x, int y, MOB *flooder)
4321 {
4322 SQUARE_NAMES tile;
4323 MOB *mob;
4324 BUF buf;
4325
4326 UT_ASSERT(x >= 0 && x < MAP_WIDTH);
4327 UT_ASSERT(y >= 0 && y < MAP_HEIGHT);
4328
4329 // Determine type at this square...
4330 tile = getTile(x, y);
4331
4332 if (newtile != tile)
4333 {
4334 const char *desc;
4335 const char *liquid = "UNKNOWN LIQUID";
4336
4337 desc = glb_squaredefs[tile].description;
4338 if (!desc)
4339 desc = "the floor";
4340
4341 switch (newtile)
4342 {
4343 case SQUARE_LAVA:
4344 liquid = "Lava";
4345 break;
4346 case SQUARE_WATER:
4347 liquid = "Water";
4348 break;
4349 case SQUARE_ACID:
4350 liquid = "Acid";
4351 break;
4352 default:
4353 UT_ASSERT(!"Unknown liquid");
4354 break;
4355 }
4356
4357 // Determine if the flood is successful. It fails
4358 // if the current tile is invulnerable.
4359 if (glb_squaredefs[tile].invulnerable)
4360 {
4361 newtile = tile;
4362 buf.sprintf("%s floods %s, leaving it unchanged.", liquid, desc);
4363 }
4364 else
4365 {
4366 // You actuallly flood...
4367 setTile(x, y, newtile);
4368 setGuiltyMob(x, y, flooder);
4369 buf.sprintf("%s floods %s.", liquid, desc);
4370 }
4371 reportMessage(buf, x, y);
4372 }
4373
4374 // Finish submerging. We recheck newtile == tile
4375 // as it may have been made equal by a failed flood.
4376 if (newtile != tile)
4377 {
4378 // Ensure anyone with IN_PIT intrinsic gets SUBMERGED.
4379 mob = getMob(x, y);
4380 if (mob)
4381 {
4382 if (mob->hasIntrinsic(INTRINSIC_INPIT))
4383 {
4384 mob->clearIntrinsic(INTRINSIC_INPIT);
4385 mob->setIntrinsic(INTRINSIC_SUBMERGED);
4386 }
4387 }
4388
4389 // Apply drop mobs to anyone still standing.
4390 dropMobs(x, y, true, flooder, true);
4391 dropItems(x, y, flooder);
4392 }
4393 }
4394
4395
4396 void
dropItems(int x,int y,MOB * revealer)4397 MAP::dropItems(int x, int y, MOB *revealer)
4398 {
4399 int tile, trap;
4400 bool drop = false;
4401 bool dropintoliquid = false;
4402 bool dropintolava = false;
4403 bool dropintoacid = false;
4404 bool dropintowater = false;
4405 bool dropintohole = false;
4406 const char *dropname = "";
4407 ITEM *item, *next;
4408 ITEM *squarefiller = 0;
4409 BUF buf;
4410
4411 tile = getTile(x, y);
4412 trap = glb_squaredefs[tile].trap;
4413 switch ((TRAP_NAMES) trap)
4414 {
4415 case TRAP_HOLE:
4416 drop = true;
4417 dropintohole = true;
4418 break;
4419 case TRAP_PIT:
4420 case TRAP_SPIKEDPIT:
4421 drop = true;
4422 dropname = glb_trapdefs[trap].name;
4423 break;
4424 case TRAP_TELEPORT:
4425 // Random teleport is handled in acquireItem.
4426 break;
4427 case TRAP_POISONVENT:
4428 case TRAP_SMOKEVENT:
4429 case TRAP_NONE:
4430 case NUM_TRAPS:
4431 // Nothing to do.
4432 break;
4433 }
4434
4435 switch (tile)
4436 {
4437 case SQUARE_WATER:
4438 drop = true;
4439 dropname = "water";
4440 dropintoliquid = true;
4441 dropintowater = true;
4442 break;
4443
4444 case SQUARE_ACID:
4445 drop = true;
4446 dropname = "acid";
4447 dropintoliquid = true;
4448 dropintoacid = true;
4449 break;
4450
4451 case SQUARE_LAVA:
4452 drop = true;
4453 dropname = "lava";
4454 dropintoliquid = true;
4455 dropintolava = true;
4456 break;
4457 }
4458
4459 if (!drop)
4460 return;
4461
4462 // Now, drop all the items here into the pit:
4463 for (item = getItemHead(); item; item = next)
4464 {
4465 next = item->getNext();
4466 if (item->getX() == x && item->getY() == y)
4467 {
4468 // If we are dealing with acid, we should dissolve what
4469 // we can.
4470 if ((dropintoacid && item->dissolve()) ||
4471 (dropintolava && item->ignite()) ||
4472 (dropintowater && item->douse()))
4473 {
4474 // Yes, this also axes waterwalk items as they dissolve
4475 // as they float in the acid!
4476 // Only exception would be flying items.
4477 dropItem(item);
4478 delete item;
4479 continue;
4480 }
4481
4482 // If the item is a boulder, mark that we should fill the square
4483 // as well.
4484 if (item->getDefinition() == ITEM_BOULDER)
4485 squarefiller = item;
4486 else if (dropintohole && item->fallInHole(this, true, revealer))
4487 {
4488 continue;
4489 }
4490
4491 // If the item is submerged, you discover it.
4492 // If not, it falls in.
4493 if (item->isBelowGrade())
4494 {
4495 if (dropintoliquid && item->hasIntrinsic(INTRINSIC_WATERWALK))
4496 {
4497 item->formatAndReport("%U <float> to the surface.");
4498 item->markBelowGrade(false);
4499 }
4500 else
4501 {
4502 if (revealer)
4503 buf = MOB::formatToString("%U <discover> %IU!",
4504 revealer, 0, 0, item);
4505 else
4506 buf = MOB::formatToString("%U <be> revealed.",
4507 0, item, 0, 0);
4508 reportMessage(buf, x, y);
4509 // Check to see if we have to fill the item with
4510 // new liquid
4511 fillBottle(item, x, y);
4512 }
4513 }
4514 else
4515 {
4516 // The item falls into the pit.
4517 // If it is going into water and the item waterwalks,
4518 // it floats.
4519 if (dropintoliquid && item->hasIntrinsic(INTRINSIC_WATERWALK))
4520 {
4521 // Do nothing.
4522 }
4523 else
4524 {
4525 buf = MOB::formatToString("%U <fall> into the %B1.",
4526 0, item, 0, 0,
4527 dropname, 0);
4528 reportMessage(buf, x, y);
4529 item->markBelowGrade(true);
4530 // Check to see if we have to fill the item with
4531 // new liquid
4532 fillBottle(item, x, y);
4533 }
4534 }
4535 }
4536 }
4537
4538 if (squarefiller)
4539 {
4540 // Note the revealer also becomes guilty of filling.
4541 fillSquare(squarefiller, revealer);
4542 }
4543 }
4544
4545 bool
fillSquare(ITEM * boulder,MOB * filler,ITEM ** newitem)4546 MAP::fillSquare(ITEM *boulder, MOB *filler, ITEM **newitem)
4547 {
4548 if (newitem)
4549 *newitem = boulder;
4550
4551 // Check this is a fillable item.
4552 if (boulder->getDefinition() != ITEM_BOULDER)
4553 return false;
4554
4555 // Check the square if fillable.
4556 SQUARE_NAMES tile, newtile;
4557 TRAP_NAMES trap;
4558 bool dofill = false;
4559 bool dropintoliquid = false;
4560 const char *fillname = 0;
4561 BUF buf;
4562 MOB *mob;
4563
4564 tile = getTile(boulder->getX(), boulder->getY());
4565 newtile = tile;
4566 trap = (TRAP_NAMES) glb_squaredefs[tile].trap;
4567
4568 switch (trap)
4569 {
4570 case TRAP_HOLE:
4571 case TRAP_PIT:
4572 case TRAP_SPIKEDPIT:
4573 fillname = glb_trapdefs[trap].name;
4574 newtile = (SQUARE_NAMES) glb_squaredefs[tile].disarmsquare;
4575 dofill = true;
4576 break;
4577
4578 case TRAP_TELEPORT:
4579 // Do nothing here, since the boulder would have already
4580 // teleported in acquireItem, or this is an impossible situation
4581 // for a teleport to exist in (called here during water->pit
4582 // transformation, for example). I hope.
4583 break;
4584
4585 case TRAP_SMOKEVENT:
4586 case TRAP_POISONVENT:
4587 case TRAP_NONE:
4588 case NUM_TRAPS:
4589 break;
4590 }
4591
4592 switch (tile)
4593 {
4594 case SQUARE_WATER:
4595 dofill = true;
4596 fillname = "water";
4597 dropintoliquid = true;
4598 newtile = SQUARE_CORRIDOR;
4599 break;
4600 case SQUARE_ACID:
4601 dofill = true;
4602 fillname = "acid";
4603 dropintoliquid = true;
4604 newtile = SQUARE_CORRIDOR;
4605 break;
4606 case SQUARE_LAVA:
4607 dofill = true;
4608 fillname = "lava";
4609 dropintoliquid = true;
4610 newtile = SQUARE_CORRIDOR;
4611 break;
4612 default:
4613 break;
4614 }
4615
4616 if (!dofill)
4617 return false;
4618
4619 UT_ASSERT(fillname != 0);
4620 if (!fillname)
4621 fillname = "ERROR";
4622
4623 // And now do the appropriate message.
4624 if (dropintoliquid)
4625 buf = MOB::formatToString("%U <splash> into the %B1, filling it.",
4626 0, boulder, 0, 0,
4627 fillname, 0);
4628 else
4629 buf = MOB::formatToString("%U <plug> the %B1.",
4630 0, boulder, 0, 0,
4631 fillname, 0);
4632
4633 reportMessage(buf, boulder->getX(), boulder->getY());
4634
4635 setGuiltyMob(boulder->getX(), boulder->getY(), filler);
4636
4637 // Chnage the tile:
4638 setTile(boulder->getX(), boulder->getY(), newtile);
4639
4640 // Transmute any buried warhammers.
4641 if (getItem(boulder->getX(), boulder->getY()))
4642 {
4643 ITEMSTACK items;
4644 int i;
4645
4646 // Only fetch buried items...
4647 getItemStack(items, boulder->getX(), boulder->getY(), true);
4648 for (i = 0; i < items.entries(); i++)
4649 {
4650 ITEM *item = items(i);
4651 if (item->getDefinition() == ITEM_WARHAMMER)
4652 {
4653 reportMessage("A yellow glow shines briefly through the ground.", boulder->getX(), boulder->getY());
4654 item->setDefinition(ITEM_EARTHHAMMER);
4655
4656 // If the boulder is an artifact tranmute the hammer
4657 if (boulder->isArtifact() && !item->isArtifact())
4658 {
4659 item->makeArtifact(boulder->getPersonalName());
4660 }
4661
4662 }
4663 }
4664 }
4665
4666 // Ensure anyone with IN_PIT intrinsic gets SUBMERGED.
4667 mob = getMob(boulder->getX(), boulder->getY());
4668 if (mob)
4669 {
4670 if (mob->hasIntrinsic(INTRINSIC_INPIT))
4671 {
4672 mob->clearIntrinsic(INTRINSIC_INPIT);
4673 mob->setIntrinsic(INTRINSIC_SUBMERGED);
4674 }
4675 }
4676
4677 // Now, we want to delete the boulder as it was be consumed.
4678 dropItem(boulder);
4679 delete boulder;
4680
4681 if (newitem)
4682 *newitem = 0;
4683
4684 return true;
4685 }
4686
4687 bool
dropMobs(int x,int y,bool forcetrap,MOB * revealer,bool noskillevade)4688 MAP::dropMobs(int x, int y, bool forcetrap, MOB *revealer, bool noskillevade)
4689 {
4690 SQUARE_NAMES tile;
4691 TRAP_NAMES trap;
4692 BUF buf;
4693 MOB *mob;
4694
4695 // Assume only one mob per square.
4696 mob = getMob(x, y);
4697 if (!mob)
4698 return true;
4699
4700 tile = getTile(x, y);
4701 trap = (TRAP_NAMES) glb_squaredefs[tile].trap;
4702
4703 // Drop us out of trees if they cease to exist.
4704 if (!glb_squaredefs[tile].isforest && mob->hasIntrinsic(INTRINSIC_INTREE))
4705 {
4706 mob->formatAndReport("%U <fall> to the ground.");
4707 mob->clearIntrinsic(INTRINSIC_INTREE);
4708 }
4709
4710 if (mob->getSize() >= SIZE_GARGANTUAN)
4711 {
4712 // Giant mobs can't fall.
4713 return true;
4714 }
4715
4716 if (trap != TRAP_NONE &&
4717 !(glb_trapdefs[trap].moveevade & mob->getMoveType()))
4718 {
4719 // Note that rangers never trigger traps. They are just
4720 // too cool looking.
4721 if (!mob->hasIntrinsic(INTRINSIC_DRESSED_RANGER) &&
4722 (noskillevade || !mob->hasIntrinsic(INTRINSIC_SKILL_EVADETRAP)) &&
4723 (forcetrap || rand_chance(glb_trapdefs[trap].triggerchance)))
4724 {
4725 // Trigger the trap!
4726 switch (trap)
4727 {
4728 case TRAP_HOLE:
4729 {
4730 MAP *nextmap;
4731 int x, y;
4732
4733 nextmap = getMapDown();
4734 if (!nextmap || !nextmap->isUnlocked())
4735 {
4736 mob->formatAndReport("%R fall <be> stopped by an invisible barrier.");
4737 break;
4738 }
4739
4740 // See if there is room...
4741 // We just go somewhere random (but moveable!)
4742 if (!nextmap->findRandomLoc(x, y, mob->getMoveType(), false,
4743 mob->getSize() >= SIZE_GARGANTUAN,
4744 false, false, false, true))
4745 {
4746 mob->formatAndReport("%U <find> the hold blocked.");
4747 break;
4748 }
4749
4750 // We must report this message before unregistering
4751 // or the avatar won't see things fall into holes.
4752 mob->formatAndReport("%U <fall> into the hole.");
4753
4754 // Move to it...
4755 unregisterMob(mob);
4756 if (!mob->move(x, y, true))
4757 return false;
4758 nextmap->registerMob(mob);
4759
4760 if (mob->isAvatar())
4761 {
4762 MAP::changeCurrentLevel(nextmap);
4763 }
4764
4765 // Any further falling should occur on the new level.
4766 return true;
4767 }
4768
4769 case TRAP_PIT:
4770 case TRAP_SPIKEDPIT:
4771 {
4772 ATTACK_NAMES attack;
4773
4774 // If we are already in the pit, not much to do!
4775 if (mob->hasIntrinsic(INTRINSIC_INPIT))
4776 break;
4777
4778 attack = (ATTACK_NAMES) glb_trapdefs[trap].attack;
4779 // Inform the user they fell into the pit.
4780 buf = MOB::formatToString("%U <fall> into the %B1.",
4781 mob, 0, 0, 0,
4782 glb_trapdefs[trap].name, 0);
4783 mob->reportMessage(buf);
4784
4785 // Set our in pit flag.
4786 // We do this prior to damage so on death we
4787 // get the proper intrinsics.
4788 mob->setIntrinsic(INTRINSIC_INPIT);
4789
4790 MOB *attacker;
4791 attacker = getGuiltyMob(x, y);
4792 if (!attacker)
4793 attacker = mob;
4794
4795 // Take the damage...
4796 if (!mob->receiveDamage(attack,
4797 attacker,
4798 0, 0,
4799 ATTACKSTYLE_MISC))
4800 {
4801 return false;
4802 }
4803
4804 break;
4805 }
4806
4807 case TRAP_SMOKEVENT:
4808 case TRAP_POISONVENT:
4809 {
4810 if (trap == TRAP_SMOKEVENT)
4811 reportMessage("Smoke billows from the vent.", x, y);
4812 else
4813 reportMessage("Poison smoke billows from the vent.", x, y);
4814
4815 setSmoke(x, y, ((trap == TRAP_SMOKEVENT)
4816 ? SMOKE_SMOKE
4817 : SMOKE_POISON),
4818 getGuiltyMob(x, y));
4819 break;
4820 }
4821
4822 case TRAP_TELEPORT:
4823 {
4824 mob->formatAndReport("%U <trigger> a teleport trap.");
4825 mob->actionTeleport();
4826 // The mob is no longer at x,y so further
4827 // drop invocation doesn't make sense
4828 // In any case, it might have died as a result of
4829 // the teleport.
4830 return true;
4831 }
4832
4833 case TRAP_NONE:
4834 case NUM_TRAPS:
4835 UT_ASSERT(!"BAD CODE PATH WITH TRAPS");
4836 break;
4837 }
4838 }
4839 else
4840 {
4841 // Evade the trap.
4842 // Report that you evade it.
4843 // If you are already in a pit, don't report.
4844 // Note that this is incorrect for evading holes. However,
4845 // I hope you can't have the inpit flag set whilst on a hole.
4846 if (!mob->hasIntrinsic(INTRINSIC_INPIT))
4847 {
4848 buf = MOB::formatToString("%U <evade> the %B1.",
4849 mob, 0, 0, 0,
4850 glb_trapdefs[trap].name, 0);
4851 mob->reportMessage(buf);
4852 }
4853 }
4854 }
4855
4856 // Determine special swimming effects.
4857 switch (tile)
4858 {
4859 case SQUARE_LAVA:
4860 case SQUARE_WATER:
4861 case SQUARE_ACID:
4862 {
4863 if (mob->hasIntrinsic(INTRINSIC_SUBMERGED))
4864 {
4865 // Already underwater, nothing to do.
4866 break;
4867 }
4868
4869 // These movetypes allow one to negate sinking.
4870 if (mob->getMoveType() & (MOVE_FLY | MOVE_PHASE))
4871 {
4872 break;
4873 }
4874
4875 if (mob->hasIntrinsic(INTRINSIC_WATERWALK))
4876 {
4877 // Water walkers can't sink.
4878 break;
4879 }
4880
4881 // Chance of sinking! 10% chance in lava.
4882 // 50% chance in water.
4883 int sink_chance;
4884 const char *liquid;
4885
4886 switch (tile)
4887 {
4888 case SQUARE_LAVA:
4889 sink_chance = 10;
4890 liquid = "lava";
4891 break;
4892 case SQUARE_WATER:
4893 sink_chance = 50;
4894 liquid = "water";
4895 break;
4896 case SQUARE_ACID:
4897 sink_chance = 50;
4898 liquid = "acid";
4899 break;
4900
4901 default:
4902 UT_ASSERT(!"UNHANDLED SQUARE TYPE");
4903 liquid = "INVALID LIQUID";
4904 sink_chance = 0;
4905 break;
4906 }
4907
4908 if (!forcetrap && !rand_chance(sink_chance))
4909 {
4910 // You manage to keep your head above the liquid.
4911 break;
4912 }
4913
4914 buf = MOB::formatToString("%U <sink> below the %B1.",
4915 mob, 0, 0, 0,
4916 liquid, 0);
4917 mob->reportMessage(buf);
4918 mob->setIntrinsic(INTRINSIC_SUBMERGED);
4919 break;
4920 }
4921
4922 default:
4923 {
4924 // You, by definition are no longer in water. If stepped
4925 // out, say so.
4926 if (mob->hasIntrinsic(INTRINSIC_SUBMERGED))
4927 {
4928 // We can't say we stepped out of the liquid as we may
4929 // have escaped from being underground!
4930 mob->formatAndReport("%U <reach> the surface.");
4931 mob->clearIntrinsic(INTRINSIC_SUBMERGED);
4932 }
4933 break;
4934 }
4935 }
4936
4937 return true;
4938 }
4939
4940 bool
createTrap(int x,int y,MOB * trapper)4941 MAP::createTrap(int x, int y, MOB *trapper)
4942 {
4943 SQUARE_NAMES tile, newtile;
4944
4945 // If spot is outside of map, it always fails
4946 // (Zapping wands of create trap doesn't bounds check, nor
4947 // should it)
4948 if (x < 0 || y < 0 || x >= MAP_WIDTH || y >= MAP_HEIGHT)
4949 return false;
4950
4951 tile = getTile(x, y);
4952 newtile = tile;
4953
4954 // Determine if it is a valid tile.
4955 if (tile == SQUARE_CORRIDOR || tile == SQUARE_FLOOR)
4956 {
4957 // Determine possible traps, which is modulated by
4958 // the dungeon level.
4959 TRAP_NAMES traps[NUM_TRAPS];
4960 int numtraps = 0, i;
4961
4962 for (i = 0; i < NUM_TRAPS; i++)
4963 {
4964 if (glb_trapdefs[i].level <= myDepth)
4965 traps[numtraps++] = (TRAP_NAMES) i;
4966 }
4967
4968 UT_ASSERT(numtraps);
4969 if (!numtraps)
4970 return false;
4971
4972 // Switch on the possible trap types:
4973 switch (traps[rand_choice(numtraps)])
4974 {
4975 case TRAP_SPIKEDPIT:
4976 if (tile == SQUARE_CORRIDOR)
4977 newtile = SQUARE_SECRETPATHSPIKEDPIT;
4978 else
4979 newtile = SQUARE_SECRETFLOORSPIKEDPIT;
4980 break;
4981 case TRAP_HOLE:
4982 // If we prohibit holes, we fall through to
4983 // pit traps.
4984 if (allowDiggingDown())
4985 {
4986 if (tile == SQUARE_CORRIDOR)
4987 newtile = SQUARE_SECRETPATHHOLE;
4988 else
4989 newtile = SQUARE_SECRETFLOORHOLE;
4990 break;
4991 }
4992 // FALL THROUGH
4993 case TRAP_PIT:
4994 if (tile == SQUARE_CORRIDOR)
4995 newtile = SQUARE_SECRETPATHPIT;
4996 else
4997 newtile = SQUARE_SECRETFLOORPIT;
4998 break;
4999 case TRAP_SMOKEVENT:
5000 if (tile == SQUARE_CORRIDOR)
5001 newtile = SQUARE_SECRETPATHSMOKEVENT;
5002 else
5003 newtile = SQUARE_SECRETFLOORSMOKEVENT;
5004 break;
5005 case TRAP_POISONVENT:
5006 if (tile == SQUARE_CORRIDOR)
5007 newtile = SQUARE_SECRETPATHPOISONVENT;
5008 else
5009 newtile = SQUARE_SECRETFLOORPOISONVENT;
5010 break;
5011 case TRAP_TELEPORT:
5012 if (tile == SQUARE_CORRIDOR)
5013 newtile = SQUARE_SECRETPATHTELEPORTER;
5014 else
5015 newtile = SQUARE_SECRETFLOORTELEPORTER;
5016 break;
5017
5018 case TRAP_NONE:
5019 case NUM_TRAPS:
5020 break;
5021 }
5022 // Done.
5023 if (newtile != tile)
5024 {
5025 setTile(x, y, newtile);
5026 setGuiltyMob(x, y, trapper);
5027 if (trapper)
5028 trapper->pietyCreateTrap();
5029 return true;
5030 }
5031 }
5032 return false;
5033 }
5034
5035 bool
freezeSquare(int x,int y,MOB * freezer)5036 MAP::freezeSquare(int x, int y, MOB *freezer)
5037 {
5038 SQUARE_NAMES tile, newtile;
5039
5040 tile = getTile(x, y);
5041
5042 switch (tile)
5043 {
5044 case SQUARE_WATER:
5045 reportMessage("The water freezes.", x, y);
5046 newtile = SQUARE_ICE;
5047
5048 // Any maces so embedded are frostified
5049 if (getItem(x, y))
5050 {
5051 ITEMSTACK items;
5052 int i;
5053
5054 // Only fetch buried items...
5055 getItemStack(items, x, y, true);
5056 for (i = 0; i < items.entries(); i++)
5057 {
5058 ITEM *item = items(i);
5059 if (item->getDefinition() == ITEM_MACE)
5060 {
5061 reportMessage("A blue glow shines briefly through the ice.", x, y);
5062 item->setDefinition(ITEM_ICEMACE);
5063 }
5064 }
5065 }
5066 break;
5067
5068 case SQUARE_LAVA:
5069 reportMessage("The lava hardens.", x, y);
5070 newtile = SQUARE_CORRIDOR;
5071 break;
5072
5073 case SQUARE_FORESTFIRE:
5074 reportMessage("The forest fire is extinguished.", x, y);
5075 newtile = SQUARE_FOREST;
5076 break;
5077
5078 case SQUARE_ACID: // Acid doesn't freeze.
5079 default:
5080 // Nothing happens.
5081 newtile = tile;
5082 break;
5083 }
5084
5085 if (newtile != tile)
5086 {
5087 setTile(x, y, newtile);
5088 setGuiltyMob(x, y, freezer);
5089 return true;
5090 }
5091 return false;
5092 }
5093
5094 bool
electrocuteSquare(int x,int y,MOB * shocker)5095 MAP::electrocuteSquare(int x, int y, MOB *shocker)
5096 {
5097 SQUARE_NAMES tile;
5098
5099 tile = getTile(x, y);
5100
5101 switch (tile)
5102 {
5103 case SQUARE_WATER:
5104 case SQUARE_ACID: // Dissolved ions implies conductivity.
5105 if (tile == SQUARE_WATER)
5106 reportMessage("Electricty arcs through the water.", x, y);
5107 else
5108 reportMessage("Electricty arcs through the acid.", x, y);
5109
5110 // TODO:
5111 // Apply an attack to all connected tiles of same type,
5112 // water acid barrier for some reason acts as insulator
5113 break;
5114
5115 default:
5116 // Nothing happens.
5117 break;
5118 }
5119
5120 return false;
5121 }
5122
5123 bool
burnSquare(int x,int y,MOB * burner)5124 MAP::burnSquare(int x, int y, MOB *burner)
5125 {
5126 SQUARE_NAMES tile, newtile;
5127 bool hadeffect = false;
5128
5129 tile = getTile(x, y);
5130
5131 switch (tile)
5132 {
5133 case SQUARE_ICE:
5134 reportMessage("The ice melts.", x, y);
5135 newtile = SQUARE_WATER;
5136 hadeffect = true;
5137 break;
5138 case SQUARE_WATER:
5139 reportMessage("The water boils.", x, y);
5140 // No change to the tile.
5141 // should fill with adjacent water.
5142 newtile = SQUARE_PATHPIT;
5143 hadeffect = true;
5144 // Generate smoke.
5145 setSmoke(x, y, SMOKE_STEAM, burner);
5146 break;
5147 case SQUARE_ACID:
5148 reportMessage("The acid boils.", x, y);
5149 newtile = SQUARE_PATHPIT;
5150 hadeffect = true;
5151 // Generate smoke.
5152 setSmoke(x, y, SMOKE_ACID, burner);
5153 break;
5154 case SQUARE_FOREST:
5155 reportMessage("The forest catches on fire.", x, y);
5156 newtile = SQUARE_FORESTFIRE;
5157 hadeffect = true;
5158 break;
5159 default:
5160 // Nothing happens.
5161 newtile = tile;
5162 break;
5163 }
5164
5165 if (newtile != tile)
5166 {
5167 setTile(x, y, newtile);
5168 setGuiltyMob(x, y, burner);
5169 dropItems(x, y, burner);
5170 // We want to demand a reaction throw.
5171 dropMobs(x, y, true, burner);
5172 // TODO: Submerged mobs should become inpit!
5173 return true;
5174 }
5175 return hadeffect;
5176 }
5177
5178 bool
electrifySquare(int x,int y,int points,MOB * shocker)5179 MAP::electrifySquare(int x, int y, int points, MOB *shocker)
5180 {
5181 SQUARE_NAMES tile, newtile;
5182 bool hadeffect = false;
5183
5184 tile = getTile(x, y);
5185
5186 switch (tile)
5187 {
5188 // There are currently no tile transitions.
5189 default:
5190 // Nothing happens.
5191 newtile = tile;
5192 break;
5193 }
5194
5195 if (newtile != tile)
5196 {
5197 setTile(x, y, newtile);
5198 setGuiltyMob(x, y, shocker);
5199 dropItems(x, y, shocker);
5200 // We want to demand a reaction throw.
5201 dropMobs(x, y, true, shocker);
5202 // TODO: Submerged mobs should become inpit!
5203 return true;
5204 }
5205
5206 // Affect any items
5207 if (getItem(x, y))
5208 {
5209 ITEMSTACK items;
5210 ITEM *item;
5211 int i;
5212
5213 // Don't want to charge underwater items
5214 getItemStack(items, x, y, false);
5215
5216 for (i = 0; i < items.entries(); i++)
5217 {
5218 item = items(i);
5219 if (item->electrify(points, shocker, 0))
5220 {
5221 dropItem(item);
5222 delete item;
5223 }
5224 }
5225 }
5226
5227 return hadeffect;
5228 }
5229
5230 bool
resurrectCorpses(int x,int y,MOB * resser)5231 MAP::resurrectCorpses(int x, int y, MOB *resser)
5232 {
5233 ITEM *cur;
5234
5235 // Can't res if a mob is already there.
5236 if (getMob(x, y))
5237 return false;
5238
5239 // Find all items on this square & res them.
5240 for (cur = getItemHead(); cur; cur = cur->getNext())
5241 {
5242 if (cur->getX() == x && cur->getY() == y)
5243 {
5244 if (cur->resurrect(this, resser, x, y))
5245 {
5246 dropItem(cur);
5247 delete cur;
5248
5249 return true;
5250 }
5251 }
5252 }
5253
5254 return false;
5255 }
5256
5257 bool
raiseZombie(int x,int y,MOB * resser)5258 MAP::raiseZombie(int x, int y, MOB *resser)
5259 {
5260 ITEM *cur;
5261
5262 // Can't res if a mob is already there.
5263 if (getMob(x, y))
5264 return false;
5265
5266 // Find all items on this square & res them.
5267 for (cur = getItemHead(); cur; cur = cur->getNext())
5268 {
5269 if (cur->getX() == x && cur->getY() == y)
5270 {
5271 if (cur->raiseZombie(this, resser))
5272 {
5273 dropItem(cur);
5274 delete cur;
5275
5276 return true;
5277 }
5278 }
5279 }
5280
5281 return false;
5282 }
5283
5284 bool
raiseSkeleton(int x,int y,MOB * resser)5285 MAP::raiseSkeleton(int x, int y, MOB *resser)
5286 {
5287 ITEM *cur;
5288
5289 // Can't res if a mob is already there.
5290 if (getMob(x, y))
5291 return false;
5292
5293 // Find all items on this square & res them.
5294 for (cur = getItemHead(); cur; cur = cur->getNext())
5295 {
5296 if (cur->getX() == x && cur->getY() == y)
5297 {
5298 if (cur->raiseSkeleton(this, resser))
5299 {
5300 dropItem(cur);
5301 delete cur;
5302
5303 return true;
5304 }
5305 }
5306 }
5307
5308 return false;
5309 }
5310
5311 void
moveSmoke()5312 MAP::moveSmoke()
5313 {
5314 int x, y, nx, ny;
5315 int ex, ey, idx, idy;
5316 int wdx, wdy;
5317
5318 // Determine if we are in a proper phase.
5319 if (!speed_isheartbeatphase())
5320 return;
5321
5322 if (ourWindCounter > 0)
5323 {
5324 wdx = ourWindDX;
5325 wdy = ourWindDY;
5326 ourWindCounter--;
5327
5328 if (!ourWindCounter)
5329 {
5330 // The wind has changed! Global alert!
5331 msg_report("The wind shifts. ");
5332 }
5333 }
5334 else
5335 {
5336 // We only have a 1 in 3 chance to want to move the smoke.
5337 if (!rand_choice(3))
5338 {
5339 wdx = rand_choice(3)-1;
5340 wdy = rand_choice(3)-1;
5341 }
5342 else
5343 {
5344 wdx = 0;
5345 wdy = 0;
5346 }
5347 }
5348
5349
5350 if (!mySmokeStack.entries())
5351 return;
5352
5353 // We want to update in a direction that ensures we make room
5354 // for the exitting smoke.
5355 // Ie, positive wdx means we should update right to left.
5356
5357 // wdx & wdy are the wind direction.
5358 // Each unit of smoke has a 1 in 10 chance of dissipating.
5359 if (wdy < 0)
5360 {
5361 y = 0;
5362 ey = MAP_HEIGHT;
5363 idy = 1;
5364 }
5365 else
5366 {
5367 y = MAP_HEIGHT-1;
5368 ey = -1;
5369 idy = -1;
5370 }
5371
5372 for (; y != ey; y += idy)
5373 {
5374 if (wdx < 0)
5375 {
5376 x = 0;
5377 ex = MAP_WIDTH;
5378 idx = 1;
5379 }
5380 else
5381 {
5382 x = MAP_WIDTH-1;
5383 ex = -1;
5384 idx = -1;
5385 }
5386 for (; x != ex; x += idx)
5387 {
5388 if (getFlag(x, y, SQUAREFLAG_SMOKE))
5389 {
5390 MOB *owner;
5391 SMOKE_NAMES smoke;
5392
5393 smoke = getSmoke(x, y, &owner);
5394
5395 // The probability of the smoke decaying depends on the
5396 // type of the smoke.
5397 if (rand_chance(glb_smokedefs[smoke].decayrate))
5398 {
5399 setSmoke(x, y, SMOKE_NONE, 0);
5400 }
5401 else
5402 {
5403 // We want to move it in the correct direction.
5404 // Check to make sure new square is moveable &
5405 // doesn't have smoke already.
5406 nx = x + wdx;
5407 ny = y + wdy;
5408 if (nx >= 0 && nx < MAP_WIDTH &&
5409 ny >= 0 && ny < MAP_HEIGHT)
5410 {
5411 if (!getFlag(nx, ny, SQUAREFLAG_SMOKE) &&
5412 (glb_squaredefs[getTile(nx, ny)].movetype &
5413 MOVE_STD_FLY))
5414 {
5415 // Move the smoke.
5416 setSmoke(x, y, SMOKE_NONE, 0);
5417 setSmoke(nx, ny, smoke, owner);
5418 }
5419 }
5420 }
5421 }
5422 }
5423 }
5424
5425 // Remove now empty entries:
5426 mySmokeStack.collapse();
5427 }
5428
5429 SMOKE_NAMES
getSmoke(int x,int y,MOB ** owner) const5430 MAP::getSmoke(int x, int y, MOB **owner) const
5431 {
5432 if (owner)
5433 *owner = 0;
5434
5435 if (x < 0 || x >= MAP_WIDTH)
5436 return SMOKE_NONE;
5437 if (y < 0 || y >= MAP_HEIGHT)
5438 return SMOKE_NONE;
5439
5440 // Verify we have smoke according to the bitflag.
5441 if (getFlag(x, y, SQUAREFLAG_SMOKE))
5442 {
5443 // Find the exact smoke type:
5444 return mySmokeStack.get(x, y, owner);
5445 }
5446
5447 return SMOKE_NONE;
5448 }
5449
5450 SIGNPOST_NAMES
getSignPost(int x,int y) const5451 MAP::getSignPost(int x, int y) const
5452 {
5453 if (x < 0 || x >= MAP_WIDTH)
5454 return SIGNPOST_NONE;
5455 if (y < 0 || y >= MAP_HEIGHT)
5456 return SIGNPOST_NONE;
5457
5458 // Ensure this is a proper signpost.
5459 if (mySignList && getTile(x, y) == SQUARE_SIGNPOST)
5460 {
5461 return mySignList->find(x, y);
5462 }
5463
5464 return SIGNPOST_NONE;
5465 }
5466
5467 void
setSmoke(int x,int y,SMOKE_NAMES smoke,MOB * owner)5468 MAP::setSmoke(int x, int y, SMOKE_NAMES smoke, MOB *owner)
5469 {
5470 UT_ASSERT(x >= 0 && x < MAP_WIDTH);
5471 UT_ASSERT(y >= 0 && y < MAP_WIDTH);
5472
5473 // Add the square flag so we know this is a smoke square.
5474 if (smoke == SMOKE_NONE)
5475 {
5476 // We early exit as a no-op if we are requested to clear
5477 // a tile where there is no smoke.
5478 if (!getFlag(x, y, SQUAREFLAG_SMOKE))
5479 return;
5480
5481 clearFlag(x, y, SQUAREFLAG_SMOKE);
5482 }
5483 else
5484 setFlag(x, y, SQUAREFLAG_SMOKE);
5485
5486 // Add the specific type to our smoke stack.
5487 mySmokeStack.set(x, y, smoke, owner);
5488 }
5489
5490 bool
hasNeighbouringTile(int x,int y,SQUARE_NAMES tile,MOB * & srcmob)5491 MAP::hasNeighbouringTile(int x, int y, SQUARE_NAMES tile, MOB *&srcmob)
5492 {
5493 if (x > 0)
5494 if (getTile(x-1, y) == tile)
5495 {
5496 srcmob = getGuiltyMob(x-1, y);
5497 return true;
5498 }
5499 if (x < MAP_WIDTH-1)
5500 if (getTile(x+1, y) == tile)
5501 {
5502 srcmob = getGuiltyMob(x+1, y);
5503 return true;
5504 }
5505 if (y > 0)
5506 if (getTile(x, y-1) == tile)
5507 {
5508 srcmob = getGuiltyMob(x, y-1);
5509 return true;
5510 }
5511 if (y < MAP_HEIGHT-1)
5512 if (getTile(x, y+1) == tile)
5513 {
5514 srcmob = getGuiltyMob(x, y+1);
5515 return true;
5516 }
5517
5518 return false;
5519 }
5520
5521 void
applySuction(int x,int y)5522 MAP::applySuction(int x, int y)
5523 {
5524 int dx, dy;
5525 int odx, ody;
5526 int mx, my;
5527 int mnx, mny;
5528 int s, t;
5529 MOB *mob;
5530 ITEMSTACK itemlist;
5531
5532 FORALL_4DIR(dx, dy)
5533 {
5534 if (glb_squaredefs[getTile(x+dx,y+dy)].isstars)
5535 break;
5536 }
5537 odx = !dx;
5538 ody = !dy;
5539
5540 for (s = 3; s >= -3; s--)
5541 {
5542 for (t = -ABS(s); t <= ABS(s); t++)
5543 {
5544 // Current suction square
5545 mx = x + dx*s + odx*t;
5546 my = y + dy*s + ody*t;
5547 // Where we suck to.
5548 mnx = mx + dx;
5549 mnx += SIGN(s*t) * odx;
5550 mny = my + dy;
5551 mny += SIGN(s*t) * ody;
5552
5553 if (mnx < 0 || mnx >= MAP_WIDTH ||
5554 mny < 0 || mny >= MAP_HEIGHT)
5555 continue;
5556 if (mx < 0 || mx >= MAP_WIDTH ||
5557 my < 0 || my >= MAP_HEIGHT)
5558 continue;
5559
5560 mob = getMob(mx, my);
5561 if (mob && mob->canMove(mnx, mny, true))
5562 {
5563 mob->move(mnx, mny);
5564 }
5565
5566 itemlist.clear();
5567 getItemStack(itemlist, mx, my);
5568
5569 ITEM *item;
5570 for (int i = 0; i < itemlist.entries(); i++)
5571 {
5572 item = itemlist(i);
5573 dropItem(item);
5574 item->markMapped(false);
5575 acquireItem(item, mnx, mny, 0);
5576 }
5577
5578 if (getFlag(mx, my, SQUAREFLAG_SMOKE) &&
5579 !getFlag(mnx, mny, SQUAREFLAG_SMOKE))
5580 {
5581 // Move the smoke over..
5582 MOB *owner;
5583 SMOKE_NAMES smoke;
5584
5585 smoke = getSmoke(mx, my, &owner);
5586
5587 if (glb_squaredefs[getTile(mnx, mny)].movetype & MOVE_STD_FLY)
5588 {
5589 setSmoke(mx, my, SMOKE_NONE, 0);
5590 setSmoke(mnx, mny, smoke, owner);
5591 }
5592 }
5593 }
5594 }
5595 }
5596
5597 void
moveFluids()5598 MAP::moveFluids()
5599 {
5600 // Determine if we are in a proper phase.
5601 if (!speed_isheartbeatphase())
5602 return;
5603
5604 if (!ourMapDirty)
5605 return;
5606
5607 // Update all map tiles with the following rules:
5608 // 1) If a pit, and posseses neighbouring liquid, fill in.
5609 // 2) If lava, and possesses neighbouring water, steam & dry out.
5610 // 3) If ice, and neighbour is lava, turn to water.
5611 // We don't want instant chain reactions, so the order of
5612 // operation is important.
5613
5614 int x, y;
5615 u8 newtiles[MAP_HEIGHT][MAP_WIDTH];
5616 int numnew = 0;
5617 MOB *srcmob;
5618
5619 for (y = 0; y < MAP_HEIGHT; y++)
5620 {
5621 for (x = 0; x < MAP_WIDTH; x++)
5622 {
5623 newtiles[y][x] = NUM_SQUARES;
5624 srcmob = 0;
5625
5626 switch (getTile(x, y))
5627 {
5628 case SQUARE_BROKENVIEWPORT:
5629 // Auto-repair technology is in effect.
5630 if (rand_chance(5))
5631 {
5632 newtiles[y][x] = SQUARE_VIEWPORT;
5633 reportMessage("The glass wall mends itself.", x, y);
5634 }
5635 else
5636 {
5637 applySuction(x, y);
5638 }
5639 numnew++;
5640 break;
5641
5642 case SQUARE_FORESTFIRE:
5643 if (hasNeighbouringTile(x, y, SQUARE_WATER, srcmob))
5644 {
5645 if (rand_chance(20))
5646 newtiles[y][x] = SQUARE_FOREST;
5647 }
5648 if (rand_chance(10))
5649 {
5650 newtiles[y][x] = SQUARE_CORRIDOR;
5651 }
5652 numnew++;
5653 if (rand_chance(20))
5654 setSmoke(x, y, SMOKE_SMOKE, 0);
5655 break;
5656 case SQUARE_FOREST:
5657 if (hasNeighbouringTile(x, y, SQUARE_LAVA, srcmob))
5658 {
5659 newtiles[y][x] = SQUARE_FORESTFIRE;
5660 // forest grower guilty if fire source unowned.
5661 if (srcmob)
5662 setGuiltyMob(x, y, srcmob);
5663 numnew++;
5664 }
5665 if (hasNeighbouringTile(x, y, SQUARE_FORESTFIRE, srcmob))
5666 {
5667 if (rand_chance(30))
5668 {
5669 newtiles[y][x] = SQUARE_FORESTFIRE;
5670 // forest grower guilty if fire source unowned.
5671 if (srcmob)
5672 setGuiltyMob(x, y, srcmob);
5673 }
5674 numnew++;
5675 }
5676 break;
5677 case SQUARE_ICE:
5678 if (hasNeighbouringTile(x, y, SQUARE_LAVA, srcmob))
5679 {
5680 newtiles[y][x] = SQUARE_WATER;
5681 if (srcmob)
5682 setGuiltyMob(x, y, srcmob);
5683 numnew++;
5684 }
5685 break;
5686
5687 case SQUARE_LAVA:
5688 if (hasNeighbouringTile(x, y, SQUARE_WATER, srcmob))
5689 {
5690 newtiles[y][x] = SQUARE_CORRIDOR;
5691 // entombment goes to lava maker first
5692 if (!getGuiltyMob(x, y))
5693 setGuiltyMob(x, y, srcmob);
5694 numnew++;
5695 }
5696 break;
5697
5698 case SQUARE_WATER:
5699 if (hasNeighbouringTile(x, y, SQUARE_LAVA, srcmob))
5700 {
5701 newtiles[y][x] = SQUARE_PATHPIT;
5702 if (srcmob)
5703 setGuiltyMob(x, y, srcmob);
5704 numnew++;
5705 }
5706 break;
5707
5708 case SQUARE_ACID:
5709 if (hasNeighbouringTile(x, y, SQUARE_LAVA, srcmob))
5710 {
5711 newtiles[y][x] = SQUARE_PATHPIT;
5712 if (srcmob)
5713 setGuiltyMob(x, y, srcmob);
5714 numnew++;
5715 }
5716 break;
5717
5718 case SQUARE_FLOORPIT:
5719 case SQUARE_PATHPIT:
5720 case SQUARE_FLOORSPIKEDPIT:
5721 case SQUARE_PATHSPIKEDPIT:
5722 // Lava first so water takes priority, as it
5723 // flows faster.
5724 if (hasNeighbouringTile(x, y, SQUARE_LAVA, srcmob))
5725 {
5726 newtiles[y][x] = SQUARE_LAVA;
5727 if (srcmob)
5728 setGuiltyMob(x, y, srcmob);
5729 numnew++;
5730 }
5731 if (hasNeighbouringTile(x, y, SQUARE_ACID, srcmob))
5732 {
5733 newtiles[y][x] = SQUARE_ACID;
5734 if (srcmob)
5735 setGuiltyMob(x, y, srcmob);
5736 numnew++;
5737 }
5738 if (hasNeighbouringTile(x, y, SQUARE_WATER, srcmob))
5739 {
5740 newtiles[y][x] = SQUARE_WATER;
5741 if (srcmob)
5742 setGuiltyMob(x, y, srcmob);
5743 numnew++;
5744 }
5745 break;
5746 default:
5747 break;
5748 }
5749 }
5750 }
5751
5752 // Early exit for no-op.
5753 if (!numnew)
5754 {
5755 ourMapDirty = false;
5756 return;
5757 }
5758
5759 // Process all the tiles.
5760 for (y = 0; y < MAP_HEIGHT; y++)
5761 {
5762 for (x = 0; x < MAP_WIDTH; x++)
5763 {
5764 if (newtiles[y][x] == NUM_SQUARES)
5765 continue;
5766
5767 // Because we propagated guilt already, we can use our own guilt
5768 // for source.
5769 // This system built at very high altitude likely over the Pacific
5770 // (the map feature is broken on this plane, the only
5771 // entertainment I like :<)
5772 // Hopefully it holds together at sea level.
5773 switch (getTile(x, y))
5774 {
5775 case SQUARE_ICE:
5776 burnSquare(x, y, getGuiltyMob(x, y));
5777 break;
5778
5779 case SQUARE_LAVA:
5780 freezeSquare(x, y, getGuiltyMob(x, y));
5781 break;
5782
5783 case SQUARE_WATER:
5784 burnSquare(x,y, getGuiltyMob(x, y));
5785 break;
5786
5787 case SQUARE_ACID:
5788 // WTF? Why burn?
5789 burnSquare(x,y, getGuiltyMob(x, y));
5790 break;
5791
5792 case SQUARE_FOREST:
5793 // Only transition is to burn.
5794 burnSquare(x, y, getGuiltyMob(x, y));
5795 break;
5796
5797 case SQUARE_BROKENVIEWPORT:
5798 if (newtiles[y][x] == SQUARE_VIEWPORT)
5799 setTile(x, y, SQUARE_VIEWPORT);
5800 break;
5801
5802 case SQUARE_FORESTFIRE:
5803 // May have burned out or been doused.
5804 if (newtiles[y][x] == SQUARE_CORRIDOR)
5805 {
5806 // Directly extinguish
5807 setTile(x, y, SQUARE_CORRIDOR);
5808 // Drop from trees.
5809 dropMobs(x, y, true, getGuiltyMob(x, y), true);
5810 }
5811 else
5812 {
5813 // Freeze is same as douse.
5814 freezeSquare(x, y, getGuiltyMob(x, y));
5815 }
5816 break;
5817
5818
5819 case SQUARE_FLOORPIT:
5820 case SQUARE_PATHPIT:
5821 case SQUARE_FLOORSPIKEDPIT:
5822 case SQUARE_PATHSPIKEDPIT:
5823 floodSquare((SQUARE_NAMES)newtiles[y][x], x, y,
5824 getGuiltyMob(x, y));
5825 break;
5826 default:
5827 break;
5828 }
5829 }
5830 }
5831 }
5832
5833 void
reportMessage(const char * str,int x,int y) const5834 MAP::reportMessage(const char *str, int x, int y) const
5835 {
5836 if (!str)
5837 return;
5838
5839 // Ignore non-level messages.
5840 if (this != glbCurLevel)
5841 return;
5842
5843 UT_ASSERT(x >= 0 && x < MAP_WIDTH);
5844 UT_ASSERT(y >= 0 && y < MAP_HEIGHT);
5845
5846 // Ignore out of bound messages.
5847 if (x < 0 || x >= MAP_WIDTH || y < 0 || y >= MAP_HEIGHT)
5848 return;
5849
5850 // The FOV check is guaranteed to be the same as the hasLOS for
5851 // the avatar.
5852 if (MOB::getAvatar() &&
5853 !hasLOS(MOB::getAvatar()->getX(), MOB::getAvatar()->getY(),
5854 x, y))
5855 {
5856 // Eat the message.
5857 return;
5858 }
5859
5860 msg_report(gram_capitalize(str));
5861 // We always trail with some spaces, this will not cause line wrap,
5862 // but will ensure successive messages are all froody.
5863 msg_append(" ");
5864 }
5865
5866 bool
isMobOnMap(MOB * mob) const5867 MAP::isMobOnMap(MOB *mob) const
5868 {
5869 MOB *m;
5870
5871 for (m = myMobHead; m; m = m->getNext())
5872 {
5873 if (m == mob)
5874 return true;
5875 }
5876 return false;
5877 }
5878
5879 MAP *
findMapWithMob(MOB * mob,bool usebranch) const5880 MAP::findMapWithMob(MOB *mob, bool usebranch) const
5881 {
5882 MAP *map, *branch;
5883
5884 if (isMobOnMap(mob))
5885 return (MAP *)this;
5886
5887 if (usebranch && peekMapBranch())
5888 {
5889 map = peekMapBranch()->findMapWithMob(mob, false);
5890 if (map)
5891 return map;
5892 }
5893
5894 for (map = peekMapUp(); map; map = map->peekMapUp())
5895 {
5896 if (map->isMobOnMap(mob))
5897 return map;
5898
5899 if (map->peekMapBranch())
5900 {
5901 branch = map->peekMapBranch()->findMapWithMob(mob, false);
5902 if (branch)
5903 return branch;
5904 }
5905 }
5906
5907 for (map = peekMapDown(); map; map = map->peekMapDown())
5908 {
5909 if (map->isMobOnMap(mob))
5910 return map;
5911
5912 if (map->peekMapBranch())
5913 {
5914 branch = map->peekMapBranch()->findMapWithMob(mob, false);
5915 if (branch)
5916 return branch;
5917 }
5918 }
5919
5920 // Couldn't find the mob anywhere...
5921 return 0;
5922 }
5923
5924 void
collapseDungeon(bool dobranch)5925 MAP::collapseDungeon(bool dobranch)
5926 {
5927 MAP *map;
5928
5929 // Rough guestimate of memory usage:
5930 // items: 20 bytes each
5931 // intrinscs, 167 -> 21 bytes
5932 // mobs: 48 + 21 = 69 bytes each
5933 // maps: 2k each
5934 // Need 27 maps, 54k
5935 // In 200k, 2968 mobs or 10,240 items fit.
5936 // Estimate ~500 mobs in bad game, say 72 bytes each, say 32bytes for
5937 // items gives 5,275 items. Seems like I'm still missing somethiing
5938 // big.
5939 // Static maps: itemmap, 2k, mobmap 2k, wallmap 1k, distmap 2k, smell 2k
5940 // celllist 4k, cutouts 4k, refmap 2k, itemmap 4k, all told is 23k.
5941 // That cuts it to 4,539 items :>
5942
5943 if (dobranch)
5944 {
5945 if (peekMapBranch())
5946 {
5947 peekMapBranch()->collapseThisDungeon();
5948 peekMapBranch()->collapseDungeon(false);
5949 }
5950 }
5951
5952 for (map = peekMapUp(); map; map = map->peekMapUp())
5953 {
5954 map->collapseThisDungeon();
5955 }
5956 for (map = peekMapDown(); map; map = map->peekMapDown())
5957 {
5958 map->collapseThisDungeon();
5959 }
5960 }
5961
5962 void
collapseThisDungeon()5963 MAP::collapseThisDungeon()
5964 {
5965 // Destroy all items that are destroyable..
5966 ITEM *item, *next;
5967
5968 for (item = getItemHead(); item; item = next)
5969 {
5970 next = item->getNext();
5971
5972 if (!item->defn().isquest)
5973 {
5974 dropItem(item);
5975 delete item;
5976 }
5977 }
5978
5979 }
5980
5981 void
delayedLevelChange(MAP * newcurrent)5982 MAP::delayedLevelChange(MAP *newcurrent)
5983 {
5984 ourDelayedLevelChange = newcurrent;
5985 }
5986
5987 void
checkForAvatarMapChange()5988 MAP::checkForAvatarMapChange()
5989 {
5990 if (ourDelayedLevelChange)
5991 {
5992 changeCurrentLevel(ourDelayedLevelChange);
5993 ourDelayedLevelChange = 0;
5994 }
5995 }
5996
5997
5998 void
changeCurrentLevel(MAP * newcurrent)5999 MAP::changeCurrentLevel(MAP *newcurrent)
6000 {
6001 int x, y;
6002
6003 // Whatever it was is now invalid.
6004 ourDelayedLevelChange = 0;
6005
6006 glbCurLevel = newcurrent;
6007 if (glbCurLevel)
6008 glbCurLevel->refreshPtrMaps();
6009
6010 // Reset our smell map...
6011 memset(ourAvatarSmell, 0xff, sizeof(u16) * MAP_WIDTH * MAP_HEIGHT);
6012 // Reset our watermark
6013 ourAvatarSmellWatermark = 64;
6014
6015 // Ensure we start looking for water and stuff to move
6016 ourMapDirty = true;
6017
6018 // Reset our guilty map
6019 for (y = 0; y < MAP_HEIGHT; y++)
6020 for (x = 0; x < MAP_WIDTH; x++)
6021 ourMobGuiltyMap[(y) * MAP_WIDTH + x].setMob(0);
6022
6023 // Output any level feelings...
6024 if (newcurrent && newcurrent->branchName() == BRANCH_MAIN)
6025 {
6026 switch (newcurrent->getDepth())
6027 {
6028 case 20:
6029 msg_announce("><0|V|: Expand your horizons! ");
6030 break;
6031 case 21:
6032 msg_announce("Belweir: Welcome to my library! ");
6033 break;
6034 case 22:
6035 msg_announce("Quizar: Find your way through! ");
6036 break;
6037 case 23:
6038 msg_announce("H'ruth: Prove your worth in my combat arena! ");
6039 break;
6040 case 24:
6041 msg_announce("Klaskov: This village is under attack from orcs! ");
6042 break;
6043 case 25:
6044 msg_report("You have entered the fiery pits of hell! ");
6045 break;
6046 }
6047 }
6048 }
6049
6050 void
refreshPtrMaps()6051 MAP::refreshPtrMaps()
6052 {
6053 MOB *mob;
6054 ITEM *item;
6055 int x, y;
6056
6057 for (y = 0; y < MAP_HEIGHT; y++)
6058 for (x = 0; x < MAP_WIDTH; x++)
6059 ourMobRefMap[(y) * MAP_WIDTH + x].setMob(0);
6060
6061 memset(ourItemPtrMap, 0, sizeof(ITEM *) * MAP_WIDTH * MAP_HEIGHT);
6062 for (mob = getMobHead(); mob; mob = mob->getNext())
6063 {
6064 ourMobRefMap[mob->getY() * MAP_WIDTH + mob->getX()].setMob(mob);
6065 if (mob->getSize() >= SIZE_GARGANTUAN)
6066 {
6067 int dx, dy;
6068 for (dx = 0; dx < 2; dx++)
6069 for (dy = 0; dy < 2; dy++)
6070 ourMobRefMap[(mob->getY() + dy) * MAP_WIDTH + mob->getX() + dx].setMob(mob);
6071 }
6072 }
6073 for (item = getItemHead(); item; item = item->getNext())
6074 {
6075 int offset = item->getY() * MAP_WIDTH + item->getX();
6076
6077 // We want to auto-sort to ensure the right people are on top.
6078 if (!ourItemPtrMap[offset] ||
6079 ourItemPtrMap[offset]->getStackOrder() <= item->getStackOrder())
6080 ourItemPtrMap[offset] = item;
6081 }
6082 }
6083
6084 void
setWindDirection(int dx,int dy,int duration)6085 MAP::setWindDirection(int dx, int dy, int duration)
6086 {
6087 ourWindCounter = duration;
6088 ourWindDX = dx;
6089 ourWindDY = dy;
6090 }
6091
6092 void
getWindDirection(int & dx,int & dy)6093 MAP::getWindDirection(int &dx, int &dy)
6094 {
6095 if (ourWindCounter)
6096 {
6097 dx = ourWindDX;
6098 dy = ourWindDY;
6099 }
6100 else
6101 {
6102 dx = 0;
6103 dy = 0;
6104 }
6105 }
6106
6107 void
getAmbientWindDirection(int & dx,int & dy)6108 MAP::getAmbientWindDirection(int &dx, int &dy)
6109 {
6110 if (ourWindCounter)
6111 {
6112 dx = ourWindDX;
6113 dy = ourWindDY;
6114 }
6115 else
6116 {
6117 dx = rand_choice(3)-1;
6118 dy = rand_choice(3)-1;
6119 }
6120 }
6121
6122 bool
isUnlocked() const6123 MAP::isUnlocked() const
6124 {
6125 #ifdef iPOWDER
6126 if (hamfake_isunlocked())
6127 return true;
6128
6129 if (getDepth() > 15)
6130 return false;
6131 return true;
6132 #else
6133 return true;
6134 #endif
6135 }
6136
6137 #ifdef MAPSTATS
6138 void
printStatsHeader(FILE * fp)6139 MAP::printStatsHeader(FILE *fp)
6140 {
6141 fprintf(fp, "Seed, Walkable, LadderDist, NumMob, TotalMobLevel, WallRatio\n");
6142 }
6143
6144 bool
printStats(FILE * fp)6145 MAP::printStats(FILE *fp)
6146 {
6147 fprintf(fp, "%d, ", mySeed);
6148
6149 int numwalk = 0;
6150 int difficulty = 0;
6151 int nummob = 0;
6152 int numwall = 0;
6153 int x, y;
6154 MOB *mob;
6155
6156 for (y = 0; y < MAP_HEIGHT; y++)
6157 {
6158 for (x = 0; x < MAP_WIDTH; x++)
6159 {
6160 if (canMove(x, y, MOVE_STD_FLY,
6161 true, true, true))
6162 {
6163 int dx, dy;
6164
6165 numwalk++;
6166 FORALL_4DIR(dx, dy)
6167 {
6168 if (canMove(x+dx, y+dy, MOVE_STD_FLY,
6169 true, true, true))
6170 {
6171 numwall++;
6172 }
6173 }
6174 }
6175 }
6176 }
6177 fprintf(fp, "%d, ", numwalk);
6178
6179 for (mob = getMobHead(); mob; mob = mob->getNext())
6180 {
6181 if (mob->isAvatar())
6182 continue;
6183
6184 difficulty += mob->getExpLevel();
6185 nummob++;
6186 }
6187
6188
6189 int ux = 0, uy = 0, dx = 0, dy = 0;
6190 bool founda, foundb;
6191 founda = findTile(SQUARE_LADDERUP, ux, uy);
6192 foundb = findTile(SQUARE_LADDERDOWN, dx, dy);
6193 UT_ASSERT(founda && foundb);
6194 buildMoveDistance(dx, dy, MOVE_STD_FLY);
6195
6196 int maxdist = 256;
6197 int dist = ourDistanceMap[uy * MAP_WIDTH + ux];
6198 if (dist > maxdist)
6199 dist = maxdist;
6200 fprintf(fp, "%d, ", dist);
6201
6202 fprintf(fp, "%d, ", nummob);
6203 fprintf(fp, "%d, ", difficulty);
6204 fprintf(fp, "%f, ", ((float)numwall) / numwalk);
6205 fprintf(fp, "\n");
6206
6207 dist--;
6208 #if 1
6209 if (dist == maxdist)
6210 {
6211 for (y = 0; y < MAP_HEIGHT; y++)
6212 {
6213 for (x = 0; x < MAP_WIDTH; x++)
6214 {
6215 int dist = ourDistanceMap[y * MAP_WIDTH + x];
6216
6217 if (getTile(x, y) == SQUARE_LADDERUP)
6218 fprintf(fp, "z");
6219 else if (getTile(x, y) == SQUARE_LADDERDOWN)
6220 fprintf(fp, "y");
6221 else if (dist >= maxdist)
6222 {
6223 if (canMove(x, y, MOVE_STD_FLY,
6224 true, true, true))
6225 {
6226 fprintf(fp, "+");
6227 }
6228 else if (getTile(x, y) == SQUARE_SECRETDOOR ||
6229 getTile(x, y) == SQUARE_DOOR ||
6230 getTile(x, y) == SQUARE_BLOCKEDDOOR)
6231 {
6232 fprintf(fp, "/");
6233 }
6234 else
6235 {
6236 fprintf(fp, "#");
6237 }
6238 }
6239 else
6240 fprintf(fp, ".");
6241 }
6242 fprintf(fp, "\n");
6243 }
6244 }
6245 #endif
6246
6247 return dist == maxdist;
6248 }
6249
6250 #endif
6251