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