1 /* GemRB - Infinity Engine Emulator
2  * Copyright (C) 2003-2004 The GemRB Project
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13 
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  *
19  */
20 
21 // This class represents the .ARE (game area) files in the engine
22 
23 #include "Map.h"
24 
25 #include "Ambient.h"
26 #include "AmbientMgr.h"
27 #include "Audio.h"
28 #include "DisplayMessage.h"
29 #include "Game.h"
30 #include "GameData.h"
31 #include "IniSpawn.h"
32 #include "MapMgr.h"
33 #include "MapReverb.h"
34 #include "MusicMgr.h"
35 #include "ImageMgr.h"
36 #include "Palette.h"
37 #include "Particles.h"
38 #include "PluginMgr.h"
39 #include "Projectile.h"
40 #include "SaveGameIterator.h"
41 #include "ScriptedAnimation.h"
42 #include "TileMap.h"
43 #include "VEFObject.h"
44 #include "Video.h"
45 #include "WorldMap.h"
46 #include "strrefs.h"
47 #include "ie_cursors.h"
48 #include "GameScript/GSUtils.h"
49 #include "GUI/GameControl.h"
50 #include "GUI/Window.h"
51 #include "RNG.h"
52 #include "Scriptable/Container.h"
53 #include "Scriptable/Door.h"
54 #include "Scriptable/InfoPoint.h"
55 #include "System/StringBuffer.h"
56 
57 #include <cmath>
58 #include <cassert>
59 #include <limits>
60 
61 namespace GemRB {
62 
63 #define YESNO(x) ( (x)?"Yes":"No")
64 
65 // TODO: fix this hardcoded resource reference
66 static ieResRef PortalResRef={"EF03TPR3"};
67 static unsigned int PortalTime = 15;
68 static unsigned int MAX_CIRCLESIZE = 8;
69 static int MaxVisibility = 30;
70 static int VisibilityPerimeter; //calculated from MaxVisibility
71 static int NormalCost = 10;
72 static int AdditionalCost = 4;
73 static PathMapFlags Passable[16] = {
74 	PathMapFlags::NO_SEE,
75 	PathMapFlags::PASSABLE,
76 	PathMapFlags::PASSABLE,
77 	PathMapFlags::PASSABLE,
78 	PathMapFlags::PASSABLE,
79 	PathMapFlags::PASSABLE,
80 	PathMapFlags::PASSABLE,
81 	PathMapFlags::PASSABLE,
82 	PathMapFlags::IMPASSABLE,
83 	PathMapFlags::PASSABLE,
84 	PathMapFlags::SIDEWALL,
85 	PathMapFlags::IMPASSABLE,
86 	PathMapFlags::IMPASSABLE,
87 	PathMapFlags::IMPASSABLE,
88 	PathMapFlags::PASSABLE | PathMapFlags::TRAVEL,
89 	PathMapFlags::PASSABLE
90 };
91 static Point **VisibilityMasks=NULL;
92 
93 static bool PathFinderInited = false;
94 static Variables Spawns;
95 static int LargeFog;
96 static TerrainSounds *terrainsounds=NULL;
97 static int tsndcount = -1;
98 static ieDword oldGameTime = 0;
99 
ReleaseSpawnGroup(void * poi)100 static void ReleaseSpawnGroup(void *poi)
101 {
102 	delete (SpawnGroup *) poi;
103 }
104 
Spawn()105 Spawn::Spawn() {
106 	Creatures = NULL;
107 	NextSpawn = Method = sduration = Count = Maximum = Difficulty = 0;
108 	DayChance = NightChance = Enabled = Frequency = 0;
109 	rwdist = owdist = appearance = 0;
110 	Name[0] = 0;
111 }
112 
ReleaseMemory()113 void Map::ReleaseMemory()
114 {
115 	if (VisibilityMasks) {
116 		for (int i=0;i<MaxVisibility;i++) {
117 			free(VisibilityMasks[i]);
118 		}
119 		free(VisibilityMasks);
120 		VisibilityMasks = NULL;
121 	}
122 	Spawns.RemoveAll(ReleaseSpawnGroup);
123 	PathFinderInited = false;
124 	if (terrainsounds) {
125 		delete [] terrainsounds;
126 		terrainsounds = NULL;
127 	}
128 }
129 
SelectObject(const Actor * actor,int q,const AreaAnimation * a,const VEFObject * sca,const Particles * spark,const Projectile * pro,const Container * pile)130 static inline AnimationObjectType SelectObject(const Actor *actor, int q, const AreaAnimation *a, const VEFObject *sca, const Particles *spark, const Projectile *pro, const Container *pile)
131 {
132 	int actorh;
133 	if (actor) {
134 		actorh = actor->Pos.y;
135 		if (q) actorh = 0;
136 	} else {
137 		actorh = 0x7fffffff;
138 	}
139 
140 	int aah;
141 	if (a) {
142 		//aah = a->Pos.y;//+a->height;
143 		aah = a->GetHeight();
144 	} else {
145 		aah = 0x7fffffff;
146 	}
147 
148 	int scah;
149 	if (sca) {
150 		scah = sca->Pos.y;//+sca->ZPos;
151 	} else {
152 		scah = 0x7fffffff;
153 	}
154 
155 	int spah;
156 	if (spark) {
157 		//no idea if this should be plus or minus (or here at all)
158 		spah = spark->GetHeight();//+spark->pos.h;
159 	} else {
160 		spah = 0x7fffffff;
161 	}
162 
163 	int proh;
164 	if (pro) {
165 		proh = pro->GetHeight();
166 	} else {
167 		proh = 0x7fffffff;
168 	}
169 
170 	// piles should always be drawn last, except if there is a corpse in the way
171 	if (actor && (actor->GetStat(IE_STATE_ID) & STATE_DEAD)) {
172 		return AOT_ACTOR;
173 	}
174 	if (pile) {
175 		return AOT_PILE;
176 	}
177 
178 	if (proh<actorh && proh<scah && proh<aah && proh<spah) return AOT_PROJECTILE;
179 
180 	if (spah<actorh && spah<scah && spah<aah) return AOT_SPARK;
181 
182 	if (aah<actorh && aah<scah) return AOT_AREA;
183 
184 	if (scah<actorh) return AOT_SCRIPTED;
185 
186 	return AOT_ACTOR;
187 }
188 
189 //returns true if creature must be embedded in the area
190 //npcs in saved game shouldn't be embedded either
MustSave(const Actor * actor)191 static inline bool MustSave(const Actor *actor)
192 {
193 	if (actor->Persistent()) {
194 		return false;
195 	}
196 
197 	//check for familiars, summons?
198 	return true;
199 }
200 
201 //Preload spawn group entries (creature resrefs that reference groups of creatures)
InitSpawnGroups()202 static void InitSpawnGroups()
203 {
204 	ieResRef GroupName;
205 
206 	AutoTable tab("spawngrp", true);
207 
208 	Spawns.RemoveAll(NULL);
209 	Spawns.SetType( GEM_VARIABLES_POINTER );
210 
211 	if (!tab)
212 		return;
213 
214 	int i = tab->GetColNamesCount();
215 	while (i--) {
216 		int j=tab->GetRowCount();
217 		while (j--) {
218 			const char *crename = tab->QueryField( j,i );
219 			if (strcmp(crename, tab->QueryDefault())) break;
220 		}
221 		if (j>0) {
222 			SpawnGroup *creatures = new SpawnGroup(j);
223 			//difficulty
224 			creatures->Level = (ieDword) atoi( tab->QueryField(0,i) );
225 			for (;j;j--) {
226 				strnlwrcpy( creatures->ResRefs[j-1], tab->QueryField(j,i), 8 );
227 			}
228 			strnlwrcpy( GroupName, tab->GetColumnName( i ), 8 );
229 			Spawns.SetAt( GroupName, (void*) creatures );
230 		}
231 	}
232 }
233 
234 //Preload the searchmap configuration
InitPathFinder()235 static void InitPathFinder()
236 {
237 	PathFinderInited = true;
238 	tsndcount = 0;
239 	AutoTable tm("pathfind");
240 
241 	if (!tm) {
242 		return;
243 	}
244 
245 	const char* poi;
246 
247 	for (int i = 0; i < 16; i++) {
248 		poi = tm->QueryField( 0, i );
249 		if (*poi != '*')
250 			Passable[i] = PathMapFlags(atoi(poi));
251 	}
252 	poi = tm->QueryField( 1, 0 );
253 	if (*poi != '*')
254 		NormalCost = atoi( poi );
255 	poi = tm->QueryField( 1, 1 );
256 	if (*poi != '*')
257 		AdditionalCost = atoi( poi );
258 	int rc = tm->GetRowCount()-2;
259 	if (rc>0) {
260 		terrainsounds = new TerrainSounds[rc];
261 		tsndcount = rc;
262 		while(rc--) {
263 			strnuprcpy(terrainsounds[rc].Group,tm->GetRowName(rc+2), sizeof(ieResRef)-1 );
264 			for(int i = 0; i<16;i++) {
265 				strnuprcpy(terrainsounds[rc].Sounds[i], tm->QueryField(rc+2, i), sizeof(ieResRef)-1 );
266 			}
267 		}
268 	}
269 }
270 
AddLOS(int destx,int desty,int slot)271 static void AddLOS(int destx, int desty, int slot)
272 {
273 	for (int i=0;i<MaxVisibility;i++) {
274 		int x = ((destx*i + MaxVisibility/2) / MaxVisibility) * 16;
275 		int y = ((desty*i + MaxVisibility/2) / MaxVisibility) * 12;
276 		if (LargeFog) {
277 			x += 16;
278 			y += 12;
279 		}
280 		VisibilityMasks[i][slot].x=(short) x;
281 		VisibilityMasks[i][slot].y=(short) y;
282 	}
283 }
284 
InitExplore()285 static void InitExplore()
286 {
287 	LargeFog = !core->HasFeature(GF_SMALL_FOG);
288 
289 	//circle perimeter size for MaxVisibility
290 	int x = MaxVisibility;
291 	int y = 0;
292 	int xc = 1 - ( 2 * MaxVisibility );
293 	int yc = 1;
294 	int re = 0;
295 	VisibilityPerimeter = 0;
296 	while (x>=y) {
297 		VisibilityPerimeter+=8;
298 		y++;
299 		re += yc;
300 		yc += 2;
301 		if (( ( 2 * re ) + xc ) > 0) {
302 			x--;
303 			re += xc;
304 			xc += 2;
305 		}
306 	}
307 
308 	VisibilityMasks = (Point **) malloc(MaxVisibility * sizeof(Point *) );
309 	for (int i = 0; i < MaxVisibility; i++) {
310 		VisibilityMasks[i] = (Point *) malloc(VisibilityPerimeter*sizeof(Point) );
311 	}
312 
313 	x = MaxVisibility;
314 	y = 0;
315 	xc = 1 - ( 2 * MaxVisibility );
316 	yc = 1;
317 	re = 0;
318 	VisibilityPerimeter = 0;
319 	while (x>=y) {
320 		AddLOS (x, y, VisibilityPerimeter++);
321 		AddLOS (-x, y, VisibilityPerimeter++);
322 		AddLOS (-x, -y, VisibilityPerimeter++);
323 		AddLOS (x, -y, VisibilityPerimeter++);
324 		AddLOS (y, x, VisibilityPerimeter++);
325 		AddLOS (-y, x, VisibilityPerimeter++);
326 		AddLOS (-y, -x, VisibilityPerimeter++);
327 		AddLOS (y, -x, VisibilityPerimeter++);
328 		y++;
329 		re += yc;
330 		yc += 2;
331 		if (( ( 2 * re ) + xc ) > 0) {
332 			x--;
333 			re += xc;
334 			xc += 2;
335 		}
336 	}
337 }
338 
Map(void)339 Map::Map(void)
340 	: Scriptable( ST_AREA )
341 {
342 	area=this;
343 	TMap = NULL;
344 	LightMap = NULL;
345 	HeightMap = NULL;
346 	SrchMap = NULL;
347 	queue[PR_SCRIPT] = NULL;
348 	queue[PR_DISPLAY] = NULL;
349 	INISpawn = NULL;
350 	//no one needs this queue
351 	//queue[PR_IGNORE] = NULL;
352 	Qcount[PR_SCRIPT] = 0;
353 	Qcount[PR_DISPLAY] = 0;
354 	//no one needs this queue
355 	//Qcount[PR_IGNORE] = 0;
356 	lastActorCount[PR_SCRIPT] = 0;
357 	lastActorCount[PR_DISPLAY] = 0;
358 	//no one needs this
359 	//lastActorCount[PR_IGNORE] = 0;
360 	if (!PathFinderInited) {
361 		InitPathFinder();
362 		InitSpawnGroups();
363 		InitExplore();
364 	}
365 	ExploredBitmap = NULL;
366 	VisibleBitmap = NULL;
367 	version = 0;
368 	MasterArea = core->GetGame()->MasterArea(scriptName);
369 	Background = NULL;
370 	BgDuration = 0;
371 	LastGoCloser = 0;
372 	AreaFlags = AreaType = AreaDifficulty = 0;
373 	Rain = Snow = Fog = Lightning = DayNight = 0;
374 	trackString = trackFlag = trackDiff = 0;
375 	Width = Height = 0;
376 	RestHeader.Difficulty = RestHeader.CreatureNum = RestHeader.Maximum = RestHeader.Enabled = 0;
377 	RestHeader.DayChance = RestHeader.NightChance = RestHeader.sduration = RestHeader.rwdist = RestHeader.owdist = 0;
378 	SongHeader.reverbID = SongHeader.MainDayAmbientVol = SongHeader.MainNightAmbientVol = 0;
379 	reverb = NULL;
380 	MaterialMap = NULL;
381 	wallStencil = NULL;
382 }
383 
~Map(void)384 Map::~Map(void)
385 {
386 	free( SrchMap );
387 	free( MaterialMap );
388 
389 	//close the current container if it was owned by this map, this avoids a crash
390 	const Container *c = core->GetCurrentContainer();
391 	if (c && c->GetCurrentArea()==this) {
392 		core->CloseCurrentContainer();
393 	}
394 
395 	delete TMap;
396 	delete INISpawn;
397 	 for (auto anim : animations) {
398 		delete anim;
399 	}
400 
401 	for (auto actor : actors) {
402 		//don't delete NPC/PC
403 		if (actor && !actor->Persistent()) {
404 			delete actor;
405 		}
406 	}
407 
408 	for (auto entrance : entrances) {
409 		delete entrance;
410 	}
411 	for (auto spawn : spawns) {
412 		delete spawn;
413 	}
414 	delete LightMap;
415 	delete HeightMap;
416 
417 	for (int i = 0; i < QUEUE_COUNT; i++) {
418 		free(queue[i]);
419 		queue[i] = NULL;
420 	}
421 
422 	for (auto projectile : projectiles) {
423 		delete projectile;
424 	}
425 
426 	for (auto vvc : vvcCells) {
427 		delete vvc;
428 	}
429 
430 	for (auto particle : particles) {
431 		delete particle;
432 	}
433 
434 	AmbientMgr *ambim = core->GetAudioDrv()->GetAmbientMgr();
435 	ambim->reset();
436 	for (auto ambient : ambients) {
437 		delete ambient;
438 	}
439 
440 	if (reverb) {
441 		delete reverb;
442 	}
443 
444 	//malloc-d in AREImp
445 	free( ExploredBitmap );
446 	free( VisibleBitmap );
447 }
448 
ChangeTileMap(Image * lm,Holder<Sprite2D> sm)449 void Map::ChangeTileMap(Image* lm, Holder<Sprite2D> sm)
450 {
451 	delete LightMap;
452 
453 	LightMap = lm;
454 	SmallMap = sm;
455 
456 	TMap->UpdateDoors();
457 }
458 
AddTileMap(TileMap * tm,Image * lm,Bitmap * sr,Holder<Sprite2D> sm,Bitmap * hm)459 void Map::AddTileMap(TileMap* tm, Image* lm, Bitmap* sr, Holder<Sprite2D> sm, Bitmap* hm)
460 {
461 	// CHECKME: leaks? Should the old TMap, LightMap, etc... be freed?
462 	TMap = tm;
463 	LightMap = lm;
464 	HeightMap = hm;
465 	SmallMap = sm;
466 	Width = (unsigned int) (TMap->XCellCount * 4);
467 	Height = (unsigned int) (( TMap->YCellCount * 64 + 63) / 12);
468 	unsigned int SRWidth = sr->GetWidth();
469 	unsigned int y = sr->GetHeight();
470 	assert(Width >= SRWidth && Height >= y);
471 	//Internal Searchmap
472 	SrchMap = (PathMapFlags *) calloc(Width * Height, sizeof(PathMapFlags));
473 	MaterialMap = (unsigned short *) calloc(Width * Height, sizeof(unsigned short));
474 	while(y--) {
475 		int x = SRWidth;
476 		while(x--) {
477 			uint8_t value = uint8_t(PathMapFlags(sr->GetAt(x,y)) & PathMapFlags::AREAMASK);
478 			size_t index = y * Width + x;
479 			SrchMap[index] = Passable[value];
480 			MaterialMap[index] = value;
481 		}
482 	}
483 
484 	//delete the original searchmap
485 	delete sr;
486 }
AutoLockDoors() const487 void Map::AutoLockDoors() const
488 {
489 	GetTileMap()->AutoLockDoors();
490 }
491 
MoveToNewArea(const char * area,const char * entrance,unsigned int direction,int EveryOne,Actor * actor) const492 void Map::MoveToNewArea(const char *area, const char *entrance, unsigned int direction, int EveryOne, Actor *actor) const
493 {
494 	char command[256];
495 
496 	//change loader MOS image here
497 	//check worldmap entry, if that doesn't contain anything,
498 	//make a random pick
499 
500 	Game* game = core->GetGame();
501 	if (EveryOne==CT_WHOLE) {
502 		//copy the area name if it exists on the worldmap
503 		unsigned int index;
504 
505 		const WMPAreaEntry* entry = core->GetWorldMap()->FindNearestEntry(area, index);
506 		if (entry) {
507 			memcpy (game->PreviousArea, entry->AreaName, 8);
508 		}
509 
510 		//perform autosave
511 		core->GetSaveGameIterator()->CreateSaveGame(0, false);
512 	}
513 	const Map *map = game->GetMap(area, false);
514 	if (!map) {
515 		Log(ERROR, "Map", "Invalid map: %s", area);
516 		command[0]=0;
517 		return;
518 	}
519 	const Entrance *ent = nullptr;
520 	if (entrance[0]) {
521 		ent = map->GetEntrance( entrance );
522 		if (!ent) {
523 			Log(ERROR, "Map", "Invalid entrance '%s' for area %s", entrance, area);
524 		}
525 	}
526 	int X,Y, face;
527 	if (!ent) {
528 		// no entrance found, try using direction flags
529 
530 		face = -1; // should this be handled per-case?
531 
532 		// ok, so the original engine tries these in a different order
533 		// (north first, then south) but it doesn't seem to matter
534 		if (direction & ADIRF_NORTH) {
535 			X = map->TMap->XCellCount * 32;
536 			Y = 0;
537 		} else if (direction & ADIRF_EAST) {
538 			X = map->TMap->XCellCount * 64;
539 			Y = map->TMap->YCellCount * 32;
540 		} else if (direction & ADIRF_SOUTH) {
541 			X = map->TMap->XCellCount * 32;
542 			Y = map->TMap->YCellCount * 64;
543 		} else if (direction & ADIRF_WEST) {
544 			X = 0;
545 			Y = map->TMap->YCellCount * 32;
546 		} else if (direction & ADIRF_CENTER) {
547 			X = map->TMap->XCellCount * 32;
548 			Y = map->TMap->YCellCount * 32;
549 		} else {
550 			// crashes in original engine
551 			Log(WARNING, "Map", "WARNING!!! EntryPoint '%s' does not exist and direction %d is invalid",
552 				entrance, direction);
553 			X = map->TMap->XCellCount * 64;
554 			Y = map->TMap->YCellCount * 64;
555 		}
556 	} else {
557 		X = ent->Pos.x;
558 		Y = ent->Pos.y;
559 		face = ent->Face;
560 	}
561 	//LeaveArea is the same in ALL engine versions
562 	snprintf(command, sizeof(command), "LeaveArea(\"%s\",[%d.%d],%d)", area, X, Y, face);
563 
564 	if (EveryOne&CT_GO_CLOSER) {
565 		int i=game->GetPartySize(false);
566 		while (i--) {
567 			Actor *pc = game->GetPC(i,false);
568 			if (pc->GetCurrentArea()==this) {
569 			  pc->MovementCommand(command);
570 			}
571 		}
572 		i = game->GetNPCCount();
573 		while(i--) {
574 			Actor *npc = game->GetNPC(i);
575 			if ((npc->GetCurrentArea()==this) && (npc->GetStat(IE_EA)<EA_GOODCUTOFF) ) {
576 				npc->MovementCommand(command);
577 			}
578 		}
579 		return;
580 	}
581 	if (EveryOne&CT_SELECTED) {
582 		int i=game->GetPartySize(false);
583 		while (i--) {
584 			Actor *pc = game->GetPC(i,false);
585 
586 			if (!pc->IsSelected()) {
587 				continue;
588 			}
589 			if (pc->GetCurrentArea()==this) {
590 				pc->MovementCommand(command);
591 			}
592 		}
593 		i = game->GetNPCCount();
594 		while(i--) {
595 			Actor *npc = game->GetNPC(i);
596 			if (npc->IsSelected() && (npc->GetCurrentArea()==this)) {
597 				npc->MovementCommand(command);
598 			}
599 		}
600 		return;
601 	}
602 
603 	actor->MovementCommand(command);
604 }
605 
UseExit(Actor * actor,InfoPoint * ip)606 void Map::UseExit(Actor *actor, InfoPoint *ip)
607 {
608 	const Game *game = core->GetGame();
609 
610 	int EveryOne = ip->CheckTravel(actor);
611 	switch(EveryOne) {
612 	case CT_GO_CLOSER:
613 		if (LastGoCloser<game->Ticks) {
614 			displaymsg->DisplayConstantString(STR_WHOLEPARTY, DMC_WHITE); //white
615 			LastGoCloser = game->Ticks+6000;
616 		}
617 		if (game->EveryoneStopped()) {
618 			ip->Flags&=~TRAP_RESET; //exit triggered
619 		}
620 		return;
621 		//no ingame message for these events
622 	case CT_CANTMOVE: case CT_SELECTED:
623 		return;
624 	case CT_ACTIVE: case CT_WHOLE: case CT_MOVE_SELECTED:
625 		break;
626 	}
627 
628 	if (ip->Destination[0] != 0) {
629 		// the 0 here is default orientation, can infopoints specify that or
630 		// is an entrance always provided?
631 		MoveToNewArea(ip->Destination, ip->EntranceName, 0, EveryOne, actor);
632 		return;
633 	}
634 	if (ip->Scripts[0]) {
635 		ip->AddTrigger(TriggerEntry(trigger_entered, actor->GetGlobalID()));
636 		// FIXME
637 		ip->ExecuteScript( 1 );
638 		ip->ProcessActions();
639 	}
640 }
641 
642 //Draw two overlapped animations to achieve the original effect
643 //PlayOnce makes sure that if we stop drawing them, they will go away
DrawPortal(const InfoPoint * ip,int enable)644 void Map::DrawPortal(const InfoPoint *ip, int enable)
645 {
646 	ieDword gotportal = HasVVCCell(PortalResRef, ip->Pos);
647 
648 	if (enable) {
649 		if (gotportal>PortalTime) return;
650 		ScriptedAnimation *sca = gamedata->GetScriptedAnimation(PortalResRef, false);
651 		if (sca) {
652 			sca->SetBlend();
653 			sca->PlayOnce();
654 			//exact position, because HasVVCCell depends on the coordinates, PST had no coordinate offset anyway
655 			sca->Pos = ip->Pos;
656 			//this is actually ordered by time, not by height
657 			sca->ZOffset = gotportal;
658 			AddVVCell( new VEFObject(sca));
659 		}
660 		return;
661 	}
662 }
663 
UpdateScripts()664 void Map::UpdateScripts()
665 {
666 	bool has_pcs = false;
667 	for (auto actor : actors) {
668 		if (actor->InParty) {
669 			has_pcs = true;
670 			break;
671 		}
672 	}
673 
674 	GenerateQueues();
675 	SortQueues();
676 
677 	// if masterarea, then we allow 'any' actors
678 	// if not masterarea, we allow only players
679 	// if (!GetActorCount(MasterArea) ) {
680 	// fuzzie changed this because the previous code was wrong
681 	// (GetActorCount(false) returns only non-PCs) - it is not
682 	// well-tested so feel free to change if there are problems
683 	// (for example, the CanFree seems like it would be needed to
684 	// check for any running scripts, such as following, but it seems
685 	// to work ok anyway in my testing - if you change it you probably
686 	// also want to change the actor updating code below so it doesn't
687 	// add new actions while we are trying to get rid of the area!)
688 	if (!has_pcs && !(MasterArea && actors.size()) /*&& !CanFree()*/) {
689 		return;
690 	}
691 
692 	// fuzzie added this check because some area scripts (eg, AR1600 when
693 	// escaping Brynnlaw) were executing after they were meant to be done,
694 	// and this seems the nicest way of handling that for now - it's quite
695 	// possibly wrong (so if you have problems, revert this and find
696 	// another way)
697 	if (has_pcs) {
698 		//Run all the Map Scripts (as in the original)
699 		//The default area script is in the last slot anyway
700 		//ExecuteScript( MAX_SCRIPTS );
701 		Update();
702 	} else {
703 		ProcessActions();
704 	}
705 
706 	// If scripts frozen, return.
707 	// This fixes starting a new IWD game. The above ProcessActions pauses the
708 	// game for a textscreen, but one of the actor->ProcessActions calls
709 	// below starts a cutscene, hiding the mouse. - wjp, 20060805
710 	if (core->GetGameControl()->GetDialogueFlags() & DF_FREEZE_SCRIPTS) return;
711 
712 	Game *game = core->GetGame();
713 	bool timestop = game->IsTimestopActive();
714 	if (!timestop) {
715 		game->SetTimestopOwner(NULL);
716 	}
717 
718 	ieDword time = game->Ticks; // make sure everything moves at the same time
719 
720 	//Run actor scripts (only for 0 priority)
721 	int q = Qcount[PR_SCRIPT];
722 	while (q--) {
723 		Actor* actor = queue[PR_SCRIPT][q];
724 		//actor just moved away, don't run its script from this side
725 		if (actor->GetCurrentArea()!=this) {
726 			continue;
727 		}
728 
729 		if (game->TimeStoppedFor(actor)) {
730 			continue;
731 		}
732 
733 		//Avenger moved this here from ApplyAllEffects (this one modifies the effect queue)
734 		//.. but then fuzzie moved this here from UpdateActorState, because otherwise
735 		//immobile actors (see check below) never become mobile again!
736 		//Avenger again: maybe this should be before the timestop check above
737 		//definitely try to move it up if you experience freezes after timestop
738 		actor->fxqueue.Cleanup();
739 
740 		//if the actor is immobile, don't run the scripts
741 		//FIXME: this is not universally true, only some states have this effect
742 		// paused targets do something similar, but are handled in the effect
743 		if (!game->StateOverrideFlag && !game->StateOverrideTime) {
744 			//it looks like STATE_SLEEP allows scripts, probably it is STATE_HELPLESS what disables scripts
745 			//if that isn't true either, remove this block completely
746 			if (actor->GetStat(IE_STATE_ID) & STATE_HELPLESS) {
747 				actor->SetInternalFlag(IF_JUSTDIED, OP_NAND);
748 				continue;
749 			}
750 		}
751 
752 		/*
753 		 * we run scripts all at once because one of the actions in ProcessActions
754 		 * might remove us from a cutscene and then bad things can happen when
755 		 * scripts are queued unexpectedly (such as an ogre in a cutscene -> dialog
756 		 * -> cutscene transition in the first bg1 cutscene exploiting the race
757 		 * condition to murder player1) - it is entirely possible that we should be
758 		 * doing this differently (for example by storing the cutscene state at the
759 		 * start of this function, or by changing the cutscene state at a later
760 		 * point, etc), but i did it this way for now because it seems least painful
761 		 * and we should probably be staggering the script executions anyway
762 		 */
763 		actor->Update();
764 		actor->UpdateActorState();
765 		actor->SetSpeed(false);
766 
767 		if (actor->GetRandomBackoff()) {
768 			actor->DecreaseBackoff();
769 			if (!actor->GetRandomBackoff() && actor->GetSpeed() > 0) {
770 				actor->NewPath();
771 			}
772 		} else if (actor->GetStep() && actor->GetSpeed()) {
773 			// Make actors pathfind if there are others nearby
774 			// in order to avoid bumping when possible
775 			const Actor* nearActor = GetActorInRadius(actor->Pos, GA_NO_DEAD|GA_NO_UNSCHEDULED, actor->GetAnims()->GetCircleSize());
776 			if (nearActor && nearActor != actor) {
777 				actor->NewPath();
778 			}
779 			DoStepForActor(actor, time);
780 		} else {
781 			DoStepForActor(actor, time);
782 		}
783 	}
784 
785 	//clean up effects on dead actors too
786 	q = Qcount[PR_DISPLAY];
787 	while(q--) {
788 		Actor* actor = queue[PR_DISPLAY][q];
789 		actor->fxqueue.Cleanup();
790 	}
791 
792 	//Check if we need to start some door scripts
793 	int doorCount = 0;
794 	while (true) {
795 		Door* door = TMap->GetDoor( doorCount++ );
796 		if (!door)
797 			break;
798 		door->Update();
799 	}
800 
801 	//Check if we need to start some container scripts
802 	int containerCount = 0;
803 	while (true) {
804 		Container* container = TMap->GetContainer( containerCount++ );
805 		if (!container)
806 			break;
807 		container->Update();
808 	}
809 
810 	//Check if we need to start some trap scripts
811 	int ipCount = 0;
812 	while (true) {
813 		//For each InfoPoint in the map
814 		InfoPoint* ip = TMap->GetInfoPoint( ipCount++ );
815 		if (!ip)
816 			break;
817 
818 		if (ip->IsPortal()) {
819 			DrawPortal(ip, ip->Trapped&PORTAL_TRAVEL);
820 		}
821 
822 		//If this InfoPoint has no script and it is not a Travel Trigger, skip it
823 		// InfoPoints of all types don't run scripts if TRAP_DEACTIVATED is set
824 		// (eg, TriggerActivation changes this, see lightning room from SoA)
825 		int wasActive = (!(ip->Flags&TRAP_DEACTIVATED) ) || (ip->Type==ST_TRAVEL);
826 		if (!wasActive) continue;
827 
828 		if (ip->Type == ST_TRIGGER) {
829 			ip->Update();
830 			continue;
831 		}
832 
833 		q = Qcount[PR_SCRIPT];
834 		ieDword exitID = ip->GetGlobalID();
835 		while (q--) {
836 			Actor *actor = queue[PR_SCRIPT][q];
837 			if (ip->Type == ST_PROXIMITY) {
838 				if (ip->Entered(actor)) {
839 					// if trap triggered, then mark actor
840 					actor->SetInTrap(ipCount);
841 					wasActive |= _TRAP_USEPOINT;
842 				}
843 			} else {
844 				// ST_TRAVEL
845 				// don't move if doing something else
846 				// added CurrentAction as part of blocking action fixes
847 				if (actor->CannotPassEntrance(exitID)) {
848 					continue;
849 				}
850 				// this is needed, otherwise the travel
851 				// trigger would be activated anytime
852 				// Well, i don't know why is it here, but lets try this
853 				if (ip->Entered(actor)) {
854 					UseExit(actor, ip);
855 				}
856 			}
857 		}
858 
859 		// Play the PST specific enter sound
860 		if (wasActive & _TRAP_USEPOINT) {
861 			core->GetAudioDrv()->Play(ip->EnterWav, SFX_CHAN_ACTIONS,
862 				ip->TrapLaunch.x, ip->TrapLaunch.y);
863 		}
864 		ip->Update();
865 	}
866 
867 	UpdateSpawns();
868 	GenerateQueues();
869 	SortQueues();
870 }
871 
ResolveTerrainSound(ieResRef & sound,const Point & Pos) const872 void Map::ResolveTerrainSound(ieResRef &sound, const Point &Pos) const
873 {
874 	for(int i=0;i<tsndcount;i++) {
875 		if (!memcmp(sound, terrainsounds[i].Group, sizeof(ieResRef) ) ) {
876 			int type = MaterialMap[Pos.x/16 + Pos.y/12 * Width];
877 			memcpy(sound, terrainsounds[i].Sounds[type], sizeof(ieResRef) );
878 			return;
879 		}
880 	}
881 }
882 
DoStepForActor(Actor * actor,ieDword time) const883 void Map::DoStepForActor(Actor *actor, ieDword time) const
884 {
885 	int walkScale = actor->GetSpeed();
886 	// Immobile, dead and actors in another map can't walk here
887 	if (actor->Immobile() || walkScale == 0 || actor->GetCurrentArea() != this
888 		|| !actor->ValidTarget(GA_NO_DEAD)) {
889 		return;
890 	}
891 
892 	if (!(actor->GetBase(IE_STATE_ID)&STATE_CANTMOVE) ) {
893 		actor->DoStep(walkScale, time);
894 	}
895 }
896 
ClearSearchMapFor(const Movable * actor)897 void Map::ClearSearchMapFor(const Movable *actor) {
898 	std::vector<Actor *> nearActors = GetAllActorsInRadius(actor->Pos, GA_NO_SELF|GA_NO_DEAD|GA_NO_LOS|GA_NO_UNSCHEDULED, MAX_CIRCLE_SIZE*3, actor);
899 	BlockSearchMap(actor->Pos, actor->size, PathMapFlags::UNMARKED);
900 
901 	// Restore the searchmap areas of any nearby actors that could
902 	// have been cleared by this BlockSearchMap(..., PathMapFlags::UNMARKED).
903 	// (Necessary since blocked areas of actors may overlap.)
904 	for (const Actor *neighbour : nearActors) {
905 		if (neighbour->BlocksSearchMap()) {
906 			BlockSearchMap(neighbour->Pos, neighbour->size, neighbour->IsPartyMember() ? PathMapFlags::PC : PathMapFlags::NPC);
907 		}
908 	}
909 }
910 
FogMapSize() const911 Size Map::FogMapSize() const
912 {
913 	// Ratio of bg tile size and fog tile size
914 	constexpr int CELL_RATIO = 2;
915 	return Size(TMap->XCellCount * CELL_RATIO + LargeFog, TMap->YCellCount * CELL_RATIO + LargeFog);
916 }
917 
FogTileUncovered(const Point & p,const uint8_t * mask) const918 bool Map::FogTileUncovered(const Point &p, const uint8_t* mask) const
919 {
920 	// Returns true if map at (x;y) was explored, else false.
921 	const Size fogSize = FogMapSize();
922 	if (p.x < 0 || p.x >= fogSize.w || p.y < 0 || p.y >= fogSize.h) {
923 		// out of bounds is always foggy
924 		return false;
925 	}
926 
927 	if (mask == nullptr) return true;
928 
929 	div_t res = div(fogSize.w * p.y + p.x, 8);
930 	return bool(mask[res.quot] & (1 << res.rem));
931 }
932 
DrawFogOfWar(const ieByte * explored_mask,const ieByte * visible_mask,const Region & vp)933 void Map::DrawFogOfWar(const ieByte* explored_mask, const ieByte* visible_mask, const Region& vp)
934 {
935 	// Size of Fog-Of-War shadow tile (and bitmap)
936 	constexpr int CELL_SIZE = 32;
937 
938 	// the amount of fuzzing to apply to map edges wehn the viewport overscans
939 	constexpr int FUZZ_AMT = 8;
940 
941 	// size for explored_mask and visible_mask
942 	const Size fogSize = FogMapSize();
943 
944 	const Point start = Clamp(ConvertPointToFog(vp.Origin()), Point(), Point(fogSize.w, fogSize.h));
945 	const Point end = Clamp(ConvertPointToFog(vp.Maximum()) + Point(2 + LargeFog, 2 + LargeFog), Point(), Point(fogSize.w, fogSize.h));
946 	const int x0 = (start.x * CELL_SIZE - vp.x) - (LargeFog * CELL_SIZE / 2);
947 	const int y0 = (start.y * CELL_SIZE - vp.y) - (LargeFog * CELL_SIZE / 2);
948 
949 	const Size mapSize = GetSize();
950 
951 	enum Directions : uint8_t {
952 		N = 1,
953 		W = 2,
954 		NW = N|W,
955 		S = 4,
956 		SW = S|W,
957 		E = 8,
958 		NE = N|E,
959 		SE = S|E
960 	};
961 
962 	Video* vid = core->GetVideoDriver();
963 	if (vp.y < 0) { // top border
964 		Region r(0, 0, vp.w, -vp.y);
965 		vid->DrawRect(r, ColorBlack, true);
966 		r.y += r.h;
967 		r.h = FUZZ_AMT;
968 		for (int x = r.x + x0; x < r.w; x += CELL_SIZE) {
969 			vid->BlitSprite(core->FogSprites[N], Point(x, r.y), &r);
970 		}
971 	}
972 
973 	if (vp.y + vp.h > mapSize.h) { // bottom border
974 		Region r(0, mapSize.h - vp.y, vp.w, vp.y + vp.h - mapSize.h);
975 		vid->DrawRect(r, ColorBlack, true);
976 		r.y -= FUZZ_AMT;
977 		r.h = FUZZ_AMT;
978 		for (int x = r.x + x0; x < r.w; x += CELL_SIZE) {
979 			vid->BlitSprite(core->FogSprites[S], Point(x, r.y), &r);
980 		}
981 	}
982 
983 	if (vp.x < 0) { // left border
984 		Region r(0, std::max(0, -vp.y), -vp.x, mapSize.h);
985 		vid->DrawRect(r, ColorBlack, true);
986 		r.x += r.w;
987 		r.w = FUZZ_AMT;
988 		for (int y = r.y + y0; y < r.h; y += CELL_SIZE) {
989 			vid->BlitSprite(core->FogSprites[W], Point(r.x, y), &r);
990 		}
991 	}
992 
993 	if (vp.x + vp.w > mapSize.w) { // right border
994 		Region r(mapSize.w -vp.x, std::max(0, -vp.y), vp.x + vp.w - mapSize.w, mapSize.h);
995 		vid->DrawRect(r, ColorBlack, true);
996 		r.x -= FUZZ_AMT;
997 		r.w = FUZZ_AMT;
998 		for (int y = r.y + y0; y < r.h; y += CELL_SIZE) {
999 			vid->BlitSprite(core->FogSprites[E], Point(r.x, y), &r);
1000 		}
1001 	}
1002 
1003 	auto IsExplored = [=](int x, int y) {
1004 		return FogTileUncovered(Point(x, y), explored_mask);
1005 	};
1006 
1007 	auto IsVisible = [=](int x, int y) {
1008 		return FogTileUncovered(Point(x, y), visible_mask);
1009 	};
1010 
1011 	auto ConvertPointToScreen = [=](int x, int y) {
1012 		x = (x - start.x) * CELL_SIZE + x0;
1013 		y = (y - start.y) * CELL_SIZE + y0;
1014 		return Point(x, y);
1015 	};
1016 
1017 	auto FillFog = [=](int x, int y, int count, BlitFlags flags) {
1018 		Region r(ConvertPointToScreen(x, y), Size(CELL_SIZE * count, CELL_SIZE));
1019 		vid->DrawRect(r, ColorBlack, true, flags);
1020 	};
1021 
1022 	auto Fill = [=](int x, int y, uint8_t dirs, BlitFlags flags) {
1023 		// If an explored tile is adjacent to an
1024 		//   unexplored one, we draw border sprite
1025 		//   (gradient black <-> transparent)
1026 		// Tiles in four cardinal directions have these
1027 		//   values.
1028 		//
1029 		//      1
1030 		//    2   8
1031 		//      4
1032 		//
1033 		// Values of those unexplored are
1034 		//   added together, the resulting number being
1035 		//   an index of shadow sprite to use. For now,
1036 		//   some tiles are made 'on the fly' by
1037 		//   drawing two or more tiles
1038 
1039 		assert((dirs & 0xf0) == 0);
1040 
1041 		Point p = ConvertPointToScreen(x, y);
1042 		switch (dirs & 0x0f) {
1043 			case N:
1044 			case W:
1045 			case NW:
1046 			case S:
1047 			case SW:
1048 			case E:
1049 			case NE:
1050 			case SE:
1051 				vid->BlitGameSprite(core->FogSprites[dirs], p, flags);
1052 				return true;
1053 			case N|S:
1054 				vid->BlitGameSprite(core->FogSprites[N], p, flags);
1055 				vid->BlitGameSprite(core->FogSprites[S], p, flags);
1056 				return true;
1057 			case NW|SW:
1058 				vid->BlitGameSprite(core->FogSprites[NW], p, flags);
1059 				vid->BlitGameSprite(core->FogSprites[SW], p, flags);
1060 				return true;
1061 			case W|E:
1062 				vid->BlitGameSprite(core->FogSprites[W], p, flags);
1063 				vid->BlitGameSprite(core->FogSprites[E], p, flags);
1064 				return true;
1065 			case NW|NE:
1066 				vid->BlitGameSprite(core->FogSprites[NW], p, flags);
1067 				vid->BlitGameSprite(core->FogSprites[NE], p, flags);
1068 				return true;
1069 			case NE|SE:
1070 				vid->BlitGameSprite(core->FogSprites[NE], p, flags);
1071 				vid->BlitGameSprite(core->FogSprites[SE], p, flags);
1072 				return true;
1073 			case SW|SE:
1074 				vid->BlitGameSprite(core->FogSprites[SW], p, flags);
1075 				vid->BlitGameSprite(core->FogSprites[SE], p, flags);
1076 				return true;
1077 			default: // a fully surrounded tile is filled
1078 				return false;
1079 		}
1080 	};
1081 
1082 	const static BlitFlags opaque = BlitFlags::NONE;
1083 	const static BlitFlags trans = BlitFlags::HALFTRANS | BlitFlags::BLENDED;
1084 
1085 	auto FillExplored = [=](int x, int y) {
1086 		int dirs = !IsExplored(x, y - 1); // N
1087 		if (!IsExplored(x - 1, y)) dirs |= W;
1088 		if (!IsExplored(x, y + 1)) dirs |= S;
1089 		if (!IsExplored(x + 1, y )) dirs |= E;
1090 
1091 		if (dirs && !Fill(x, y, dirs, BlitFlags::BLENDED)) {
1092 			FillFog(x, y, 1, opaque);
1093 		}
1094 	};
1095 
1096 	auto FillVisible = [=](int x, int y) {
1097 		int dirs = !IsVisible( x, y - 1); // N
1098 		if (!IsVisible(x - 1, y)) dirs |= W;
1099 		if (!IsVisible(x, y + 1)) dirs |= S;
1100 		if (!IsVisible(x + 1, y)) dirs |= E;
1101 
1102 		if (dirs && !Fill(x, y, dirs, trans)) {
1103 			FillFog(x, y, 1, trans);
1104 		}
1105 	};
1106 
1107 	for (int y = start.y; y < end.y; y++) {
1108 		int unexploredQueue = 0;
1109 		int shroudedQueue = 0;
1110 		int x = start.x;
1111 		for (; x < end.x; x++) {
1112 			if (IsExplored(x, y)) {
1113 				if (unexploredQueue) {
1114 					FillFog(x - unexploredQueue, y, unexploredQueue, opaque);
1115 					unexploredQueue = 0;
1116 				}
1117 
1118 				if (IsVisible(x, y)) {
1119 					if (shroudedQueue) {
1120 						FillFog(x - shroudedQueue, y, shroudedQueue, trans);
1121 						shroudedQueue = 0;
1122 					}
1123 					FillVisible(x, y);
1124 				} else {
1125 					// coalese all horizontally adjacent shrouded cells
1126 					++shroudedQueue;
1127 				}
1128 
1129 				FillExplored(x, y);
1130 			} else {
1131 				// coalese all horizontally adjacent unexplored cells
1132 				++unexploredQueue;
1133 				if (shroudedQueue) {
1134 					FillFog(x - shroudedQueue, y, shroudedQueue, trans);
1135 					shroudedQueue = 0;
1136 				}
1137 			}
1138 		}
1139 
1140 		if (shroudedQueue) {
1141 			FillFog(x - (shroudedQueue + unexploredQueue), y, shroudedQueue, trans);
1142 		}
1143 
1144 		if (unexploredQueue) {
1145 			FillFog(x - unexploredQueue, y, unexploredQueue, opaque);
1146 		}
1147 	}
1148 }
1149 
DrawHighlightables(const Region & viewport) const1150 void Map::DrawHighlightables(const Region& viewport) const
1151 {
1152 	// NOTE: piles are drawn in the main queue
1153 	unsigned int i = 0;
1154 	Container *c;
1155 	while ((c = TMap->GetContainer(i++)) != NULL) {
1156 		if (c->Type != IE_CONTAINER_PILE) {
1157 			// don't highlight containers behind closed doors
1158 			// how's ar9103 chest has a Pos outside itself, so we check the bounding box instead
1159 			// FIXME: inefficient, check for overlap in AREImporter and only recheck here if a flag was set
1160 			const Door *door = TMap->GetDoor(c->BBox.Center());
1161 			if (door && !(door->Flags & (DOOR_OPEN|DOOR_TRANSPARENT))) continue;
1162 			if (c->Highlight) {
1163 				c->DrawOutline(viewport.Origin());
1164 			} else if (debugFlags & DEBUG_SHOW_CONTAINERS) {
1165 				c->outlineColor = ColorCyan;
1166 				c->DrawOutline(viewport.Origin());
1167 			}
1168 		}
1169 	}
1170 
1171 	Door *d;
1172 	i = 0;
1173 	while ( (d = TMap->GetDoor(i++))!=NULL ) {
1174 		if (d->Highlight) {
1175 			d->DrawOutline(viewport.Origin());
1176 		} else if (debugFlags & DEBUG_SHOW_DOORS && !(d->Flags & DOOR_SECRET)) {
1177 			d->outlineColor = ColorCyan;
1178 			d->DrawOutline(viewport.Origin());
1179 		} else if (debugFlags & DEBUG_SHOW_DOORS_SECRET && d->Flags & DOOR_FOUND) {
1180 			d->outlineColor = ColorMagenta;
1181 			d->DrawOutline(viewport.Origin());
1182 		}
1183 	}
1184 
1185 	InfoPoint *p;
1186 	i = 0;
1187 	while ( (p = TMap->GetInfoPoint(i++))!=NULL ) {
1188 		if (p->Highlight) {
1189 			p->DrawOutline(viewport.Origin());
1190 		} else if (debugFlags & DEBUG_SHOW_INFOPOINTS) {
1191 			if (p->VisibleTrap(true)) {
1192 				p->outlineColor = ColorRed;
1193 			} else {
1194 				p->outlineColor = ColorBlue;
1195 			}
1196 			p->DrawOutline(viewport.Origin());
1197 		}
1198 	}
1199 }
1200 
GetNextPile(int & index) const1201 Container *Map::GetNextPile(int &index) const
1202 {
1203 	Container *c = TMap->GetContainer(index++);
1204 
1205 	while (c) {
1206 		if (c->Type == IE_CONTAINER_PILE) {
1207 			return c;
1208 		}
1209 		c = TMap->GetContainer(index++);
1210 	}
1211 	return NULL;
1212 }
1213 
GetNextActor(int & q,int & index) const1214 Actor *Map::GetNextActor(int &q, int &index) const
1215 {
1216 retry:
1217 	switch(q) {
1218 		case PR_SCRIPT:
1219 			if (index--)
1220 				return queue[q][index];
1221 			q--;
1222 			return NULL;
1223 		case PR_DISPLAY:
1224 			if (index--)
1225 				return queue[q][index];
1226 			q--;
1227 			index = Qcount[q];
1228 			goto retry;
1229 		default:
1230 			return NULL;
1231 	}
1232 }
1233 
GetNextAreaAnimation(aniIterator & iter,ieDword gametime) const1234 AreaAnimation *Map::GetNextAreaAnimation(aniIterator &iter, ieDword gametime) const
1235 {
1236 retry:
1237 	if (iter==animations.end()) {
1238 		return NULL;
1239 	}
1240 	AreaAnimation *a = *(iter++);
1241 	if (!a->Schedule(gametime) ) {
1242 		goto retry;
1243 	}
1244 	if ((a->Flags & A_ANI_NOT_IN_FOG) ? !IsVisible(a->Pos) : !IsExplored(a->Pos)) {
1245 		goto retry;
1246 	}
1247 
1248 	return a;
1249 }
1250 
GetNextSpark(const spaIterator & iter) const1251 Particles *Map::GetNextSpark(const spaIterator &iter) const
1252 {
1253 	if (iter==particles.end()) {
1254 		return NULL;
1255 	}
1256 	return *iter;
1257 }
1258 
1259 //doesn't increase iterator, because we might need to erase it from the list
GetNextProjectile(const proIterator & iter) const1260 Projectile *Map::GetNextProjectile(const proIterator &iter) const
1261 {
1262 	if (iter==projectiles.end()) {
1263 		return NULL;
1264 	}
1265 	return *iter;
1266 }
1267 
GetNextTrap(proIterator & iter) const1268 Projectile *Map::GetNextTrap(proIterator &iter) const
1269 {
1270 	Projectile *pro;
1271 
1272 	do {
1273 		pro=GetNextProjectile(iter);
1274 		if (pro) iter++;
1275 		// find dormant traps (thieves', skull traps, glyphs of warding ...)
1276 		if (pro && pro->GetPhase() == P_TRIGGER) break;
1277 	} while(pro);
1278 	return pro;
1279 }
1280 
GetProjectileCount(proIterator & iter) const1281 size_t Map::GetProjectileCount(proIterator &iter) const
1282 {
1283 	iter = projectiles.begin();
1284 	return projectiles.size();
1285 }
1286 
GetTrapCount(proIterator & iter) const1287 int Map::GetTrapCount(proIterator &iter) const
1288 {
1289 	int cnt = 0;
1290 	iter=projectiles.begin();
1291 	while(GetNextTrap(iter)) {
1292 		cnt++;
1293 	}
1294 	//
1295 	iter = projectiles.begin();
1296 	return cnt;
1297 }
1298 
1299 
1300 //doesn't increase iterator, because we might need to erase it from the list
GetNextScriptedAnimation(const scaIterator & iter) const1301 VEFObject *Map::GetNextScriptedAnimation(const scaIterator &iter) const
1302 {
1303 	if (iter==vvcCells.end()) {
1304 		return NULL;
1305 	}
1306 	return *iter;
1307 }
1308 
1309 //Draw the game area (including overlays, actors, animations, weather)
DrawMap(const Region & viewport,uint32_t dFlags)1310 void Map::DrawMap(const Region& viewport, uint32_t dFlags)
1311 {
1312 	assert(TMap);
1313 	debugFlags = dFlags;
1314 
1315 	Game *game = core->GetGame();
1316 	ieDword gametime = game->GameTime;
1317 	bool timestop = game->IsTimestopActive();
1318 
1319 	//area specific spawn.ini files (a PST feature)
1320 	if (INISpawn) {
1321 		INISpawn->CheckSpawn();
1322 	}
1323 
1324 	// Map Drawing Strategy
1325 	// 1. Draw background
1326 	// 2. Draw overlays (weather)
1327 	// 3. Create a stencil set: a WF_COVERANIMS wall stencil and an opaque wall stencil
1328 	// 4. set the video stencil buffer to animWallStencil
1329 	// 5. Draw background animations (BlitFlags::STENCIL_GREEN)
1330 	// 6. set the video stencil buffer to wallStencil
1331 	// 7. draw scriptables (depending on scriptable->ForceDither() return value)
1332 	// 8. draw fog (BlitFlags::BLENDED)
1333 	// 9. draw text (BlitFlags::BLENDED)
1334 
1335 	//Blit the Background Map Animations (before actors)
1336 	Video* video = core->GetVideoDriver();
1337 	int bgoverride = false;
1338 
1339 	if (Background) {
1340 		if (BgDuration < gametime) {
1341 			Background = nullptr;
1342 		} else {
1343 			video->BlitSprite(Background, Point());
1344 			bgoverride = true;
1345 		}
1346 	}
1347 
1348 	if (!bgoverride) {
1349 		int rain = 0;
1350 		BlitFlags flags = BlitFlags::NONE;
1351 
1352 		if (timestop) {
1353 			flags = BlitFlags::GREY;
1354 		} else if (AreaFlags&AF_DREAM) {
1355 			flags = BlitFlags::SEPIA;
1356 		}
1357 
1358 		if (HasWeather()) {
1359 			//zero when the weather particles are all gone
1360 			rain = game->weather->GetPhase()-P_EMPTY;
1361 		}
1362 
1363 		TMap->DrawOverlays( viewport, rain, flags );
1364 	}
1365 
1366 	const auto& viewportWalls = WallsIntersectingRegion(viewport, false);
1367 	RedrawScreenStencil(viewport, viewportWalls.first);
1368 	video->SetStencilBuffer(wallStencil);
1369 
1370 	//draw all background animations first
1371 	aniIterator aniidx = animations.begin();
1372 
1373 	auto DrawAreaAnimation = [&, this](const AreaAnimation *a) {
1374 		BlitFlags flags = SetDrawingStencilForAreaAnimation(a, viewport);
1375 		flags |= BlitFlags::COLOR_MOD | BlitFlags::BLENDED;
1376 
1377 		if (timestop) {
1378 			flags |= BlitFlags::GREY;
1379 		}
1380 
1381 		Color tint = ColorWhite;
1382 		if (a->Flags & A_ANI_NO_SHADOW) {
1383 			tint = LightMap->GetPixel(a->Pos.x / 16, a->Pos.y / 12);
1384 		}
1385 
1386 		game->ApplyGlobalTint(tint, flags);
1387 
1388 		a->Draw(viewport, tint, flags);
1389 		return GetNextAreaAnimation(aniidx, gametime);
1390 	};
1391 
1392 	const AreaAnimation *a = GetNextAreaAnimation(aniidx, gametime);
1393 	while (a && a->GetHeight() == ANI_PRI_BACKGROUND) {
1394 		a = DrawAreaAnimation(a);
1395 	}
1396 
1397 	if (!bgoverride) {
1398 		//Draw Outlines
1399 		DrawHighlightables(viewport);
1400 	}
1401 
1402 	//drawing queues 1 and 0
1403 	//starting with lower priority
1404 	//so displayed, but inactive actors (dead) will be drawn over
1405 	int q = PR_DISPLAY;
1406 	int index = Qcount[q];
1407 	Actor* actor = GetNextActor(q, index);
1408 
1409 	scaIterator scaidx = vvcCells.begin();
1410 	proIterator proidx = projectiles.begin();
1411 	spaIterator spaidx = particles.begin();
1412 	int pileidx = 0;
1413 	Container *pile = GetNextPile(pileidx);
1414 
1415 	VEFObject *sca = GetNextScriptedAnimation(scaidx);
1416 	Projectile *pro = GetNextProjectile(proidx);
1417 	Particles *spark = GetNextSpark(spaidx);
1418 
1419 	// TODO: In at least HOW/IWD2 actor ground circles will be hidden by
1420 	// an area animation with height > 0 even if the actors themselves are not
1421 	// hidden by it.
1422 
1423 	while (actor || a || sca || spark || pro || pile) {
1424 		switch(SelectObject(actor,q,a,sca,spark,pro,pile)) {
1425 		case AOT_ACTOR:
1426 			{
1427 				bool visible = false;
1428 				// always update the animations even if we arent visible
1429 				if (actor->UpdateDrawingState() && IsExplored(actor->Pos)) {
1430 					// apparently birds and the dead are always visible?
1431 					visible = IsVisible(actor->Pos) || (actor->Modified[IE_DONOTJUMP] & DNJ_BIRD) || (actor->GetInternalFlag() & IF_REALLYDIED);
1432 					if (visible) {
1433 						BlitFlags flags = SetDrawingStencilForScriptable(actor, viewport);
1434 						if (game->TimeStoppedFor(actor)) {
1435 							// when time stops, almost everything turns dull grey,
1436 							// the caster and immune actors being the most notable exceptions
1437 							flags |= BlitFlags::GREY;
1438 						}
1439 
1440 						Color baseTint = area->LightMap->GetPixel(actor->Pos.x / 16, actor->Pos.y / 12);
1441 						Color tint(baseTint);
1442 						game->ApplyGlobalTint(tint, flags);
1443 						actor->Draw(viewport, baseTint, tint, flags|BlitFlags::BLENDED);
1444 					}
1445 				}
1446 
1447 				if (!visible || (actor->GetInternalFlag() & (IF_REALLYDIED|IF_ACTIVE)) == (IF_REALLYDIED|IF_ACTIVE)) {
1448 					actor->SetInternalFlag(IF_TRIGGER_AP, OP_NAND);
1449 					//turning actor inactive if there is no action next turn
1450 					actor->HibernateIfAble();
1451 				}
1452 
1453 				actor = GetNextActor(q, index);
1454 			}
1455 			break;
1456 		case AOT_PILE:
1457 			// draw piles
1458 			if (!bgoverride) {
1459 				const Container* c = TMap->GetContainer(pileidx - 1);
1460 
1461 				BlitFlags flags = SetDrawingStencilForScriptable(c, viewport);
1462 				flags |= BlitFlags::COLOR_MOD | BlitFlags::BLENDED;
1463 
1464 				if (timestop) {
1465 					flags |= BlitFlags::GREY;
1466 				}
1467 
1468 				Color tint = LightMap->GetPixel(c->Pos.x / 16, c->Pos.y / 12);
1469 				game->ApplyGlobalTint(tint, flags);
1470 
1471 				if (c->Highlight || (debugFlags & DEBUG_SHOW_CONTAINERS)) {
1472 					c->Draw(true, viewport, tint, flags);
1473 				} else {
1474 					c->Draw(false, viewport, tint, flags);
1475 				}
1476 				pile = GetNextPile(pileidx);
1477 			}
1478 			break;
1479 		case AOT_AREA:
1480 			{
1481 				a = DrawAreaAnimation(a);
1482 			}
1483 			break;
1484 		case AOT_SCRIPTED:
1485 			{
1486 				bool endReached = sca->UpdateDrawingState(-1);
1487 				if (endReached) {
1488 					delete sca;
1489 					scaidx = vvcCells.erase(scaidx);
1490 				} else {
1491 					video->SetStencilBuffer(wallStencil);
1492 					Color tint = LightMap->GetPixel( sca->Pos.x / 16, sca->Pos.y / 12);
1493 					tint.a = 255;
1494 
1495 					// FIXME: these should actually make use of SetDrawingStencilForObject too
1496 					BlitFlags flags = (core->DitherSprites) ? BlitFlags::STENCIL_BLUE : BlitFlags::STENCIL_RED;
1497 
1498 					if (timestop) {
1499 						flags |= BlitFlags::GREY;
1500 					}
1501 
1502 					game->ApplyGlobalTint(tint, flags);
1503 
1504 					sca->Draw(viewport, tint, 0, flags);
1505 					scaidx++;
1506 				}
1507 			}
1508 			sca = GetNextScriptedAnimation(scaidx);
1509 			break;
1510 		case AOT_PROJECTILE:
1511 			{
1512 				int drawn;
1513 				if (gametime > oldGameTime) {
1514 					drawn = pro->Update();
1515 				} else {
1516 					drawn = 1;
1517 				}
1518 				if (drawn) {
1519 					pro->Draw( viewport );
1520 					proidx++;
1521 				} else {
1522 					delete pro;
1523 					proidx = projectiles.erase(proidx);
1524 				}
1525 			}
1526 			pro = GetNextProjectile(proidx);
1527 			break;
1528 		case AOT_SPARK:
1529 			{
1530 				int drawn;
1531 				if (gametime > oldGameTime) {
1532 					drawn = spark->Update();
1533 				} else {
1534 					drawn = 1;
1535 				}
1536 				if (drawn) {
1537 					spark->Draw(viewport.Origin());
1538 					spaidx++;
1539 				} else {
1540 					delete( spark );
1541 					spaidx=particles.erase(spaidx);
1542 				}
1543 			}
1544 			spark = GetNextSpark(spaidx);
1545 			break;
1546 		default:
1547 			error("Map", "Trying to draw unknown animation type.\n");
1548 		}
1549 	}
1550 
1551 	video->SetStencilBuffer(NULL);
1552 
1553 	bool update_scripts = (core->GetGameControl()->GetDialogueFlags() & DF_FREEZE_SCRIPTS) == 0;
1554 	game->DrawWeather(update_scripts);
1555 
1556 	if (dFlags & DEBUG_SHOW_SEARCHMAP) {
1557 		DrawSearchMap(viewport);
1558 	}
1559 
1560 	const uint8_t* exploredBits = (dFlags & DEBUG_SHOW_FOG_UNEXPLORED) ? nullptr : ExploredBitmap;
1561 	const uint8_t* visibleBits = (dFlags & DEBUG_SHOW_FOG_INVISIBLE) ? nullptr : VisibleBitmap;
1562 	DrawFogOfWar(exploredBits, visibleBits, viewport);
1563 
1564 	int ipCount = 0;
1565 	while (true) {
1566 		//For each InfoPoint in the map
1567 		InfoPoint* ip = TMap->GetInfoPoint( ipCount++ );
1568 		if (!ip)
1569 			break;
1570 		ip->DrawOverheadText();
1571 	}
1572 
1573 	int cnCount = 0;
1574 	while (true) {
1575 		//For each Container in the map
1576 		Container* cn = TMap->GetContainer( cnCount++ );
1577 		if (!cn)
1578 			break;
1579 		cn->DrawOverheadText();
1580 	}
1581 
1582 	int drCount = 0;
1583 	while (true) {
1584 		//For each Door in the map
1585 		Door* dr = TMap->GetDoor( drCount++ );
1586 		if (!dr)
1587 			break;
1588 		dr->DrawOverheadText();
1589 	}
1590 
1591 	size_t i = actors.size();
1592 	while (i--) {
1593 		//For each Actor present
1594 		//This must go AFTER the fog!
1595 		//(maybe we should be using the queue?)
1596 		Actor* actor = actors[i];
1597 		actor->DrawOverheadText();
1598 	}
1599 
1600 	oldGameTime = gametime;
1601 
1602 	// Show wallpolygons
1603 	if (debugFlags & (DEBUG_SHOW_WALLS_ALL|DEBUG_SHOW_DOORS_DISABLED)) {
1604 		const auto& viewportWalls = WallsIntersectingRegion(viewport, true);
1605 		for (const auto& poly : viewportWalls.first) {
1606 			const Point& origin = poly->BBox.Origin() - viewport.Origin();
1607 
1608 			if (poly->wall_flag&WF_DISABLED) {
1609 				if (debugFlags & DEBUG_SHOW_DOORS_DISABLED) {
1610 					video->DrawPolygon( poly.get(), origin, ColorGray, true, BlitFlags::BLENDED|BlitFlags::HALFTRANS);
1611 				}
1612 				continue;
1613 			}
1614 
1615 			if ((debugFlags & (DEBUG_SHOW_WALLS|DEBUG_SHOW_WALLS_ANIM_COVER)) == 0) {
1616 				continue;
1617 			}
1618 
1619 			Color c = ColorYellow;
1620 
1621 			if (debugFlags & DEBUG_SHOW_WALLS_ANIM_COVER) {
1622 				if (poly->wall_flag & WF_COVERANIMS) {
1623 					// darker yellow for walls with WF_COVERANIMS
1624 					c.r -= 0x80;
1625 					c.g -= 0x80;
1626 				}
1627 			} else if ((debugFlags & DEBUG_SHOW_WALLS) == 0) {
1628 				continue;
1629 			}
1630 
1631 			video->DrawPolygon( poly.get(), origin, c, true, BlitFlags::BLENDED|BlitFlags::HALFTRANS);
1632 
1633 			if (poly->wall_flag & WF_BASELINE) {
1634 				video->DrawLine(poly->base0 - viewport.Origin(), poly->base1 - viewport.Origin(), ColorMagenta);
1635 			}
1636 		}
1637 	}
1638 }
1639 
WallsIntersectingRegion(Region r,bool includeDisabled,const Point * loc) const1640 WallPolygonSet Map::WallsIntersectingRegion(Region r, bool includeDisabled, const Point* loc) const
1641 {
1642 	// WallGroups are collections that contain a reference to all wall polygons intersecting
1643 	// a 640x480 region moving from top left to bottom right of the map
1644 
1645 	constexpr uint32_t groupHeight = 480;
1646 	constexpr uint32_t groupWidth = 640;
1647 
1648 	if (r.x < 0) {
1649 		r.w += r.x;
1650 		r.x = 0;
1651 	}
1652 
1653 	if (r.y < 0) {
1654 		r.h += r.y;
1655 		r.y = 0;
1656 	}
1657 
1658 	uint32_t pitch = CeilDiv<uint32_t>(TMap->XCellCount * 64, groupWidth);
1659 	uint32_t ymin = r.y / groupHeight;
1660 	uint32_t maxHeight = CeilDiv<uint32_t>(TMap->YCellCount * 64, groupHeight);
1661 	uint32_t ymax = std::min(maxHeight, CeilDiv<uint32_t>(r.y + r.h, groupHeight));
1662 	uint32_t xmin = r.x / groupWidth;
1663 	uint32_t xmax = std::min(pitch, CeilDiv<uint32_t>(r.x + r.w, groupWidth));
1664 
1665 	WallPolygonSet set;
1666 	WallPolygonGroup& infront = set.first;
1667 	WallPolygonGroup& behind = set.second;
1668 
1669 	for (uint32_t y = ymin; y < ymax; ++y) {
1670 		for (uint32_t x = xmin; x < xmax; ++x) {
1671 			const auto& group = wallGroups[y * pitch + x];
1672 
1673 			for (const auto& wp : group) {
1674 				if ((wp->wall_flag&WF_DISABLED) && includeDisabled == false) {
1675 					continue;
1676 				}
1677 
1678 				if (!r.IntersectsRegion(wp->BBox)) {
1679 					continue;
1680 				}
1681 
1682 				if (loc == nullptr || wp->PointBehind(*loc)) {
1683 					infront.push_back(wp);
1684 				} else {
1685 					behind.push_back(wp);
1686 				}
1687 			}
1688 		}
1689 	}
1690 
1691 	return set;
1692 }
1693 
SetDrawingStencilForObject(const void * object,const Region & objectRgn,const WallPolygonSet & walls,const Point & viewPortOrigin)1694 void Map::SetDrawingStencilForObject(const void* object, const Region& objectRgn, const WallPolygonSet& walls, const Point& viewPortOrigin)
1695 {
1696 	VideoBufferPtr stencil = nullptr;
1697 	Video* video = core->GetVideoDriver();
1698 	Color debugColor = ColorGray;
1699 
1700 	const bool behindWall = walls.first.size();
1701 	const bool inFrontOfWall = walls.second.size();
1702 
1703 	if (behindWall && inFrontOfWall) {
1704 		// we need a custom stencil if both behind and in front of a wall
1705 		auto it = objectStencils.find(object);
1706 		if (it != objectStencils.end()) {
1707 			// we already made one
1708 			const auto& pair = it->second;
1709 			if (pair.second.RectInside(objectRgn)) {
1710 				// and it is still good
1711 				stencil = pair.first;
1712 			}
1713 		}
1714 
1715 		if (stencil == nullptr) {
1716 			Region stencilRgn = Region(objectRgn.Origin() - viewPortOrigin, objectRgn.Dimensions());
1717 			if (stencilRgn.Dimensions().IsEmpty()) {
1718 				stencil = wallStencil;
1719 			} else {
1720 				stencil = video->CreateBuffer(stencilRgn, Video::BufferFormat::DISPLAY_ALPHA);
1721 				DrawStencil(stencil, objectRgn, walls.first);
1722 				objectStencils[object] = std::make_pair(stencil, objectRgn);
1723 			}
1724 		} else {
1725 			// TODO: we only need to do this because a door might have changed state over us
1726 			// if we could detect that we could avoid doing this expensive operation
1727 			// we could add another wall flag to mark doors and then we only need to do this if one of the "walls" over us has that flag set
1728 			stencil->Clear();
1729 			stencil->SetOrigin(objectRgn.Origin() - viewPortOrigin);
1730 			DrawStencil(stencil, objectRgn, walls.first);
1731 		}
1732 
1733 		debugColor = ColorRed;
1734 	} else {
1735 		stencil = wallStencil;
1736 
1737 		if (behindWall) {
1738 			debugColor = ColorBlue;
1739 		} else if (inFrontOfWall) {
1740 			debugColor = ColorMagenta;
1741 		}
1742 	}
1743 
1744 	assert(stencil);
1745 	video->SetStencilBuffer(stencil);
1746 
1747 	if (debugFlags & DEBUG_SHOW_WALLS) {
1748 		const Region& r = Region(objectRgn.Origin() - viewPortOrigin, objectRgn.Dimensions());
1749 		video->DrawRect(r, debugColor, false);
1750 	}
1751 }
1752 
SetDrawingStencilForScriptable(const Scriptable * scriptable,const Region & vp)1753 BlitFlags Map::SetDrawingStencilForScriptable(const Scriptable* scriptable, const Region& vp)
1754 {
1755 	if (scriptable->Type == ST_ACTOR) {
1756 		const Actor* actor = static_cast<const Actor*>(scriptable);
1757 		// birds are never occluded
1758 		if (actor->GetStat(IE_DONOTJUMP) & DNJ_BIRD) {
1759 			return BlitFlags::NONE;
1760 		}
1761 	}
1762 
1763 	const Region& bbox = scriptable->DrawingRegion();
1764 	if (bbox.IntersectsRegion(vp) == false) {
1765 		return BlitFlags::NONE;;
1766 	}
1767 
1768 	WallPolygonSet walls = WallsIntersectingRegion(bbox, false, &scriptable->Pos);
1769 	SetDrawingStencilForObject(scriptable, bbox, walls, vp.Origin());
1770 
1771 	// check this after SetDrawingStencilForObject for debug drawing purposes
1772 	if (walls.first.empty()) {
1773 		return BlitFlags::NONE;; // not behind a wall, no stencil required
1774 	}
1775 
1776 	ieDword always_dither;
1777 	core->GetDictionary()->Lookup("Always Dither", always_dither);
1778 
1779 	BlitFlags flags = BlitFlags::STENCIL_DITHER; // TODO: make dithering configurable
1780 	if (always_dither) {
1781 		flags |= BlitFlags::STENCIL_ALPHA;
1782 	} else if (core->DitherSprites == false) {
1783 		// dithering is set to disabled
1784 		flags |= BlitFlags::STENCIL_BLUE;
1785 	} else if (scriptable->Type == ST_ACTOR) {
1786 		const Actor* a = static_cast<const Actor*>(scriptable);
1787 		if (a->IsSelected() || a->Over) {
1788 			flags |= BlitFlags::STENCIL_ALPHA;
1789 		} else {
1790 			flags |= BlitFlags::STENCIL_RED;
1791 		}
1792 	} else if (scriptable->Type == ST_CONTAINER) {
1793 		const Container* c = static_cast<const Container*>(scriptable);
1794 		if (c->Highlight) {
1795 			flags |= BlitFlags::STENCIL_ALPHA;
1796 		} else {
1797 			flags |= BlitFlags::STENCIL_RED;
1798 		}
1799 	}
1800 
1801 	assert(flags & BLIT_STENCIL_MASK); // we needed a stencil so we must require a stencil flag
1802 	return flags;
1803 }
1804 
SetDrawingStencilForAreaAnimation(const AreaAnimation * anim,const Region & vp)1805 BlitFlags Map::SetDrawingStencilForAreaAnimation(const AreaAnimation* anim, const Region& vp)
1806 {
1807 	const Region& bbox = anim->DrawingRegion();
1808 	if (bbox.IntersectsRegion(vp) == false) {
1809 		return BlitFlags::NONE;
1810 	}
1811 
1812 	Point p = anim->Pos;
1813 	p.y += anim->height;
1814 
1815 	WallPolygonSet walls = WallsIntersectingRegion(bbox, false, &p);
1816 
1817 	SetDrawingStencilForObject(anim, bbox, walls, vp.Origin());
1818 
1819 	// check this after SetDrawingStencilForObject for debug drawing purposes
1820 	if (walls.first.empty()) {
1821 		return BlitFlags::NONE; // not behind a wall, no stencil required
1822 	}
1823 
1824 	return (anim->Flags & A_ANI_NO_WALL) ? BlitFlags::NONE : BlitFlags::STENCIL_GREEN;
1825 }
1826 
DrawSearchMap(const Region & vp) const1827 void Map::DrawSearchMap(const Region &vp) const
1828 {
1829 	assert(SrchMap);
1830 
1831 	static const Color inaccessible = ColorGray;
1832 	static const Color impassible(128, 64, 64, 0xff); // red-ish
1833 	static const Color sidewall(64, 64, 128, 0xff); // blue-ish
1834 	static const Color actor(128, 64, 128, 128); // purple-ish
1835 	static const BlitFlags flags = BlitFlags::BLENDED | BlitFlags::HALFTRANS;
1836 
1837 	Video *vid=core->GetVideoDriver();
1838 	Region block(0,0,16,12);
1839 
1840 	int w = vp.w/16+2;
1841 	int h = vp.h/12+2;
1842 
1843 	for(int x=0;x<w;x++) {
1844 		for(int y=0;y<h;y++) {
1845 			PathMapFlags blockvalue = GetBlocked(x + vp.x / 16, y + vp.y / 12);
1846 			block.x = x * 16 - (vp.x % 16);
1847 			block.y = y * 12 - (vp.y % 12);
1848 			if (!(blockvalue & PathMapFlags::PASSABLE)) {
1849 				if (blockvalue == PathMapFlags::IMPASSABLE) { // 0
1850 					vid->DrawRect(block, impassible, true, flags);
1851 				} else if (bool(blockvalue & PathMapFlags::SIDEWALL)) {
1852 					vid->DrawRect(block, sidewall, true, flags);
1853 				} else if (!(blockvalue & PathMapFlags::ACTOR)){
1854 					vid->DrawRect(block, inaccessible, true, flags);
1855 				}
1856 			}
1857 			if (bool(blockvalue & PathMapFlags::ACTOR)) {
1858 				vid->DrawRect(block, actor);
1859 			}
1860 		}
1861 	}
1862 
1863 	// draw also pathfinding waypoints
1864 	const Actor *act = core->GetFirstSelectedActor();
1865 	if (!act) return;
1866 	const PathNode *path = act->GetPath();
1867 	if (!path) return;
1868 	const PathNode *step = path->Next;
1869 	Color waypoint(0, 64, 128, 128); // darker blue-ish
1870 	int i = 0;
1871 	block.w = 8;
1872 	block.h = 6;
1873 	while (step) {
1874 		block.x = (step->x+64) - vp.x;
1875 		block.y = (step->y+6) - vp.y;
1876 		print("Waypoint %d at (%d, %d)", i, step->x, step->y);
1877 		vid->DrawRect(block, waypoint);
1878 		step = step->Next;
1879 		i++;
1880 	}
1881 }
1882 
1883 //adding animation in order, based on its height parameter
AddAnimation(AreaAnimation * panim)1884 void Map::AddAnimation(AreaAnimation* panim)
1885 {
1886 	//copy external memory to core memory for msvc's sake
1887 	AreaAnimation *anim = new AreaAnimation(panim);
1888 
1889 	aniIterator iter;
1890 
1891 	int Height = anim->GetHeight();
1892 	for (iter = animations.begin(); (iter != animations.end()) && ((*iter)->GetHeight() < Height); ++iter) ;
1893 	animations.insert(iter, anim);
1894 }
1895 
1896 //reapplying all of the effects on the actors of this map
1897 //this might be unnecessary later
UpdateEffects()1898 void Map::UpdateEffects()
1899 {
1900 	size_t i = actors.size();
1901 	while (i--) {
1902 		actors[i]->RefreshEffects(NULL);
1903 	}
1904 }
1905 
Shout(const Actor * actor,int shoutID,bool global) const1906 void Map::Shout(const Actor* actor, int shoutID, bool global) const
1907 {
1908 	for (auto listener : actors) {
1909 		// skip the shouter, so gpshout's InMyGroup(LastHeardBy(Myself)) can get two distinct actors
1910 		if (listener == actor) {
1911 			continue;
1912 		}
1913 
1914 		if (!global) {
1915 			if (!WithinAudibleRange(actor, listener->Pos)) {
1916 				continue;
1917 			}
1918 		}
1919 		if (shoutID) {
1920 			listener->AddTrigger(TriggerEntry(trigger_heard, actor->GetGlobalID(), shoutID));
1921 			listener->LastHeard = actor->GetGlobalID();
1922 		} else {
1923 			listener->AddTrigger(TriggerEntry(trigger_help, actor->GetGlobalID()));
1924 			listener->LastHelp = actor->GetGlobalID();
1925 		}
1926 	}
1927 }
1928 
CountSummons(ieDword flags,ieDword sex) const1929 int Map::CountSummons(ieDword flags, ieDword sex) const
1930 {
1931 	int count = 0;
1932 
1933 	for (const Actor *actor : actors) {
1934 		if (!actor->ValidTarget(flags) ) {
1935 			continue;
1936 		}
1937 		if (actor->GetStat(IE_SEX)==sex) {
1938 			count++;
1939 		}
1940 	}
1941 	return count;
1942 }
1943 
AnyEnemyNearPoint(const Point & p) const1944 bool Map::AnyEnemyNearPoint(const Point &p) const
1945 {
1946 	ieDword gametime = core->GetGame()->GameTime;
1947 	for (const Actor *actor : actors) {
1948 		if (!actor->Schedule(gametime, true) ) {
1949 			continue;
1950 		}
1951 		if (actor->IsDead() ) {
1952 			continue;
1953 		}
1954 		if (actor->GetStat(IE_AVATARREMOVAL)) {
1955 			continue;
1956 		}
1957 		if (Distance(actor->Pos, p) > SPAWN_RANGE) {
1958 			continue;
1959 		}
1960 		if (actor->GetStat(IE_EA)<=EA_EVILCUTOFF) {
1961 			continue;
1962 		}
1963 
1964 		return true;
1965 	}
1966 	return false;
1967 }
1968 
ActorSpottedByPlayer(const Actor * actor) const1969 void Map::ActorSpottedByPlayer(const Actor *actor) const
1970 {
1971 	unsigned int animid;
1972 
1973 	if(core->HasFeature(GF_HAS_BEASTS_INI)) {
1974 		animid=actor->BaseStats[IE_ANIMATION_ID];
1975 		if(core->HasFeature(GF_ONE_BYTE_ANIMID)) {
1976 			animid&=0xff;
1977 		}
1978 		if (animid < (ieDword)CharAnimations::GetAvatarsCount()) {
1979 			const AvatarStruct *avatar = CharAnimations::GetAvatarStruct(animid);
1980 			core->GetGame()->SetBeastKnown(avatar->Bestiary);
1981 		}
1982 	}
1983 }
1984 
1985 // Call this for any visible actor.  do_pause can be false if hostile
1986 // actors were already seen on the map.  We used to check AnyPCInCombat,
1987 // which is less reliable.  Returns true if this is a hostile enemy
1988 // that should trigger pause.
HandleAutopauseForVisible(Actor * actor,bool doPause) const1989 bool Map::HandleAutopauseForVisible(Actor *actor, bool doPause) const
1990 {
1991 	if (actor->Modified[IE_EA] > EA_EVILCUTOFF && !(actor->GetInternalFlag() & IF_STOPATTACK)) {
1992 		if (doPause && !(actor->GetInternalFlag() & IF_TRIGGER_AP))
1993 			core->Autopause(AP_ENEMY, actor);
1994 		actor->SetInternalFlag(IF_TRIGGER_AP, OP_OR);
1995 		return true;
1996 	}
1997 	return false;
1998 }
1999 
2000 //call this once, after area was loaded
InitActors()2001 void Map::InitActors()
2002 {
2003 	// setting the map can run effects, so play on the safe side and ignore any actors that might get added
2004 	size_t i = actors.size();
2005 	while (i--) {
2006 		Actor *actor = actors[i];
2007 		actor->SetMap(this);
2008 		// make sure to bump away in case someone or something is already there
2009 		actor->SetPosition(actor->Pos, 1);
2010 		InitActor(actor);
2011 	}
2012 }
2013 
InitActor(const Actor * actor)2014 void Map::InitActor(const Actor *actor)
2015 {
2016 	if (actor->InParty && core->HasFeature(GF_AREA_VISITED_VAR)) {
2017 		char key[32];
2018 		const size_t len = snprintf(key, sizeof(key),"%s_visited", scriptName);
2019 		if (len > sizeof(key)) {
2020 			Log(ERROR, "Map", "Area %s has a too long script name for generating _visited globals!", scriptName);
2021 		}
2022 		core->GetGame()->locals->SetAt(key, 1);
2023 	}
2024 }
2025 
AddActor(Actor * actor,bool init)2026 void Map::AddActor(Actor* actor, bool init)
2027 {
2028 	//setting the current area for the actor as this one
2029 	strnlwrcpy(actor->Area, scriptName, 8);
2030 	if (!HasActor(actor)) {
2031 		actors.push_back( actor );
2032 	}
2033 	if (init) {
2034 		actor->SetMap(this);
2035 		InitActor(actor);
2036 	}
2037 }
2038 
AnyPCSeesEnemy() const2039 bool Map::AnyPCSeesEnemy() const
2040 {
2041 	ieDword gametime = core->GetGame()->GameTime;
2042 	for (const Actor *actor : actors) {
2043 		if (actor->Modified[IE_EA]>=EA_EVILCUTOFF) {
2044 			if (IsVisible(actor->Pos) && actor->Schedule(gametime, true) ) {
2045 				return true;
2046 			}
2047 		}
2048 	}
2049 	return false;
2050 }
2051 
2052 //Make an actor gone for (almost) good
2053 //If the actor was in the party, it will be moved to the npc storage
2054 //If the actor is in the NPC storage, its area and some other fields
2055 //that are needed for proper reentry will be zeroed out
2056 //If the actor isn't in the NPC storage, it is destructed
DeleteActor(int i)2057 void Map::DeleteActor(int i)
2058 {
2059 	Actor *actor = actors[i];
2060 	if (actor) {
2061 		Game *game = core->GetGame();
2062 		//this makes sure that a PC will be demoted to NPC
2063 		game->LeaveParty( actor );
2064 		//this frees up the spot under the feet circle
2065 		ClearSearchMapFor( actor );
2066 		//remove the area reference from the actor
2067 		actor->SetMap(NULL);
2068 		CopyResRef(actor->Area, "");
2069 		objectStencils.erase(actor);
2070 		//don't destroy the object in case it is a persistent object
2071 		//otherwise there is a dead reference causing a crash on save
2072 		if (game->InStore(actor) < 0) {
2073 			delete actor;
2074 		}
2075 	}
2076 	//remove the actor from the area's actor list
2077 	actors.erase( actors.begin()+i );
2078 }
2079 
GetScriptableByGlobalID(ieDword objectID)2080 Scriptable *Map::GetScriptableByGlobalID(ieDword objectID)
2081 {
2082 	if (!objectID) return NULL;
2083 
2084 	Scriptable *scr = GetActorByGlobalID(objectID);
2085 	if (scr)
2086 		return scr;
2087 
2088 	scr = GetInfoPointByGlobalID(objectID);
2089 	if (scr)
2090 		return scr;
2091 
2092 	scr = GetContainerByGlobalID(objectID);
2093 	if (scr)
2094 		return scr;
2095 
2096 	scr = GetDoorByGlobalID(objectID);
2097 	if (scr)
2098 		return scr;
2099 
2100 	if (GetGlobalID() == objectID)
2101 		scr = this;
2102 
2103 	return scr;
2104 }
2105 
GetDoorByGlobalID(ieDword objectID) const2106 Door *Map::GetDoorByGlobalID(ieDword objectID) const
2107 {
2108 	if (!objectID) return NULL;
2109 
2110 	int doorCount = 0;
2111 	while (true) {
2112 		Door* door = TMap->GetDoor( doorCount++ );
2113 		if (!door)
2114 			return NULL;
2115 		if (door->GetGlobalID() == objectID)
2116 			return door;
2117 	}
2118 }
2119 
GetContainerByGlobalID(ieDword objectID) const2120 Container *Map::GetContainerByGlobalID(ieDword objectID) const
2121 {
2122 	if (!objectID) return NULL;
2123 
2124 	int containerCount = 0;
2125 	while (true) {
2126 		Container* container = TMap->GetContainer( containerCount++ );
2127 		if (!container)
2128 			return NULL;
2129 		if (container->GetGlobalID() == objectID)
2130 			return container;
2131 	}
2132 }
2133 
GetInfoPointByGlobalID(ieDword objectID) const2134 InfoPoint *Map::GetInfoPointByGlobalID(ieDword objectID) const
2135 {
2136 	if (!objectID) return NULL;
2137 
2138 	int ipCount = 0;
2139 	while (true) {
2140 		InfoPoint* ip = TMap->GetInfoPoint( ipCount++ );
2141 		if (!ip)
2142 			return NULL;
2143 		if (ip->GetGlobalID() == objectID)
2144 			return ip;
2145 	}
2146 }
2147 
GetActorByGlobalID(ieDword objectID) const2148 Actor* Map::GetActorByGlobalID(ieDword objectID) const
2149 {
2150 	if (!objectID) {
2151 		return NULL;
2152 	}
2153 	for (auto actor : actors) {
2154 		if (actor->GetGlobalID()==objectID) {
2155 			return actor;
2156 		}
2157 	}
2158 	return NULL;
2159 }
2160 
2161 /** flags:
2162  GA_SELECT    16  - unselectable actors don't play
2163  GA_NO_DEAD   32  - dead actors don't play
2164  GA_POINT     64  - not actor specific
2165  GA_NO_HIDDEN 128 - hidden actors don't play
2166 */
GetActor(const Point & p,int flags,const Movable * checker) const2167 Actor* Map::GetActor(const Point &p, int flags, const Movable *checker) const
2168 {
2169 	for (auto actor : actors) {
2170 		if (!actor->IsOver( p ))
2171 			continue;
2172 		if (!actor->ValidTarget(flags, checker) ) {
2173 			continue;
2174 		}
2175 		return actor;
2176 	}
2177 	return NULL;
2178 }
2179 
GetActorInRadius(const Point & p,int flags,unsigned int radius) const2180 Actor* Map::GetActorInRadius(const Point &p, int flags, unsigned int radius) const
2181 {
2182 	for (auto actor : actors) {
2183 		if (PersonalDistance( p, actor ) > radius)
2184 			continue;
2185 		if (!actor->ValidTarget(flags) ) {
2186 			continue;
2187 		}
2188 		return actor;
2189 	}
2190 	return NULL;
2191 }
2192 
GetAllActorsInRadius(const Point & p,int flags,unsigned int radius,const Scriptable * see) const2193 std::vector<Actor *> Map::GetAllActorsInRadius(const Point &p, int flags, unsigned int radius, const Scriptable *see) const
2194 {
2195 	std::vector<Actor *> neighbours;
2196 	for (auto actor : actors) {
2197 		if (!WithinRange(actor, p, radius)) {
2198 			continue;
2199 		}
2200 		if (!actor->ValidTarget(flags, see) ) {
2201 			continue;
2202 		}
2203 		if (!(flags&GA_NO_LOS)) {
2204 			//line of sight visibility
2205 			if (!IsVisibleLOS(actor->Pos, p)) {
2206 				continue;
2207 			}
2208 		}
2209 		neighbours.emplace_back(actor);
2210 	}
2211 	return neighbours;
2212 }
2213 
2214 
GetActor(const char * Name,int flags) const2215 Actor* Map::GetActor(const char* Name, int flags) const
2216 {
2217 	for (auto actor : actors) {
2218 		if (strnicmp( actor->GetScriptName(), Name, 32 ) == 0) {
2219 			// there can be more with the same scripting name, see bg2/ar0014.baf
2220 			if (!actor->ValidTarget(flags) ) {
2221 				continue;
2222 			}
2223 			return actor;
2224 		}
2225 	}
2226 	return NULL;
2227 }
2228 
GetActorCount(bool any) const2229 int Map::GetActorCount(bool any) const
2230 {
2231 	if (any) {
2232 		return (int) actors.size();
2233 	}
2234 	int ret = 0;
2235 	for (const Actor *actor : actors) {
2236 		if (MustSave(actor)) {
2237 			ret++;
2238 		}
2239 	}
2240 	return ret;
2241 }
2242 
JumpActors(bool jump)2243 void Map::JumpActors(bool jump)
2244 {
2245 	for (auto actor : actors) {
2246 		if (actor->Modified[IE_DONOTJUMP]&DNJ_JUMP) {
2247 			if (jump && !(actor->GetStat(IE_DONOTJUMP) & DNJ_BIRD)) {
2248 				ClearSearchMapFor(actor);
2249 				AdjustPositionNavmap(actor->Pos);
2250 				actor->ImpedeBumping();
2251 			}
2252 			actor->SetBase(IE_DONOTJUMP,0);
2253 		}
2254 	}
2255 }
2256 
SelectActors() const2257 void Map::SelectActors() const
2258 {
2259 	for (auto actor : actors) {
2260 		if (actor->Modified[IE_EA]<EA_CONTROLLABLE) {
2261 			core->GetGame()->SelectActor(actor, true, SELECT_QUIET);
2262 		}
2263 	}
2264 }
2265 
2266 //before writing the area out, perform some cleanups
PurgeArea(bool items)2267 void Map::PurgeArea(bool items)
2268 {
2269 	InternalFlags |= IF_JUSTDIED; //area marked for swapping out
2270 
2271 	//1. remove dead actors without 'keep corpse' flag
2272 	int i=(int) actors.size();
2273 	while (i--) {
2274 		Actor *ac = actors[i];
2275 		//we're going to drop the map from memory so clear the reference
2276 		ac->SetMap(NULL);
2277 
2278 		if (ac->Modified[IE_STATE_ID]&STATE_NOSAVE) {
2279 			if (ac->Modified[IE_MC_FLAGS] & MC_KEEP_CORPSE) {
2280 				continue;
2281 			}
2282 
2283 			if (ac->RemovalTime > core->GetGame()->GameTime) {
2284 				continue;
2285 			}
2286 
2287 			//don't delete persistent actors
2288 			if (ac->Persistent()) {
2289 				continue;
2290 			}
2291 			//even if you delete it, be very careful!
2292 			DeleteActor (i);
2293 		}
2294 	}
2295 	//2. remove any non critical items
2296 	if (items) {
2297 		i=(int) TMap->GetContainerCount();
2298 		while (i--) {
2299 			Container *c = TMap->GetContainer(i);
2300 			unsigned int j=c->inventory.GetSlotCount();
2301 			while (j--) {
2302 				const CREItem *itemslot = c->inventory.GetSlotItem(j);
2303 				if (itemslot->Flags&IE_INV_ITEM_CRITICAL) {
2304 					continue;
2305 				}
2306 			}
2307 			TMap->CleanupContainer(c);
2308 			objectStencils.erase(c);
2309 		}
2310 	}
2311 	// 3. reset living neutral actors to their HomeLocation,
2312 	// in case they RandomWalked/flew themselves into a "corner" (mirroring original behaviour)
2313 	for (Actor *actor : actors) {
2314 		if (!actor->GetRandomWalkCounter()) continue;
2315 		if (actor->GetStat(IE_MC_FLAGS) & MC_IGNORE_RETURN) continue;
2316 		if (!actor->ValidTarget(GA_NO_DEAD|GA_NO_UNSCHEDULED|GA_NO_ALLY|GA_NO_ENEMY)) continue;
2317 		if (!actor->HomeLocation.isnull() && !actor->HomeLocation.isempty() && actor->Pos != actor->HomeLocation) {
2318 			actor->Pos = actor->HomeLocation;
2319 		}
2320 	}
2321 }
2322 
GetActor(int index,bool any) const2323 Actor* Map::GetActor(int index, bool any) const
2324 {
2325 	if (any) {
2326 		return actors[index];
2327 	}
2328 	unsigned int i=0;
2329 	while (i<actors.size() ) {
2330 		Actor *ac = actors[i++];
2331 		if (MustSave(ac) ) {
2332 			if (!index--) {
2333 				return ac;
2334 			}
2335 		}
2336 	}
2337 	return NULL;
2338 }
2339 
GetActorByDialog(const char * resref) const2340 Scriptable *Map::GetActorByDialog(const char *resref) const
2341 {
2342 	for (auto actor : actors) {
2343 		//if a busy or hostile actor shouldn't be found
2344 		//set this to GD_CHECK
2345 		if (strnicmp( actor->GetDialog(GD_NORMAL), resref, 8 ) == 0) {
2346 			return actor;
2347 		}
2348 	}
2349 
2350 	if (!core->HasFeature(GF_INFOPOINT_DIALOGS)) {
2351 		return NULL;
2352 	}
2353 
2354 	// pst has plenty of talking infopoints, eg. in ar0508 (Lothar's cabinet)
2355 	unsigned int i = TMap->GetInfoPointCount();
2356 	while (i--) {
2357 		InfoPoint* ip = TMap->GetInfoPoint(i);
2358 		if (strnicmp(ip->GetDialog(), resref, 8) == 0) {
2359 			return ip;
2360 		}
2361 	}
2362 
2363 	// move higher if someone needs talking doors
2364 	i = TMap->GetDoorCount();
2365 	while (i--) {
2366 		Door* door = TMap->GetDoor(i);
2367 		if (strnicmp(door->GetDialog(), resref, 8) == 0) {
2368 			return door;
2369 		}
2370 	}
2371 	return NULL;
2372 }
2373 
2374 // NOTE: this function is not as general as it sounds
2375 // currently only looks at the party, since it is enough for the only known user
2376 // relies on an override item we create, with the resref matching the dialog one!
2377 // currently only handles dmhead, since no other users have been found yet (to avoid checking whole inventory)
GetItemByDialog(ieResRef resref) const2378 Scriptable *Map::GetItemByDialog(ieResRef resref) const
2379 {
2380 	const Game *game = core->GetGame();
2381 	ieResRef itemref;
2382 	// choose the owner of the dialog via passed dialog ref
2383 	if (strnicmp(resref, "dmhead", 8)) {
2384 		Log(WARNING, "Map", "Encountered new candidate item for GetItemByDialog? %s", resref);
2385 		return NULL;
2386 	}
2387 	CopyResRef(itemref, "mertwyn");
2388 
2389 	int i = game->GetPartySize(true);
2390 	while (i--) {
2391 		const Actor *pc = game->GetPC(i, true);
2392 		int slot = pc->inventory.FindItem(itemref, 0);
2393 		if (slot == -1) continue;
2394 		const CREItem *citem = pc->inventory.GetSlotItem(slot);
2395 		if (!citem) continue;
2396 		const Item *item = gamedata->GetItem(citem->ItemResRef);
2397 		if (!item) continue;
2398 		if (strnicmp(item->Dialog, resref, 8)) continue;
2399 
2400 		// finally, spawn (dmhead.cre) from our override as a substitute talker
2401 		// the cre file is set up to be invisible, invincible and immune to several things
2402 		Actor *surrogate = gamedata->GetCreature(resref);
2403 		if (!surrogate) {
2404 			error("Map", "GetItemByDialog found the right item, but creature is missing: %s!", resref);
2405 			// error is fatal
2406 		}
2407 		Map *map = pc->GetCurrentArea();
2408 		map->AddActor(surrogate, true);
2409 		surrogate->SetPosition(pc->Pos, 0);
2410 
2411 		return surrogate;
2412 	}
2413 	return NULL;
2414 }
2415 
2416 //this function finds an actor by its original resref (not correct yet)
GetActorByResource(const char * resref) const2417 Actor *Map::GetActorByResource(const char *resref) const
2418 {
2419 	for (auto actor : actors) {
2420 		if (strnicmp( actor->GetScriptName(), resref, 8 ) == 0) { //temporarily!
2421 			return actor;
2422 		}
2423 	}
2424 	return NULL;
2425 }
2426 
GetActorByScriptName(const char * name) const2427 Actor *Map::GetActorByScriptName(const char *name) const
2428 {
2429 	for (auto actor : actors) {
2430 		if (strnicmp( actor->GetScriptName(), name, 8 ) == 0) {
2431 			return actor;
2432 		}
2433 	}
2434 	return NULL;
2435 }
2436 
GetActorsInRect(Actor ** & actorlist,const Region & rgn,int excludeFlags) const2437 int Map::GetActorsInRect(Actor**& actorlist, const Region& rgn, int excludeFlags) const
2438 {
2439 	actorlist = ( Actor * * ) malloc( actors.size() * sizeof( Actor * ) );
2440 	int count = 0;
2441 	for (auto actor : actors) {
2442 		if (!actor->ValidTarget(excludeFlags))
2443 			continue;
2444 		if (!rgn.PointInside(actor->Pos)
2445 			&& !actor->IsOver(rgn.Origin())) // imagine drawing a tiny box inside the circle, but not over the center
2446 			continue;
2447 
2448 		actorlist[count++] = actor;
2449 	}
2450 	if (count) {
2451 		actorlist = (Actor **) realloc(actorlist, count * sizeof(Actor *));
2452 	}
2453 	return count;
2454 }
2455 
SpawnsAlive() const2456 bool Map::SpawnsAlive() const
2457 {
2458 	for (auto actor : actors) {
2459 		if (!actor->ValidTarget(GA_NO_DEAD|GA_NO_UNSCHEDULED))
2460 			continue;
2461 		if (actor->Spawned) {
2462 			return true;
2463 		}
2464 	}
2465 	return false;
2466 }
2467 
PlayAreaSong(int SongType,bool restart,bool hard) const2468 void Map::PlayAreaSong(int SongType, bool restart, bool hard) const
2469 {
2470 	//Ok, we use a non constant pointer here, so it is easy to disable
2471 	//a faulty music list on the fly. I don't want to add a method just for that
2472 	//crap when we already have that pointer at hand!
2473 	char* poi = core->GetMusicPlaylist( SongHeader.SongList[SongType] );
2474 	// for subareas fall back to the main list
2475 	// needed eg. in bg1 ar2607 (intro candlekeep ambush south)
2476 	// it's not the correct music, perhaps it needs the one from the master area
2477 	// it would match for ar2607 and ar2600, but very annoying (see GetMasterArea)
2478 	if (!poi && !MasterArea && SongType == SONG_BATTLE) {
2479 		poi = core->GetMusicPlaylist(SongType);
2480 	}
2481 	if (!poi) return;
2482 
2483 	//check if restart needed (either forced or the current song is different)
2484 	if (!restart && core->GetMusicMgr()->CurrentPlayList(poi)) return;
2485 	int ret = core->GetMusicMgr()->SwitchPlayList( poi, hard );
2486 	//Here we disable the faulty musiclist entry
2487 	if (ret) {
2488 		//Apparently, the playlist manager prefers a *
2489 		*poi='*';
2490 		return;
2491 	}
2492 	if (SongType == SONG_BATTLE) {
2493 		core->GetGame()->CombatCounter = 150;
2494 	}
2495 }
2496 
2497 // a more thorough, but more expensive version for the cases when it matters
GetBlocked(unsigned int x,unsigned int y,int size) const2498 PathMapFlags Map::GetBlocked(unsigned int x, unsigned int y, int size) const
2499 {
2500 	if (size == -1) {
2501 		return GetBlocked(x, y);
2502 	} else {
2503 		return GetBlockedInRadius(x * 16, y * 12, size);
2504 	}
2505 }
2506 
GetBlockedNavmap(unsigned int x,unsigned int y) const2507 PathMapFlags Map::GetBlockedNavmap(unsigned int x, unsigned int y) const
2508 {
2509 	return GetBlocked(x / 16, y / 12);
2510 }
2511 
GetBlockedNavmap(const Point & c) const2512 PathMapFlags Map::GetBlockedNavmap(const Point &c) const
2513 {
2514 	return GetBlockedNavmap(c.x, c.y);
2515 }
2516 
2517 // Args are in searchmap coordinates
2518 // The default behavior is for actors to be blocking
2519 // If they shouldn't be, the caller should check for PathMapFlags::PASSABLE | PathMapFlags::ACTOR
GetBlocked(unsigned int x,unsigned int y) const2520 PathMapFlags Map::GetBlocked(unsigned int x, unsigned int y) const
2521 {
2522 	if (y>=Height || x>=Width) {
2523 		return PathMapFlags::IMPASSABLE;
2524 	}
2525 	PathMapFlags ret = SrchMap[y*Width+x];
2526 	if (bool(ret & (PathMapFlags::DOOR_IMPASSABLE|PathMapFlags::ACTOR))) {
2527 		ret &= ~PathMapFlags::PASSABLE;
2528 	}
2529 	if (bool(ret & PathMapFlags::DOOR_OPAQUE)) {
2530 		ret = PathMapFlags::SIDEWALL;
2531 	}
2532 	return ret;
2533 }
2534 
2535 // Args are in navmap coordinates
GetBlockedInRadius(unsigned int px,unsigned int py,unsigned int size,bool stopOnImpassable) const2536 PathMapFlags Map::GetBlockedInRadius(unsigned int px, unsigned int py, unsigned int size, bool stopOnImpassable) const
2537 {
2538 	// We check a circle of radius size-2 around (px,py)
2539 	// Note that this does not exactly match BG2. BG2's approximations of
2540 	// these circles are slightly different for sizes 7 and up.
2541 
2542 	if (size > MAX_CIRCLESIZE) size = MAX_CIRCLESIZE;
2543 	if (size < 2) size = 2;
2544 	PathMapFlags ret = PathMapFlags::IMPASSABLE;
2545 
2546 	unsigned int r = (size - 2) * (size - 2) + 1;
2547 	if (size == 2) r = 0;
2548 	for (unsigned int i = 0; i < size - 1; i++) {
2549 		for (unsigned int j = 0; j < size - 1; j++) {
2550 			if (i * i + j * j <= r) {
2551 				PathMapFlags retBotRight = GetBlockedNavmap(px + i * 16, py + j * 12);
2552 				PathMapFlags retTopRight = GetBlockedNavmap(px + i * 16, py - j * 12);
2553 				PathMapFlags retBotLeft = GetBlockedNavmap(px - i * 16, py + j * 12);
2554 				PathMapFlags retTopLeft = GetBlockedNavmap(px - i * 16, py - j * 12);
2555 				if (stopOnImpassable) {
2556 					if (retBotRight == PathMapFlags::IMPASSABLE || retBotLeft == PathMapFlags::IMPASSABLE || retTopRight == PathMapFlags::IMPASSABLE || retTopLeft == PathMapFlags::IMPASSABLE) {
2557 						return PathMapFlags::IMPASSABLE;
2558 					}
2559 				}
2560 				ret |= (retBotRight | retTopRight | retBotLeft | retTopLeft);
2561 			}
2562 		}
2563 	}
2564 	if (bool(ret & (PathMapFlags::DOOR_IMPASSABLE|PathMapFlags::ACTOR|PathMapFlags::SIDEWALL))) {
2565 		ret &= ~PathMapFlags::PASSABLE;
2566 	}
2567 	if (bool(ret & PathMapFlags::DOOR_OPAQUE)) {
2568 		ret = PathMapFlags::SIDEWALL;
2569 	}
2570 
2571 	return ret;
2572 }
2573 
GetBlockedInLine(const Point & s,const Point & d,bool stopOnImpassable,const Actor * caller) const2574 PathMapFlags Map::GetBlockedInLine(const Point &s, const Point &d, bool stopOnImpassable, const Actor *caller) const
2575 {
2576 	PathMapFlags ret = PathMapFlags::IMPASSABLE;
2577 	Point p = s;
2578 	while (p != d) {
2579 		double dx = d.x - p.x;
2580 		double dy = d.y - p.y;
2581 		double factor = caller && caller->GetSpeed() ? double(gamedata->GetStepTime()) / double(caller->GetSpeed()) : 1;
2582 		NormalizeDeltas(dx, dy, factor);
2583 		p.x += dx;
2584 		p.y += dy;
2585 		PathMapFlags blockStatus = GetBlockedNavmap(p.x, p.y);
2586 		if (stopOnImpassable && blockStatus == PathMapFlags::IMPASSABLE) {
2587 			return PathMapFlags::IMPASSABLE;
2588 		}
2589 		ret |= blockStatus;
2590 	}
2591 	if (bool(ret & (PathMapFlags::DOOR_IMPASSABLE|PathMapFlags::ACTOR|PathMapFlags::SIDEWALL))) {
2592 		ret &= ~PathMapFlags::PASSABLE;
2593 	}
2594 	if (bool(ret & PathMapFlags::DOOR_OPAQUE)) {
2595 		ret = PathMapFlags::SIDEWALL;
2596 	}
2597 
2598 	return ret;
2599 }
2600 
2601 // PathMapFlags::SIDEWALL obstructs LOS, while PathMapFlags::IMPASSABLE doesn't
IsVisibleLOS(const Point & s,const Point & d,const Actor * caller) const2602 bool Map::IsVisibleLOS(const Point &s, const Point &d, const Actor *caller) const
2603 {
2604 	PathMapFlags ret = GetBlockedInLine(s, d, false, caller);
2605 	return !bool(ret & PathMapFlags::SIDEWALL);
2606 }
2607 
2608 // Used by the pathfinder, so PathMapFlags::IMPASSABLE obstructs walkability
IsWalkableTo(const Point & s,const Point & d,bool actorsAreBlocking,const Actor * caller) const2609 bool Map::IsWalkableTo(const Point &s, const Point &d, bool actorsAreBlocking, const Actor *caller) const
2610 {
2611 	PathMapFlags ret = GetBlockedInLine(s, d, true, caller);
2612 	PathMapFlags mask = PathMapFlags::PASSABLE | PathMapFlags::TRAVEL | (actorsAreBlocking ? PathMapFlags::UNMARKED : PathMapFlags::ACTOR);
2613 	return bool(ret & mask);
2614 }
2615 
RedrawScreenStencil(const Region & vp,const WallPolygonGroup & walls)2616 void Map::RedrawScreenStencil(const Region& vp, const WallPolygonGroup& walls)
2617 {
2618 	// FIXME: how do we know if a door changed state?
2619 	// we need to redraw the stencil when that happens
2620 	// see TODO in Map::SetDrawingStencilForScriptable for another example of something that could use this
2621 
2622 	if (stencilViewport == vp) {
2623 		assert(wallStencil);
2624 		return;
2625 	}
2626 
2627 	stencilViewport = vp;
2628 
2629 	if (wallStencil == NULL) {
2630 		// FIXME: this should be forced 8bit*4 color format
2631 		// but currently that is forcing some performance killing conversion issues on some platforms
2632 		// for now things will break if we use 16 bit color settings
2633 		Video* video = core->GetVideoDriver();
2634 		wallStencil = video->CreateBuffer(Region(Point(), vp.Dimensions()), Video::BufferFormat::DISPLAY_ALPHA);
2635 	}
2636 
2637 	wallStencil->Clear();
2638 
2639 	DrawStencil(wallStencil, vp, walls);
2640 }
2641 
DrawStencil(const VideoBufferPtr & stencilBuffer,const Region & vp,const WallPolygonGroup & walls) const2642 void Map::DrawStencil(const VideoBufferPtr& stencilBuffer, const Region& vp, const WallPolygonGroup& walls) const
2643 {
2644 	Video* video = core->GetVideoDriver();
2645 
2646 	// color is used as follows:
2647 	// the 'r' channel is for the native value for all walls
2648 	// the 'g' channel is for the native value for only WF_COVERANIMS walls
2649 	// the 'b' channel is for always opaque (always 0xff, 100% opaque)
2650 	// the 'a' channel is for always dithered (always 0x80, 50% transparent)
2651 	// IMPORTANT: 'a' channel must be always dithered because the "raw" SDL2 driver can only do one stencil and it must be 'a'
2652 	Color stencilcol(0, 0, 0xff, 0x80);
2653 	video->PushDrawingBuffer(stencilBuffer);
2654 
2655 	for (const auto& wp : walls) {
2656 		const Point& origin = wp->BBox.Origin() - vp.Origin();
2657 
2658 		if (wp->wall_flag & WF_DITHER) {
2659 			stencilcol.r = 0x80;
2660 		} else {
2661 			stencilcol.r = 0xff;
2662 		}
2663 
2664 		if (wp->wall_flag & WF_COVERANIMS) {
2665 			stencilcol.g = stencilcol.r;
2666 		} else {
2667 			stencilcol.g = 0;
2668 		}
2669 
2670 		video->DrawPolygon(wp.get(), origin, stencilcol, true);
2671 	}
2672 
2673 	video->PopDrawingBuffer();
2674 }
2675 
BehindWall(const Point & pos,const Region & r) const2676 bool Map::BehindWall(const Point& pos, const Region& r) const
2677 {
2678 	const auto& polys = WallsIntersectingRegion(r, false, &pos);
2679 	return polys.first.size();
2680 }
2681 
2682 //this function determines actor drawing order
2683 //it should be extended to wallgroups, animations, effects!
GenerateQueues()2684 void Map::GenerateQueues()
2685 {
2686 	int priority;
2687 
2688 	unsigned int i=(unsigned int) actors.size();
2689 	for (priority=0;priority<QUEUE_COUNT;priority++) {
2690 		if (lastActorCount[priority] != i) {
2691 			if (queue[priority]) {
2692 				free(queue[priority]);
2693 				queue[priority] = NULL;
2694 			}
2695 			queue[priority] = (Actor **) calloc( i, sizeof(Actor *) );
2696 			lastActorCount[priority] = i;
2697 		}
2698 		Qcount[priority] = 0;
2699 	}
2700 
2701 	ieDword gametime = core->GetGame()->GameTime;
2702 	bool hostiles_new = false;
2703 	while (i--) {
2704 		Actor* actor = actors[i];
2705 
2706 		if (actor->CheckOnDeath()) {
2707 			DeleteActor( i );
2708 			continue;
2709 		}
2710 
2711 		ieDword stance = actor->GetStance();
2712 		ieDword internalFlag = actor->GetInternalFlag();
2713 
2714 		if (internalFlag&IF_ACTIVE) {
2715 			if ((stance == IE_ANI_TWITCH) && (internalFlag&IF_IDLE) ) {
2716 				priority = PR_DISPLAY; //display
2717 			} else {
2718 				//if actor is unscheduled, don't run its scripts
2719 				if (actor->Schedule(gametime, false) ) {
2720 					priority = PR_SCRIPT; //run scripts and display
2721 				} else {
2722 					priority = PR_IGNORE; //don't run scripts for out of schedule actors
2723 				}
2724 			}
2725 			if (IsVisible(actor->Pos))
2726 				hostiles_new |= HandleAutopauseForVisible(actor, !hostiles_visible);
2727 		} else {
2728 			//dead actors are always visible on the map, but run no scripts
2729 			if ((stance == IE_ANI_TWITCH) || (stance == IE_ANI_DIE) ) {
2730 				priority = PR_DISPLAY;
2731 			} else {
2732 				//isvisible flag is false (visibilitymap) here,
2733 				//coz we want to reactivate creatures that
2734 				//just became visible
2735 				if (IsVisible(actor->Pos) && actor->Schedule(gametime, false) ) {
2736 					priority = PR_SCRIPT; //run scripts and display, activated now
2737 					//more like activate!
2738 					actor->Activate();
2739 					ActorSpottedByPlayer(actor);
2740 					hostiles_new |= HandleAutopauseForVisible(actor, !hostiles_visible);
2741 				} else {
2742 					priority = PR_IGNORE;
2743 				}
2744 			}
2745 		}
2746 
2747 		//we ignore priority 2
2748 		if (priority>=PR_IGNORE) continue;
2749 
2750 		queue[priority][Qcount[priority]] = actor;
2751 		Qcount[priority]++;
2752 	}
2753 	hostiles_visible = hostiles_new;
2754 }
2755 
2756 //the original qsort implementation was flawed
SortQueues() const2757 void Map::SortQueues() const
2758 {
2759 	for (int q=0;q<QUEUE_COUNT;q++) {
2760 		Actor **baseline=queue[q];
2761 		int n = Qcount[q];
2762 		int i = n/2;
2763 		int parent, child;
2764 		Actor * tmp;
2765 
2766 		for (;;) {
2767 			if (i>0) {
2768 				i--;
2769 				tmp = baseline[i];
2770 			} else {
2771 				n--;
2772 				if (n<=0) break; //breaking loop
2773 				tmp = baseline[n];
2774 				baseline[n] = baseline[0];
2775 			}
2776 			parent = i;
2777 			child = i*2+1;
2778 			while(child<n) {
2779 				int chp = child+1;
2780 				if (chp<n && baseline[chp]->Pos.y < baseline[child]->Pos.y) {
2781 					child=chp;
2782 				}
2783 				if (baseline[child]->Pos.y<tmp->Pos.y) {
2784 					baseline[parent] = baseline[child];
2785 					parent = child;
2786 					child = parent*2+1;
2787 				} else
2788 					break;
2789 			}
2790 			baseline[parent]=tmp;
2791 		}
2792 	}
2793 }
2794 
AddProjectile(Projectile * pro,const Point & source,ieDword actorID,bool fake)2795 void Map::AddProjectile(Projectile *pro, const Point &source, ieDword actorID, bool fake)
2796 {
2797 	proIterator iter;
2798 
2799 	pro->MoveTo(this,source);
2800 	pro->SetTarget(actorID, fake);
2801 	int height = pro->GetHeight();
2802 	for(iter=projectiles.begin();iter!=projectiles.end() && (*iter)->GetHeight()<height; iter++) ;
2803 	projectiles.insert(iter, pro);
2804 }
2805 
2806 //adding projectile in order, based on its height parameter
AddProjectile(Projectile * pro,const Point & source,const Point & dest)2807 void Map::AddProjectile(Projectile* pro, const Point &source, const Point &dest)
2808 {
2809 	proIterator iter;
2810 
2811 	pro->MoveTo(this,source);
2812 	pro->SetTarget(dest);
2813 	int height = pro->GetHeight();
2814 	for(iter=projectiles.begin();iter!=projectiles.end() && (*iter)->GetHeight()<height; iter++) ;
2815 	projectiles.insert(iter, pro);
2816 }
2817 
2818 //returns the longest duration of the VVC cell named 'resource' (if it exists)
2819 //if P is empty, the position won't be checked
HasVVCCell(const ieResRef resource,const Point & p) const2820 ieDword Map::HasVVCCell(const ieResRef resource, const Point &p) const
2821 {
2822 	ieDword ret = 0;
2823 
2824 	for (const VEFObject *vvc: vvcCells) {
2825 		if (!p.isempty()) {
2826 			if (vvc->Pos.x != p.x) continue;
2827 			if (vvc->Pos.y != p.y) continue;
2828 		}
2829 		if (strnicmp(resource, vvc->ResName, sizeof(ieResRef))) continue;
2830 		const ScriptedAnimation *sca = vvc->GetSingleObject();
2831 		if (sca) {
2832 			ieDword tmp = sca->GetSequenceDuration(AI_UPDATE_TIME)-sca->GetCurrentFrame();
2833 			if (tmp>ret) {
2834 				ret = tmp;
2835 			}
2836 		} else {
2837 			ret = 1;
2838 		}
2839 	}
2840 	return ret;
2841 }
2842 
2843 //adding videocell in order, based on its height parameter
AddVVCell(VEFObject * vvc)2844 void Map::AddVVCell(VEFObject* vvc)
2845 {
2846 	scaIterator iter;
2847 
2848 	for(iter=vvcCells.begin();iter!=vvcCells.end() && (*iter)->Pos.y < vvc->Pos.y; iter++) ;
2849 	vvcCells.insert(iter, vvc);
2850 }
2851 
GetAnimation(const char * Name) const2852 AreaAnimation *Map::GetAnimation(const char *Name) const
2853 {
2854 	for (auto anim : animations) {
2855 		if (anim->Name[0] && (strnicmp(anim->Name, Name, 32) == 0)) {
2856 			return anim;
2857 		}
2858 	}
2859 	return NULL;
2860 }
2861 
AddSpawn(char * Name,int XPos,int YPos,ieResRef * creatures,unsigned int count)2862 Spawn *Map::AddSpawn(char* Name, int XPos, int YPos, ieResRef *creatures, unsigned int count)
2863 {
2864 	Spawn* sp = new Spawn();
2865 	strnspccpy(sp->Name, Name, 32);
2866 	if (count>MAX_RESCOUNT) {
2867 		count=MAX_RESCOUNT;
2868 	}
2869 	sp->Pos.x = (ieWord) XPos;
2870 	sp->Pos.y = (ieWord) YPos;
2871 	sp->Count = count;
2872 	sp->Creatures = (ieResRef *) calloc( count, sizeof(ieResRef) );
2873 	for( unsigned int i=0;i<count;i++) {
2874 		strnlwrcpy(sp->Creatures[i],creatures[i],8);
2875 	}
2876 	spawns.push_back( sp );
2877 	return sp;
2878 }
2879 
AddEntrance(const char * Name,int XPos,int YPos,short Face)2880 void Map::AddEntrance(const char* Name, int XPos, int YPos, short Face)
2881 {
2882 	Entrance* ent = new Entrance();
2883 	strlcpy( ent->Name, Name, sizeof(ent->Name) );
2884 	ent->Pos.x = (ieWord) XPos;
2885 	ent->Pos.y = (ieWord) YPos;
2886 	ent->Face = (ieWord) Face;
2887 	entrances.push_back( ent );
2888 }
2889 
GetEntrance(const char * Name) const2890 Entrance *Map::GetEntrance(const char *Name) const
2891 {
2892 	for (auto entrance : entrances) {
2893 		if (strnicmp(entrance->Name, Name, 32) == 0) {
2894 			return entrance;
2895 		}
2896 	}
2897 	return NULL;
2898 }
2899 
HasActor(const Actor * actor) const2900 bool Map::HasActor(const Actor *actor) const
2901 {
2902 	for (const Actor *act : actors) {
2903 		if (act == actor) {
2904 			return true;
2905 		}
2906 	}
2907 	return false;
2908 }
2909 
RemoveActor(Actor * actor)2910 void Map::RemoveActor(Actor* actor)
2911 {
2912 	size_t i=actors.size();
2913 	while (i--) {
2914 		if (actors[i] == actor) {
2915 			//path is invalid outside this area, but actions may be valid
2916 			actor->ClearPath(true);
2917 			ClearSearchMapFor(actor);
2918 			actor->SetMap(NULL);
2919 			CopyResRef(actor->Area, "");
2920 			actors.erase( actors.begin()+i );
2921 			return;
2922 		}
2923 	}
2924 	Log(WARNING, "Map", "RemoveActor: actor not found?");
2925 }
2926 
2927 //returns true if none of the partymembers are on the map
2928 //and noone is trying to follow the party out
CanFree()2929 bool Map::CanFree()
2930 {
2931 	for (auto actor : actors) {
2932 		if (actor->IsPartyMember()) {
2933 			return false;
2934 		}
2935 
2936 		if (actor->GetInternalFlag()&IF_USEEXIT) {
2937 			return false;
2938 		}
2939 	}
2940 	//we expect the area to be swapped out, so we simply remove the corpses now
2941 	PurgeArea(false);
2942 	return true;
2943 }
2944 
dump(bool show_actors) const2945 void Map::dump(bool show_actors) const
2946 {
2947 	StringBuffer buffer;
2948 	buffer.appendFormatted( "Debugdump of Area %s:\n", scriptName );
2949 	buffer.append("Scripts:");
2950 
2951 	for (size_t i = 0; i < MAX_SCRIPTS; i++) {
2952 		const char* poi = "<none>";
2953 		if (Scripts[i]) {
2954 			poi = Scripts[i]->GetName();
2955 		}
2956 		buffer.appendFormatted( " %.8s", poi );
2957 	}
2958 	buffer.append("\n");
2959 	buffer.appendFormatted( "Area Global ID:  %d\n", GetGlobalID());
2960 	buffer.appendFormatted( "OutDoor: %s\n", YESNO(AreaType & AT_OUTDOOR ) );
2961 	buffer.appendFormatted( "Day/Night: %s\n", YESNO(AreaType & AT_DAYNIGHT ) );
2962 	buffer.appendFormatted( "Extended night: %s\n", YESNO(AreaType & AT_EXTENDED_NIGHT ) );
2963 	buffer.appendFormatted( "Weather: %s\n", YESNO(AreaType & AT_WEATHER ) );
2964 	buffer.appendFormatted( "Area Type: %d\n", AreaType & (AT_CITY|AT_FOREST|AT_DUNGEON) );
2965 	buffer.appendFormatted("Can rest: %s\n", YESNO(!core->GetGame()->CanPartyRest(REST_AREA)));
2966 
2967 	if (show_actors) {
2968 		buffer.append("\n");
2969 		for (auto actor : actors) {
2970 			if (actor->ValidTarget(GA_NO_DEAD|GA_NO_UNSCHEDULED)) {
2971 				buffer.appendFormatted("Actor: %s (%d %s) at %d.%d\n", actor->GetName(1), actor->GetGlobalID(), actor->GetScriptName(), actor->Pos.x, actor->Pos.y);
2972 			}
2973 		}
2974 	}
2975 	Log(DEBUG, "Map", buffer);
2976 }
2977 
AdjustPositionX(Point & goal,unsigned int radiusx,unsigned int radiusy,int size) const2978 bool Map::AdjustPositionX(Point &goal, unsigned int radiusx, unsigned int radiusy, int size) const
2979 {
2980 	unsigned int minx = 0;
2981 	if ((unsigned int) goal.x > radiusx)
2982 		minx = goal.x - radiusx;
2983 	unsigned int maxx = goal.x + radiusx + 1;
2984 	if (maxx > Width)
2985 		maxx = Width;
2986 
2987 	for (unsigned int scanx = minx; scanx < maxx; scanx++) {
2988 		if ((unsigned int) goal.y >= radiusy) {
2989 			if (bool(GetBlocked(scanx, goal.y - radiusy, size) & PathMapFlags::PASSABLE)) {
2990 				goal.x = (ieWord) scanx;
2991 				goal.y = (ieWord) (goal.y - radiusy);
2992 				return true;
2993 			}
2994 		}
2995 		if (goal.y + radiusy < Height) {
2996 			if (bool(GetBlocked(scanx, goal.y + radiusy, size) & PathMapFlags::PASSABLE)) {
2997 				goal.x = (ieWord) scanx;
2998 				goal.y = (ieWord) (goal.y + radiusy);
2999 				return true;
3000 			}
3001 		}
3002 	}
3003 	return false;
3004 }
3005 
AdjustPositionY(Point & goal,unsigned int radiusx,unsigned int radiusy,int size) const3006 bool Map::AdjustPositionY(Point &goal, unsigned int radiusx,  unsigned int radiusy, int size) const
3007 {
3008 	unsigned int miny = 0;
3009 	if ((unsigned int) goal.y > radiusy)
3010 		miny = goal.y - radiusy;
3011 	unsigned int maxy = goal.y + radiusy + 1;
3012 	if (maxy > Height)
3013 		maxy = Height;
3014 	for (unsigned int scany = miny; scany < maxy; scany++) {
3015 		if ((unsigned int) goal.x >= radiusx) {
3016 			if (bool(GetBlocked(goal.x - radiusx, scany, size) & PathMapFlags::PASSABLE)) {
3017 				goal.x = (ieWord) (goal.x - radiusx);
3018 				goal.y = (ieWord) scany;
3019 				return true;
3020 			}
3021 		}
3022 		if (goal.x + radiusx < Width) {
3023 			if (bool(GetBlocked(goal.x + radiusx, scany, size) & PathMapFlags::PASSABLE)) {
3024 				goal.x = (ieWord) (goal.x + radiusx);
3025 				goal.y = (ieWord) scany;
3026 				return true;
3027 			}
3028 		}
3029 	}
3030 	return false;
3031 }
3032 
AdjustPositionNavmap(NavmapPoint & goal,unsigned int radiusx,unsigned int radiusy) const3033 void Map::AdjustPositionNavmap(NavmapPoint &goal, unsigned int radiusx, unsigned int radiusy) const
3034 {
3035 	NavmapPoint smptGoal(goal.x / 16, goal.y / 12);
3036 	AdjustPosition(smptGoal, radiusx, radiusy);
3037 	goal.x = smptGoal.x * 16 + 8;
3038 	goal.y = smptGoal.y * 12 + 6;
3039 }
3040 
AdjustPosition(SearchmapPoint & goal,unsigned int radiusx,unsigned int radiusy,int size) const3041 void Map::AdjustPosition(SearchmapPoint &goal, unsigned int radiusx, unsigned int radiusy, int size) const
3042 {
3043 	if ((unsigned int) goal.x > Width) {
3044 		goal.x = (ieWord) Width;
3045 	}
3046 	if ((unsigned int) goal.y > Height) {
3047 		goal.y = (ieWord) Height;
3048 	}
3049 
3050 	while(radiusx<Width || radiusy<Height) {
3051 		//lets make it slightly random where the actor will appear
3052 		if (RAND(0,1)) {
3053 			if (AdjustPositionX(goal, radiusx, radiusy, size)) {
3054 				return;
3055 			}
3056 			if (AdjustPositionY(goal, radiusx, radiusy, size)) {
3057 				return;
3058 			}
3059 		} else {
3060 			if (AdjustPositionY(goal, radiusx, radiusy, size)) {
3061 				return;
3062 			}
3063 			if (AdjustPositionX(goal, radiusx, radiusy, size)) {
3064 				return;
3065 			}
3066 		}
3067 		if (radiusx<Width) {
3068 			radiusx++;
3069 		}
3070 		if (radiusy<Height) {
3071 			radiusy++;
3072 		}
3073 	}
3074 }
3075 
ConvertPointToFog(const Point & p) const3076 Point Map::ConvertPointToFog(const Point &p) const
3077 {
3078 	return Point(p.x / 32, p.y / 32);
3079 }
3080 
IsVisible(const Point & pos) const3081 bool Map::IsVisible(const Point &pos) const
3082 {
3083 	return FogTileUncovered(ConvertPointToFog(pos), VisibleBitmap);
3084 }
3085 
IsExplored(const Point & pos) const3086 bool Map::IsExplored(const Point &pos) const
3087 {
3088 	return FogTileUncovered(ConvertPointToFog(pos), ExploredBitmap);
3089 }
3090 
3091 //returns direction of area boundary, returns -1 if it isn't a boundary
WhichEdge(const Point & s) const3092 int Map::WhichEdge(const Point &s) const
3093 {
3094 	unsigned int sX=s.x/16;
3095 	unsigned int sY=s.y/12;
3096 	if (!(GetBlocked(sX, sY) & PathMapFlags::TRAVEL)) {
3097 		Log(DEBUG, "Map", "This isn't a travel region [%d.%d]?",
3098 			sX, sY);
3099 		return -1;
3100 	}
3101 	sX*=Height;
3102 	sY*=Width;
3103 	if (sX>sY) { //north or east
3104 		if (Width*Height>sX+sY) { //
3105 			return WMP_NORTH;
3106 		}
3107 		return WMP_EAST;
3108 	}
3109 	//south or west
3110 	if (Width*Height<sX+sY) { //
3111 		return WMP_SOUTH;
3112 	}
3113 	return WMP_WEST;
3114 }
3115 
3116 //--------ambients----------------
SetupAmbients() const3117 void Map::SetupAmbients() const
3118 {
3119 	AmbientMgr *ambim = core->GetAudioDrv()->GetAmbientMgr();
3120 	if (!ambim) return;
3121 	ambim->reset();
3122 	ambim->setAmbients( ambients );
3123 }
3124 
GetAmbientCount(bool toSave) const3125 ieWord Map::GetAmbientCount(bool toSave) const
3126 {
3127 	if (!toSave) return static_cast<ieWord>(ambients.size());
3128 
3129 	ieWord ambiCount = 0;
3130 	for (const Ambient *ambient : ambients) {
3131 		if (!(ambient->flags & IE_AMBI_NOSAVE)) ambiCount++;
3132 	}
3133 	return ambiCount;
3134 }
3135 
3136 //--------mapnotes----------------
3137 //text must be a pointer we can claim ownership of
AddMapNote(const Point & point,ieWord color,String * text,bool readonly)3138 void Map::AddMapNote(const Point& point, ieWord color, String* text, bool readonly)
3139 {
3140 	AddMapNote(point, MapNote(text, color, readonly));
3141 }
3142 
AddMapNote(const Point & point,ieWord color,ieStrRef strref,bool readonly)3143 void Map::AddMapNote(const Point& point, ieWord color, ieStrRef strref, bool readonly)
3144 {
3145 	AddMapNote(point, MapNote(strref, color, readonly));
3146 }
3147 
AddMapNote(const Point & point,const MapNote & note)3148 void Map::AddMapNote(const Point &point, const MapNote& note)
3149 {
3150 	RemoveMapNote(point);
3151 	mapnotes.push_back(note);
3152 	mapnotes.back().Pos = point;
3153 }
3154 
RemoveMapNote(const Point & point)3155 void Map::RemoveMapNote(const Point &point)
3156 {
3157 	std::vector<MapNote>::iterator it = mapnotes.begin();
3158 	for (; it != mapnotes.end(); ++it) {
3159 		if (!it->readonly && it->Pos == point) {
3160 			mapnotes.erase(it);
3161 			break;
3162 		}
3163 	}
3164 }
3165 
MapNoteAtPoint(const Point & point,unsigned int radius) const3166 const MapNote* Map::MapNoteAtPoint(const Point& point, unsigned int radius) const
3167 {
3168 	size_t i = mapnotes.size();
3169 	while (i--) {
3170 		if (Distance(point, mapnotes[i].Pos) < radius) {
3171 			return &mapnotes[i];
3172 		}
3173 	}
3174 	return NULL;
3175 }
3176 
3177 //--------spawning------------------
LoadIniSpawn()3178 void Map::LoadIniSpawn()
3179 {
3180 	INISpawn = new IniSpawn(this);
3181 	if (core->HasFeature(GF_RESDATA_INI)) {
3182 		// 85 cases where we'd miss the ini and 1 where we'd use the wrong one
3183 		INISpawn->InitSpawn(scriptName);
3184 	} else {
3185 		INISpawn->InitSpawn(WEDResRef);
3186 	}
3187 }
3188 
SpawnCreature(const Point & pos,const char * creResRef,int radiusx,int radiusy,ieWord rwdist,int * difficulty,unsigned int * creCount)3189 bool Map::SpawnCreature(const Point &pos, const char *creResRef, int radiusx, int radiusy, ieWord rwdist, int *difficulty, unsigned int *creCount)
3190 {
3191 	bool spawned = false;
3192 	const SpawnGroup *sg = nullptr;
3193 	void *lookup;
3194 	bool first = (creCount ? *creCount == 0 : true);
3195 	int level = (difficulty ? *difficulty : core->GetGame()->GetTotalPartyLevel(true));
3196 	int count = 1;
3197 
3198 	if (Spawns.Lookup(creResRef, lookup)) {
3199 		sg = (SpawnGroup *) lookup;
3200 		if (first || (level >= (int) sg->Level)) {
3201 			count = sg->Count;
3202 		} else {
3203 			count = 0;
3204 		}
3205 	}
3206 
3207 	while (count--) {
3208 		Actor *creature = gamedata->GetCreature(sg ? sg->ResRefs[count] : creResRef);
3209 		if (creature) {
3210 			// ensure a minimum power level, since many creatures have this as 0
3211 			int cpl = creature->Modified[IE_XP] ? creature->Modified[IE_XP] : 1;
3212 
3213 			//SpawnGroups are all or nothing but make sure we spawn
3214 			//at least one creature if this is the first
3215 			if (level >= cpl || sg || first) {
3216 				AddActor(creature, true);
3217 				creature->SetPosition(pos, true, radiusx, radiusy);
3218 				creature->HomeLocation = pos;
3219 				creature->maxWalkDistance = rwdist;
3220 				creature->Spawned = true;
3221 				creature->RefreshEffects(NULL);
3222 				if (difficulty && !sg) *difficulty -= cpl;
3223 				if (creCount) (*creCount)++;
3224 				spawned = true;
3225 			}
3226 		}
3227 	}
3228 
3229 	if (spawned && sg && difficulty) {
3230 		*difficulty -= sg->Level;
3231 	}
3232 
3233 	return spawned;
3234 }
3235 
TriggerSpawn(Spawn * spawn)3236 void Map::TriggerSpawn(Spawn *spawn)
3237 {
3238 	//is it still active
3239 	if (!spawn->Enabled) {
3240 		return;
3241 	}
3242 	//temporarily disabled?
3243 	if ((spawn->Method & (SPF_NOSPAWN|SPF_WAIT)) == (SPF_NOSPAWN|SPF_WAIT)) {
3244 		return;
3245 	}
3246 
3247 	//check schedule
3248 	ieDword time = core->GetGame()->GameTime;
3249 	if (!Schedule(spawn->appearance, time)) {
3250 		return;
3251 	}
3252 
3253 	//check day or night chance
3254 	bool day = core->GetGame()->IsDay();
3255 	int chance = RAND(0, 99);
3256 	if ((day && chance > spawn->DayChance) ||
3257 		(!day && chance > spawn->NightChance)) {
3258 		spawn->NextSpawn = time + spawn->Frequency * AI_UPDATE_TIME * 60;
3259 		spawn->Method |= SPF_WAIT;
3260 		return;
3261 	}
3262 	//create spawns
3263 	int difficulty = spawn->Difficulty * core->GetGame()->GetTotalPartyLevel(true);
3264 	unsigned int spawncount = 0, i = RAND(0, spawn->Count-1);
3265 	while (difficulty >= 0 && spawncount < spawn->Maximum) {
3266 		if (!SpawnCreature(spawn->Pos, spawn->Creatures[i], 0, 0, spawn->rwdist, &difficulty, &spawncount)) {
3267 			break;
3268 		}
3269 		if (++i >= spawn->Count) {
3270 			i = 0;
3271 		}
3272 
3273 	}
3274 	//disable spawnpoint
3275 	if (spawn->Method & SPF_ONCE || !(spawn->Method & SPF_NOSPAWN)) {
3276 		spawn->Enabled = 0;
3277 	} else {
3278 		spawn->NextSpawn = time + spawn->Frequency * AI_UPDATE_TIME * 60;
3279 		spawn->Method |= SPF_WAIT;
3280 	}
3281 }
3282 
UpdateSpawns() const3283 void Map::UpdateSpawns() const
3284 {
3285 	//don't reactivate if there are spawns left in the area
3286 	if (SpawnsAlive()) {
3287 		return;
3288 	}
3289 	ieDword time = core->GetGame()->GameTime;
3290 	for (auto spawn : spawns) {
3291 		if ((spawn->Method & (SPF_NOSPAWN|SPF_WAIT)) == (SPF_NOSPAWN|SPF_WAIT)) {
3292 			//only reactivate the spawn point if the party cannot currently see it;
3293 			//also make sure the party has moved away some
3294 			if (spawn->NextSpawn < time && !IsVisible(spawn->Pos) &&
3295 				!GetActorInRadius(spawn->Pos, GA_NO_DEAD|GA_NO_ENEMY|GA_NO_NEUTRAL|GA_NO_UNSCHEDULED, SPAWN_RANGE * 2)) {
3296 				spawn->Method &= ~SPF_WAIT;
3297 			}
3298 		}
3299 	}
3300 }
3301 
3302 //--------restheader----------------
3303 /*
3304 Every spawn has a difficulty associated with it. For CREs this is the xp stat
3305 and for groups it's the value in the difficulty row.
3306 For every spawn, the difficulty sum of all spawns up to now (including the
3307 current) is compared against (party level * rest header difficulty). If it's
3308 greater, the spawning is aborted. If all the other conditions are true, at
3309 least one creature is summoned, regardless the difficulty cap.
3310 */
CheckRestInterruptsAndPassTime(const Point & pos,int hours,int day)3311 int Map::CheckRestInterruptsAndPassTime(const Point &pos, int hours, int day)
3312 {
3313 	if (!RestHeader.CreatureNum || !RestHeader.Enabled || !RestHeader.Maximum) {
3314 		core->GetGame()->AdvanceTime(hours * core->Time.hour_size);
3315 		return 0;
3316 	}
3317 
3318 	// TODO: it appears there was a limit on how many rest encounters can
3319 	// be triggered in a row (or area?), since HOFMode should increase it
3320 	// by 1. It doesn't look like it was stored in the header, so perhaps
3321 	// it was just a hardcoded limit to make the game more forgiving
3322 
3323 	//based on ingame timer
3324 	int chance=day?RestHeader.DayChance:RestHeader.NightChance;
3325 	bool interrupt = RAND(0, 99) < chance;
3326 	unsigned int spawncount = 0;
3327 	int spawnamount = core->GetGame()->GetTotalPartyLevel(true) * RestHeader.Difficulty;
3328 	if (spawnamount < 1) spawnamount = 1;
3329 	for (int i=0;i<hours;i++) {
3330 		if (interrupt) {
3331 			int idx = RAND(0, RestHeader.CreatureNum-1);
3332 			const Actor *creature = gamedata->GetCreature(RestHeader.CreResRef[idx]);
3333 			if (!creature) {
3334 				core->GetGame()->AdvanceTime(core->Time.hour_size);
3335 				continue;
3336 			}
3337 
3338 			displaymsg->DisplayString( RestHeader.Strref[idx], DMC_GOLD, IE_STR_SOUND );
3339 			while (spawnamount > 0 && spawncount < RestHeader.Maximum) {
3340 				if (!SpawnCreature(pos, RestHeader.CreResRef[idx], 20, 20, RestHeader.rwdist, &spawnamount, &spawncount)) {
3341 					break;
3342 				}
3343 			}
3344 			return hours-i;
3345 		}
3346 		// advance the time in hourly steps, so an interruption is timed properly
3347 		core->GetGame()->AdvanceTime(core->Time.hour_size);
3348 	}
3349 	return 0;
3350 }
3351 
GetSize() const3352 Size Map::GetSize() const
3353 {
3354 	return TMap->GetMapSize();
3355 }
3356 
3357 //--------explored bitmap-----------
GetExploredMapSize() const3358 int Map::GetExploredMapSize() const
3359 {
3360 	Size fogSize = FogMapSize();
3361 	return (fogSize.w * fogSize.h + 7) / 8;
3362 }
3363 
FillExplored(bool explored)3364 void Map::FillExplored(bool explored)
3365 {
3366 	std::fill(ExploredBitmap, ExploredBitmap + GetExploredMapSize(), explored ? 0xff : 0x00);
3367 }
3368 
ExploreTile(const Point & p)3369 void Map::ExploreTile(const Point &p)
3370 {
3371 	Point fogP = ConvertPointToFog(p);
3372 
3373 	const Size fogSize = FogMapSize();
3374 	if (fogP.x < 0 || fogP.x >= fogSize.w || fogP.y < 0 || fogP.y >= fogSize.h) {
3375 		return;
3376 	}
3377 
3378 	div_t res = div(fogSize.w * fogP.y + fogP.x, 8);
3379 	ExploredBitmap[res.quot] |= (1 << res.rem);
3380 	VisibleBitmap[res.quot] |= (1 << res.rem);
3381 }
3382 
ExploreMapChunk(const Point & Pos,int range,int los)3383 void Map::ExploreMapChunk(const Point &Pos, int range, int los)
3384 {
3385 	Point Tile;
3386 
3387 	if (range>MaxVisibility) {
3388 		range=MaxVisibility;
3389 	}
3390 	int p=VisibilityPerimeter;
3391 	while (p--) {
3392 		int Pass = 2;
3393 		bool block = false;
3394 		bool sidewall = false ;
3395 		for (int i=0;i<range;i++) {
3396 			Tile.x = Pos.x+VisibilityMasks[i][p].x;
3397 			Tile.y = Pos.y+VisibilityMasks[i][p].y;
3398 
3399 			if (los) {
3400 				if (!block) {
3401 					PathMapFlags type = GetBlocked(Tile.x / 16, Tile.y / 12);
3402 					if (bool(type & PathMapFlags::NO_SEE)) {
3403 						block=true;
3404 					} else if (bool(type & PathMapFlags::SIDEWALL)) {
3405 						sidewall = true;
3406 					} else if (sidewall)
3407 					{
3408 						block=true ;
3409 					}
3410 				}
3411 				if (block) {
3412 					Pass--;
3413 					if (!Pass) break;
3414 				}
3415 			}
3416 			ExploreTile(Tile);
3417 		}
3418 	}
3419 }
3420 
UpdateFog()3421 void Map::UpdateFog()
3422 {
3423 	std::fill(VisibleBitmap, VisibleBitmap + GetExploredMapSize(), 0);
3424 
3425 	for (size_t i = 0; i < actors.size(); i++) {
3426 		const Actor *actor = actors[i];
3427 		if (!actor->Modified[ IE_EXPLORE ] ) continue;
3428 
3429 		int state = actor->Modified[IE_STATE_ID];
3430 		if (state & STATE_CANTSEE) continue;
3431 
3432 		int vis2 = actor->Modified[IE_VISUALRANGE];
3433 		if ((state&STATE_BLIND) || (vis2<2)) vis2=2; //can see only themselves
3434 		ExploreMapChunk (actor->Pos, vis2+actor->GetAnims()->GetCircleSize(), 1);
3435 
3436 		Spawn *sp = GetSpawnRadius(actor->Pos, SPAWN_RANGE); //30 * 12
3437 		if (sp) {
3438 			TriggerSpawn(sp);
3439 		}
3440 	}
3441 }
3442 
3443 // Valid values are - PathMapFlags::UNMARKED, PathMapFlags::PC, PathMapFlags::NPC
BlockSearchMap(const Point & Pos,unsigned int size,PathMapFlags value)3444 void Map::BlockSearchMap(const Point &Pos, unsigned int size, PathMapFlags value)
3445 {
3446 	// We block a circle of radius size-1 around (px,py)
3447 	// Note that this does not exactly match BG2. BG2's approximations of
3448 	// these circles are slightly different for sizes 6 and up.
3449 
3450 	// Note: this is a larger circle than the one tested in GetBlocked.
3451 	// This means that an actor can get closer to a wall than to another
3452 	// actor. This matches the behaviour of the original BG2.
3453 
3454 	if (size > MAX_CIRCLESIZE) size = MAX_CIRCLESIZE;
3455 	if (size < 1) size = 1;
3456 	unsigned int ppx = Pos.x/16;
3457 	unsigned int ppy = Pos.y/12;
3458 	unsigned int r=(size-1)*(size-1)+1;
3459 	for (unsigned int i=0; i<size; i++) {
3460 		for (unsigned int j=0; j<size; j++) {
3461 			if (i*i+j*j <= r) {
3462 				unsigned int ppxpi = ppx+i;
3463 				unsigned int ppypj = ppy+j;
3464 				unsigned int ppxmi = ppx-i;
3465 				unsigned int ppymj = ppy-j;
3466 				unsigned int pos = ppypj * Width + ppxpi;
3467 				if (ppxpi < Width && ppypj < Height && SrchMap[pos] != PathMapFlags::IMPASSABLE) {
3468 					SrchMap[pos] = (SrchMap[pos] & PathMapFlags::NOTACTOR) | value;
3469 				}
3470 				pos = ppymj * Width + ppxpi;
3471 				if (ppxpi < Width && ppymj < Height && SrchMap[pos] != PathMapFlags::IMPASSABLE) {
3472 					SrchMap[pos] = (SrchMap[pos] & PathMapFlags::NOTACTOR) | value;
3473 				}
3474 				pos = ppypj * Width + ppxmi;
3475 				if (ppxmi < Width && ppypj < Height && SrchMap[pos] != PathMapFlags::IMPASSABLE) {
3476 					SrchMap[pos] = (SrchMap[pos] & PathMapFlags::NOTACTOR) | value;
3477 				}
3478 				pos = ppymj * Width + ppxmi;
3479 				if (ppxmi < Width && ppymj < Height && SrchMap[pos] != PathMapFlags::IMPASSABLE) {
3480 					SrchMap[pos] = (SrchMap[pos] & PathMapFlags::NOTACTOR) | value;
3481 				}
3482 			}
3483 		}
3484 	}
3485 }
3486 
GetSpawn(const char * Name) const3487 Spawn* Map::GetSpawn(const char *Name) const
3488 {
3489 	for (auto spawn : spawns) {
3490 		if (stricmp(spawn->Name, Name) == 0) {
3491 			return spawn;
3492 		}
3493 	}
3494 	return NULL;
3495 }
3496 
GetSpawnRadius(const Point & point,unsigned int radius) const3497 Spawn *Map::GetSpawnRadius(const Point &point, unsigned int radius) const
3498 {
3499 	for (auto spawn : spawns) {
3500 		if (Distance(point, spawn->Pos) < radius) {
3501 			return spawn;
3502 		}
3503 	}
3504 	return NULL;
3505 }
3506 
ConsolidateContainers()3507 int Map::ConsolidateContainers()
3508 {
3509 	int itemcount = 0;
3510 	int containercount = (int) TMap->GetContainerCount();
3511 	while (containercount--) {
3512 		Container * c = TMap->GetContainer( containercount);
3513 
3514 		if (TMap->CleanupContainer(c) ) {
3515 			objectStencils.erase(c);
3516 			continue;
3517 		}
3518 		itemcount += c->inventory.GetSlotCount();
3519 	}
3520 	return itemcount;
3521 }
3522 
3523 //Pos could be [-1,-1] in which case it copies the ground piles to their
3524 //original position in the second area
CopyGroundPiles(Map * othermap,const Point & Pos) const3525 void Map::CopyGroundPiles(Map *othermap, const Point &Pos) const
3526 {
3527 	int containercount = (int) TMap->GetContainerCount();
3528 	while (containercount--) {
3529 		Container * c = TMap->GetContainer( containercount);
3530 		if (c->Type==IE_CONTAINER_PILE) {
3531 			//creating (or grabbing) the container in the other map at the given position
3532 			Container *othercontainer;
3533 			if (Pos.isempty()) {
3534 				othercontainer = othermap->GetPile(c->Pos);
3535 			} else {
3536 				othercontainer = othermap->GetPile(Pos);
3537 			}
3538 			//transfer the pile to the other container
3539 			unsigned int i=c->inventory.GetSlotCount();
3540 			while (i--) {
3541 				CREItem *item = c->RemoveItem(i, 0);
3542 				othercontainer->AddItem(item);
3543 			}
3544 		}
3545 	}
3546 }
3547 
3548 // merges pile 1 into pile 2
MergePiles(Container * donorPile,Container * pile)3549 static void MergePiles(Container *donorPile, Container *pile)
3550 {
3551 	unsigned int i = donorPile->inventory.GetSlotCount();
3552 	while (i--) {
3553 		CREItem *item = donorPile->RemoveItem(i, 0);
3554 		int count = pile->inventory.CountItems(item->ItemResRef, 0);
3555 		if (count == 0) {
3556 			pile->AddItem(item);
3557 			continue;
3558 		}
3559 
3560 		// ensure slots are stacked fully before adding new ones
3561 		int skipped = count;
3562 		while (count) {
3563 			int slot = pile->inventory.FindItem(item->ItemResRef, 0, --count);
3564 			if (slot == -1) {
3565 				// probably an inventory bug, shouldn't happen
3566 				Log(DEBUG, "Map", "MoveVisibleGroundPiles found unaccessible pile item: %s", item->ItemResRef);
3567 				skipped--;
3568 				continue;
3569 			}
3570 			const CREItem *otheritem = pile->inventory.GetSlotItem(slot);
3571 			if (otheritem->Usages[0] == otheritem->MaxStackAmount) {
3572 				// already full (or nonstackable), nothing to do here
3573 				skipped--;
3574 				continue;
3575 			}
3576 			if (pile->inventory.MergeItems(slot, item) != ASI_SUCCESS) {
3577 				// the merge either failed (add whole) or went over the limit (add remainder)
3578 				pile->AddItem(item);
3579 			}
3580 			skipped = 1; // just in case we would be eligible for the safety net below
3581 			break;
3582 		}
3583 
3584 		// all found slots were already unsuitable, so just dump the item to a new one
3585 		if (!skipped) {
3586 			pile->AddItem(item);
3587 		}
3588 	}
3589 }
3590 
MoveVisibleGroundPiles(const Point & Pos)3591 void Map::MoveVisibleGroundPiles(const Point &Pos)
3592 {
3593 	//creating the container at the given position
3594 	Container *othercontainer;
3595 	othercontainer = GetPile(Pos);
3596 
3597 	int containercount = (int) TMap->GetContainerCount();
3598 	while (containercount--) {
3599 		Container * c = TMap->GetContainer( containercount);
3600 		if (c->Type==IE_CONTAINER_PILE && IsExplored(c->Pos)) {
3601 			//transfer the pile to the other container
3602 			MergePiles(c, othercontainer);
3603 		}
3604 	}
3605 
3606 	// reshuffle the items so they are sorted
3607 	unsigned int i = othercontainer->inventory.GetSlotCount();
3608 	if (i < 3) {
3609 		// nothing to do
3610 		return;
3611 	}
3612 
3613 	// sort by removing all items that have copies and readding them at the end
3614 	while (i--) {
3615 		const CREItem *item = othercontainer->inventory.GetSlotItem(i);
3616 		int count = othercontainer->inventory.CountItems(item->ItemResRef, 0);
3617 		if (count == 1) continue;
3618 
3619 		while (count) {
3620 			int slot = othercontainer->inventory.FindItem(item->ItemResRef, 0, --count);
3621 			if (slot == -1) continue;
3622 			// containers don't really care about position, so every new item is placed at the last spot
3623 			CREItem *item = othercontainer->RemoveItem(slot, 0);
3624 			othercontainer->AddItem(item);
3625 		}
3626 	}
3627 }
3628 
GetPile(Point position)3629 Container *Map::GetPile(Point position)
3630 {
3631 	char heapname[32];
3632 
3633 	//converting to search square
3634 	position.x=position.x/16;
3635 	position.y=position.y/12;
3636 	snprintf(heapname, sizeof(heapname), "heap_%hd.%hd", position.x, position.y);
3637 	//pixel position is centered on search square
3638 	position.x=position.x*16+8;
3639 	position.y=position.y*12+6;
3640 	Container *container = TMap->GetContainer(position,IE_CONTAINER_PILE);
3641 	if (!container) {
3642 		container = AddContainer(heapname, IE_CONTAINER_PILE, nullptr);
3643 		container->Pos=position;
3644 		//bounding box covers the search square
3645 		container->BBox = Region::RegionFromPoints(Point(position.x-8, position.y-6), Point(position.x+8,position.y+6));
3646 	}
3647 	return container;
3648 }
3649 
AddItemToLocation(const Point & position,CREItem * item)3650 void Map::AddItemToLocation(const Point &position, CREItem *item)
3651 {
3652 	Container *container = GetPile(position);
3653 	container->AddItem(item);
3654 }
3655 
AddContainer(const char * Name,unsigned short Type,std::shared_ptr<Gem_Polygon> outline)3656 Container* Map::AddContainer(const char* Name, unsigned short Type,
3657 							 std::shared_ptr<Gem_Polygon> outline)
3658 {
3659 	Container* c = new Container();
3660 	c->SetScriptName( Name );
3661 	c->Type = Type;
3662 	c->outline = outline;
3663 	c->SetMap(this);
3664 	if (outline) {
3665 		c->BBox = outline->BBox;
3666 	}
3667 	TMap->AddContainer( c );
3668 	return c;
3669 }
3670 
GetCursor(const Point & p) const3671 int Map::GetCursor(const Point &p) const
3672 {
3673 	if (!IsExplored(p)) {
3674 		return IE_CURSOR_INVALID;
3675 	}
3676 	switch (GetBlocked(p.x / 16, p.y / 12) & (PathMapFlags::PASSABLE | PathMapFlags::TRAVEL)) {
3677 		case PathMapFlags::IMPASSABLE:
3678 			return IE_CURSOR_BLOCKED;
3679 		case PathMapFlags::PASSABLE:
3680 			return IE_CURSOR_WALK;
3681 		default:
3682 			return IE_CURSOR_TRAVEL;
3683 	}
3684 }
3685 
HasWeather() const3686 bool Map::HasWeather() const
3687 {
3688 	if ((AreaType & (AT_WEATHER|AT_OUTDOOR) ) != (AT_WEATHER|AT_OUTDOOR) ) {
3689 		return false;
3690 	}
3691 	ieDword tmp = 1;
3692 	core->GetDictionary()->Lookup("Weather", tmp);
3693 	return !!tmp;
3694 }
3695 
GetWeather() const3696 int Map::GetWeather() const
3697 {
3698 	if (Rain>=core->Roll(1,100,0) ) {
3699 		if (Lightning>=core->Roll(1,100,0) ) {
3700 			return WB_RARELIGHTNING|WB_RAIN;
3701 		}
3702 		return WB_RAIN;
3703 	}
3704 	if (Snow>=core->Roll(1,100,0) ) {
3705 		return WB_SNOW;
3706 	}
3707 	// TODO: handle WB_FOG the same way when we start drawing it
3708 	return WB_NORMAL;
3709 }
3710 
FadeSparkle(const Point & pos,bool forced) const3711 void Map::FadeSparkle(const Point &pos, bool forced) const
3712 {
3713 	for (auto particle : particles) {
3714 		if (particle->MatchPos(pos)) {
3715 			if (forced) {
3716 				//particles.erase(iter);
3717 				particle->SetPhase(P_EMPTY);
3718 			} else {
3719 				particle->SetPhase(P_FADE);
3720 			}
3721 			return;
3722 		}
3723 	}
3724 }
3725 
Sparkle(ieDword duration,ieDword color,ieDword type,const Point & pos,unsigned int FragAnimID,int Zpos)3726 void Map::Sparkle(ieDword duration, ieDword color, ieDword type, const Point &pos, unsigned int FragAnimID, int Zpos)
3727 {
3728 	int style, path, grow, size, width, ttl;
3729 
3730 	if (!Zpos) {
3731 		Zpos = 30;
3732 	}
3733 
3734 	//the high word is ignored in the original engine (compatibility hack)
3735 	switch(type&0xffff) {
3736 	case SPARKLE_SHOWER: //simple falling sparks
3737 		path = SP_PATH_FALL;
3738 		grow = SP_SPAWN_FULL;
3739 		size = 100;
3740 		width = 40;
3741 		ttl = duration;
3742 		break;
3743 	case SPARKLE_PUFF:
3744 		path = SP_PATH_FOUNT; //sparks go up and down
3745 		grow = SP_SPAWN_SOME;
3746 		size = 40;
3747 		width = 40;
3748 		ttl = core->GetGame()->GameTime+Zpos;
3749 		break;
3750 	case SPARKLE_EXPLOSION: //this isn't in the original engine, but it is a nice effect to have
3751 		path = SP_PATH_EXPL;
3752 		grow = SP_SPAWN_SOME;
3753 		size = 10;
3754 		width = 40;
3755 		ttl = core->GetGame()->GameTime+Zpos;
3756 		break;
3757 	default:
3758 		path = SP_PATH_FLIT;
3759 		grow = SP_SPAWN_SOME;
3760 		size = 100;
3761 		width = 40;
3762 		ttl = duration;
3763 		break;
3764 	}
3765 	Particles *sparkles = new Particles(size);
3766 	sparkles->SetOwner(this);
3767 	sparkles->SetRegion(pos.x-width/2, pos.y-Zpos, width, Zpos);
3768 	sparkles->SetTimeToLive(ttl);
3769 
3770 	if (FragAnimID) {
3771 		style = SP_TYPE_BITMAP;
3772 		sparkles->SetBitmap(FragAnimID);
3773 	}
3774 	else {
3775 		style = SP_TYPE_POINT;
3776 	}
3777 	sparkles->SetType(style, path, grow);
3778 	sparkles->SetColor(color);
3779 	sparkles->SetPhase(P_GROW);
3780 
3781 	spaIterator iter;
3782 	for(iter=particles.begin(); (iter!=particles.end()) && ((*iter)->GetHeight()<pos.y); iter++) ;
3783 	particles.insert(iter, sparkles);
3784 }
3785 
3786 //remove flags from actor if it has left the trigger area it had last entered
ClearTrap(Actor * actor,ieDword InTrap) const3787 void Map::ClearTrap(Actor *actor, ieDword InTrap) const
3788 {
3789 	const InfoPoint *trap = TMap->GetInfoPoint(InTrap);
3790 	if (!trap) {
3791 		actor->SetInTrap(0);
3792 	} else {
3793 		if(!trap->outline->PointIn(actor->Pos)) {
3794 			actor->SetInTrap(0);
3795 		}
3796 	}
3797 }
3798 
SetTrackString(ieStrRef strref,int flg,int difficulty)3799 void Map::SetTrackString(ieStrRef strref, int flg, int difficulty)
3800 {
3801 	trackString = strref;
3802 	trackFlag = flg;
3803 	trackDiff = (ieWord) difficulty;
3804 }
3805 
DisplayTrackString(const Actor * target) const3806 bool Map::DisplayTrackString(const Actor *target) const
3807 {
3808 	// this stat isn't saved
3809 	// according to the HoW manual the chance of success is:
3810 	// +5% for every three levels and +5% per point of wisdom
3811 	int skill = target->GetStat(IE_TRACKING);
3812 	int success;
3813 	if (core->HasFeature(GF_3ED_RULES)) {
3814 		// ~Wilderness Lore check. Wilderness Lore (skill + D20 roll + WIS modifier) =  %d vs. ((Area difficulty pct / 5) + 10) = %d ( Skill + WIS MOD = %d ).~
3815 		skill += target->LuckyRoll(1, 20, 0) + target->GetAbilityBonus(IE_WIS);
3816 		success = skill > (trackDiff/5 + 10);
3817 	} else {
3818 		skill += (target->GetStat(IE_LEVEL)/3)*5 + target->GetStat(IE_WIS)*5;
3819 		success = core->Roll(1, 100, trackDiff) > skill;
3820 	}
3821 	if (!success) {
3822 		displaymsg->DisplayConstantStringName(STR_TRACKINGFAILED, DMC_LIGHTGREY, target);
3823 		return true;
3824 	}
3825 	if (trackFlag) {
3826 			char * str = core->GetCString( trackString);
3827 			core->GetTokenDictionary()->SetAt( "CREATURE", str);
3828 			displaymsg->DisplayConstantStringName(STR_TRACKING, DMC_LIGHTGREY, target);
3829 			return false;
3830 	}
3831 	displaymsg->DisplayStringName(trackString, DMC_LIGHTGREY, target, 0);
3832 	return false;
3833 }
3834 
3835 // returns a lightness level in the range of [0-100]
3836 // since the lightmap is much smaller than the area, we need to interpolate
GetLightLevel(const Point & Pos) const3837 unsigned int Map::GetLightLevel(const Point &Pos) const
3838 {
3839 	Color c = LightMap->GetPixel(Pos.x/16, Pos.y/12);
3840 	// at night/dusk/dawn the lightmap color is adjusted by the color overlay. (Only get's darker.)
3841 	const Color *tint = core->GetGame()->GetGlobalTint();
3842 	if (tint) {
3843 		return ((c.r-tint->r)*114 + (c.g-tint->g)*587 + (c.b-tint->b)*299)/2550;
3844 	}
3845 	return (c.r*114+c.g*587+c.b*299)/2550;
3846 }
3847 
3848 ////////////////////AreaAnimation//////////////////
3849 //Area animation
3850 
AreaAnimation()3851 AreaAnimation::AreaAnimation()
3852 {
3853 	animation=NULL;
3854 	animcount=0;
3855 	appearance = sequence = frame = transparency = height = 0;
3856 	Flags = originalFlags = startFrameRange = skipcycle = startchance = 0;
3857 	unknown48 = 0;
3858 	Name[0] = 0;
3859 	BAM[0] = 0;
3860 	PaletteRef[0] = 0;
3861 }
3862 
AreaAnimation(const AreaAnimation * src)3863 AreaAnimation::AreaAnimation(const AreaAnimation *src)
3864 {
3865 	animcount = src->animcount;
3866 	sequence = src->sequence;
3867 	animation = NULL;
3868 	Flags = src->Flags;
3869 	originalFlags = src->originalFlags;
3870 	Pos.x = src->Pos.x;
3871 	Pos.y = src->Pos.y;
3872 	appearance = src->appearance;
3873 	frame = src->frame;
3874 	transparency = src->transparency;
3875 	height = src->height;
3876 	startFrameRange = src->startFrameRange;
3877 	skipcycle = src->skipcycle;
3878 	startchance = src->startchance;
3879 	unknown48 = 0;
3880 
3881 	memcpy(PaletteRef, src->PaletteRef, sizeof(PaletteRef));
3882 	memcpy(Name, src->Name, sizeof(ieVariable));
3883 	memcpy(BAM, src->BAM, sizeof(ieResRef));
3884 
3885 	palette = src->palette ? src->palette->Copy() : NULL;
3886 
3887 	// handles the rest: animation, resets animcount
3888 	InitAnimation();
3889 }
3890 
~AreaAnimation()3891 AreaAnimation::~AreaAnimation()
3892 {
3893 	for(int i=0;i<animcount;i++) {
3894 		if (animation[i]) {
3895 			delete (animation[i]);
3896 		}
3897 	}
3898 	free(animation);
3899 }
3900 
GetAnimationPiece(AnimationFactory * af,int animCycle)3901 Animation *AreaAnimation::GetAnimationPiece(AnimationFactory *af, int animCycle)
3902 {
3903 	Animation *anim = af->GetCycle( ( unsigned char ) animCycle );
3904 	if (!anim)
3905 		anim = af->GetCycle( 0 );
3906 	if (!anim) {
3907 		print("Cannot load animation: %s", BAM);
3908 		return NULL;
3909 	}
3910 	//this will make the animation stop when the game is stopped
3911 	//a possible gemrb feature to have this flag settable in .are
3912 	anim->gameAnimation = true;
3913 	anim->SetPos(frame); // sanity check it first
3914 	anim->Flags = Flags;
3915 	anim->x = Pos.x;
3916 	anim->y = Pos.y;
3917 	if (anim->Flags&A_ANI_MIRROR) {
3918 		anim->MirrorAnimation();
3919 	}
3920 
3921 	return anim;
3922 }
3923 
InitAnimation()3924 void AreaAnimation::InitAnimation()
3925 {
3926 	AnimationFactory* af = ( AnimationFactory* )
3927 		gamedata->GetFactoryResource( BAM, IE_BAM_CLASS_ID );
3928 	if (!af) {
3929 		print("Cannot load animation: %s", BAM);
3930 		return;
3931 	}
3932 
3933 	//freeing up the previous animation
3934 	for (int i=0; i<animcount && animation; i++) {
3935 		delete animation[i];
3936 	}
3937 	free(animation);
3938 
3939 	animcount = (int) af->GetCycleCount();
3940 	if (Flags & A_ANI_ALLCYCLES && animcount > 0) {
3941 		animation = (Animation **) malloc(animcount * sizeof(Animation *) );
3942 		for(int j=0;j<animcount;j++) {
3943 			animation[j]=GetAnimationPiece(af, j);
3944 		}
3945 	} else {
3946 		animcount = 1;
3947 		animation = (Animation **) malloc( sizeof(Animation *) );
3948 		animation[0]=GetAnimationPiece(af, sequence);
3949 	}
3950 	if (Flags & A_ANI_PALETTE) {
3951 		SetPalette(PaletteRef);
3952 	}
3953 	if (Flags&A_ANI_BLEND) {
3954 		BlendAnimation();
3955 	}
3956 }
3957 
SetPalette(ieResRef Pal)3958 void AreaAnimation::SetPalette(ieResRef Pal)
3959 {
3960 	Flags |= A_ANI_PALETTE;
3961 	gamedata->FreePalette(palette, PaletteRef);
3962 	strnlwrcpy(PaletteRef, Pal, 8);
3963 	palette = gamedata->GetPalette(PaletteRef);
3964 	if (Flags&A_ANI_BLEND) {
3965 		//re-blending after palette change
3966 		BlendAnimation();
3967 	}
3968 }
3969 
BlendAnimation()3970 void AreaAnimation::BlendAnimation()
3971 {
3972 	//Warning! This function will modify a shared palette
3973 	if (!palette) {
3974 		// CHECKME: what should we do here? Currently copying palette
3975 		// from first frame of first animation
3976 
3977 		if (animcount == 0 || !animation[0]) return;
3978 		Holder<Sprite2D> spr = animation[0]->GetFrame(0);
3979 		if (!spr) return;
3980 		palette = spr->GetPalette()->Copy();
3981 		PaletteRef[0] = 0;
3982 	}
3983 	palette->CreateShadedAlphaChannel();
3984 }
3985 
Schedule(ieDword gametime) const3986 bool AreaAnimation::Schedule(ieDword gametime) const
3987 {
3988 	if (!(Flags&A_ANI_ACTIVE) ) {
3989 		return false;
3990 	}
3991 
3992 	//check for schedule
3993 	return GemRB::Schedule(appearance, gametime);
3994 }
3995 
GetHeight() const3996 int AreaAnimation::GetHeight() const
3997 {
3998 	return (Flags&A_ANI_BACKGROUND) ? ANI_PRI_BACKGROUND : height;
3999 }
4000 
DrawingRegion() const4001 Region AreaAnimation::DrawingRegion() const
4002 {
4003 	Region r(Pos, Size());
4004 	int ac = animcount;
4005 	while (ac--) {
4006 		const Animation *anim = animation[ac];
4007 		Region animRgn = anim->animArea;
4008 		animRgn.x += Pos.x;
4009 		animRgn.y += Pos.y;
4010 
4011 		r.ExpandToRegion(animRgn);
4012 	}
4013 	return r;
4014 }
4015 
Draw(const Region & viewport,Color tint,BlitFlags flags) const4016 void AreaAnimation::Draw(const Region &viewport, Color tint, BlitFlags flags) const
4017 {
4018 	Video* video = core->GetVideoDriver();
4019 
4020 	if (transparency) {
4021 		tint.a = 255 - transparency;
4022 		flags |= BlitFlags::ALPHA_MOD;
4023 	} else {
4024 		tint.a = 255;
4025 	}
4026 
4027 	int ac = animcount;
4028 	while (ac--) {
4029 		Animation *anim = animation[ac];
4030 		Holder<Sprite2D> frame = anim->NextFrame();
4031 
4032 		video->BlitGameSpriteWithPalette(frame, palette, Pos - viewport.Origin(), flags, tint);
4033 	}
4034 }
4035 
4036 //change the tileset if needed and possible, return true if changed
4037 //day_or_night = 1 means the normal day lightmap
ChangeMap(bool day_or_night)4038 bool Map::ChangeMap(bool day_or_night)
4039 {
4040 	//no need of change if the area is not extended night
4041 	//if (((AreaType&(AT_DAYNIGHT|AT_EXTENDED_NIGHT))!=(AT_DAYNIGHT|AT_EXTENDED_NIGHT))) return false;
4042 	if (!(AreaType&AT_EXTENDED_NIGHT)) return false;
4043 	//no need of change if the area already has the right tilemap
4044 	if ((DayNight == day_or_night) && GetTileMap()) return false;
4045 
4046 	PluginHolder<MapMgr> mM(IE_ARE_CLASS_ID);
4047 	//no need to open and read the .are file again
4048 	//using the ARE class for this because ChangeMap is similar to LoadMap
4049 	//it loads the lightmap and the minimap too, besides swapping the tileset
4050 	if (!mM->ChangeMap(this, day_or_night) && !day_or_night) {
4051 		Log(WARNING, "Map", "Invalid night lightmap, falling back to day lightmap.");
4052 		mM->ChangeMap(this, 1);
4053 		DayNight = day_or_night;
4054 	}
4055 	return true;
4056 }
4057 
SeeSpellCast(Scriptable * caster,ieDword spell) const4058 void Map::SeeSpellCast(Scriptable *caster, ieDword spell) const
4059 {
4060 	if (caster->Type!=ST_ACTOR) {
4061 		return;
4062 	}
4063 
4064 	// FIXME: this seems clearly wrong, but matches old gemrb behaviour
4065 	unsigned short triggerType = trigger_spellcast;
4066 	if (spell >= 3000)
4067 		triggerType = trigger_spellcastinnate;
4068 	else if (spell < 2000)
4069 		triggerType = trigger_spellcastpriest;
4070 
4071 	caster->AddTrigger(TriggerEntry(triggerType, caster->GetGlobalID(), spell));
4072 
4073 	size_t i = actors.size();
4074 	while (i--) {
4075 		const Actor *witness = actors[i];
4076 		if (CanSee(witness, caster, true, 0)) {
4077 			caster->AddTrigger(TriggerEntry(triggerType, caster->GetGlobalID(), spell));
4078 		}
4079 	}
4080 }
4081 
GetInternalSearchMap(int x,int y) const4082 PathMapFlags Map::GetInternalSearchMap(int x, int y) const
4083 {
4084 	if ((unsigned)x >= Width || (unsigned)y >= Height) {
4085 		return PathMapFlags::UNMARKED;
4086 	}
4087 	return SrchMap[x+y*Width];
4088 }
4089 
SetInternalSearchMap(int x,int y,PathMapFlags value)4090 void Map::SetInternalSearchMap(int x, int y, PathMapFlags value)
4091 {
4092 	if ((unsigned)x >= Width || (unsigned)y >= Height) {
4093 		return;
4094 	}
4095 	SrchMap[x+y*Width] = value;
4096 }
4097 
SetBackground(const ieResRef & bgResRef,ieDword duration)4098 void Map::SetBackground(const ieResRef &bgResRef, ieDword duration)
4099 {
4100 	ResourceHolder<ImageMgr> bmp = GetResourceHolder<ImageMgr>(bgResRef);
4101 
4102 	Background = bmp->GetSprite2D();
4103 	BgDuration = duration;
4104 }
4105 
SetupReverbInfo()4106 void Map::SetupReverbInfo() {
4107 	if (!reverb) {
4108 		reverb = new MapReverb(*this);
4109 	}
4110 }
4111 
4112 }
4113