GameScript: Move initialization out of constructor.
[gemrb.git] / gemrb / core / Map.cpp
blob4920d5e734b302a2fc9b3492f4a0bc15a7ef7a7d
1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003-2004 The GemRB Project
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.
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.
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.
21 // This class represents the .ARE (game area) files in the engine
23 #include <cmath>
24 #include <cassert>
26 #include "win32def.h"
27 #include "Map.h"
28 #include "Interface.h"
29 #include "PathFinder.h"
30 #include "Ambient.h"
31 #include "strrefs.h"
32 #include "AmbientMgr.h"
33 #include "TileMap.h"
34 #include "ScriptedAnimation.h"
35 #include "Projectile.h"
36 #include "Video.h"
37 #include "Audio.h"
38 #include "MusicMgr.h"
39 #include "Game.h"
40 #include "WorldMap.h"
41 #include "GameControl.h"
42 #include "Palette.h"
43 #include "MapMgr.h"
44 #include "GSUtils.h"
45 #include "GameData.h"
47 #define YESNO(x) ( (x)?"Yes":"No")
49 // TODO: fix this hardcoded resource reference
50 static ieResRef PortalResRef={"EF03TPR3"};
51 static unsigned int PortalTime = 15;
52 static unsigned int MAX_CIRCLESIZE = 8;
53 static int MaxVisibility = 30;
54 static int VisibilityPerimeter; //calculated from MaxVisibility
55 static int NormalCost = 10;
56 static int AdditionalCost = 4;
57 static unsigned char Passable[16] = {
58 4, 1, 1, 1, 1, 1, 1, 1, 0, 1, 8, 0, 0, 0, 3, 1
60 static Point **VisibilityMasks=NULL;
62 static bool PathFinderInited = false;
63 static Variables Spawns;
64 static int LargeFog;
65 static ieWord globalActorCounter;
67 void ReleaseSpawnGroup(void *poi)
69 delete (SpawnGroup *) poi;
72 void Map::ReleaseMemory()
74 if (VisibilityMasks) {
75 for (int i=0;i<MaxVisibility;i++) {
76 free(VisibilityMasks[i]);
78 free(VisibilityMasks);
79 VisibilityMasks=NULL;
82 Spawns.RemoveAll(ReleaseSpawnGroup);
83 PathFinderInited = false;
86 inline static AnimationObjectType SelectObject(Actor *actor, int q, AreaAnimation *a, ScriptedAnimation *sca, Particles *spark, Projectile *pro)
88 int actorh;
89 if (actor) {
90 actorh = actor->Pos.y;
91 if (q) actorh = 0;
92 } else {
93 actorh = 0x7fffffff;
96 int aah;
97 if (a) {
98 aah = a->Pos.y;//+a->height;
99 } else {
100 aah = 0x7fffffff;
103 int scah;
104 if (sca) {
105 scah = sca->YPos;//+sca->ZPos;
106 } else {
107 scah = 0x7fffffff;
110 int spah;
111 if (spark) {
112 //no idea if this should be plus or minus (or here at all)
113 spah = spark->GetHeight();//+spark->pos.h;
114 } else {
115 spah = 0x7fffffff;
118 int proh;
119 if (pro) {
120 proh = pro->GetHeight();
121 } else {
122 proh = 0x7fffffff;
125 if (proh<actorh && proh<scah && proh<aah && proh<spah) return AOT_PROJECTILE;
127 if (spah<actorh && spah<scah && spah<aah) return AOT_SPARK;
129 if (aah<actorh && aah<scah) return AOT_AREA;
131 if (scah<actorh) return AOT_SCRIPTED;
133 return AOT_ACTOR;
136 //returns true if creature must be embedded in the area
137 //npcs in saved game shouldn't be embedded either
138 inline static bool MustSave(Actor *actor)
140 if (actor->Persistent()) {
141 return false;
144 //check for familiars, summons?
145 return true;
148 //Preload spawn group entries (creature resrefs that reference groups of creatures)
149 void InitSpawnGroups()
151 ieResRef GroupName;
152 int i;
154 AutoTable tab("spawngrp");
156 Spawns.RemoveAll(NULL);
157 Spawns.SetType( GEM_VARIABLES_POINTER );
159 if (!tab)
160 return;
162 i=tab->GetColNamesCount();
163 while (i--) {
164 int j=tab->GetRowCount();
165 while (j--) {
166 const char *crename = tab->QueryField( j,i );
167 if (crename[0] != '*') break;
169 if (j>0) {
170 SpawnGroup *creatures = new SpawnGroup(j);
171 //difficulty
172 creatures->Level = (ieDword) atoi( tab->QueryField(0,i) );
173 for (;j;j--) {
174 strnlwrcpy( creatures->ResRefs[j-1], tab->QueryField(j,i), 8 );
176 strnlwrcpy( GroupName, tab->GetColumnName( i ), 8 );
177 Spawns.SetAt( GroupName, (void*) creatures );
182 //Preload the searchmap configuration
183 void InitPathFinder()
185 PathFinderInited = true;
186 AutoTable tm("pathfind");
187 if (tm) {
188 const char* poi;
190 for (int i = 0; i < 16; i++) {
191 poi = tm->QueryField( 0, i );
192 if (*poi != '*')
193 Passable[i] = atoi( poi );
195 poi = tm->QueryField( 1, 0 );
196 if (*poi != '*')
197 NormalCost = atoi( poi );
198 poi = tm->QueryField( 1, 1 );
199 if (*poi != '*')
200 AdditionalCost = atoi( poi );
204 void AddLOS(int destx, int desty, int slot)
206 for (int i=0;i<MaxVisibility;i++) {
207 int x=(destx*i+MaxVisibility/2)/MaxVisibility*16;
208 int y=(desty*i+MaxVisibility/2)/MaxVisibility*12;
209 if (LargeFog) {
210 x += 16;
211 y += 12;
213 VisibilityMasks[i][slot].x=(short) x;
214 VisibilityMasks[i][slot].y=(short) y;
218 void InitExplore()
220 LargeFog = !core->HasFeature(GF_SMALL_FOG);
222 //circle perimeter size for MaxVisibility
223 int x = MaxVisibility;
224 int y = 0;
225 int xc = 1 - ( 2 * MaxVisibility );
226 int yc = 1;
227 int re = 0;
228 VisibilityPerimeter = 0;
229 while (x>=y) {
230 VisibilityPerimeter+=8;
231 y++;
232 re += yc;
233 yc += 2;
234 if (( ( 2 * re ) + xc ) > 0) {
235 x--;
236 re += xc;
237 xc += 2;
241 int i;
242 VisibilityMasks = (Point **) malloc(MaxVisibility * sizeof(Point *) );
243 for (i=0;i<MaxVisibility;i++) {
244 VisibilityMasks[i] = (Point *) malloc(VisibilityPerimeter*sizeof(Point) );
247 x = MaxVisibility;
248 y = 0;
249 xc = 1 - ( 2 * MaxVisibility );
250 yc = 1;
251 re = 0;
252 VisibilityPerimeter = 0;
253 while (x>=y) {
254 AddLOS (x, y, VisibilityPerimeter++);
255 AddLOS (-x, y, VisibilityPerimeter++);
256 AddLOS (-x, -y, VisibilityPerimeter++);
257 AddLOS (x, -y, VisibilityPerimeter++);
258 AddLOS (y, x, VisibilityPerimeter++);
259 AddLOS (-y, x, VisibilityPerimeter++);
260 AddLOS (-y, -x, VisibilityPerimeter++);
261 AddLOS (y, -x, VisibilityPerimeter++);
262 y++;
263 re += yc;
264 yc += 2;
265 if (( ( 2 * re ) + xc ) > 0) {
266 x--;
267 re += xc;
268 xc += 2;
273 Map::Map(void)
274 : Scriptable( ST_AREA )
276 area=this;
277 TMap = NULL;
278 LightMap = NULL;
279 SearchMap = NULL;
280 HeightMap = NULL;
281 SmallMap = NULL;
282 MapSet = NULL;
283 Walls = NULL;
284 WallCount = 0;
285 queue[PR_SCRIPT] = NULL;
286 queue[PR_DISPLAY] = NULL;
287 INISpawn = NULL;
288 //no one needs this queue
289 //queue[PR_IGNORE] = NULL;
290 Qcount[PR_SCRIPT] = 0;
291 Qcount[PR_DISPLAY] = 0;
292 //no one needs this queue
293 //Qcount[PR_IGNORE] = 0;
294 lastActorCount[PR_SCRIPT] = 0;
295 lastActorCount[PR_DISPLAY] = 0;
296 //no one needs this
297 //lastActorCount[PR_IGNORE] = 0;
298 if (!PathFinderInited) {
299 InitPathFinder();
300 InitSpawnGroups();
301 InitExplore();
302 globalActorCounter = 0;
304 ExploredBitmap = NULL;
305 VisibleBitmap = NULL;
306 version = 0;
307 localActorCounter = 0;
308 MasterArea = core->GetGame()->MasterArea(scriptName);
311 Map::~Map(void)
313 unsigned int i;
315 free( MapSet );
316 delete TMap;
317 delete INISpawn;
318 aniIterator aniidx;
319 for (aniidx = animations.begin(); aniidx != animations.end(); aniidx++) {
320 delete (*aniidx);
323 for (i = 0; i < actors.size(); i++) {
324 Actor* a = actors[i];
325 //don't delete NPC/PC
326 if (a && !a->Persistent() ) {
327 delete a;
331 for (i = 0; i < entrances.size(); i++) {
332 delete entrances[i];
334 for (i = 0; i < spawns.size(); i++) {
335 delete spawns[i];
337 delete LightMap;
338 delete SearchMap;
339 delete HeightMap;
340 core->GetVideoDriver()->FreeSprite( SmallMap );
341 for (i = 0; i < QUEUE_COUNT; i++) {
342 free(queue[i]);
343 queue[i] = NULL;
346 proIterator pri;
348 for (pri = projectiles.begin(); pri != projectiles.end(); pri++) {
349 delete (*pri);
352 scaIterator sci;
354 for (sci = vvcCells.begin(); sci != vvcCells.end(); sci++) {
355 delete (*sci);
358 spaIterator spi;
360 for (spi = particles.begin(); spi != particles.end(); spi++) {
361 delete (*spi);
364 for (i = 0; i < ambients.size(); i++) {
365 delete ambients[i];
367 for (i = 0; i < mapnotes.size(); i++) {
368 delete mapnotes[i];
371 //malloc-d in AREImp
372 free( ExploredBitmap );
373 free( VisibleBitmap );
374 if (Walls) {
375 for(i=0;i<WallCount;i++) {
376 delete Walls[i];
378 free( Walls );
380 WallCount=0;
383 void Map::ChangeTileMap(Image* lm, Sprite2D* sm)
385 delete LightMap;
386 core->GetVideoDriver()->FreeSprite(SmallMap);
388 LightMap = lm;
389 SmallMap = sm;
391 TMap->UpdateDoors();
394 void Map::AddTileMap(TileMap* tm, Image* lm, Bitmap* sr, Sprite2D* sm, Bitmap* hm)
396 // CHECKME: leaks? Should the old TMap, LightMap, etc... be freed?
397 TMap = tm;
398 LightMap = lm;
399 SearchMap = sr;
400 HeightMap = hm;
401 SmallMap = sm;
402 Width = (unsigned int) (TMap->XCellCount * 4);
403 Height = (unsigned int) (( TMap->YCellCount * 64 ) / 12);
404 //Filling Matrices
405 MapSet = (unsigned short *) malloc(sizeof(unsigned short) * Width * Height);
406 //converting searchmap to internal format
407 int y=SearchMap->GetHeight();
408 while(y--) {
409 int x=SearchMap->GetWidth();
410 while(x--) {
411 SearchMap->SetAt(x,y,Passable[SearchMap->GetAt(x,y)&PATH_MAP_AREAMASK]);
416 void Map::MoveToNewArea(const char *area, const char *entrance, unsigned int direction, int EveryOne, Actor *actor)
418 char command[256];
420 //change loader MOS image here
421 //check worldmap entry, if that doesn't contain anything,
422 //make a random pick
424 if (EveryOne==CT_WHOLE) {
425 core->GetGameControl()->AutoSave();
427 Game* game = core->GetGame();
428 Map* map = game->GetMap(area, false);
429 if (!map) {
430 printMessage("Map", " ", LIGHT_RED);
431 printf("Invalid map: %s\n",area);
432 command[0]=0;
433 return;
435 Entrance* ent = map->GetEntrance( entrance );
436 int X,Y, face;
437 if (!ent) {
438 // no entrance found, try using direction flags
440 face = -1; // should this be handled per-case?
442 // ok, so the original engine tries these in a different order
443 // (north first, then south) but it doesn't seem to matter
444 if (direction & 0x1) { // north
445 X = map->TMap->XCellCount * 32;
446 Y = 0;
447 } else if (direction & 0x2) { // east
448 X = map->TMap->XCellCount * 64;
449 Y = map->TMap->YCellCount * 32;
450 } else if (direction & 0x4) { // south
451 X = map->TMap->XCellCount * 32;
452 Y = map->TMap->YCellCount * 64;
453 } else if (direction & 0x8) { // west
454 X = 0;
455 Y = map->TMap->YCellCount * 32;
456 } else {
457 // crashes in original engine
458 printMessage("Map", " ", YELLOW);
459 printf( "WARNING!!! EntryPoint '%s' does not exist and direction %d is invalid\n", entrance, direction );
460 X = map->TMap->XCellCount * 64;
461 Y = map->TMap->YCellCount * 64;
463 } else {
464 X = ent->Pos.x;
465 Y = ent->Pos.y;
466 face = ent->Face;
468 //LeaveArea is the same in ALL engine versions
469 sprintf(command, "LeaveArea(\"%s\",[%d.%d],%d)", area, X, Y, face);
471 if (EveryOne&CT_GO_CLOSER) {
472 int i=game->GetPartySize(false);
473 while (i--) {
474 Actor *pc = game->GetPC(i,false);
475 if (pc->GetCurrentArea()==this) {
476 pc->ClearPath();
477 pc->ClearActions();
478 pc->AddAction( GenerateAction( command ) );
479 pc->ProcessActions(true);
482 return;
484 if (EveryOne&CT_SELECTED) {
485 int i=game->GetPartySize(false);
486 while (i--) {
487 Actor *pc = game->GetPC(i,false);
489 if (!pc->IsSelected()) {
490 continue;
492 if (pc->GetCurrentArea()==this) {
493 pc->ClearPath();
494 pc->ClearActions();
495 pc->AddAction( GenerateAction( command ) );
496 pc->ProcessActions(true);
499 return;
502 actor->ClearPath();
503 actor->ClearActions();
504 actor->AddAction( GenerateAction( command ) );
505 actor->ProcessActions(true);
508 void Map::UseExit(Actor *actor, InfoPoint *ip)
510 Game *game=core->GetGame();
512 int EveryOne = ip->CheckTravel(actor);
513 switch(EveryOne) {
514 case CT_GO_CLOSER:
515 core->DisplayConstantString(STR_WHOLEPARTY,0xffffff); //white
516 if (game->EveryoneStopped()) {
517 ip->Flags&=~TRAP_RESET; //exit triggered
519 return;
520 //no ingame message for these events
521 case CT_CANTMOVE: case CT_SELECTED:
522 return;
523 case CT_ACTIVE: case CT_WHOLE: case CT_MOVE_SELECTED:
524 break;
527 actor->UseExit(false);
528 if (ip->Destination[0] != 0) {
529 // 0 here is direction, can infopoints specify that or is an entrance always provided?
530 MoveToNewArea(ip->Destination, ip->EntranceName, 0, EveryOne, actor);
531 return;
533 if (ip->Scripts[0]) {
534 ip->LastTriggerObject = ip->LastTrigger = ip->LastEntered = actor->GetID();
535 ip->ExecuteScript( 1 );
536 ip->ProcessActions(true);
540 //Draw two overlapped animations to achieve the original effect
541 //PlayOnce makes sure that if we stop drawing them, they will go away
542 void Map::DrawPortal(InfoPoint *ip, int enable)
544 ieDword gotportal = HasVVCCell(PortalResRef, ip->Pos);
546 if (enable) {
547 if (gotportal>PortalTime) return;
548 ScriptedAnimation *sca = gamedata->GetScriptedAnimation(PortalResRef, false);
549 if (sca) {
550 sca->SetBlend();
551 sca->PlayOnce();
552 sca->XPos=ip->Pos.x;
553 sca->YPos=ip->Pos.y;
554 sca->ZPos=gotportal;
555 AddVVCell(sca);
557 return;
561 void Map::UpdateScripts()
563 bool has_pcs = false;
564 size_t i=actors.size();
565 while (i--) {
566 if (actors[i]->InParty) {
567 has_pcs = true;
568 break;
572 // if masterarea, then we allow 'any' actors
573 // if not masterarea, we allow only players
574 // if (!GetActorCount(MasterArea) ) {
575 // fuzzie changed this because the previous code was wrong
576 // (GetActorCount(false) returns only non-PCs) - it is not
577 // well-tested so feel free to change if there are problems
578 // (for example, the CanFree seems like it would be needed to
579 // check for any running scripts, such as following, but it seems
580 // to work ok anyway in my testing - if you change it you probably
581 // also want to change the actor updating code below so it doesn't
582 // add new actions while we are trying to get rid of the area!)
583 if (!has_pcs && !(MasterArea && actors.size()) /*&& !CanFree()*/) {
584 return;
587 // fuzzie added this check because some area scripts (eg, AR1600 when
588 // escaping Brynnlaw) were executing after they were meant to be done,
589 // and this seems the nicest way of handling that for now - it's quite
590 // possibly wrong (so if you have problems, revert this and find
591 // another way)
592 if (has_pcs) {
593 //Run the Map Script
594 ExecuteScript( 1 );
597 //Execute Pending Actions
598 //if it is only here, then the drawing will fail
599 ProcessActions(false);
601 // If scripts frozen, return.
602 // This fixes starting a new IWD game. The above ProcessActions pauses the
603 // game for a textscreen, but one of the actor->ProcessActions calls
604 // below starts a cutscene, hiding the mouse. - wjp, 20060805
605 if (core->GetGameControl()->GetDialogueFlags() & DF_FREEZE_SCRIPTS) return;
607 //Run actor scripts (only for 0 priority)
608 int q=Qcount[PR_SCRIPT];
610 Game *game = core->GetGame();
611 Actor *timestop_owner = game->timestop_owner;
612 bool timestop = game->timestop_end>game->GameTime;
614 // this is silly, the speed should be pre-calculated somewhere
615 //int *actor_speeds = (int *)calloc(Qcount[PR_SCRIPT], sizeof(int));
617 //bool *no_more_steps_for_actor = (bool *)calloc(Qcount[PR_SCRIPT], sizeof(bool));
619 while (q--) {
620 Actor* actor = queue[PR_SCRIPT][q];
621 //actor just moved away, don't run its script from this side
622 if (actor->GetCurrentArea()!=this) {
623 actor->no_more_steps = true;
624 continue;
626 if (timestop && actor!=timestop_owner && actor->Modified[IE_DISABLETIMESTOP] ) {
627 actor->no_more_steps = true;
628 continue;
631 //Avenger moved this here from ApplyAllEffects (this one modifies the effect queue)
632 //.. but then fuzzie moved this here from UpdateActorState, because otherwise
633 //immobile actors (see check below) never become mobile again!
634 actor->fxqueue.Cleanup();
636 //if the actor is immobile, don't run the scripts
637 if (!game->StateOverrideFlag && !game->StateOverrideTime) {
638 if (actor->Immobile()) {
639 actor->no_more_steps = true;
640 continue;
643 actor->no_more_steps = false;
646 * we run scripts all at once because one of the actions in ProcessActions
647 * might remove us from a cutscene and then bad things can happen when
648 * scripts are queued unexpectedly (such as an ogre in a cutscene -> dialog
649 * -> cutscene transition in the first bg1 cutscene exploiting the race
650 * condition to murder player1) - it is entirely possible that we should be
651 * doing this differently (for example by storing the cutscene state at the
652 * start of this function, or by changing the cutscene state at a later
653 * point, etc), but i did it this way for now because it seems least painful
654 * and we should probably be staggering the script executions anyway
656 actor->ExecuteScript( MAX_SCRIPTS );
660 q=Qcount[PR_SCRIPT];
661 while (q--) {
662 Actor* actor = queue[PR_SCRIPT][q];
663 if (actor->no_more_steps) continue;
665 actor->ProcessActions(false);
667 actor->UpdateActorState(game->GameTime);
669 actor->inventory.CalculateWeight();
670 actor->SetBase( IE_ENCUMBRANCE, actor->inventory.GetWeight() );
672 //TODO:calculate actor speed!
673 int speed = (int) actor->GetStat(IE_MOVEMENTRATE);
674 if (speed) {
675 speed = 1500/speed;
677 if (core->GetResDataINI()) {
678 ieDword animid = actor->BaseStats[IE_ANIMATION_ID];
679 if (core->HasFeature(GF_ONE_BYTE_ANIMID)) {
680 animid = animid & 0xff;
682 if (animid < (ieDword)CharAnimations::GetAvatarsCount()) {
683 AvatarStruct *avatar = CharAnimations::GetAvatarStruct(animid);
684 if (avatar->RunScale && (actor->GetInternalFlag() & IF_RUNNING)) {
685 speed = avatar->RunScale;
686 } else if (avatar->WalkScale) {
687 speed = avatar->WalkScale;
688 } else {
689 //printf("no walkscale for anim %d!\n", actor->BaseStats[IE_ANIMATION_ID]);
693 actor->speed = speed;
696 // We need to step through the list of actors until all of them are done
697 // taking steps.
698 bool more_steps = true;
699 ieDword time = game->Ticks; // make sure everything moves at the same time
700 while (more_steps) {
701 more_steps = false;
703 q=Qcount[PR_SCRIPT];
704 while (q--) {
705 Actor* actor = queue[PR_SCRIPT][q];
706 if (actor->no_more_steps) continue;
708 // try to exclude actors which only just died
709 // (shouldn't we not be stepping actors which don't have a path anyway?)
710 // following fails on Immobile creatures, don't think it's a problem, but replace with next line if it is
711 if (!actor->ValidTarget(GA_NO_DEAD)) continue;
712 //if (actor->GetStat(IE_STATE_ID)&STATE_DEAD || actor->GetInternalFlag() & IF_JUSTDIED) continue;
714 actor->no_more_steps = DoStepForActor(actor, actor->speed, time);
715 if (!actor->no_more_steps) more_steps = true;
719 //Check if we need to start some door scripts
720 int doorCount = 0;
721 while (true) {
722 Door* door = TMap->GetDoor( doorCount++ );
723 if (!door)
724 break;
725 if (door->Scripts[0])
726 door->ExecuteScript( 1 );
727 //Execute Pending Actions
728 door->ProcessActions(false);
731 //Check if we need to start some container scripts
732 int containerCount = 0;
733 while (true) {
734 Container* container = TMap->GetContainer( containerCount++ );
735 if (!container)
736 break;
737 if (container->Scripts[0])
738 container->ExecuteScript( 1 );
739 //Execute Pending Actions
740 container->ProcessActions(false);
743 //Check if we need to start some trap scripts
744 int ipCount = 0;
745 while (true) {
746 //For each InfoPoint in the map
747 InfoPoint* ip = TMap->GetInfoPoint( ipCount++ );
748 if (!ip)
749 break;
750 //If this InfoPoint has no script and it is not a Travel Trigger, skip it
751 bool wasActive = (ip->Scripts[0] || ( ip->Type == ST_TRAVEL ));
752 // InfoPoints of all types don't run scripts if TRAP_DEACTIVATED is set
753 // (eg, TriggerActivation changes this, see lightning room from SoA)
754 if (wasActive)
755 wasActive = !(ip->Flags&TRAP_DEACTIVATED);
757 //If this InfoPoint is a Switch Trigger
758 if (ip->Type == ST_TRIGGER) {
759 //Check if this InfoPoint was activated
760 if (ip->LastTrigger) {
761 if (wasActive) {
762 //Run the InfoPoint script
763 ip->ExecuteScript( 1 );
766 //Execute Pending Actions
767 ip->ProcessActions(false);
768 continue;
771 if (ip->IsPortal()) {
772 DrawPortal(ip, ip->Trapped&PORTAL_TRAVEL);
775 if (wasActive) {
776 q=Qcount[PR_SCRIPT];
777 while (q--) {
778 Actor* actor = queue[PR_SCRIPT][q];
779 if (ip->Type == ST_PROXIMITY) {
780 if(ip->Entered(actor)) {
781 //if trap triggered, then mark actor
782 actor->SetInTrap(ipCount);
784 } else {
785 //ST_TRAVEL
786 //don't move if doing something else
787 // added CurrentAction as part of blocking action fixes
788 if (actor->CannotPassEntrance() ) {
789 continue;
791 //this is needed, otherwise the travel
792 //trigger would be activated anytime
793 //Well, i don't know why is it here, but lets try this
794 if (ip->Entered(actor)) {
795 UseExit(actor, ip);
801 if (wasActive) {
802 ip->ExecuteScript( 1 );
804 //Execute Pending Actions
805 ip->ProcessActions(false);
809 bool Map::DoStepForActor(Actor *actor, int speed, ieDword time) {
810 bool no_more_steps = true;
812 if (actor->BlocksSearchMap()) {
813 ClearSearchMapFor(actor);
815 PathNode * step = actor->GetNextStep();
816 if (step && step->Next) {
817 //we should actually wait for a short time and check then
818 if (GetBlocked(step->Next->x*16+8,step->Next->y*12+6,actor->size)) {
819 actor->NewPath();
823 if (!(actor->GetBase(IE_STATE_ID)&STATE_CANTMOVE) ) {
824 if (!actor->Immobile()) {
825 no_more_steps = actor->DoStep( speed, time );
826 if (actor->BlocksSearchMap()) {
827 BlockSearchMap( actor->Pos, actor->size, actor->InParty?PATH_MAP_PC:PATH_MAP_NPC);
832 return no_more_steps;
835 void Map::ClearSearchMapFor( Movable *actor ) {
836 Actor** nearActors = GetAllActorsInRadius(actor->Pos, GA_NO_DEAD, MAX_CIRCLE_SIZE*2*16);
837 BlockSearchMap( actor->Pos, actor->size, PATH_MAP_FREE);
839 // Restore the searchmap areas of any nearby actors that could
840 // have been cleared by this BlockSearchMap(..., 0).
841 // (Necessary since blocked areas of actors may overlap.)
842 int i=0;
843 while(nearActors[i]!=NULL) {
844 if(nearActors[i]!=actor && nearActors[i]->BlocksSearchMap())
845 BlockSearchMap( nearActors[i]->Pos, nearActors[i]->size, nearActors[i]->InParty?PATH_MAP_PC:PATH_MAP_NPC);
846 ++i;
848 free(nearActors);
851 void Map::DrawHighlightables( Region screen )
853 Region vp = core->GetVideoDriver()->GetViewport();
854 unsigned int i = 0;
855 Container *c;
857 while ( (c = TMap->GetContainer(i++))!=NULL ) {
858 Color tint = LightMap->GetPixel( c->Pos.x / 16, c->Pos.y / 12);
859 tint.a = 255;
861 if (c->Highlight) {
862 if (c->Type==IE_CONTAINER_PILE) {
863 Color tint = LightMap->GetPixel( c->Pos.x / 16, c->Pos.y / 12);
864 tint.a = 255;
865 c->DrawPile(true, screen, tint);
866 } else {
867 c->DrawOutline();
869 } else if (c->Type==IE_CONTAINER_PILE) {
870 if (c->outline->BBox.InsideRegion( vp )) {
871 c->DrawPile(false, screen, tint);
876 Door *d;
877 i = 0;
878 while ( (d = TMap->GetDoor(i++))!=NULL ) {
879 if (d->Highlight) d->DrawOutline();
882 InfoPoint *p;
883 i = 0;
884 while ( (p = TMap->GetInfoPoint(i++))!=NULL ) {
885 if (p->Highlight) p->DrawOutline();
889 Actor *Map::GetNextActor(int &q, int &index)
891 retry:
892 switch(q) {
893 case PR_SCRIPT:
894 if (index--)
895 return queue[q][index];
896 q--;
897 return NULL;
898 case PR_DISPLAY:
899 if (index--)
900 return queue[q][index];
901 q--;
902 index = Qcount[q];
903 goto retry;
904 default:
905 return NULL;
909 AreaAnimation *Map::GetNextAreaAnimation(aniIterator &iter, ieDword gametime)
911 retry:
912 if (iter==animations.end()) {
913 return NULL;
915 AreaAnimation *a = *(iter++);
916 if (!a->Schedule(gametime) ) {
917 goto retry;
919 if (!IsVisible( a->Pos, !(a->Flags & A_ANI_NOT_IN_FOG)) ) {
920 goto retry;
922 return a;
925 Particles *Map::GetNextSpark(spaIterator &iter)
927 if (iter==particles.end()) {
928 return NULL;
930 return *iter;
933 //doesn't increase iterator, because we might need to erase it from the list
934 Projectile *Map::GetNextProjectile(proIterator &iter)
936 if (iter==projectiles.end()) {
937 return NULL;
939 return *iter;
942 Projectile *Map::GetNextTrap(proIterator &iter)
944 Projectile *pro;
946 do {
947 pro=GetNextProjectile(iter);
948 iter++;
949 //logic to determine dormant traps
950 //if (pro && pro->IsTrap()) break;
951 } while(pro);
952 return pro;
955 size_t Map::GetProjectileCount(proIterator &iter)
957 iter = projectiles.begin();
958 return projectiles.size();
961 ieDword Map::GetTrapCount(proIterator &iter)
963 ieDword cnt=0;
964 iter=projectiles.begin();
965 while(GetNextTrap(iter)) {
966 cnt++;
969 iter = projectiles.begin();
970 return cnt;
974 //doesn't increase iterator, because we might need to erase it from the list
975 ScriptedAnimation *Map::GetNextScriptedAnimation(scaIterator &iter)
977 if (iter==vvcCells.end()) {
978 return NULL;
980 return *iter;
983 static ieDword oldgametime = 0;
985 //Draw the game area (including overlays, actors, animations, weather)
986 void Map::DrawMap(Region screen)
988 if (!TMap) {
989 return;
991 Game *game = core->GetGame();
992 ieDword gametime = game->GameTime;
994 //area specific spawn.ini files (a PST feature)
995 if (INISpawn) {
996 INISpawn->CheckSpawn();
999 int rain;
1000 if (HasWeather()) {
1001 //zero when the weather particles are all gone
1002 rain = game->weather->GetPhase()-P_EMPTY;
1003 } else {
1004 rain = 0;
1006 TMap->DrawOverlays( screen, rain );
1008 //Blit the Background Map Animations (before actors)
1009 Video* video = core->GetVideoDriver();
1011 //Draw Outlines
1012 DrawHighlightables( screen );
1014 Region vp = video->GetViewport();
1015 //if it is only here, then the scripting will fail?
1016 GenerateQueues();
1017 SortQueues();
1018 //drawing queues 1 and 0
1019 //starting with lower priority
1020 //so displayed, but inactive actors (dead) will be drawn over
1021 int q = PR_DISPLAY;
1022 int index = Qcount[q];
1023 Actor* actor = GetNextActor(q, index);
1024 aniIterator aniidx = animations.begin();
1025 scaIterator scaidx = vvcCells.begin();
1026 proIterator proidx = projectiles.begin();
1027 spaIterator spaidx = particles.begin();
1029 AreaAnimation *a = GetNextAreaAnimation(aniidx, gametime);
1030 ScriptedAnimation *sca = GetNextScriptedAnimation(scaidx);
1031 Projectile *pro = GetNextProjectile(proidx);
1032 Particles *spark = GetNextSpark(spaidx);
1034 while (actor || a || sca || spark || pro) {
1035 switch(SelectObject(actor,q,a,sca,spark,pro)) {
1036 case AOT_ACTOR:
1037 actor->Draw( screen );
1038 actor = GetNextActor(q, index);
1039 break;
1040 case AOT_AREA:
1041 //draw animation
1042 a->Draw( screen, this );
1043 a = GetNextAreaAnimation(aniidx,gametime);
1044 break;
1045 case AOT_SCRIPTED:
1047 Point Pos(0,0);
1049 Color tint = LightMap->GetPixel( sca->XPos / 16, sca->YPos / 12);
1050 tint.a = 255;
1051 bool endReached = sca->Draw(screen, Pos, tint, this, 0, -1);
1052 if (endReached) {
1053 delete( sca );
1054 scaidx=vvcCells.erase(scaidx);
1055 } else {
1056 scaidx++;
1059 sca = GetNextScriptedAnimation(scaidx);
1060 break;
1061 case AOT_PROJECTILE:
1063 int drawn;
1064 if (gametime>oldgametime) {
1065 drawn = pro->Update();
1066 } else {
1067 drawn = 1;
1069 if (drawn) {
1070 pro->Draw( screen );
1071 proidx++;
1072 } else {
1073 delete( pro );
1074 proidx = projectiles.erase(proidx);
1077 pro = GetNextProjectile(proidx);
1078 break;
1079 case AOT_SPARK:
1081 int drawn;
1082 if (gametime>oldgametime) {
1083 drawn = spark->Update();
1084 } else {
1085 drawn = 1;
1087 if (drawn) {
1088 spark->Draw( screen );
1089 spaidx++;
1090 } else {
1091 delete( spark );
1092 spaidx=particles.erase(spaidx);
1095 spark = GetNextSpark(spaidx);
1096 break;
1097 default:
1098 abort();
1102 if ((core->FogOfWar&FOG_DRAWSEARCHMAP) && SearchMap) {
1103 DrawSearchMap(screen);
1104 } else {
1105 if ((core->FogOfWar&FOG_DRAWFOG) && TMap) {
1106 TMap->DrawFogOfWar( ExploredBitmap, VisibleBitmap, screen );
1110 int ipCount = 0;
1111 while (true) {
1112 //For each InfoPoint in the map
1113 InfoPoint* ip = TMap->GetInfoPoint( ipCount++ );
1114 if (!ip)
1115 break;
1116 ip->DrawOverheadText(screen);
1119 int cnCount = 0;
1120 while (true) {
1121 //For each Container in the map
1122 Container* cn = TMap->GetContainer( cnCount++ );
1123 if (!cn)
1124 break;
1125 cn->DrawOverheadText(screen);
1128 int drCount = 0;
1129 while (true) {
1130 //For each Door in the map
1131 Door* dr = TMap->GetDoor( drCount++ );
1132 if (!dr)
1133 break;
1134 dr->DrawOverheadText(screen);
1137 size_t i = actors.size();
1138 while (i--) {
1139 //For each Actor present
1140 //This must go AFTER the fog!
1141 //(maybe we should be using the queue?)
1142 Actor* actor = actors[i];
1143 actor->DrawOverheadText(screen);
1146 oldgametime=gametime;
1149 void Map::DrawSearchMap(Region &screen)
1151 Color inaccessible = { 128, 128, 128, 128 };
1152 Video *vid=core->GetVideoDriver();
1153 Region rgn=vid->GetViewport();
1154 Region block;
1156 block.w=16;
1157 block.h=12;
1158 int w = screen.w/16+2;
1159 int h = screen.h/12+2;
1161 for(int x=0;x<w;x++) {
1162 for(int y=0;y<h;y++) {
1163 if (!(GetBlocked(x+rgn.x/16, y+rgn.y/12) & PATH_MAP_PASSABLE) ) {
1164 block.x=screen.x+x*16-(rgn.x % 16);
1165 block.y=screen.y+y*12-(rgn.y % 12);
1166 vid->DrawRect(block,inaccessible);
1172 //adding animation in order, based on its height parameter
1173 void Map::AddAnimation(AreaAnimation* anim)
1175 //this hack is to make sure animations flagged with background
1176 //are always drawn first (-9999 seems sufficiently small)
1177 if (anim->Flags&A_ANI_BACKGROUND) {
1178 anim->height=-9999;
1181 aniIterator iter;
1182 for(iter=animations.begin(); (iter!=animations.end()) && ((*iter)->height<anim->height); iter++) ;
1183 animations.insert(iter, anim);
1185 Animation *a = anim->animation[0];
1186 anim->SetSpriteCover(BuildSpriteCover(anim->Pos.x, anim->Pos.y,-a->animArea.x,
1187 -a->animArea.y, a->animArea.w, a->animArea.h,0
1192 //reapplying all of the effects on the actors of this map
1193 //this might be unnecessary later
1194 void Map::UpdateEffects()
1196 size_t i = actors.size();
1197 while (i--) {
1198 actors[i]->RefreshEffects(NULL);
1202 void Map::Shout(Actor* actor, int shoutID, unsigned int radius)
1204 size_t i=actors.size();
1205 while (i--) {
1206 Actor *listener = actors[i];
1208 if (radius) {
1209 if (Distance(actor->Pos, listener->Pos)>radius) {
1210 continue;
1213 if (shoutID) {
1214 listener->LastHeard = actor->GetID();
1215 listener->LastShout = shoutID;
1216 } else {
1217 listener->LastHelp = actor->GetID();
1222 bool Map::AnyEnemyNearPoint(Point &p)
1224 ieDword gametime = core->GetGame()->GameTime;
1225 size_t i = actors.size();
1226 while (i--) {
1227 Actor *actor = actors[i];
1229 if (actor->Schedule(gametime, true) ) {
1230 continue;
1232 if (Distance(actor->Pos, p) > SPAWN_RANGE) {
1233 continue;
1235 if (actor->GetStat(IE_EA)<EA_EVILCUTOFF) {
1236 continue;
1238 return true;
1240 return false;
1243 void Map::ActorSpottedByPlayer(Actor *actor)
1245 unsigned int animid;
1247 if(core->HasFeature(GF_HAS_BEASTS_INI)) {
1248 animid=actor->BaseStats[IE_ANIMATION_ID];
1249 if(core->HasFeature(GF_ONE_BYTE_ANIMID)) {
1250 animid&=0xff;
1252 if (animid < (ieDword)CharAnimations::GetAvatarsCount()) {
1253 AvatarStruct *avatar = CharAnimations::GetAvatarStruct(animid);
1254 core->GetGame()->SetBeastKnown(avatar->Bestiary);
1258 if (!(actor->GetInternalFlag()&IF_STOPATTACK)) {
1259 if (actor->Modified[IE_EA]>=EA_EVILCUTOFF) {
1260 core->Autopause(AP_ENEMY);
1265 void Map::AddActor(Actor* actor)
1267 //setting the current area for the actor as this one
1268 strnlwrcpy(actor->Area, scriptName, 8);
1269 //if actor->globalID was already set, don't change it
1270 actor->SetMap(this, ++localActorCounter,
1271 actor->globalID?actor->globalID:++globalActorCounter);
1272 actors.push_back( actor );
1273 //if a visible aggressive actor was put on the map, it is an autopause reason
1274 //guess game is always loaded? if not, then we'll crash
1275 ieDword gametime = core->GetGame()->GameTime;
1277 if (IsVisible(actor->Pos, false) && actor->Schedule(gametime, true) ) {
1278 ActorSpottedByPlayer(actor);
1280 if (actor->InParty && core->HasFeature(GF_AREA_VISITED_VAR)) {
1281 char key[32];
1282 snprintf(key, sizeof(key),"%s_visited", scriptName);
1283 core->GetGame()->locals->SetAt(key, 1);
1288 bool Map::AnyPCSeesEnemy()
1290 ieDword gametime = core->GetGame()->GameTime;
1291 size_t i = actors.size();
1292 while (i--) {
1293 Actor* actor = actors[i];
1294 if (actor->Modified[IE_EA]>=EA_EVILCUTOFF) {
1295 if (IsVisible(actor->Pos, false) && actor->Schedule(gametime, true) ) {
1296 return true;
1300 return false;
1303 void Map::DeleteActor(int i)
1305 Actor *actor = actors[i];
1307 Game *game = core->GetGame();
1308 game->LeaveParty( actor );
1309 game->DelNPC( game->InStore(actor) );
1311 ClearSearchMapFor(actor);
1313 actors.erase( actors.begin()+i );
1314 delete actor;
1317 Actor* Map::GetActorByGlobalID(ieDword objectID)
1319 if (!objectID) {
1320 return NULL;
1322 //truncation is intentional
1323 ieWord globalID = (ieWord) objectID;
1324 size_t i = actors.size();
1325 while (i--) {
1326 Actor* actor = actors[i];
1328 if (actor->globalID==globalID) {
1329 return actor;
1332 return NULL;
1335 /** flags:
1336 GA_SELECT 16 - unselectable actors don't play
1337 GA_NO_DEAD 32 - dead actors don't play
1338 GA_POINT 64 - not actor specific
1339 GA_NO_HIDDEN 128 - hidden actors don't play
1341 Actor* Map::GetActor(Point &p, int flags)
1343 ieDword gametime = core->GetGame()->GameTime;
1344 size_t i = actors.size();
1345 while (i--) {
1346 Actor* actor = actors[i];
1348 if (!actor->IsOver( p ))
1349 continue;
1350 if (!actor->ValidTarget(flags) ) {
1351 continue;
1353 if (!actor->Schedule(gametime, true) ) {
1354 continue;
1356 return actor;
1358 return NULL;
1361 Actor* Map::GetActorInRadius(Point &p, int flags, unsigned int radius)
1363 ieDword gametime = core->GetGame()->GameTime;
1364 size_t i = actors.size();
1365 while (i--) {
1366 Actor* actor = actors[i];
1368 if (PersonalDistance( p, actor ) > radius)
1369 continue;
1370 if (!actor->ValidTarget(flags) ) {
1371 continue;
1373 if (!actor->Schedule(gametime, true) ) {
1374 continue;
1376 return actor;
1378 return NULL;
1381 Actor **Map::GetAllActorsInRadius(Point &p, int flags, unsigned int radius)
1383 ieDword count = 1;
1384 size_t i;
1386 ieDword gametime = core->GetGame()->GameTime;
1387 i = actors.size();
1388 while (i--) {
1389 Actor* actor = actors[i];
1391 if (PersonalDistance( p, actor ) > radius)
1392 continue;
1393 if (!actor->ValidTarget(flags) ) {
1394 continue;
1396 if (!actor->Schedule(gametime, true) ) {
1397 continue;
1399 count++;
1402 Actor **ret = (Actor **) malloc( sizeof(Actor*) * count);
1403 i = actors.size();
1404 int j = 0;
1405 while (i--) {
1406 Actor* actor = actors[i];
1408 if (PersonalDistance( p, actor ) > radius)
1409 continue;
1410 if (!actor->ValidTarget(flags) ) {
1411 continue;
1413 if (!actor->Schedule(gametime, true) ) {
1414 continue;
1416 ret[j++]=actor;
1419 ret[j]=NULL;
1420 return ret;
1424 Actor* Map::GetActor(const char* Name, int flags)
1426 size_t i = actors.size();
1427 while (i--) {
1428 Actor* actor = actors[i];
1429 if (strnicmp( actor->GetScriptName(), Name, 32 ) == 0) {
1430 if (!actor->ValidTarget(flags) ) {
1431 return NULL;
1433 return actor;
1436 return NULL;
1439 int Map::GetActorCount(bool any) const
1441 if (any) {
1442 return (int) actors.size();
1444 int ret = 0;
1445 size_t i=actors.size();
1446 while (i--) {
1447 if (MustSave(actors[i])) {
1448 ret++;
1451 return ret;
1454 void Map::JumpActors(bool jump)
1456 size_t i = actors.size();
1457 while (i--) {
1458 Actor* actor = actors[i];
1459 if (actor->Modified[IE_DONOTJUMP]&DNJ_JUMP) {
1460 if (jump) {
1461 actor->FixPosition();
1463 actor->SetBase(IE_DONOTJUMP,0);
1468 //before writing the area out, perform some cleanups
1469 void Map::PurgeArea(bool items)
1471 InternalFlags |= IF_JUSTDIED; //area marked for swapping out
1473 //1. remove dead actors without 'keep corpse' flag
1474 int i=(int) actors.size();
1475 while (i--) {
1476 Actor *ac = actors[i];
1478 if (ac->Modified[IE_STATE_ID]&STATE_NOSAVE) {
1479 if (ac->Modified[IE_MC_FLAGS] & MC_KEEP_CORPSE) {
1480 continue;
1482 delete ac;
1483 actors.erase( actors.begin()+i );
1486 //2. remove any non critical items
1487 if (items) {
1488 i=(int) TMap->GetContainerCount();
1489 while (i--) {
1490 Container *c = TMap->GetContainer(i);
1491 unsigned int j=c->inventory.GetSlotCount();
1492 while (j--) {
1493 CREItem *itemslot = c->inventory.GetSlotItem(j);
1494 if (itemslot->Flags&IE_INV_ITEM_CRITICAL) {
1495 continue;
1498 TMap->CleanupContainer(c);
1503 Actor* Map::GetActor(int index, bool any)
1505 if (any) {
1506 return actors[index];
1508 unsigned int i=0;
1509 while (i<actors.size() ) {
1510 Actor *ac = actors[i++];
1511 if (MustSave(ac) ) {
1512 if (!index--) {
1513 return ac;
1517 return NULL;
1520 Actor* Map::GetActorByDialog(const char *resref)
1522 size_t i = actors.size();
1523 while (i--) {
1524 Actor* actor = actors[i];
1525 //if a busy or hostile actor shouldn't be found
1526 //set this to GD_CHECK
1527 if (strnicmp( actor->GetDialog(GD_NORMAL), resref, 8 ) == 0) {
1528 return actor;
1531 return NULL;
1534 //this function finds an actor by its original resref (not correct yet)
1535 Actor* Map::GetActorByResource(const char *resref)
1537 size_t i = actors.size();
1538 while (i--) {
1539 Actor* actor = actors[i];
1540 if (strnicmp( actor->GetScriptName(), resref, 8 ) == 0) { //temporarily!
1541 return actor;
1544 return NULL;
1547 int Map::GetActorInRect(Actor**& actorlist, Region& rgn, bool onlyparty)
1549 actorlist = ( Actor * * ) malloc( actors.size() * sizeof( Actor * ) );
1550 int count = 0;
1551 size_t i = actors.size();
1552 while (i--) {
1553 Actor* actor = actors[i];
1554 //use this function only for party?
1555 if (onlyparty && actor->GetStat(IE_EA)>EA_CHARMED) {
1556 continue;
1558 if (!actor->ValidTarget(GA_SELECT|GA_NO_DEAD) )
1559 continue;
1560 if ((actor->Pos.x<rgn.x) || (actor->Pos.y<rgn.y))
1561 continue;
1562 if ((actor->Pos.x>rgn.x+rgn.w) || (actor->Pos.y>rgn.y+rgn.h) )
1563 continue;
1564 actorlist[count++] = actor;
1566 actorlist = ( Actor * * ) realloc( actorlist, count * sizeof( Actor * ) );
1567 return count;
1570 void Map::PlayAreaSong(int SongType, bool restart)
1572 //Ok, we use a non constant pointer here, so it is easy to disable
1573 //a faulty music list on the fly. I don't want to add a method just for that
1574 //crap when we already have that pointer at hand!
1575 char* poi = core->GetMusicPlaylist( SongHeader.SongList[SongType] );
1576 if (!poi) return;
1577 if (!restart && core->GetMusicMgr()->CurrentPlayList(poi)) return;
1578 int ret = core->GetMusicMgr()->SwitchPlayList( poi, true );
1579 //Here we disable the faulty musiclist entry
1580 if (ret) {
1581 //Apparently, the playlist manager prefers a *
1582 *poi='*';
1586 unsigned char Map::GetBlocked(unsigned int x, unsigned int y)
1588 unsigned char ret = SearchMap->GetAt( x, y );
1589 if (ret&(PATH_MAP_DOOR_TRANSPARENT|PATH_MAP_ACTOR)) {
1590 ret&=~PATH_MAP_PASSABLE;
1592 if (ret&PATH_MAP_DOOR_OPAQUE) {
1593 ret=PATH_MAP_SIDEWALL;
1595 return ret;
1598 bool Map::GetBlocked(unsigned int px, unsigned int py, unsigned int size)
1600 // We check a circle of radius size-2 around (px,py)
1601 // Note that this does not exactly match BG2. BG2's approximations of
1602 // these circles are slightly different for sizes 7 and up.
1604 if (size > MAX_CIRCLESIZE) size = MAX_CIRCLESIZE;
1605 if (size < 2) size = 2;
1607 unsigned int ppx = px/16;
1608 unsigned int ppy = py/12;
1609 unsigned int r=(size-2)*(size-2)+1;
1610 if (size == 2) r = 0;
1611 for (unsigned int i=0; i<size-1; i++) {
1612 for (unsigned int j=0; j<size-1; j++) {
1613 if (i*i+j*j <= r) {
1614 if (!(GetBlocked(ppx+i,ppy+j)&PATH_MAP_PASSABLE)) return true;
1615 if (!(GetBlocked(ppx+i,ppy-j)&PATH_MAP_PASSABLE)) return true;
1616 if (!(GetBlocked(ppx-i,ppy+j)&PATH_MAP_PASSABLE)) return true;
1617 if (!(GetBlocked(ppx-i,ppy-j)&PATH_MAP_PASSABLE)) return true;
1621 return false;
1624 unsigned char Map::GetBlocked(Point &c)
1626 return GetBlocked(c.x/16, c.y/12);
1629 //flags:0 - never dither (full cover)
1630 // 1 - dither if polygon wants it
1631 // 2 - always dither
1633 SpriteCover* Map::BuildSpriteCover(int x, int y, int xpos, int ypos,
1634 unsigned int width, unsigned int height, int flags)
1636 SpriteCover* sc = new SpriteCover;
1637 sc->worldx = x;
1638 sc->worldy = y;
1639 sc->XPos = xpos;
1640 sc->YPos = ypos;
1641 sc->Width = width;
1642 sc->Height = height;
1644 Video* video = core->GetVideoDriver();
1645 video->InitSpriteCover(sc, flags);
1647 unsigned int wpcount = GetWallCount();
1648 unsigned int i;
1650 for (i = 0; i < wpcount; ++i)
1652 Wall_Polygon* wp = GetWallGroup(i);
1653 if (!wp) continue;
1654 if (!wp->PointCovered(x, y)) continue;
1656 video->AddPolygonToSpriteCover(sc, wp);
1659 return sc;
1662 void Map::ActivateWallgroups(unsigned int baseindex, unsigned int count, int flg)
1664 unsigned int i;
1666 if (!Walls) {
1667 return;
1669 for(i=baseindex; i < baseindex+count; ++i) {
1670 Wall_Polygon* wp = GetWallGroup(i);
1671 if (!wp)
1672 continue;
1673 ieDword value=wp->GetPolygonFlag();
1674 if (flg)
1675 value&=~WF_DISABLED;
1676 else
1677 value|=WF_DISABLED;
1678 wp->SetPolygonFlag(value);
1680 //all actors will have to generate a new spritecover
1681 i=(int) actors.size();
1682 while(i--) {
1683 actors[i]->SetSpriteCover(NULL);
1688 //this function determines actor drawing order
1689 //it should be extended to wallgroups, animations, effects!
1690 void Map::GenerateQueues()
1692 int priority;
1694 unsigned int i=(unsigned int) actors.size();
1695 for (priority=0;priority<QUEUE_COUNT;priority++) {
1696 if (lastActorCount[priority] != i) {
1697 if (queue[priority]) {
1698 free(queue[priority]);
1699 queue[priority] = NULL;
1701 queue[priority] = (Actor **) calloc( i, sizeof(Actor *) );
1702 lastActorCount[priority] = i;
1704 Qcount[priority] = 0;
1707 ieDword gametime = core->GetGame()->GameTime;
1708 while (i--) {
1709 Actor* actor = actors[i];
1711 if (actor->CheckOnDeath()) {
1712 DeleteActor( i );
1713 continue;
1716 ieDword stance = actor->GetStance();
1717 ieDword internalFlag = actor->GetInternalFlag();
1719 if (internalFlag&IF_ACTIVE) {
1720 if ((stance == IE_ANI_TWITCH) && (internalFlag&IF_IDLE) ) {
1721 priority = PR_DISPLAY; //display
1722 } else {
1723 priority = PR_SCRIPT; //run scripts and display
1725 } else {
1726 //dead actors are always visible on the map, but run no scripts
1727 if (stance == IE_ANI_TWITCH || stance == IE_ANI_DIE) {
1728 priority = PR_DISPLAY;
1729 } else {
1730 //isvisible flag is false (visibilitymap) here,
1731 //coz we want to reactivate creatures that
1732 //just became visible
1733 if (IsVisible(actor->Pos, false) && actor->Schedule(gametime, false) ) {
1734 priority = PR_SCRIPT; //run scripts and display, activated now
1735 //more like activate!
1736 actor->Activate();
1737 ActorSpottedByPlayer(actor);
1738 } else {
1739 priority = PR_IGNORE;
1744 //we ignore priority 2
1745 if (priority>=PR_IGNORE) continue;
1747 queue[priority][Qcount[priority]] = actor;
1748 Qcount[priority]++;
1752 //the original qsort implementation was flawed
1753 void Map::SortQueues()
1755 for (int q=0;q<QUEUE_COUNT;q++) {
1756 Actor **baseline=queue[q];
1757 int n = Qcount[q];
1758 int i = n/2;
1759 int parent, child;
1760 Actor * tmp;
1762 for (;;) {
1763 if (i>0) {
1764 i--;
1765 tmp = baseline[i];
1766 } else {
1767 n--;
1768 if (n<=0) break; //breaking loop
1769 tmp = baseline[n];
1770 baseline[n] = baseline[0];
1772 parent = i;
1773 child = i*2+1;
1774 while(child<n) {
1775 int chp = child+1;
1776 if (chp<n && baseline[chp]->Pos.y < baseline[child]->Pos.y) {
1777 child=chp;
1779 if (baseline[child]->Pos.y<tmp->Pos.y) {
1780 baseline[parent] = baseline[child];
1781 parent = child;
1782 child = parent*2+1;
1783 } else
1784 break;
1786 baseline[parent]=tmp;
1791 void Map::AddProjectile(Projectile* pro, Point &source, ieWord actorID)
1793 proIterator iter;
1795 pro->MoveTo(this,source);
1796 pro->SetTarget(actorID);
1797 int height = pro->GetHeight();
1798 for(iter=projectiles.begin();iter!=projectiles.end() && (*iter)->GetHeight()<height; iter++) ;
1799 projectiles.insert(iter, pro);
1802 //adding projectile in order, based on its height parameter
1803 void Map::AddProjectile(Projectile* pro, Point &source, Point &dest)
1805 proIterator iter;
1807 pro->MoveTo(this,source);
1808 pro->SetTarget(dest);
1809 int height = pro->GetHeight();
1810 for(iter=projectiles.begin();iter!=projectiles.end() && (*iter)->GetHeight()<height; iter++) ;
1811 projectiles.insert(iter, pro);
1814 //returns the longest duration of the VVC cell named 'resource' (if it exists)
1815 //if P is empty, the position won't be checked
1816 ieDword Map::HasVVCCell(const ieResRef resource, Point &p)
1818 scaIterator iter;
1819 ieDword ret = 0;
1821 for(iter=vvcCells.begin();iter!=vvcCells.end(); iter++) {
1822 if (!p.isempty()) {
1823 if ((*iter)->XPos!=p.x) continue;
1824 if ((*iter)->YPos!=p.y) continue;
1826 if (strnicmp(resource, (*iter)->ResName, sizeof(ieResRef) )) continue;
1827 ieDword tmp = (*iter)->GetSequenceDuration(15)-(*iter)->GetCurrentFrame();
1828 if (tmp>ret) {
1829 ret = tmp;
1832 return ret;
1835 //adding videocell in order, based on its height parameter
1836 void Map::AddVVCell(ScriptedAnimation* vvc)
1838 scaIterator iter;
1840 for(iter=vvcCells.begin();iter!=vvcCells.end() && (*iter)->ZPos<vvc->ZPos; iter++) ;
1841 vvcCells.insert(iter, vvc);
1844 AreaAnimation* Map::GetAnimation(const char* Name)
1846 aniIterator iter;
1848 for(iter=animations.begin();iter!=animations.end();iter++) {
1849 AreaAnimation *anim = *iter;
1851 if (anim->Name && (strnicmp( anim->Name, Name, 32 ) == 0)) {
1852 return anim;
1855 return NULL;
1858 Spawn *Map::AddSpawn(char* Name, int XPos, int YPos, ieResRef *creatures, unsigned int count)
1860 Spawn* sp = new Spawn();
1861 strnspccpy(sp->Name, Name, 32);
1862 if (count>MAX_RESCOUNT) {
1863 count=MAX_RESCOUNT;
1865 sp->Pos.x = (ieWord) XPos;
1866 sp->Pos.y = (ieWord) YPos;
1867 sp->Count = count;
1868 sp->Creatures = (ieResRef *) calloc( count, sizeof(ieResRef) );
1869 for( unsigned int i=0;i<count;i++) {
1870 strnlwrcpy(sp->Creatures[i],creatures[i],8);
1872 spawns.push_back( sp );
1873 return sp;
1876 void Map::AddEntrance(char* Name, int XPos, int YPos, short Face)
1878 Entrance* ent = new Entrance();
1879 strncpy( ent->Name, Name, 32 );
1880 ent->Pos.x = (ieWord) XPos;
1881 ent->Pos.y = (ieWord) YPos;
1882 ent->Face = (ieWord) Face;
1883 entrances.push_back( ent );
1886 Entrance* Map::GetEntrance(const char* Name)
1888 size_t i=entrances.size();
1889 while (i--) {
1890 Entrance *e = entrances[i];
1892 if (strnicmp( e->Name, Name, 32 ) == 0) {
1893 return e;
1896 return NULL;
1899 bool Map::HasActor(Actor *actor)
1901 size_t i=actors.size();
1902 while (i--) {
1903 if (actors[i] == actor) {
1904 return true;
1907 return false;
1910 void Map::RemoveActor(Actor* actor)
1912 size_t i=actors.size();
1913 while (i--) {
1914 if (actors[i] == actor) {
1915 //BlockSearchMap(actor->Pos, actor->size, PATH_MAP_FREE);
1916 ClearSearchMapFor(actor);
1917 actors.erase( actors.begin()+i );
1918 return;
1921 printMessage("Map","RemoveActor: actor not found?",YELLOW);
1924 //returns true if none of the partymembers are on the map
1925 bool Map::CanFree()
1927 size_t i=actors.size();
1928 while (i--) {
1929 if (actors[i]->InParty) {
1930 return false;
1933 if (actors[i]->GetInternalFlag()&(IF_ACTIVE|IF_USEEXIT) ) {
1934 return false;
1937 //we expect the area to be swapped out, so we simply remove the corpses now
1938 PurgeArea(false);
1939 return true;
1942 void Map::DebugDump(bool show_actors)
1944 printf( "DebugDump of Area %s:\n", scriptName );
1945 printf( "OutDoor: %s\n", YESNO(AreaType & AT_OUTDOOR ) );
1946 printf( "Day/Night: %s\n", YESNO(AreaType & AT_DAYNIGHT ) );
1947 printf( "Extended night: %s\n", YESNO(AreaType & AT_EXTENDED_NIGHT ) );
1948 printf( "Weather: %s\n", YESNO(AreaType & AT_WEATHER ) );
1949 printf( "Area Type: %d\n", AreaType & (AT_CITY|AT_FOREST|AT_DUNGEON) );
1950 printf( "Can rest: %s\n", YESNO(AreaType & AT_CAN_REST) );
1952 if (show_actors) {
1953 printf("\n");
1954 size_t i = actors.size();
1955 while (i--) {
1956 if (!(actors[i]->GetInternalFlag()&(IF_JUSTDIED|IF_REALLYDIED))) {
1957 printf("Actor: %s at %d.%d\n", actors[i]->GetName(1), actors[i]->Pos.x, actors[i]->Pos.y);
1963 /******************************************************************************/
1965 void Map::Leveldown(unsigned int px, unsigned int py,
1966 unsigned int& level, Point &n, unsigned int& diff)
1968 int pos;
1969 unsigned int nlevel;
1971 if (( px >= Width ) || ( py >= Height )) {
1972 return;
1973 } //walked off the map
1974 pos = py * Width + px;
1975 nlevel = MapSet[pos];
1976 if (!nlevel) {
1977 return;
1978 } //not even considered
1979 if (level <= nlevel) {
1980 return;
1982 unsigned int ndiff = level - nlevel;
1983 if (ndiff > diff) {
1984 level = nlevel;
1985 diff = ndiff;
1986 n.x = (ieWord) px;
1987 n.y = (ieWord) py;
1991 void Map::SetupNode(unsigned int x, unsigned int y, unsigned int size, unsigned int Cost)
1993 unsigned int pos;
1995 if (( x >= Width ) || ( y >= Height )) {
1996 return;
1998 pos = y * Width + x;
1999 if (MapSet[pos]) {
2000 return;
2002 if (GetBlocked(x*16+8,y*12+6,size)) {
2003 MapSet[pos] = 65535;
2004 return;
2006 MapSet[pos] = (ieWord) Cost;
2007 InternalStack.push( ( x << 16 ) | y );
2010 bool Map::AdjustPositionX(Point &goal, unsigned int radius)
2012 unsigned int minx = 0;
2013 if ((unsigned int) goal.x > radius)
2014 minx = goal.x - radius;
2015 unsigned int maxx = goal.x + radius + 1;
2016 if (maxx > Width)
2017 maxx = Width;
2019 for (unsigned int scanx = minx; scanx < maxx; scanx++) {
2020 if ((unsigned int) goal.y >= radius) {
2021 if (GetBlocked( scanx, goal.y - radius ) & PATH_MAP_PASSABLE) {
2022 goal.x = (ieWord) scanx;
2023 goal.y = (ieWord) (goal.y - radius);
2024 return true;
2027 if (goal.y + radius < Height) {
2028 if (GetBlocked( scanx, goal.y + radius ) & PATH_MAP_PASSABLE) {
2029 goal.x = (ieWord) scanx;
2030 goal.y = (ieWord) (goal.y + radius);
2031 return true;
2035 return false;
2038 bool Map::AdjustPositionY(Point &goal, unsigned int radius)
2040 unsigned int miny = 0;
2041 if ((unsigned int) goal.y > radius)
2042 miny = goal.y - radius;
2043 unsigned int maxy = goal.y + radius + 1;
2044 if (maxy > Height)
2045 maxy = Height;
2046 for (unsigned int scany = miny; scany < maxy; scany++) {
2047 if ((unsigned int) goal.x >= radius) {
2048 if (GetBlocked( goal.x - radius, scany ) & PATH_MAP_PASSABLE) {
2049 goal.x = (ieWord) (goal.x - radius);
2050 goal.y = (ieWord) scany;
2051 return true;
2054 if (goal.x + radius < Width) {
2055 if (GetBlocked( goal.x + radius, scany ) & PATH_MAP_PASSABLE) {
2056 goal.x = (ieWord) (goal.x + radius);
2057 goal.y = (ieWord) scany;
2058 return true;
2062 return false;
2065 void Map::AdjustPosition(Point &goal, unsigned int radius)
2067 unsigned int maxr = Width;
2068 if (maxr < Height) {
2069 maxr = Height;
2071 if ((unsigned int) goal.x > Width) {
2072 goal.x = (ieWord) Width;
2074 if ((unsigned int) goal.y > Height) {
2075 goal.y = (ieWord) Height;
2078 for (; radius < maxr; radius++) {
2079 //lets make it slightly random where the actor will appear
2080 if (rand()&1) {
2081 if (AdjustPositionX(goal, radius)) {
2082 return;
2084 if (AdjustPositionY(goal, radius)) {
2085 return;
2087 } else {
2088 if (AdjustPositionY(goal, radius)) {
2089 return;
2091 if (AdjustPositionX(goal, radius)) {
2092 return;
2098 //run away from dX, dY (ie.: find the best path of limited length that brings us the farthest from dX, dY)
2099 PathNode* Map::RunAway(Point &s, Point &d, unsigned int size, unsigned int PathLen, int flags)
2101 Point start(s.x/16, s.y/12);
2102 Point goal (d.x/16, d.y/12);
2103 unsigned int dist;
2105 //MapSet entries are made of 16 bits
2106 if (PathLen>65535) {
2107 PathLen = 65535;
2110 memset( MapSet, 0, Width * Height * sizeof( unsigned short ) );
2111 while (InternalStack.size())
2112 InternalStack.pop();
2114 if (!( GetBlocked( start.x, start.y) & PATH_MAP_PASSABLE )) {
2115 AdjustPosition( start );
2117 unsigned int pos = ( start.x << 16 ) | start.y;
2118 InternalStack.push( pos );
2119 MapSet[start.y * Width + start.x] = 1;
2120 dist = 0;
2121 Point best = start;
2122 while (InternalStack.size()) {
2123 pos = InternalStack.front();
2124 InternalStack.pop();
2125 unsigned int x = pos >> 16;
2126 unsigned int y = pos & 0xffff;
2127 long tx = ( x - goal.x );
2128 long ty = ( y - goal.y );
2129 unsigned int distance = (unsigned int) sqrt( ( double ) ( tx* tx + ty* ty ) );
2130 if (dist<distance) {
2131 best.x=(ieWord) x;
2132 best.y=(ieWord) y;
2133 dist=distance;
2136 unsigned int Cost = MapSet[y * Width + x] + NormalCost;
2137 if (Cost > PathLen) {
2138 //printf("Path not found!\n");
2139 break;
2141 SetupNode( x - 1, y - 1, size, Cost );
2142 SetupNode( x + 1, y - 1, size, Cost );
2143 SetupNode( x + 1, y + 1, size, Cost );
2144 SetupNode( x - 1, y + 1, size, Cost );
2146 Cost += AdditionalCost;
2147 SetupNode( x, y - 1, size, Cost );
2148 SetupNode( x + 1, y, size, Cost );
2149 SetupNode( x, y + 1, size, Cost );
2150 SetupNode( x - 1, y, size, Cost );
2153 //find path backwards from best to start
2154 PathNode* StartNode = new PathNode;
2155 PathNode* Return = StartNode;
2156 StartNode->Next = NULL;
2157 StartNode->x = best.x;
2158 StartNode->y = best.y;
2159 if (flags) {
2160 StartNode->orient = GetOrient( start, best );
2161 } else {
2162 StartNode->orient = GetOrient( best, start );
2164 Point p = best;
2165 unsigned int pos2 = start.y * Width + start.x;
2166 while (( pos = p.y * Width + p.x ) != pos2) {
2167 Return = new PathNode;
2168 StartNode->Parent = Return;
2169 Return->Next = StartNode;
2170 StartNode = Return;
2171 unsigned int level = MapSet[pos];
2172 unsigned int diff = 0;
2173 Point n;
2174 Leveldown( p.x, p.y + 1, level, n, diff );
2175 Leveldown( p.x + 1, p.y, level, n, diff );
2176 Leveldown( p.x - 1, p.y, level, n, diff );
2177 Leveldown( p.x, p.y - 1, level, n, diff );
2178 Leveldown( p.x - 1, p.y + 1, level, n, diff );
2179 Leveldown( p.x + 1, p.y + 1, level, n, diff );
2180 Leveldown( p.x + 1, p.y - 1, level, n, diff );
2181 Leveldown( p.x - 1, p.y - 1, level, n, diff );
2182 Return->x = n.x;
2183 Return->y = n.y;
2185 if (flags) {
2186 Return->orient = GetOrient( p, n );
2187 } else {
2188 Return->orient = GetOrient( n, p );
2190 p = n;
2191 if (!diff) {
2192 break;
2195 Return->Parent = NULL;
2196 return Return;
2199 bool Map::TargetUnreachable(Point &s, Point &d, unsigned int size)
2201 Point start( s.x/16, s.y/12 );
2202 Point goal ( d.x/16, d.y/12 );
2203 memset( MapSet, 0, Width * Height * sizeof( unsigned short ) );
2204 while (InternalStack.size())
2205 InternalStack.pop();
2207 if (GetBlocked( d.x, d.y, size )) {
2208 return true;
2210 if (GetBlocked( s.x, s.y, size )) {
2211 return true;
2214 unsigned int pos = ( goal.x << 16 ) | goal.y;
2215 unsigned int pos2 = ( start.x << 16 ) | start.y;
2216 InternalStack.push( pos );
2217 MapSet[goal.y * Width + goal.x] = 1;
2219 while (InternalStack.size() && pos!=pos2) {
2220 pos = InternalStack.front();
2221 InternalStack.pop();
2222 unsigned int x = pos >> 16;
2223 unsigned int y = pos & 0xffff;
2225 SetupNode( x - 1, y - 1, size, 1 );
2226 SetupNode( x + 1, y - 1, size, 1 );
2227 SetupNode( x + 1, y + 1, size, 1 );
2228 SetupNode( x - 1, y + 1, size, 1 );
2229 SetupNode( x, y - 1, size, 1 );
2230 SetupNode( x + 1, y, size, 1 );
2231 SetupNode( x, y + 1, size, 1 );
2232 SetupNode( x - 1, y, size, 1 );
2234 return pos!=pos2;
2237 /* Use this function when you target something by a straight line projectile (like a lightning bolt, arrow, etc)
2240 PathNode* Map::GetLine(Point &start, Point &dest, int flags)
2242 int Orientation = GetOrient(start, dest);
2243 return GetLine(start, dest, 1, Orientation, flags);
2246 PathNode* Map::GetLine(Point &start, int Steps, int Orientation, int flags)
2248 Point dest=start;
2250 unsigned int st = Steps>=MaxVisibility?MaxVisibility-1:Steps;
2251 int p = VisibilityPerimeter*Orientation/MAX_ORIENT;
2252 dest.x += VisibilityMasks[st][p].x;
2253 dest.y += VisibilityMasks[st][p].y;
2254 //FIXME: calculate dest based on distance and orientation
2255 return GetLine(start, dest, Steps, Orientation, flags);
2258 PathNode* Map::GetLine(Point &start, Point &dest, int Speed, int Orientation, int flags)
2260 PathNode* StartNode = new PathNode;
2261 PathNode *Return = StartNode;
2262 StartNode->Next = NULL;
2263 StartNode->Parent = NULL;
2264 StartNode->x = start.x;
2265 StartNode->y = start.y;
2266 StartNode->orient = Orientation;
2268 int Count = 0;
2269 int Max = Distance(start,dest);
2270 for (int Steps = 0; Steps<Max; Steps++) {
2271 if (!Count) {
2272 StartNode->Next = new PathNode;
2273 StartNode->Next->Parent = StartNode;
2274 StartNode = StartNode->Next;
2275 StartNode->Next = NULL;
2276 Count=Speed;
2277 } else {
2278 Count--;
2281 Point p;
2282 p.x = (ieWord) start.x + ((dest.x - start.x) * Steps / Max);
2283 p.y = (ieWord) start.y + ((dest.y - start.y) * Steps / Max);
2285 //the path ends here as it would go off the screen, causing problems
2286 //maybe there is a better way, but i needed a quick hack to fix
2287 //the crash in projectiles
2288 if ((signed) p.x<0 || (signed) p.y<0) {
2289 return Return;
2291 if ((ieWord) p.x>Width*16 || (ieWord) p.y>Height*12) {
2292 return Return;
2295 StartNode->x = p.x;
2296 StartNode->y = p.y;
2297 StartNode->orient = Orientation;
2298 bool wall = !( GetBlocked( p ) & PATH_MAP_PASSABLE );
2299 if (wall) switch (flags) {
2300 case GL_REBOUND:
2301 Orientation = (Orientation + 8) &15;
2302 //recalculate dest (mirror it)
2303 break;
2304 case GL_PASS:
2305 break;
2306 default: //premature end
2307 return Return;
2311 return Return;
2315 * find a path from start to goal, ending at the specified distance from the
2316 * target (the goal must be in sight of the end, if 'sight' is specified)
2318 * if you don't need to find an optimal path near the goal then use FindPath
2319 * instead, but don't change this one without testing with combat and dialog,
2320 * you can't predict the goal point for those, you *must* path!
2322 PathNode* Map::FindPathNear(const Point &s, const Point &d, unsigned int size, unsigned int MinDistance, bool sight)
2324 // adjust the start/goal points to be searchmap locations
2325 Point start( s.x/16, s.y/12 );
2326 Point goal ( d.x/16, d.y/12 );
2327 Point orig_goal = goal;
2329 // re-initialise the path finding structures
2330 memset( MapSet, 0, Width * Height * sizeof( unsigned short ) );
2331 while (InternalStack.size())
2332 InternalStack.pop();
2334 // set the start point in the path finding structures
2335 unsigned int pos2 = ( goal.x << 16 ) | goal.y;
2336 unsigned int pos = ( start.x << 16 ) | start.y;
2337 InternalStack.push( pos );
2338 MapSet[start.y * Width + start.x] = 1;
2340 unsigned int squaredmindistance = MinDistance * MinDistance;
2341 bool found_path = false;
2342 while (InternalStack.size()) {
2343 pos = InternalStack.front();
2344 InternalStack.pop();
2345 unsigned int x = pos >> 16;
2346 unsigned int y = pos & 0xffff;
2348 if (pos == pos2) {
2349 // we got all the way to the target!
2350 found_path = true;
2351 break;
2352 } else if (MinDistance) {
2353 /* check minimum distance:
2354 * as an obvious optimisation we only check squared distance: this is a
2355 * possible overestimate since the sqrt Distance() rounds down
2356 * (some other optimisations could be made here, but you'd be better off
2357 * fixing the pathfinder to do A* properly)
2358 * caller should have already done PersonalDistance adjustments, this is
2359 * simply between the specified points
2362 int distx = (x - orig_goal.x)*16;
2363 int disty = (y - orig_goal.y)*12;
2364 if ((unsigned int)(distx*distx + disty*disty) <= squaredmindistance) {
2365 // we are within the minimum distance of the goal
2366 Point ourpos(x*16 + 8, y*12 + 6);
2367 // sight check is *slow* :(
2368 if (!sight || IsVisible(ourpos, d)) {
2369 // we got all the way to a suitable goal!
2370 goal = Point(x, y);
2371 found_path = true;
2372 break;
2377 unsigned int Cost = MapSet[y * Width + x] + NormalCost;
2378 if (Cost > 65500) {
2379 // cost is far too high, no path found
2380 break;
2383 // diagonal movements
2384 SetupNode( x - 1, y - 1, size, Cost );
2385 SetupNode( x + 1, y - 1, size, Cost );
2386 SetupNode( x + 1, y + 1, size, Cost );
2387 SetupNode( x - 1, y + 1, size, Cost );
2389 // direct movements
2390 Cost += AdditionalCost;
2391 SetupNode( x, y - 1, size, Cost );
2392 SetupNode( x + 1, y, size, Cost );
2393 SetupNode( x, y + 1, size, Cost );
2394 SetupNode( x - 1, y, size, Cost );
2397 // find path from goal to start
2398 PathNode* StartNode = new PathNode;
2399 PathNode* Return = StartNode;
2400 StartNode->Next = NULL;
2401 StartNode->Parent = NULL;
2402 if (!found_path) {
2403 // this is not really great, we should be finding the path that
2404 // went nearest to where we wanted
2405 StartNode->x = start.x;
2406 StartNode->y = start.y;
2407 StartNode->orient = GetOrient( goal, start );
2408 return Return;
2410 StartNode->x = goal.x;
2411 StartNode->y = goal.y;
2412 bool fixup_orient = false;
2413 if (orig_goal != goal) {
2414 StartNode->orient = GetOrient( orig_goal, goal );
2415 } else {
2416 // we pathed all the way to original goal!
2417 // we don't know correct orientation until we find previous step
2418 fixup_orient = true;
2419 StartNode->orient = GetOrient( goal, start );
2421 Point p = goal;
2422 pos2 = start.y * Width + start.x;
2423 while (( pos = p.y * Width + p.x ) != pos2) {
2424 unsigned int level = MapSet[pos];
2425 unsigned int diff = 0;
2426 Point n;
2427 Leveldown( p.x, p.y + 1, level, n, diff );
2428 Leveldown( p.x + 1, p.y, level, n, diff );
2429 Leveldown( p.x - 1, p.y, level, n, diff );
2430 Leveldown( p.x, p.y - 1, level, n, diff );
2431 Leveldown( p.x - 1, p.y + 1, level, n, diff );
2432 Leveldown( p.x + 1, p.y + 1, level, n, diff );
2433 Leveldown( p.x + 1, p.y - 1, level, n, diff );
2434 Leveldown( p.x - 1, p.y - 1, level, n, diff );
2435 if (!diff)
2436 return Return;
2438 if (fixup_orient) {
2439 // don't change orientation at end of path? this seems best
2440 StartNode->orient = GetOrient( p, n );
2443 Return = new PathNode;
2444 Return->Next = StartNode;
2445 Return->Next->Parent = Return;
2446 StartNode = Return;
2448 StartNode->x = n.x;
2449 StartNode->y = n.y;
2450 StartNode->orient = GetOrient( p, n );
2451 p = n;
2454 return Return;
2457 PathNode* Map::FindPath(const Point &s, const Point &d, unsigned int size, int MinDistance)
2459 Point start( s.x/16, s.y/12 );
2460 Point goal ( d.x/16, d.y/12 );
2461 memset( MapSet, 0, Width * Height * sizeof( unsigned short ) );
2462 while (InternalStack.size())
2463 InternalStack.pop();
2465 if (GetBlocked( d.x, d.y, size )) {
2466 AdjustPosition( goal );
2468 unsigned int pos = ( goal.x << 16 ) | goal.y;
2469 unsigned int pos2 = ( start.x << 16 ) | start.y;
2470 InternalStack.push( pos );
2471 MapSet[goal.y * Width + goal.x] = 1;
2473 while (InternalStack.size()) {
2474 pos = InternalStack.front();
2475 InternalStack.pop();
2476 unsigned int x = pos >> 16;
2477 unsigned int y = pos & 0xffff;
2479 if (pos == pos2) {
2480 //We've found _a_ path
2481 //printf("GOAL!!!\n");
2482 break;
2484 unsigned int Cost = MapSet[y * Width + x] + NormalCost;
2485 if (Cost > 65500) {
2486 //printf("Path not found!\n");
2487 break;
2489 SetupNode( x - 1, y - 1, size, Cost );
2490 SetupNode( x + 1, y - 1, size, Cost );
2491 SetupNode( x + 1, y + 1, size, Cost );
2492 SetupNode( x - 1, y + 1, size, Cost );
2494 Cost += AdditionalCost;
2495 SetupNode( x, y - 1, size, Cost );
2496 SetupNode( x + 1, y, size, Cost );
2497 SetupNode( x, y + 1, size, Cost );
2498 SetupNode( x - 1, y, size, Cost );
2501 //find path from start to goal
2502 PathNode* StartNode = new PathNode;
2503 PathNode* Return = StartNode;
2504 StartNode->Next = NULL;
2505 StartNode->Parent = NULL;
2506 StartNode->x = start.x;
2507 StartNode->y = start.y;
2508 StartNode->orient = GetOrient( goal, start );
2509 if (pos != pos2) {
2510 return Return;
2512 Point p = start;
2513 pos2 = goal.y * Width + goal.x;
2514 while (( pos = p.y * Width + p.x ) != pos2) {
2515 StartNode->Next = new PathNode;
2516 StartNode->Next->Parent = StartNode;
2517 StartNode = StartNode->Next;
2518 StartNode->Next = NULL;
2519 unsigned int level = MapSet[pos];
2520 unsigned int diff = 0;
2521 Point n;
2522 Leveldown( p.x, p.y + 1, level, n, diff );
2523 Leveldown( p.x + 1, p.y, level, n, diff );
2524 Leveldown( p.x - 1, p.y, level, n, diff );
2525 Leveldown( p.x, p.y - 1, level, n, diff );
2526 Leveldown( p.x - 1, p.y + 1, level, n, diff );
2527 Leveldown( p.x + 1, p.y + 1, level, n, diff );
2528 Leveldown( p.x + 1, p.y - 1, level, n, diff );
2529 Leveldown( p.x - 1, p.y - 1, level, n, diff );
2530 if (!diff)
2531 return Return;
2532 StartNode->x = n.x;
2533 StartNode->y = n.y;
2534 StartNode->orient = GetOrient( n, p );
2535 p = n;
2537 //stepping back on the calculated path
2538 if (MinDistance) {
2539 while (StartNode->Parent) {
2540 Point tar;
2542 tar.x=StartNode->Parent->x*16;
2543 tar.y=StartNode->Parent->y*12;
2544 int dist = Distance(tar,d);
2545 if (dist+14>=MinDistance) {
2546 break;
2548 StartNode = StartNode->Parent;
2549 delete StartNode->Next;
2550 StartNode->Next = NULL;
2553 return Return;
2556 //single point visible or not (visible/exploredbitmap)
2557 //if explored = true then explored otherwise currently visible
2558 bool Map::IsVisible(const Point &pos, int explored)
2560 if (!VisibleBitmap)
2561 return false;
2562 int sX=pos.x/32;
2563 int sY=pos.y/32;
2564 if (sX<0) return false;
2565 if (sY<0) return false;
2566 int w = TMap->XCellCount * 2 + LargeFog;
2567 int h = TMap->YCellCount * 2 + LargeFog;
2568 if (sX>=w) return false;
2569 if (sY>=h) return false;
2570 int b0 = (sY * w) + sX;
2571 int by = b0/8;
2572 int bi = 1<<(b0%8);
2574 if (explored) return (ExploredBitmap[by] & bi)!=0;
2575 return (VisibleBitmap[by] & bi)!=0;
2578 //point a is visible from point b (searchmap)
2579 bool Map::IsVisible(const Point &s, const Point &d)
2581 int sX=s.x/16;
2582 int sY=s.y/12;
2583 int dX=d.x/16;
2584 int dY=d.y/12;
2585 int diffx = sX - dX;
2586 int diffy = sY - dY;
2587 if (abs( diffx ) >= abs( diffy )) {
2588 //vertical
2589 double elevationy = fabs((double)diffx ) / diffy;
2590 if (sX > dX) {
2591 for (int startx = sX; startx > dX; startx--) {
2592 if (GetBlocked( startx, sY - ( int ) ( ( sX - startx ) / elevationy ) ) & PATH_MAP_NO_SEE)
2593 return false;
2595 } else {
2596 for (int startx = sX; startx < dX; startx++) {
2597 if (GetBlocked( startx, sY + ( int ) ( ( sX - startx ) / elevationy ) ) & PATH_MAP_NO_SEE)
2598 return false;
2601 } else {
2602 double elevationx = fabs((double)diffy ) / diffx;
2603 if (sY > dY) {
2604 for (int starty = sY; starty > dY; starty--) {
2605 if (GetBlocked( sX - ( int ) ( ( sY - starty ) / elevationx ), starty ) & PATH_MAP_NO_SEE)
2606 return false;
2608 } else {
2609 for (int starty = sY; starty < dY; starty++) {
2610 if (GetBlocked( sX + ( int ) ( ( sY - starty ) / elevationx ), starty ) & PATH_MAP_NO_SEE)
2611 return false;
2615 return true;
2618 //returns direction of area boundary, returns -1 if it isn't a boundary
2619 int Map::WhichEdge(Point &s)
2621 unsigned int sX=s.x/16;
2622 unsigned int sY=s.y/12;
2623 if (!(GetBlocked( sX, sY )&PATH_MAP_TRAVEL)) {
2624 printMessage("Map"," ",YELLOW);
2625 printf("This isn't a travel region [%d.%d]?\n",sX, sY);
2626 return -1;
2628 sX*=Height;
2629 sY*=Width;
2630 if (sX>sY) { //north or east
2631 if (Width*Height>sX+sY) { //
2632 return WMP_NORTH;
2634 return WMP_EAST;
2636 //south or west
2637 if (Width*Height<sX+sY) { //
2638 return WMP_SOUTH;
2640 return WMP_WEST;
2643 //--------ambients----------------
2644 void Map::SetupAmbients()
2646 AmbientMgr *ambim = core->GetAudioDrv()->GetAmbientMgr();
2647 if (!ambim) return;
2648 ambim->reset();
2649 ambim->setAmbients( ambients );
2651 //--------mapnotes----------------
2652 //text must be a pointer we can claim ownership of
2653 void Map::AddMapNote(Point &point, int color, char *text, ieStrRef strref)
2655 MapNote *mn = new MapNote;
2657 mn->strref = strref;
2658 mn->Pos = point;
2659 mn->color = (ieWord) color;
2660 mn->text = text;
2661 RemoveMapNote(point); //delete previous mapnote
2662 mapnotes.push_back(mn);
2665 void Map::RemoveMapNote(Point &point)
2667 size_t i = mapnotes.size();
2668 while (i--) {
2669 if ((point.x==mapnotes[i]->Pos.x) &&
2670 (point.y==mapnotes[i]->Pos.y)) {
2671 delete mapnotes[i];
2672 mapnotes.erase(mapnotes.begin()+i);
2677 MapNote *Map::GetMapNote(Point &point)
2679 size_t i = mapnotes.size();
2680 while (i--) {
2681 if (Distance(point, mapnotes[i]->Pos) < 10 ) {
2682 return mapnotes[i];
2685 return NULL;
2687 //--------spawning------------------
2688 void Map::LoadIniSpawn()
2690 INISpawn = new IniSpawn(this);
2691 INISpawn->InitSpawn(WEDResRef);
2694 void Map::SpawnCreature(Point &pos, const char *CreName, int radius)
2696 SpawnGroup *sg=NULL;
2697 Actor *creature;
2698 void* lookup;
2699 if ( !Spawns.Lookup( CreName, lookup) ) {
2700 creature = gamedata->GetCreature(CreName);
2701 if ( creature ) {
2702 AddActor(creature);
2703 creature->SetPosition( pos, true, radius );
2704 creature->RefreshEffects(NULL);
2706 return;
2708 sg = (SpawnGroup*)lookup;
2709 unsigned int count = 0;
2710 int amount = core->GetGame()->GetPartyLevel(true);
2711 // if the difficulty is too high, distribute it equally over all the
2712 // critters and summon as many as the summed difficulty allows
2713 if (amount - (signed)sg->Level < 0) {
2714 unsigned int share = sg->Level/sg->Count;
2715 amount -= share;
2716 if (amount < 0) {
2717 // a single critter is also too powerful
2718 return;
2720 while (amount >= 0) {
2721 count++;
2722 amount -= share;
2724 } else {
2725 count = sg->Count;
2728 while ( count-- ) {
2729 creature = gamedata->GetCreature(sg->ResRefs[count]);
2730 if ( creature ) {
2731 AddActor(creature);
2732 creature->SetPosition( pos, true, radius );
2733 creature->RefreshEffects(NULL);
2738 void Map::TriggerSpawn(Spawn *spawn)
2740 //is it still active
2741 if (!spawn->Enabled) {
2742 return;
2744 //check schedule
2745 ieDword bit = 1<<((core->GetGame()->GameTime/AI_UPDATE_TIME)%7200/300);
2746 if (!(spawn->appearance & bit)) {
2747 return;
2750 //check day or night chance
2751 if (rand()%100>spawn->DayChance) {
2752 return;
2754 // the difficulty check is done in SpawnCreature
2755 //create spawns
2756 for(unsigned int i = 0;i<spawn->Count;i++) {
2757 SpawnCreature(spawn->Pos, spawn->Creatures[i], 0);
2759 //disable spawnpoint
2760 spawn->Enabled = 0;
2763 //--------restheader----------------
2765 Every spawn has a difficulty associated with it. For CREs this is the xp stat
2766 and for groups it's the value in the difficulty row.
2767 For every spawn, the difficulty sum of all spawns up to now (including the
2768 current) is compared against (party level * rest header difficulty). If it's
2769 greater, the spawning is aborted. If all the other conditions are true, at
2770 least one creature is summoned, regardless the difficulty cap.
2772 bool Map::Rest(Point &pos, int hours, int day)
2774 if (!RestHeader.CreatureNum || !RestHeader.Enabled || !RestHeader.Maximum) {
2775 return false;
2778 //based on ingame timer
2779 int chance=day?RestHeader.DayChance:RestHeader.NightChance;
2780 int spawncount = 1;
2781 int spawnamount = core->GetGame()->GetPartyLevel(true) * RestHeader.Difficulty;
2782 if (spawnamount < 1) spawnamount = 1;
2783 for (int i=0;i<hours;i++) {
2784 if ( rand()%100<chance ) {
2785 int idx = rand()%RestHeader.CreatureNum;
2786 Actor *creature = gamedata->GetCreature(RestHeader.CreResRef[idx]);
2787 if (!creature) continue;
2788 // ensure a minimum power level, since many creatures have this as 0
2789 int cpl = creature->Modified[IE_XP] ? creature->Modified[IE_XP] : 1;
2791 core->DisplayString( RestHeader.Strref[idx], 0x00404000, IE_STR_SOUND );
2792 while (spawnamount > 0 && spawncount <= RestHeader.Maximum) {
2793 SpawnCreature(pos, RestHeader.CreResRef[idx], 20);
2794 spawnamount -= cpl;
2795 spawncount++;
2797 return true;
2800 return false;
2803 //--------explored bitmap-----------
2804 int Map::GetExploredMapSize() const
2806 int x = TMap->XCellCount*2;
2807 int y = TMap->YCellCount*2;
2808 if (LargeFog) {
2809 x++;
2810 y++;
2812 return (x*y+7)/8;
2815 void Map::Explore(int setreset)
2817 memset (ExploredBitmap, setreset, GetExploredMapSize() );
2820 void Map::SetMapVisibility(int setreset)
2822 memset( VisibleBitmap, setreset, GetExploredMapSize() );
2825 // x, y are in tile coordinates
2826 void Map::ExploreTile(Point &pos)
2828 int h = TMap->YCellCount * 2 + LargeFog;
2829 int y = pos.y/32;
2830 if (y < 0 || y >= h)
2831 return;
2833 int w = TMap->XCellCount * 2 + LargeFog;
2834 int x = pos.x/32;
2835 if (x < 0 || x >= w)
2836 return;
2838 int b0 = (y * w) + x;
2839 int by = b0/8;
2840 int bi = 1<<(b0%8);
2842 ExploredBitmap[by] |= bi;
2843 VisibleBitmap[by] |= bi;
2846 void Map::ExploreMapChunk(Point &Pos, int range, int los)
2848 Point Tile;
2850 if (range>MaxVisibility) {
2851 range=MaxVisibility;
2853 int p=VisibilityPerimeter;
2854 while (p--) {
2855 int Pass = 2;
2856 bool block = false;
2857 bool sidewall = false ;
2858 for (int i=0;i<range;i++) {
2859 Tile.x = Pos.x+VisibilityMasks[i][p].x;
2860 Tile.y = Pos.y+VisibilityMasks[i][p].y;
2862 if (los) {
2863 if (!block) {
2864 int type = GetBlocked(Tile);
2865 if (type & PATH_MAP_NO_SEE) {
2866 block=true;
2867 } else if (type & PATH_MAP_SIDEWALL) {
2868 sidewall = true;
2869 } else if (sidewall)
2871 block=true ;
2874 if (block) {
2875 Pass--;
2876 if (!Pass) break;
2879 ExploreTile(Tile);
2884 void Map::UpdateFog()
2886 if (!(core->FogOfWar&FOG_DRAWFOG) ) {
2887 SetMapVisibility( -1 );
2888 return;
2891 SetMapVisibility( 0 );
2892 for (unsigned int e = 0; e<actors.size(); e++) {
2893 Actor *actor = actors[e];
2894 if (!actor->Modified[ IE_EXPLORE ] ) continue;
2895 int state = actor->Modified[IE_STATE_ID];
2896 if (state & STATE_CANTSEE) continue;
2897 int vis2 = actor->Modified[IE_VISUALRANGE];
2898 if ((state&STATE_BLIND) || (vis2<2)) vis2=2; //can see only themselves
2899 ExploreMapChunk (actor->Pos, vis2, 1);
2900 Spawn *sp = GetSpawnRadius(actor->Pos, SPAWN_RANGE); //30 * 12
2901 if (sp) {
2902 TriggerSpawn(sp);
2907 //Valid values are - PATH_MAP_FREE, PATH_MAP_PC, PATH_MAP_NPC
2908 void Map::BlockSearchMap(Point &Pos, unsigned int size, unsigned int value)
2910 // We block a circle of radius size-1 around (px,py)
2911 // Note that this does not exactly match BG2. BG2's approximations of
2912 // these circles are slightly different for sizes 6 and up.
2914 // Note: this is a larger circle than the one tested in GetBlocked.
2915 // This means that an actor can get closer to a wall than to another
2916 // actor. This matches the behaviour of the original BG2.
2918 if (size > MAX_CIRCLESIZE) size = MAX_CIRCLESIZE;
2919 if (size < 2) size = 2;
2920 unsigned int ppx = Pos.x/16;
2921 unsigned int ppy = Pos.y/12;
2922 unsigned int r=(size-1)*(size-1)+1;
2923 if (size == 1) r = 0;
2924 for (unsigned int i=0; i<size; i++) {
2925 for (unsigned int j=0; j<size; j++) {
2926 if (i*i+j*j <= r) {
2927 unsigned char tmp;
2929 tmp = SearchMap->GetAt(ppx+i,ppy+j)&PATH_MAP_NOTACTOR;
2930 SearchMap->SetAt(ppx+i,ppy+j,tmp|value);
2932 tmp = SearchMap->GetAt(ppx+i,ppy-j)&PATH_MAP_NOTACTOR;
2933 SearchMap->SetAt(ppx+i,ppy-j,tmp|value);
2935 tmp = SearchMap->GetAt(ppx-i,ppy+j)&PATH_MAP_NOTACTOR;
2936 SearchMap->SetAt(ppx-i,ppy+j,tmp|value);
2938 tmp = SearchMap->GetAt(ppx-i,ppy-j)&PATH_MAP_NOTACTOR;
2939 SearchMap->SetAt(ppx-i,ppy-j,tmp|value);
2945 Spawn* Map::GetSpawn(const char* Name)
2947 for (size_t i = 0; i < spawns.size(); i++) {
2948 Spawn* sp = spawns[i];
2950 if (stricmp( sp->Name, Name ) == 0)
2951 return sp;
2953 return NULL;
2956 Spawn *Map::GetSpawnRadius(Point &point, unsigned int radius)
2958 for (size_t i = 0; i < spawns.size(); i++) {
2959 Spawn* sp = spawns[i];
2961 if (Distance(point, sp->Pos)<radius) {
2962 return sp;
2965 return NULL;
2968 int Map::ConsolidateContainers()
2970 int itemcount = 0;
2971 int containercount = (int) TMap->GetContainerCount();
2972 while (containercount--) {
2973 Container * c = TMap->GetContainer( containercount);
2975 if (TMap->CleanupContainer(c) ) {
2976 continue;
2978 itemcount += c->inventory.GetSlotCount();
2980 return itemcount;
2983 //Pos could be [-1,-1] in which case it copies the ground piles to their
2984 //original position in the second area
2985 void Map::CopyGroundPiles(Map *othermap, Point &Pos)
2987 int containercount = (int) TMap->GetContainerCount();
2988 while (containercount--) {
2989 Container * c = TMap->GetContainer( containercount);
2990 if (c->Type==IE_CONTAINER_PILE) {
2991 //creating (or grabbing) the container in the other map at the given position
2992 Container *othercontainer;
2993 if (Pos.isempty()) {
2994 othercontainer = othermap->GetPile(c->Pos);
2995 } else {
2996 othercontainer = othermap->GetPile(Pos);
2998 //transfer the pile to the other container
2999 unsigned int i=c->inventory.GetSlotCount();
3000 while (i--) {
3001 CREItem *item = c->RemoveItem(i, 0);
3002 othercontainer->AddItem(item);
3008 Container *Map::GetPile(Point &position)
3010 Point tmp[4];
3011 char heapname[32];
3013 //converting to search square
3014 position.x=position.x/16;
3015 position.y=position.y/12;
3016 sprintf(heapname,"heap_%hd.%hd",position.x,position.y);
3017 //pixel position is centered on search square
3018 position.x=position.x*16+8;
3019 position.y=position.y*12+6;
3020 Container *container = TMap->GetContainer(position,IE_CONTAINER_PILE);
3021 if (!container) {
3022 //bounding box covers the search square
3023 tmp[0].x=position.x-8;
3024 tmp[0].y=position.y-6;
3025 tmp[1].x=position.x+8;
3026 tmp[1].y=position.y-6;
3027 tmp[2].x=position.x+8;
3028 tmp[2].y=position.y+6;
3029 tmp[3].x=position.x-8;
3030 tmp[3].y=position.y+6;
3031 Gem_Polygon* outline = new Gem_Polygon( tmp, 4 );
3032 container = AddContainer(heapname, IE_CONTAINER_PILE, outline);
3033 container->Pos=position;
3035 return container;
3038 void Map::AddItemToLocation(Point &position, CREItem *item)
3040 Container *container = GetPile(position);
3041 container->AddItem(item);
3044 Container* Map::AddContainer(const char* Name, unsigned short Type,
3045 Gem_Polygon* outline)
3047 Container* c = new Container();
3048 c->SetScriptName( Name );
3049 c->Type = Type;
3050 c->outline = outline;
3051 c->SetMap(this);
3052 TMap->AddContainer( c );
3053 return c;
3056 int Map::GetCursor( Point &p)
3058 if (!IsVisible( p, true ) ) {
3059 return IE_CURSOR_INVALID;
3061 switch (GetBlocked( p ) & (PATH_MAP_PASSABLE|PATH_MAP_TRAVEL)) {
3062 case 0:
3063 return IE_CURSOR_BLOCKED;
3064 case PATH_MAP_PASSABLE:
3065 return IE_CURSOR_WALK;
3066 default:
3067 return IE_CURSOR_TRAVEL;
3071 bool Map::HasWeather()
3073 if ((AreaType & (AT_WEATHER|AT_OUTDOOR) ) != (AT_WEATHER|AT_OUTDOOR) ) {
3074 return false;
3076 return true;
3079 int Map::GetWeather()
3081 if (Rain>=core->Roll(1,100,0) ) {
3082 if (Lightning>=core->Roll(1,100,0) ) {
3083 return WB_LIGHTNING|WB_RAIN;
3085 return WB_RAIN;
3087 if (Snow>=core->Roll(1,100,0) ) {
3088 return WB_SNOW;
3090 if (Fog>=core->Roll(1,100,0) ) {
3091 return WB_FOG;
3093 return WB_NORMAL;
3096 void Map::FadeSparkle(Point &pos, bool forced)
3098 spaIterator iter;
3100 for(iter=particles.begin(); iter!=particles.end();iter++) {
3101 if ((*iter)->MatchPos(pos) ) {
3102 if (forced) {
3103 //particles.erase(iter);
3104 (*iter)->SetPhase(P_EMPTY);
3105 } else {
3106 (*iter)->SetPhase(P_FADE);
3108 return;
3113 void Map::Sparkle(ieDword color, ieDword type, Point &pos, unsigned int FragAnimID)
3115 int style, path, grow, size, width;
3117 //the high word is ignored in the original engine (compatibility hack)
3118 switch(type&0xffff) {
3119 case SPARKLE_SHOWER: //simple falling sparks
3120 path = SP_PATH_FALL;
3121 grow = SP_SPAWN_FULL;
3122 size = 100;
3123 width = 40;
3124 break;
3125 case SPARKLE_PUFF:
3126 path = SP_PATH_FOUNT; //sparks go up and down
3127 grow = SP_SPAWN_FULL;
3128 size = 100;
3129 width = 40;
3130 break;
3131 case SPARKLE_EXPLOSION: //this isn't in the original engine, but it is a nice effect to have
3132 path = SP_PATH_EXPL;
3133 grow = SP_SPAWN_FULL;
3134 size = 10;
3135 width = 140;
3136 break;
3137 default:
3138 path = SP_PATH_FLIT;
3139 grow = SP_SPAWN_SOME;
3140 size = 100;
3141 width = 40;
3142 break;
3144 Particles *sparkles = new Particles(size);
3145 sparkles->SetOwner(this);
3146 sparkles->SetRegion(pos.x-width/2, pos.y-80, width, 80);
3148 if (FragAnimID) {
3149 style = SP_TYPE_BITMAP;
3150 sparkles->SetBitmap(FragAnimID);
3152 else {
3153 style = SP_TYPE_POINT;
3155 sparkles->SetType(style, path, grow);
3156 sparkles->SetColor(color);
3157 sparkles->SetPhase(P_GROW);
3158 printf("sparkle: %d %d\n", color, type);
3159 printf("Position: %d.%d\n", pos.x,pos.y);
3161 //AddParticle(sparkles, pos);
3162 spaIterator iter;
3163 for(iter=particles.begin(); (iter!=particles.end()) && ((*iter)->GetHeight()<pos.y); iter++) ;
3164 particles.insert(iter, sparkles);
3167 //remove flags from actor if it has left the trigger area it had last entered
3168 void Map::ClearTrap(Actor *actor, ieDword InTrap)
3170 InfoPoint *trap = TMap->GetInfoPoint(InTrap);
3171 if (!trap) {
3172 actor->SetInTrap(0);
3173 } else {
3174 if(!trap->outline->PointIn(actor->Pos)) {
3175 actor->SetInTrap(0);
3180 void Map::SetTrackString(ieStrRef strref, int flg, int difficulty)
3182 trackString = strref;
3183 trackFlag = flg;
3184 trackDiff = (ieWord) difficulty;
3187 bool Map::DisplayTrackString(Actor *target)
3189 // this stat isn't saved
3190 // according to the HoW manual the chance of success is:
3191 // +5% for every three levels and +5% per point of wisdom
3192 int skill = target->GetStat(IE_TRACKING);
3193 skill += (target->GetStat(IE_LEVEL)/3)*5 + target->GetStat(IE_WIS)*5;
3194 if (core->Roll(1, 100, trackDiff) > skill) {
3195 core->DisplayConstantStringName(STR_TRACKINGFAILED, 0xd7d7be, target);
3196 return true;
3198 if (trackFlag) {
3199 char * str = core->GetString( trackString);
3200 core->GetTokenDictionary()->SetAt( "CREATURE", str);
3201 core->DisplayConstantStringName(STR_TRACKING, 0xd7d7be, target);
3202 return false;
3204 core->DisplayStringName(trackString, 0xd7d7be, target, 0);
3205 return false;
3208 // returns a lightness level in the range of [0-100]
3209 // since the lightmap is much smaller than the area, we need to interpolate
3210 unsigned int Map::GetLightLevel(Point &Pos)
3212 Color c = LightMap->GetPixel(Pos.x/16, Pos.y/12);
3213 // at night/dusk/dawn the lightmap color is adjusted by the color overlay. (Only get's darker.)
3214 const Color *tint = core->GetGame()->GetGlobalTint();
3215 if (tint) {
3216 return ((c.r-tint->r)*114 + (c.g-tint->g)*587 + (c.b-tint->b)*299)/2550;
3218 return (c.r*114+c.g*587+c.b*299)/2550;
3221 ////////////////////AreaAnimation//////////////////
3222 //Area animation
3224 AreaAnimation::AreaAnimation()
3226 animation=NULL;
3227 animcount=0;
3228 palette=NULL;
3229 covers=NULL;
3232 AreaAnimation::~AreaAnimation()
3234 for(int i=0;i<animcount;i++) {
3235 if (animation[i]) {
3236 delete (animation[i]);
3239 free(animation);
3240 gamedata->FreePalette(palette, PaletteRef);
3241 if (covers) {
3242 for(int i=0;i<animcount;i++) {
3243 delete covers[i];
3245 free (covers);
3249 Animation *AreaAnimation::GetAnimationPiece(AnimationFactory *af, int animCycle)
3251 Animation *anim = af->GetCycle( ( unsigned char ) animCycle );
3252 if (!anim)
3253 anim = af->GetCycle( 0 );
3254 if (!anim) {
3255 printf("Cannot load animation: %s\n", BAM);
3256 return NULL;
3258 //this will make the animation stop when the game is stopped
3259 //a possible gemrb feature to have this flag settable in .are
3260 anim->gameAnimation = true;
3261 anim->pos = frame;
3262 anim->Flags = Flags;
3263 anim->x = Pos.x;
3264 anim->y = Pos.y;
3265 if (anim->Flags&A_ANI_MIRROR) {
3266 anim->MirrorAnimation();
3269 return anim;
3272 void AreaAnimation::InitAnimation()
3274 AnimationFactory* af = ( AnimationFactory* )
3275 gamedata->GetFactoryResource( BAM, IE_BAM_CLASS_ID );
3276 if (!af) {
3277 printf("Cannot load animation: %s\n", BAM);
3278 return;
3281 //freeing up the previous animation
3282 for(int i=0;i<animcount;i++) {
3283 if (animation[i]) {
3284 delete (animation[i]);
3287 free(animation);
3289 if (Flags & A_ANI_ALLCYCLES) {
3290 animcount = (int) af->GetCycleCount();
3292 animation = (Animation **) malloc(animcount * sizeof(Animation *) );
3293 for(int j=0;j<animcount;j++) {
3294 animation[j]=GetAnimationPiece(af, j);
3296 } else {
3297 animcount = 1;
3298 animation = (Animation **) malloc( sizeof(Animation *) );
3299 animation[0]=GetAnimationPiece(af, sequence);
3301 if (Flags & A_ANI_PALETTE) {
3302 SetPalette(PaletteRef);
3304 if (Flags&A_ANI_BLEND) {
3305 BlendAnimation();
3309 void AreaAnimation::SetPalette(ieResRef Pal)
3311 Flags |= A_ANI_PALETTE;
3312 gamedata->FreePalette(palette, PaletteRef);
3313 strnlwrcpy(PaletteRef, Pal, 8);
3314 palette = gamedata->GetPalette(PaletteRef);
3315 if (Flags&A_ANI_BLEND) {
3316 //re-blending after palette change
3317 BlendAnimation();
3321 void AreaAnimation::BlendAnimation()
3323 //Warning! This function will modify a shared palette
3324 if (!palette) {
3325 // CHECKME: what should we do here? Currently copying palette
3326 // from first frame of first animation
3328 if (animcount == 0 || !animation[0]) return;
3329 Sprite2D* spr = animation[0]->GetFrame(0);
3330 if (!spr) return;
3331 palette = spr->GetPalette()->Copy();
3332 PaletteRef[0] = 0;
3334 palette->CreateShadedAlphaChannel();
3337 bool AreaAnimation::Schedule(ieDword gametime)
3339 if (!(Flags&A_ANI_ACTIVE) ) {
3340 return false;
3343 //check for schedule
3344 ieDword bit = 1<<((gametime/AI_UPDATE_TIME)%7200/300);
3345 if (appearance & bit) {
3346 return true;
3348 return false;
3351 void AreaAnimation::Draw(Region &screen, Map *area)
3353 int ac=animcount;
3354 Video* video = core->GetVideoDriver();
3356 //always draw the animation tinted because tint is also used for
3357 //transparency
3358 Color tint = {255,255,255,255-(ieByte) transparency};
3359 if ((Flags&A_ANI_NO_SHADOW)) {
3360 tint = area->LightMap->GetPixel( Pos.x / 16, Pos.y / 12);
3361 tint.a = 255-(ieByte) transparency;
3363 if (!(Flags&A_ANI_NO_WALL)) {
3364 if (!covers) {
3365 covers=(SpriteCover **) calloc( animcount, sizeof(SpriteCover *) );
3368 ac=animcount;
3369 while (ac--) {
3370 Animation *anim = animation[ac];
3371 Sprite2D *frame = anim->NextFrame();
3372 if(covers) {
3373 if(!covers[ac] || !covers[ac]->Covers(Pos.x, Pos.y, frame->XPos, frame->YPos, frame->Width, frame->Height)) {
3374 delete covers[ac];
3375 covers[ac] = area->BuildSpriteCover(Pos.x, Pos.y, -anim->animArea.x,
3376 -anim->animArea.y, anim->animArea.w, anim->animArea.h, 0);
3379 video->BlitGameSprite( frame, Pos.x + screen.x, Pos.y + screen.y,
3380 BLIT_TINTED, tint, covers?covers[ac]:0, palette, &screen );
3384 //change the tileset if needed and possible, return true if changed
3385 bool Map::ChangeMap(bool day_or_night)
3387 //no need of change if the area is not extended night
3388 if (! (AreaType&AT_EXTENDED_NIGHT)) return false;
3389 //no need of change if the area already has the right tilemap
3390 if ((DayNight == day_or_night) && GetTileMap()) return false;
3392 MapMgr* mM = ( MapMgr* ) core->GetInterface( IE_ARE_CLASS_ID );
3393 //no need to open and read the .are file again
3394 //using the ARE class for this because ChangeMap is similar to LoadMap
3395 //it loads the lightmap and the minimap too, besides swapping the tileset
3396 mM->ChangeMap(this, day_or_night);
3397 mM->release();
3398 return true;
3401 void Map::SeeSpellCast(Scriptable *caster, ieDword spell)
3403 if (caster->Type!=ST_ACTOR) {
3404 return;
3407 LastCasterSeen = ((Actor *) caster)->GetID();
3408 LastSpellSeen = spell;
3410 size_t i = actors.size();
3411 while (i--) {
3412 Actor* witness = actors[i];
3413 if (CanSee(witness, caster, true, 0)) {
3414 witness->LastSpellSeen=LastSpellSeen;
3415 witness->LastCasterSeen=LastCasterSeen;