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