SetupWishCore: fixed table lookup being out of bounds for the wisest pcs
[gemrb.git] / gemrb / core / Map.cpp
blob9b3beb39ad211093fab0a768ef710f3ab5913c53
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 "Map.h"
25 #include "win32def.h"
27 #include "Ambient.h"
28 #include "AmbientMgr.h"
29 #include "Audio.h"
30 #include "DisplayMessage.h"
31 #include "Game.h"
32 #include "GameData.h"
33 #include "Interface.h"
34 #include "MapMgr.h"
35 #include "MusicMgr.h"
36 #include "Palette.h"
37 #include "Particles.h"
38 #include "PathFinder.h"
39 #include "Projectile.h"
40 #include "ScriptedAnimation.h"
41 #include "TileMap.h"
42 #include "Video.h"
43 #include "WorldMap.h"
44 #include "strrefs.h"
45 #include "GameScript/GSUtils.h"
46 #include "GUI/GameControl.h"
48 #include <cmath>
49 #include <cassert>
51 #define YESNO(x) ( (x)?"Yes":"No")
53 // TODO: fix this hardcoded resource reference
54 static ieResRef PortalResRef={"EF03TPR3"};
55 static unsigned int PortalTime = 15;
56 static unsigned int MAX_CIRCLESIZE = 8;
57 static int MaxVisibility = 30;
58 static int VisibilityPerimeter; //calculated from MaxVisibility
59 static int NormalCost = 10;
60 static int AdditionalCost = 4;
61 static unsigned char Passable[16] = {
62 4, 1, 1, 1, 1, 1, 1, 1, 0, 1, 8, 0, 0, 0, 3, 1
64 static Point **VisibilityMasks=NULL;
66 static bool PathFinderInited = false;
67 static Variables Spawns;
68 static int LargeFog;
69 static ieWord globalActorCounter;
71 void ReleaseSpawnGroup(void *poi)
73 delete (SpawnGroup *) poi;
76 void Map::ReleaseMemory()
78 if (VisibilityMasks) {
79 for (int i=0;i<MaxVisibility;i++) {
80 free(VisibilityMasks[i]);
82 free(VisibilityMasks);
83 VisibilityMasks=NULL;
86 Spawns.RemoveAll(ReleaseSpawnGroup);
87 PathFinderInited = false;
90 inline static AnimationObjectType SelectObject(Actor *actor, int q, AreaAnimation *a, ScriptedAnimation *sca, Particles *spark, Projectile *pro)
92 int actorh;
93 if (actor) {
94 actorh = actor->Pos.y;
95 if (q) actorh = 0;
96 } else {
97 actorh = 0x7fffffff;
100 int aah;
101 if (a) {
102 aah = a->Pos.y;//+a->height;
103 } else {
104 aah = 0x7fffffff;
107 int scah;
108 if (sca) {
109 scah = sca->YPos;//+sca->ZPos;
110 } else {
111 scah = 0x7fffffff;
114 int spah;
115 if (spark) {
116 //no idea if this should be plus or minus (or here at all)
117 spah = spark->GetHeight();//+spark->pos.h;
118 } else {
119 spah = 0x7fffffff;
122 int proh;
123 if (pro) {
124 proh = pro->GetHeight();
125 } else {
126 proh = 0x7fffffff;
129 if (proh<actorh && proh<scah && proh<aah && proh<spah) return AOT_PROJECTILE;
131 if (spah<actorh && spah<scah && spah<aah) return AOT_SPARK;
133 if (aah<actorh && aah<scah) return AOT_AREA;
135 if (scah<actorh) return AOT_SCRIPTED;
137 return AOT_ACTOR;
140 //returns true if creature must be embedded in the area
141 //npcs in saved game shouldn't be embedded either
142 inline static bool MustSave(Actor *actor)
144 if (actor->Persistent()) {
145 return false;
148 //check for familiars, summons?
149 return true;
152 //Preload spawn group entries (creature resrefs that reference groups of creatures)
153 void InitSpawnGroups()
155 ieResRef GroupName;
156 int i;
158 AutoTable tab("spawngrp");
160 Spawns.RemoveAll(NULL);
161 Spawns.SetType( GEM_VARIABLES_POINTER );
163 if (!tab)
164 return;
166 i=tab->GetColNamesCount();
167 while (i--) {
168 int j=tab->GetRowCount();
169 while (j--) {
170 const char *crename = tab->QueryField( j,i );
171 if (crename[0] != '*') break;
173 if (j>0) {
174 SpawnGroup *creatures = new SpawnGroup(j);
175 //difficulty
176 creatures->Level = (ieDword) atoi( tab->QueryField(0,i) );
177 for (;j;j--) {
178 strnlwrcpy( creatures->ResRefs[j-1], tab->QueryField(j,i), 8 );
180 strnlwrcpy( GroupName, tab->GetColumnName( i ), 8 );
181 Spawns.SetAt( GroupName, (void*) creatures );
186 //Preload the searchmap configuration
187 void InitPathFinder()
189 PathFinderInited = true;
190 AutoTable tm("pathfind");
191 if (tm) {
192 const char* poi;
194 for (int i = 0; i < 16; i++) {
195 poi = tm->QueryField( 0, i );
196 if (*poi != '*')
197 Passable[i] = atoi( poi );
199 poi = tm->QueryField( 1, 0 );
200 if (*poi != '*')
201 NormalCost = atoi( poi );
202 poi = tm->QueryField( 1, 1 );
203 if (*poi != '*')
204 AdditionalCost = atoi( poi );
208 void AddLOS(int destx, int desty, int slot)
210 for (int i=0;i<MaxVisibility;i++) {
211 int x=(destx*i+MaxVisibility/2)/MaxVisibility*16;
212 int y=(desty*i+MaxVisibility/2)/MaxVisibility*12;
213 if (LargeFog) {
214 x += 16;
215 y += 12;
217 VisibilityMasks[i][slot].x=(short) x;
218 VisibilityMasks[i][slot].y=(short) y;
222 void InitExplore()
224 LargeFog = !core->HasFeature(GF_SMALL_FOG);
226 //circle perimeter size for MaxVisibility
227 int x = MaxVisibility;
228 int y = 0;
229 int xc = 1 - ( 2 * MaxVisibility );
230 int yc = 1;
231 int re = 0;
232 VisibilityPerimeter = 0;
233 while (x>=y) {
234 VisibilityPerimeter+=8;
235 y++;
236 re += yc;
237 yc += 2;
238 if (( ( 2 * re ) + xc ) > 0) {
239 x--;
240 re += xc;
241 xc += 2;
245 int i;
246 VisibilityMasks = (Point **) malloc(MaxVisibility * sizeof(Point *) );
247 for (i=0;i<MaxVisibility;i++) {
248 VisibilityMasks[i] = (Point *) malloc(VisibilityPerimeter*sizeof(Point) );
251 x = MaxVisibility;
252 y = 0;
253 xc = 1 - ( 2 * MaxVisibility );
254 yc = 1;
255 re = 0;
256 VisibilityPerimeter = 0;
257 while (x>=y) {
258 AddLOS (x, y, VisibilityPerimeter++);
259 AddLOS (-x, y, VisibilityPerimeter++);
260 AddLOS (-x, -y, VisibilityPerimeter++);
261 AddLOS (x, -y, VisibilityPerimeter++);
262 AddLOS (y, x, VisibilityPerimeter++);
263 AddLOS (-y, x, VisibilityPerimeter++);
264 AddLOS (-y, -x, VisibilityPerimeter++);
265 AddLOS (y, -x, VisibilityPerimeter++);
266 y++;
267 re += yc;
268 yc += 2;
269 if (( ( 2 * re ) + xc ) > 0) {
270 x--;
271 re += xc;
272 xc += 2;
277 Map::Map(void)
278 : Scriptable( ST_AREA )
280 area=this;
281 TMap = NULL;
282 LightMap = NULL;
283 SearchMap = NULL;
284 HeightMap = NULL;
285 SmallMap = NULL;
286 MapSet = NULL;
287 Walls = NULL;
288 WallCount = 0;
289 queue[PR_SCRIPT] = NULL;
290 queue[PR_DISPLAY] = NULL;
291 INISpawn = NULL;
292 //no one needs this queue
293 //queue[PR_IGNORE] = NULL;
294 Qcount[PR_SCRIPT] = 0;
295 Qcount[PR_DISPLAY] = 0;
296 //no one needs this queue
297 //Qcount[PR_IGNORE] = 0;
298 lastActorCount[PR_SCRIPT] = 0;
299 lastActorCount[PR_DISPLAY] = 0;
300 //no one needs this
301 //lastActorCount[PR_IGNORE] = 0;
302 if (!PathFinderInited) {
303 InitPathFinder();
304 InitSpawnGroups();
305 InitExplore();
306 globalActorCounter = 0;
308 ExploredBitmap = NULL;
309 VisibleBitmap = NULL;
310 version = 0;
311 localActorCounter = 0;
312 MasterArea = core->GetGame()->MasterArea(scriptName);
315 Map::~Map(void)
317 unsigned int i;
319 free( MapSet );
320 delete TMap;
321 delete INISpawn;
322 aniIterator aniidx;
323 for (aniidx = animations.begin(); aniidx != animations.end(); aniidx++) {
324 delete (*aniidx);
327 for (i = 0; i < actors.size(); i++) {
328 Actor* a = actors[i];
329 //don't delete NPC/PC
330 if (a && !a->Persistent() ) {
331 delete a;
335 for (i = 0; i < entrances.size(); i++) {
336 delete entrances[i];
338 for (i = 0; i < spawns.size(); i++) {
339 delete spawns[i];
341 delete LightMap;
342 delete SearchMap;
343 delete HeightMap;
344 core->GetVideoDriver()->FreeSprite( SmallMap );
345 for (i = 0; i < QUEUE_COUNT; i++) {
346 free(queue[i]);
347 queue[i] = NULL;
350 proIterator pri;
352 for (pri = projectiles.begin(); pri != projectiles.end(); pri++) {
353 delete (*pri);
356 scaIterator sci;
358 for (sci = vvcCells.begin(); sci != vvcCells.end(); sci++) {
359 delete (*sci);
362 spaIterator spi;
364 for (spi = particles.begin(); spi != particles.end(); spi++) {
365 delete (*spi);
368 for (i = 0; i < ambients.size(); i++) {
369 delete ambients[i];
371 for (i = 0; i < mapnotes.size(); i++) {
372 delete mapnotes[i];
375 //malloc-d in AREImp
376 free( ExploredBitmap );
377 free( VisibleBitmap );
378 if (Walls) {
379 for(i=0;i<WallCount;i++) {
380 delete Walls[i];
382 free( Walls );
384 WallCount=0;
387 void Map::ChangeTileMap(Image* lm, Sprite2D* sm)
389 delete LightMap;
390 core->GetVideoDriver()->FreeSprite(SmallMap);
392 LightMap = lm;
393 SmallMap = sm;
395 TMap->UpdateDoors();
398 void Map::AddTileMap(TileMap* tm, Image* lm, Bitmap* sr, Sprite2D* sm, Bitmap* hm)
400 // CHECKME: leaks? Should the old TMap, LightMap, etc... be freed?
401 TMap = tm;
402 LightMap = lm;
403 SearchMap = sr;
404 HeightMap = hm;
405 SmallMap = sm;
406 Width = (unsigned int) (TMap->XCellCount * 4);
407 Height = (unsigned int) (( TMap->YCellCount * 64 ) / 12);
408 //Filling Matrices
409 MapSet = (unsigned short *) malloc(sizeof(unsigned short) * Width * Height);
410 //converting searchmap to internal format
411 int y=SearchMap->GetHeight();
412 while(y--) {
413 int x=SearchMap->GetWidth();
414 while(x--) {
415 SearchMap->SetAt(x,y,Passable[SearchMap->GetAt(x,y)&PATH_MAP_AREAMASK]);
420 void Map::MoveToNewArea(const char *area, const char *entrance, unsigned int direction, int EveryOne, Actor *actor)
422 char command[256];
424 //change loader MOS image here
425 //check worldmap entry, if that doesn't contain anything,
426 //make a random pick
428 if (EveryOne==CT_WHOLE) {
429 core->GetGameControl()->AutoSave();
431 Game* game = core->GetGame();
432 Map* map = game->GetMap(area, false);
433 if (!map) {
434 printMessage("Map", " ", LIGHT_RED);
435 printf("Invalid map: %s\n",area);
436 command[0]=0;
437 return;
439 Entrance* ent = map->GetEntrance( entrance );
440 int X,Y, face;
441 if (!ent) {
442 // no entrance found, try using direction flags
444 face = -1; // should this be handled per-case?
446 // ok, so the original engine tries these in a different order
447 // (north first, then south) but it doesn't seem to matter
448 if (direction & 0x1) { // north
449 X = map->TMap->XCellCount * 32;
450 Y = 0;
451 } else if (direction & 0x2) { // east
452 X = map->TMap->XCellCount * 64;
453 Y = map->TMap->YCellCount * 32;
454 } else if (direction & 0x4) { // south
455 X = map->TMap->XCellCount * 32;
456 Y = map->TMap->YCellCount * 64;
457 } else if (direction & 0x8) { // west
458 X = 0;
459 Y = map->TMap->YCellCount * 32;
460 } else {
461 // crashes in original engine
462 printMessage("Map", " ", YELLOW);
463 printf( "WARNING!!! EntryPoint '%s' does not exist and direction %d is invalid\n", entrance, direction );
464 X = map->TMap->XCellCount * 64;
465 Y = map->TMap->YCellCount * 64;
467 } else {
468 X = ent->Pos.x;
469 Y = ent->Pos.y;
470 face = ent->Face;
472 //LeaveArea is the same in ALL engine versions
473 sprintf(command, "LeaveArea(\"%s\",[%d.%d],%d)", area, X, Y, face);
475 if (EveryOne&CT_GO_CLOSER) {
476 int i=game->GetPartySize(false);
477 while (i--) {
478 Actor *pc = game->GetPC(i,false);
479 if (pc->GetCurrentArea()==this) {
480 pc->ClearPath();
481 pc->ClearActions();
482 pc->AddAction( GenerateAction( command ) );
483 pc->ProcessActions(true);
486 return;
488 if (EveryOne&CT_SELECTED) {
489 int i=game->GetPartySize(false);
490 while (i--) {
491 Actor *pc = game->GetPC(i,false);
493 if (!pc->IsSelected()) {
494 continue;
496 if (pc->GetCurrentArea()==this) {
497 pc->ClearPath();
498 pc->ClearActions();
499 pc->AddAction( GenerateAction( command ) );
500 pc->ProcessActions(true);
503 return;
506 actor->ClearPath();
507 actor->ClearActions();
508 actor->AddAction( GenerateAction( command ) );
509 actor->ProcessActions(true);
512 void Map::UseExit(Actor *actor, InfoPoint *ip)
514 Game *game=core->GetGame();
516 int EveryOne = ip->CheckTravel(actor);
517 switch(EveryOne) {
518 case CT_GO_CLOSER:
519 displaymsg->DisplayConstantString(STR_WHOLEPARTY,0xffffff); //white
520 if (game->EveryoneStopped()) {
521 ip->Flags&=~TRAP_RESET; //exit triggered
523 return;
524 //no ingame message for these events
525 case CT_CANTMOVE: case CT_SELECTED:
526 return;
527 case CT_ACTIVE: case CT_WHOLE: case CT_MOVE_SELECTED:
528 break;
531 actor->UseExit(false);
532 if (ip->Destination[0] != 0) {
533 // 0 here is direction, can infopoints specify that or is an entrance always provided?
534 MoveToNewArea(ip->Destination, ip->EntranceName, 0, EveryOne, actor);
535 return;
537 if (ip->Scripts[0]) {
538 ip->LastTriggerObject = ip->LastTrigger = ip->LastEntered = actor->GetID();
539 ip->ExecuteScript( 1 );
540 ip->ProcessActions(true);
544 //Draw two overlapped animations to achieve the original effect
545 //PlayOnce makes sure that if we stop drawing them, they will go away
546 void Map::DrawPortal(InfoPoint *ip, int enable)
548 ieDword gotportal = HasVVCCell(PortalResRef, ip->Pos);
550 if (enable) {
551 if (gotportal>PortalTime) return;
552 ScriptedAnimation *sca = gamedata->GetScriptedAnimation(PortalResRef, false);
553 if (sca) {
554 sca->SetBlend();
555 sca->PlayOnce();
556 sca->XPos=ip->Pos.x;
557 sca->YPos=ip->Pos.y;
558 sca->ZPos=gotportal;
559 AddVVCell(sca);
561 return;
565 void Map::UpdateScripts()
567 bool has_pcs = false;
568 size_t i=actors.size();
569 while (i--) {
570 if (actors[i]->InParty) {
571 has_pcs = true;
572 break;
576 // if masterarea, then we allow 'any' actors
577 // if not masterarea, we allow only players
578 // if (!GetActorCount(MasterArea) ) {
579 // fuzzie changed this because the previous code was wrong
580 // (GetActorCount(false) returns only non-PCs) - it is not
581 // well-tested so feel free to change if there are problems
582 // (for example, the CanFree seems like it would be needed to
583 // check for any running scripts, such as following, but it seems
584 // to work ok anyway in my testing - if you change it you probably
585 // also want to change the actor updating code below so it doesn't
586 // add new actions while we are trying to get rid of the area!)
587 if (!has_pcs && !(MasterArea && actors.size()) /*&& !CanFree()*/) {
588 return;
591 // fuzzie added this check because some area scripts (eg, AR1600 when
592 // escaping Brynnlaw) were executing after they were meant to be done,
593 // and this seems the nicest way of handling that for now - it's quite
594 // possibly wrong (so if you have problems, revert this and find
595 // another way)
596 if (has_pcs) {
597 //Run the Map Script
598 ExecuteScript( 1 );
601 //Execute Pending Actions
602 //if it is only here, then the drawing will fail
603 ProcessActions(false);
605 // If scripts frozen, return.
606 // This fixes starting a new IWD game. The above ProcessActions pauses the
607 // game for a textscreen, but one of the actor->ProcessActions calls
608 // below starts a cutscene, hiding the mouse. - wjp, 20060805
609 if (core->GetGameControl()->GetDialogueFlags() & DF_FREEZE_SCRIPTS) return;
611 //Run actor scripts (only for 0 priority)
612 int q=Qcount[PR_SCRIPT];
614 Game *game = core->GetGame();
615 Actor *timestop_owner = game->timestop_owner;
616 bool timestop = game->timestop_end>game->GameTime;
618 // this is silly, the speed should be pre-calculated somewhere
619 //int *actor_speeds = (int *)calloc(Qcount[PR_SCRIPT], sizeof(int));
621 //bool *no_more_steps_for_actor = (bool *)calloc(Qcount[PR_SCRIPT], sizeof(bool));
623 while (q--) {
624 Actor* actor = queue[PR_SCRIPT][q];
625 //actor just moved away, don't run its script from this side
626 if (actor->GetCurrentArea()!=this) {
627 actor->no_more_steps = true;
628 continue;
630 if (timestop && actor!=timestop_owner && actor->Modified[IE_DISABLETIMESTOP] ) {
631 actor->no_more_steps = true;
632 continue;
635 //Avenger moved this here from ApplyAllEffects (this one modifies the effect queue)
636 //.. but then fuzzie moved this here from UpdateActorState, because otherwise
637 //immobile actors (see check below) never become mobile again!
638 actor->fxqueue.Cleanup();
640 //if the actor is immobile, don't run the scripts
641 if (!game->StateOverrideFlag && !game->StateOverrideTime) {
642 if (actor->Immobile()) {
643 actor->no_more_steps = true;
644 continue;
647 actor->no_more_steps = false;
650 * we run scripts all at once because one of the actions in ProcessActions
651 * might remove us from a cutscene and then bad things can happen when
652 * scripts are queued unexpectedly (such as an ogre in a cutscene -> dialog
653 * -> cutscene transition in the first bg1 cutscene exploiting the race
654 * condition to murder player1) - it is entirely possible that we should be
655 * doing this differently (for example by storing the cutscene state at the
656 * start of this function, or by changing the cutscene state at a later
657 * point, etc), but i did it this way for now because it seems least painful
658 * and we should probably be staggering the script executions anyway
660 actor->ExecuteScript( MAX_SCRIPTS );
664 q=Qcount[PR_SCRIPT];
665 while (q--) {
666 Actor* actor = queue[PR_SCRIPT][q];
667 if (actor->no_more_steps) continue;
669 actor->ProcessActions(false);
671 actor->UpdateActorState(game->GameTime);
673 actor->inventory.CalculateWeight();
674 actor->SetBase( IE_ENCUMBRANCE, actor->inventory.GetWeight() );
676 //TODO:calculate actor speed!
677 int speed = (int) actor->GetStat(IE_MOVEMENTRATE);
678 if (speed) {
679 speed = 1500/speed;
681 if (core->GetResDataINI()) {
682 ieDword animid = actor->BaseStats[IE_ANIMATION_ID];
683 if (core->HasFeature(GF_ONE_BYTE_ANIMID)) {
684 animid = animid & 0xff;
686 if (animid < (ieDword)CharAnimations::GetAvatarsCount()) {
687 AvatarStruct *avatar = CharAnimations::GetAvatarStruct(animid);
688 if (avatar->RunScale && (actor->GetInternalFlag() & IF_RUNNING)) {
689 speed = avatar->RunScale;
690 } else if (avatar->WalkScale) {
691 speed = avatar->WalkScale;
692 } else {
693 //printf("no walkscale for anim %d!\n", actor->BaseStats[IE_ANIMATION_ID]);
697 actor->speed = speed;
700 // We need to step through the list of actors until all of them are done
701 // taking steps.
702 bool more_steps = true;
703 ieDword time = game->Ticks; // make sure everything moves at the same time
704 while (more_steps) {
705 more_steps = false;
707 q=Qcount[PR_SCRIPT];
708 while (q--) {
709 Actor* actor = queue[PR_SCRIPT][q];
710 if (actor->no_more_steps) continue;
712 // try to exclude actors which only just died
713 // (shouldn't we not be stepping actors which don't have a path anyway?)
714 // following fails on Immobile creatures, don't think it's a problem, but replace with next line if it is
715 if (!actor->ValidTarget(GA_NO_DEAD)) continue;
716 //if (actor->GetStat(IE_STATE_ID)&STATE_DEAD || actor->GetInternalFlag() & IF_JUSTDIED) continue;
718 actor->no_more_steps = DoStepForActor(actor, actor->speed, time);
719 if (!actor->no_more_steps) more_steps = true;
723 //Check if we need to start some door scripts
724 int doorCount = 0;
725 while (true) {
726 Door* door = TMap->GetDoor( doorCount++ );
727 if (!door)
728 break;
729 if (door->Scripts[0])
730 door->ExecuteScript( 1 );
731 //Execute Pending Actions
732 door->ProcessActions(false);
735 //Check if we need to start some container scripts
736 int containerCount = 0;
737 while (true) {
738 Container* container = TMap->GetContainer( containerCount++ );
739 if (!container)
740 break;
741 if (container->Scripts[0])
742 container->ExecuteScript( 1 );
743 //Execute Pending Actions
744 container->ProcessActions(false);
747 //Check if we need to start some trap scripts
748 int ipCount = 0;
749 while (true) {
750 //For each InfoPoint in the map
751 InfoPoint* ip = TMap->GetInfoPoint( ipCount++ );
752 if (!ip)
753 break;
754 //If this InfoPoint has no script and it is not a Travel Trigger, skip it
755 bool wasActive = (ip->Scripts[0] || ( ip->Type == ST_TRAVEL ));
756 // InfoPoints of all types don't run scripts if TRAP_DEACTIVATED is set
757 // (eg, TriggerActivation changes this, see lightning room from SoA)
758 if (wasActive)
759 wasActive = !(ip->Flags&TRAP_DEACTIVATED);
761 //If this InfoPoint is a Switch Trigger
762 if (ip->Type == ST_TRIGGER) {
763 //Check if this InfoPoint was activated
764 if (ip->LastTrigger) {
765 if (wasActive) {
766 //Run the InfoPoint script
767 ip->ExecuteScript( 1 );
770 //Execute Pending Actions
771 ip->ProcessActions(false);
772 continue;
775 if (ip->IsPortal()) {
776 DrawPortal(ip, ip->Trapped&PORTAL_TRAVEL);
779 if (wasActive) {
780 q=Qcount[PR_SCRIPT];
781 while (q--) {
782 Actor* actor = queue[PR_SCRIPT][q];
783 if (ip->Type == ST_PROXIMITY) {
784 if(ip->Entered(actor)) {
785 //if trap triggered, then mark actor
786 actor->SetInTrap(ipCount);
788 } else {
789 //ST_TRAVEL
790 //don't move if doing something else
791 // added CurrentAction as part of blocking action fixes
792 if (actor->CannotPassEntrance() ) {
793 continue;
795 //this is needed, otherwise the travel
796 //trigger would be activated anytime
797 //Well, i don't know why is it here, but lets try this
798 if (ip->Entered(actor)) {
799 UseExit(actor, ip);
805 if (wasActive) {
806 ip->ExecuteScript( 1 );
808 //Execute Pending Actions
809 ip->ProcessActions(false);
813 bool Map::DoStepForActor(Actor *actor, int speed, ieDword time) {
814 bool no_more_steps = true;
816 if (actor->BlocksSearchMap()) {
817 ClearSearchMapFor(actor);
819 PathNode * step = actor->GetNextStep();
820 if (step && step->Next) {
821 //we should actually wait for a short time and check then
822 if (GetBlocked(step->Next->x*16+8,step->Next->y*12+6,actor->size)) {
823 actor->NewPath();
827 if (!(actor->GetBase(IE_STATE_ID)&STATE_CANTMOVE) ) {
828 if (!actor->Immobile()) {
829 no_more_steps = actor->DoStep( speed, time );
830 if (actor->BlocksSearchMap()) {
831 BlockSearchMap( actor->Pos, actor->size, actor->InParty?PATH_MAP_PC:PATH_MAP_NPC);
836 return no_more_steps;
839 void Map::ClearSearchMapFor( Movable *actor ) {
840 Actor** nearActors = GetAllActorsInRadius(actor->Pos, GA_NO_DEAD, MAX_CIRCLE_SIZE*2*16);
841 BlockSearchMap( actor->Pos, actor->size, PATH_MAP_FREE);
843 // Restore the searchmap areas of any nearby actors that could
844 // have been cleared by this BlockSearchMap(..., 0).
845 // (Necessary since blocked areas of actors may overlap.)
846 int i=0;
847 while(nearActors[i]!=NULL) {
848 if(nearActors[i]!=actor && nearActors[i]->BlocksSearchMap())
849 BlockSearchMap( nearActors[i]->Pos, nearActors[i]->size, nearActors[i]->InParty?PATH_MAP_PC:PATH_MAP_NPC);
850 ++i;
852 free(nearActors);
855 void Map::DrawHighlightables( Region screen )
857 Region vp = core->GetVideoDriver()->GetViewport();
858 unsigned int i = 0;
859 Container *c;
861 while ( (c = TMap->GetContainer(i++))!=NULL ) {
862 Color tint = LightMap->GetPixel( c->Pos.x / 16, c->Pos.y / 12);
863 tint.a = 255;
865 if (c->Highlight) {
866 if (c->Type==IE_CONTAINER_PILE) {
867 Color tint = LightMap->GetPixel( c->Pos.x / 16, c->Pos.y / 12);
868 tint.a = 255;
869 c->DrawPile(true, screen, tint);
870 } else {
871 c->DrawOutline();
873 } else if (c->Type==IE_CONTAINER_PILE) {
874 if (c->outline->BBox.InsideRegion( vp )) {
875 c->DrawPile(false, screen, tint);
880 Door *d;
881 i = 0;
882 while ( (d = TMap->GetDoor(i++))!=NULL ) {
883 if (d->Highlight) d->DrawOutline();
886 InfoPoint *p;
887 i = 0;
888 while ( (p = TMap->GetInfoPoint(i++))!=NULL ) {
889 if (p->Highlight) p->DrawOutline();
893 Actor *Map::GetNextActor(int &q, int &index)
895 retry:
896 switch(q) {
897 case PR_SCRIPT:
898 if (index--)
899 return queue[q][index];
900 q--;
901 return NULL;
902 case PR_DISPLAY:
903 if (index--)
904 return queue[q][index];
905 q--;
906 index = Qcount[q];
907 goto retry;
908 default:
909 return NULL;
913 AreaAnimation *Map::GetNextAreaAnimation(aniIterator &iter, ieDword gametime)
915 retry:
916 if (iter==animations.end()) {
917 return NULL;
919 AreaAnimation *a = *(iter++);
920 if (!a->Schedule(gametime) ) {
921 goto retry;
923 if (!IsVisible( a->Pos, !(a->Flags & A_ANI_NOT_IN_FOG)) ) {
924 goto retry;
926 return a;
929 Particles *Map::GetNextSpark(spaIterator &iter)
931 if (iter==particles.end()) {
932 return NULL;
934 return *iter;
937 //doesn't increase iterator, because we might need to erase it from the list
938 Projectile *Map::GetNextProjectile(proIterator &iter)
940 if (iter==projectiles.end()) {
941 return NULL;
943 return *iter;
946 Projectile *Map::GetNextTrap(proIterator &iter)
948 Projectile *pro;
950 do {
951 pro=GetNextProjectile(iter);
952 iter++;
953 //logic to determine dormant traps
954 //if (pro && pro->IsTrap()) break;
955 } while(pro);
956 return pro;
959 size_t Map::GetProjectileCount(proIterator &iter)
961 iter = projectiles.begin();
962 return projectiles.size();
965 ieDword Map::GetTrapCount(proIterator &iter)
967 ieDword cnt=0;
968 iter=projectiles.begin();
969 while(GetNextTrap(iter)) {
970 cnt++;
973 iter = projectiles.begin();
974 return cnt;
978 //doesn't increase iterator, because we might need to erase it from the list
979 ScriptedAnimation *Map::GetNextScriptedAnimation(scaIterator &iter)
981 if (iter==vvcCells.end()) {
982 return NULL;
984 return *iter;
987 static ieDword oldgametime = 0;
989 //Draw the game area (including overlays, actors, animations, weather)
990 void Map::DrawMap(Region screen)
992 if (!TMap) {
993 return;
995 Game *game = core->GetGame();
996 ieDword gametime = game->GameTime;
998 //area specific spawn.ini files (a PST feature)
999 if (INISpawn) {
1000 INISpawn->CheckSpawn();
1003 int rain;
1004 if (HasWeather()) {
1005 //zero when the weather particles are all gone
1006 rain = game->weather->GetPhase()-P_EMPTY;
1007 } else {
1008 rain = 0;
1010 TMap->DrawOverlays( screen, rain );
1012 //Blit the Background Map Animations (before actors)
1013 Video* video = core->GetVideoDriver();
1015 //Draw Outlines
1016 DrawHighlightables( screen );
1018 Region vp = video->GetViewport();
1019 //if it is only here, then the scripting will fail?
1020 GenerateQueues();
1021 SortQueues();
1022 //drawing queues 1 and 0
1023 //starting with lower priority
1024 //so displayed, but inactive actors (dead) will be drawn over
1025 int q = PR_DISPLAY;
1026 int index = Qcount[q];
1027 Actor* actor = GetNextActor(q, index);
1028 aniIterator aniidx = animations.begin();
1029 scaIterator scaidx = vvcCells.begin();
1030 proIterator proidx = projectiles.begin();
1031 spaIterator spaidx = particles.begin();
1033 AreaAnimation *a = GetNextAreaAnimation(aniidx, gametime);
1034 ScriptedAnimation *sca = GetNextScriptedAnimation(scaidx);
1035 Projectile *pro = GetNextProjectile(proidx);
1036 Particles *spark = GetNextSpark(spaidx);
1038 while (actor || a || sca || spark || pro) {
1039 switch(SelectObject(actor,q,a,sca,spark,pro)) {
1040 case AOT_ACTOR:
1041 actor->Draw( screen );
1042 actor = GetNextActor(q, index);
1043 break;
1044 case AOT_AREA:
1045 //draw animation
1046 a->Draw( screen, this );
1047 a = GetNextAreaAnimation(aniidx,gametime);
1048 break;
1049 case AOT_SCRIPTED:
1051 Point Pos(0,0);
1053 Color tint = LightMap->GetPixel( sca->XPos / 16, sca->YPos / 12);
1054 tint.a = 255;
1055 bool endReached = sca->Draw(screen, Pos, tint, this, 0, -1);
1056 if (endReached) {
1057 delete( sca );
1058 scaidx=vvcCells.erase(scaidx);
1059 } else {
1060 scaidx++;
1063 sca = GetNextScriptedAnimation(scaidx);
1064 break;
1065 case AOT_PROJECTILE:
1067 int drawn;
1068 if (gametime>oldgametime) {
1069 drawn = pro->Update();
1070 } else {
1071 drawn = 1;
1073 if (drawn) {
1074 pro->Draw( screen );
1075 proidx++;
1076 } else {
1077 delete( pro );
1078 proidx = projectiles.erase(proidx);
1081 pro = GetNextProjectile(proidx);
1082 break;
1083 case AOT_SPARK:
1085 int drawn;
1086 if (gametime>oldgametime) {
1087 drawn = spark->Update();
1088 } else {
1089 drawn = 1;
1091 if (drawn) {
1092 spark->Draw( screen );
1093 spaidx++;
1094 } else {
1095 delete( spark );
1096 spaidx=particles.erase(spaidx);
1099 spark = GetNextSpark(spaidx);
1100 break;
1101 default:
1102 abort();
1106 if ((core->FogOfWar&FOG_DRAWSEARCHMAP) && SearchMap) {
1107 DrawSearchMap(screen);
1108 } else {
1109 if ((core->FogOfWar&FOG_DRAWFOG) && TMap) {
1110 TMap->DrawFogOfWar( ExploredBitmap, VisibleBitmap, screen );
1114 int ipCount = 0;
1115 while (true) {
1116 //For each InfoPoint in the map
1117 InfoPoint* ip = TMap->GetInfoPoint( ipCount++ );
1118 if (!ip)
1119 break;
1120 ip->DrawOverheadText(screen);
1123 int cnCount = 0;
1124 while (true) {
1125 //For each Container in the map
1126 Container* cn = TMap->GetContainer( cnCount++ );
1127 if (!cn)
1128 break;
1129 cn->DrawOverheadText(screen);
1132 int drCount = 0;
1133 while (true) {
1134 //For each Door in the map
1135 Door* dr = TMap->GetDoor( drCount++ );
1136 if (!dr)
1137 break;
1138 dr->DrawOverheadText(screen);
1141 size_t i = actors.size();
1142 while (i--) {
1143 //For each Actor present
1144 //This must go AFTER the fog!
1145 //(maybe we should be using the queue?)
1146 Actor* actor = actors[i];
1147 actor->DrawOverheadText(screen);
1150 oldgametime=gametime;
1153 void Map::DrawSearchMap(const Region &screen)
1155 Color inaccessible = { 128, 128, 128, 128 };
1156 Color impassible = { 128, 64, 64, 128 }; // red-ish
1157 Color sidewall = { 64, 64, 128, 128 }; // blue-ish
1158 Video *vid=core->GetVideoDriver();
1159 Region rgn=vid->GetViewport();
1160 Region block;
1162 block.w=16;
1163 block.h=12;
1164 int w = screen.w/16+2;
1165 int h = screen.h/12+2;
1167 for(int x=0;x<w;x++) {
1168 for(int y=0;y<h;y++) {
1169 unsigned char blockvalue = GetBlocked(x+rgn.x/16, y+rgn.y/12);
1170 if (!(blockvalue & PATH_MAP_PASSABLE)) {
1171 block.x=screen.x+x*16-(rgn.x % 16);
1172 block.y=screen.y+y*12-(rgn.y % 12);
1173 if (blockvalue == PATH_MAP_IMPASSABLE) { // 0
1174 vid->DrawRect(block,impassible);
1175 } else if (blockvalue & PATH_MAP_SIDEWALL) {
1176 vid->DrawRect(block,sidewall);
1177 } else {
1178 vid->DrawRect(block,inaccessible);
1185 //adding animation in order, based on its height parameter
1186 void Map::AddAnimation(AreaAnimation* anim)
1188 //this hack is to make sure animations flagged with background
1189 //are always drawn first (-9999 seems sufficiently small)
1190 if (anim->Flags&A_ANI_BACKGROUND) {
1191 anim->height=-9999;
1194 aniIterator iter;
1195 for(iter=animations.begin(); (iter!=animations.end()) && ((*iter)->height<anim->height); iter++) ;
1196 animations.insert(iter, anim);
1198 Animation *a = anim->animation[0];
1199 anim->SetSpriteCover(BuildSpriteCover(anim->Pos.x, anim->Pos.y,-a->animArea.x,
1200 -a->animArea.y, a->animArea.w, a->animArea.h,0
1205 //reapplying all of the effects on the actors of this map
1206 //this might be unnecessary later
1207 void Map::UpdateEffects()
1209 size_t i = actors.size();
1210 while (i--) {
1211 actors[i]->RefreshEffects(NULL);
1215 void Map::Shout(Actor* actor, int shoutID, unsigned int radius)
1217 size_t i=actors.size();
1218 while (i--) {
1219 Actor *listener = actors[i];
1221 if (radius) {
1222 if (Distance(actor->Pos, listener->Pos)>radius) {
1223 continue;
1226 if (shoutID) {
1227 listener->LastHeard = actor->GetID();
1228 listener->LastShout = shoutID;
1229 } else {
1230 listener->LastHelp = actor->GetID();
1235 bool Map::AnyEnemyNearPoint(const Point &p)
1237 ieDword gametime = core->GetGame()->GameTime;
1238 size_t i = actors.size();
1239 while (i--) {
1240 Actor *actor = actors[i];
1242 if (actor->Schedule(gametime, true) ) {
1243 continue;
1245 if (Distance(actor->Pos, p) > SPAWN_RANGE) {
1246 continue;
1248 if (actor->GetStat(IE_EA)<EA_EVILCUTOFF) {
1249 continue;
1251 return true;
1253 return false;
1256 void Map::ActorSpottedByPlayer(Actor *actor)
1258 unsigned int animid;
1260 if(core->HasFeature(GF_HAS_BEASTS_INI)) {
1261 animid=actor->BaseStats[IE_ANIMATION_ID];
1262 if(core->HasFeature(GF_ONE_BYTE_ANIMID)) {
1263 animid&=0xff;
1265 if (animid < (ieDword)CharAnimations::GetAvatarsCount()) {
1266 AvatarStruct *avatar = CharAnimations::GetAvatarStruct(animid);
1267 core->GetGame()->SetBeastKnown(avatar->Bestiary);
1271 if (!(actor->GetInternalFlag()&IF_STOPATTACK)) {
1272 if (actor->Modified[IE_EA]>=EA_EVILCUTOFF) {
1273 core->Autopause(AP_ENEMY);
1278 void Map::AddActor(Actor* actor)
1280 //setting the current area for the actor as this one
1281 strnlwrcpy(actor->Area, scriptName, 8);
1282 //if actor->globalID was already set, don't change it
1283 actor->SetMap(this, ++localActorCounter,
1284 actor->globalID?actor->globalID:++globalActorCounter);
1285 actors.push_back( actor );
1286 //if a visible aggressive actor was put on the map, it is an autopause reason
1287 //guess game is always loaded? if not, then we'll crash
1288 ieDword gametime = core->GetGame()->GameTime;
1290 if (IsVisible(actor->Pos, false) && actor->Schedule(gametime, true) ) {
1291 ActorSpottedByPlayer(actor);
1293 if (actor->InParty && core->HasFeature(GF_AREA_VISITED_VAR)) {
1294 char key[32];
1295 snprintf(key, sizeof(key),"%s_visited", scriptName);
1296 core->GetGame()->locals->SetAt(key, 1);
1301 bool Map::AnyPCSeesEnemy()
1303 ieDword gametime = core->GetGame()->GameTime;
1304 size_t i = actors.size();
1305 while (i--) {
1306 Actor* actor = actors[i];
1307 if (actor->Modified[IE_EA]>=EA_EVILCUTOFF) {
1308 if (IsVisible(actor->Pos, false) && actor->Schedule(gametime, true) ) {
1309 return true;
1313 return false;
1316 //Make an actor gone for (almost) good
1317 //If the actor was in the party, it will be moved to the npc storage
1318 //If the actor is in the NPC storage, its area and some other fields
1319 //that are needed for proper reentry will be zeroed out
1320 //If the actor isn't in the NPC storage, it is destructed
1321 void Map::DeleteActor(int i)
1323 Actor *actor = actors[i];
1324 if (actor) {
1325 Game *game = core->GetGame();
1326 //this makes sure that a PC will be demoted to NPC
1327 game->LeaveParty( actor );
1328 //this frees up the spot under the feet circle
1329 ClearSearchMapFor( actor );
1330 //remove the area reference from the actor
1331 actor->SetMap(NULL,0,0);
1332 //don't destroy the object in case it is a persistent object
1333 //otherwise there is a dead reference causing a crash on save
1334 if (!game->InStore(actor) ) {
1335 delete actor;
1338 //remove the actor from the area's actor list
1339 actors.erase( actors.begin()+i );
1342 Actor* Map::GetActorByGlobalID(ieDword objectID)
1344 if (!objectID) {
1345 return NULL;
1347 //truncation is intentional
1348 ieWord globalID = (ieWord) objectID;
1349 size_t i = actors.size();
1350 while (i--) {
1351 Actor* actor = actors[i];
1353 if (actor->globalID==globalID) {
1354 return actor;
1357 return NULL;
1360 /** flags:
1361 GA_SELECT 16 - unselectable actors don't play
1362 GA_NO_DEAD 32 - dead actors don't play
1363 GA_POINT 64 - not actor specific
1364 GA_NO_HIDDEN 128 - hidden actors don't play
1366 Actor* Map::GetActor(const Point &p, int flags)
1368 ieDword gametime = core->GetGame()->GameTime;
1369 size_t i = actors.size();
1370 while (i--) {
1371 Actor* actor = actors[i];
1373 if (!actor->IsOver( p ))
1374 continue;
1375 if (!actor->ValidTarget(flags) ) {
1376 continue;
1378 if (!actor->Schedule(gametime, true) ) {
1379 continue;
1381 return actor;
1383 return NULL;
1386 Actor* Map::GetActorInRadius(const Point &p, int flags, unsigned int radius)
1388 ieDword gametime = core->GetGame()->GameTime;
1389 size_t i = actors.size();
1390 while (i--) {
1391 Actor* actor = actors[i];
1393 if (PersonalDistance( p, actor ) > radius)
1394 continue;
1395 if (!actor->ValidTarget(flags) ) {
1396 continue;
1398 if (!actor->Schedule(gametime, true) ) {
1399 continue;
1401 return actor;
1403 return NULL;
1406 Actor **Map::GetAllActorsInRadius(const Point &p, int flags, unsigned int radius)
1408 ieDword count = 1;
1409 size_t i;
1411 ieDword gametime = core->GetGame()->GameTime;
1412 i = actors.size();
1413 while (i--) {
1414 Actor* actor = actors[i];
1416 if (PersonalDistance( p, actor ) > radius)
1417 continue;
1418 if (!actor->ValidTarget(flags) ) {
1419 continue;
1421 if (!actor->Schedule(gametime, true) ) {
1422 continue;
1424 count++;
1427 Actor **ret = (Actor **) malloc( sizeof(Actor*) * count);
1428 i = actors.size();
1429 int j = 0;
1430 while (i--) {
1431 Actor* actor = actors[i];
1433 if (PersonalDistance( p, actor ) > radius)
1434 continue;
1435 if (!actor->ValidTarget(flags) ) {
1436 continue;
1438 if (!actor->Schedule(gametime, true) ) {
1439 continue;
1441 ret[j++]=actor;
1444 ret[j]=NULL;
1445 return ret;
1449 Actor* Map::GetActor(const char* Name, int flags)
1451 size_t i = actors.size();
1452 while (i--) {
1453 Actor* actor = actors[i];
1454 if (strnicmp( actor->GetScriptName(), Name, 32 ) == 0) {
1455 if (!actor->ValidTarget(flags) ) {
1456 return NULL;
1458 return actor;
1461 return NULL;
1464 int Map::GetActorCount(bool any) const
1466 if (any) {
1467 return (int) actors.size();
1469 int ret = 0;
1470 size_t i=actors.size();
1471 while (i--) {
1472 if (MustSave(actors[i])) {
1473 ret++;
1476 return ret;
1479 void Map::JumpActors(bool jump)
1481 size_t i = actors.size();
1482 while (i--) {
1483 Actor* actor = actors[i];
1484 if (actor->Modified[IE_DONOTJUMP]&DNJ_JUMP) {
1485 if (jump) {
1486 actor->FixPosition();
1488 actor->SetBase(IE_DONOTJUMP,0);
1493 //before writing the area out, perform some cleanups
1494 void Map::PurgeArea(bool items)
1496 InternalFlags |= IF_JUSTDIED; //area marked for swapping out
1498 //1. remove dead actors without 'keep corpse' flag
1499 int i=(int) actors.size();
1500 while (i--) {
1501 Actor *ac = actors[i];
1503 if (ac->Modified[IE_STATE_ID]&STATE_NOSAVE) {
1504 if (ac->Modified[IE_MC_FLAGS] & MC_KEEP_CORPSE) {
1505 continue;
1507 //don't delete persistent actors
1508 if (ac->Persistent()) {
1509 continue;
1511 //even if you delete it, be very careful!
1512 DeleteActor (i);
1515 //2. remove any non critical items
1516 if (items) {
1517 i=(int) TMap->GetContainerCount();
1518 while (i--) {
1519 Container *c = TMap->GetContainer(i);
1520 unsigned int j=c->inventory.GetSlotCount();
1521 while (j--) {
1522 CREItem *itemslot = c->inventory.GetSlotItem(j);
1523 if (itemslot->Flags&IE_INV_ITEM_CRITICAL) {
1524 continue;
1527 TMap->CleanupContainer(c);
1532 Actor* Map::GetActor(int index, bool any)
1534 if (any) {
1535 return actors[index];
1537 unsigned int i=0;
1538 while (i<actors.size() ) {
1539 Actor *ac = actors[i++];
1540 if (MustSave(ac) ) {
1541 if (!index--) {
1542 return ac;
1546 return NULL;
1549 Actor* Map::GetActorByDialog(const char *resref)
1551 size_t i = actors.size();
1552 while (i--) {
1553 Actor* actor = actors[i];
1554 //if a busy or hostile actor shouldn't be found
1555 //set this to GD_CHECK
1556 if (strnicmp( actor->GetDialog(GD_NORMAL), resref, 8 ) == 0) {
1557 return actor;
1560 return NULL;
1563 //this function finds an actor by its original resref (not correct yet)
1564 Actor* Map::GetActorByResource(const char *resref)
1566 size_t i = actors.size();
1567 while (i--) {
1568 Actor* actor = actors[i];
1569 if (strnicmp( actor->GetScriptName(), resref, 8 ) == 0) { //temporarily!
1570 return actor;
1573 return NULL;
1576 Actor* Map::GetActorByScriptName(const char *name)
1578 size_t i = actors.size();
1579 while (i--) {
1580 Actor* actor = actors[i];
1581 if (strnicmp( actor->GetScriptName(), name, 8 ) == 0) {
1582 return actor;
1585 return NULL;
1588 int Map::GetActorInRect(Actor**& actorlist, Region& rgn, bool onlyparty)
1590 actorlist = ( Actor * * ) malloc( actors.size() * sizeof( Actor * ) );
1591 int count = 0;
1592 size_t i = actors.size();
1593 while (i--) {
1594 Actor* actor = actors[i];
1595 //use this function only for party?
1596 if (onlyparty && actor->GetStat(IE_EA)>EA_CHARMED) {
1597 continue;
1599 // this is called by non-selection code..
1600 if (onlyparty && !actor->ValidTarget(GA_SELECT))
1601 continue;
1602 if (!actor->ValidTarget(GA_NO_DEAD) )
1603 continue;
1604 if ((actor->Pos.x<rgn.x) || (actor->Pos.y<rgn.y))
1605 continue;
1606 if ((actor->Pos.x>rgn.x+rgn.w) || (actor->Pos.y>rgn.y+rgn.h) )
1607 continue;
1608 actorlist[count++] = actor;
1610 actorlist = ( Actor * * ) realloc( actorlist, count * sizeof( Actor * ) );
1611 return count;
1614 void Map::PlayAreaSong(int SongType, bool restart, bool hard)
1616 //Ok, we use a non constant pointer here, so it is easy to disable
1617 //a faulty music list on the fly. I don't want to add a method just for that
1618 //crap when we already have that pointer at hand!
1619 char* poi = core->GetMusicPlaylist( SongHeader.SongList[SongType] );
1620 if (!poi) return;
1622 //check if restart needed (either forced or the current song is different)
1623 if (!restart && core->GetMusicMgr()->CurrentPlayList(poi)) return;
1624 int ret = core->GetMusicMgr()->SwitchPlayList( poi, hard );
1625 //Here we disable the faulty musiclist entry
1626 if (ret) {
1627 //Apparently, the playlist manager prefers a *
1628 *poi='*';
1629 return;
1631 if (SongType == SONG_BATTLE) {
1632 core->GetGame()->CombatCounter = 150;
1636 unsigned char Map::GetBlocked(unsigned int x, unsigned int y)
1638 unsigned char ret = SearchMap->GetAt( x, y );
1639 if (ret&(PATH_MAP_DOOR_TRANSPARENT|PATH_MAP_ACTOR)) {
1640 ret&=~PATH_MAP_PASSABLE;
1642 if (ret&PATH_MAP_DOOR_OPAQUE) {
1643 ret=PATH_MAP_SIDEWALL;
1645 return ret;
1648 bool Map::GetBlocked(unsigned int px, unsigned int py, unsigned int size)
1650 // We check a circle of radius size-2 around (px,py)
1651 // Note that this does not exactly match BG2. BG2's approximations of
1652 // these circles are slightly different for sizes 7 and up.
1654 if (size > MAX_CIRCLESIZE) size = MAX_CIRCLESIZE;
1655 if (size < 2) size = 2;
1657 unsigned int ppx = px/16;
1658 unsigned int ppy = py/12;
1659 unsigned int r=(size-2)*(size-2)+1;
1660 if (size == 2) r = 0;
1661 for (unsigned int i=0; i<size-1; i++) {
1662 for (unsigned int j=0; j<size-1; j++) {
1663 if (i*i+j*j <= r) {
1664 if (!(GetBlocked(ppx+i,ppy+j)&PATH_MAP_PASSABLE)) return true;
1665 if (!(GetBlocked(ppx+i,ppy-j)&PATH_MAP_PASSABLE)) return true;
1666 if (!(GetBlocked(ppx-i,ppy+j)&PATH_MAP_PASSABLE)) return true;
1667 if (!(GetBlocked(ppx-i,ppy-j)&PATH_MAP_PASSABLE)) return true;
1671 return false;
1674 unsigned char Map::GetBlocked(const Point &c)
1676 return GetBlocked(c.x/16, c.y/12);
1679 //flags:0 - never dither (full cover)
1680 // 1 - dither if polygon wants it
1681 // 2 - always dither
1683 SpriteCover* Map::BuildSpriteCover(int x, int y, int xpos, int ypos,
1684 unsigned int width, unsigned int height, int flags)
1686 SpriteCover* sc = new SpriteCover;
1687 sc->worldx = x;
1688 sc->worldy = y;
1689 sc->XPos = xpos;
1690 sc->YPos = ypos;
1691 sc->Width = width;
1692 sc->Height = height;
1694 Video* video = core->GetVideoDriver();
1695 video->InitSpriteCover(sc, flags);
1697 unsigned int wpcount = GetWallCount();
1698 unsigned int i;
1700 for (i = 0; i < wpcount; ++i)
1702 Wall_Polygon* wp = GetWallGroup(i);
1703 if (!wp) continue;
1704 if (!wp->PointCovered(x, y)) continue;
1706 video->AddPolygonToSpriteCover(sc, wp);
1709 return sc;
1712 void Map::ActivateWallgroups(unsigned int baseindex, unsigned int count, int flg)
1714 unsigned int i;
1716 if (!Walls) {
1717 return;
1719 for(i=baseindex; i < baseindex+count; ++i) {
1720 Wall_Polygon* wp = GetWallGroup(i);
1721 if (!wp)
1722 continue;
1723 ieDword value=wp->GetPolygonFlag();
1724 if (flg)
1725 value&=~WF_DISABLED;
1726 else
1727 value|=WF_DISABLED;
1728 wp->SetPolygonFlag(value);
1730 //all actors will have to generate a new spritecover
1731 i=(int) actors.size();
1732 while(i--) {
1733 actors[i]->SetSpriteCover(NULL);
1738 //this function determines actor drawing order
1739 //it should be extended to wallgroups, animations, effects!
1740 void Map::GenerateQueues()
1742 int priority;
1744 unsigned int i=(unsigned int) actors.size();
1745 for (priority=0;priority<QUEUE_COUNT;priority++) {
1746 if (lastActorCount[priority] != i) {
1747 if (queue[priority]) {
1748 free(queue[priority]);
1749 queue[priority] = NULL;
1751 queue[priority] = (Actor **) calloc( i, sizeof(Actor *) );
1752 lastActorCount[priority] = i;
1754 Qcount[priority] = 0;
1757 ieDword gametime = core->GetGame()->GameTime;
1758 while (i--) {
1759 Actor* actor = actors[i];
1761 if (actor->CheckOnDeath()) {
1762 DeleteActor( i );
1763 continue;
1766 ieDword stance = actor->GetStance();
1767 ieDword internalFlag = actor->GetInternalFlag();
1769 if (internalFlag&IF_ACTIVE) {
1770 if ((stance == IE_ANI_TWITCH) && (internalFlag&IF_IDLE) ) {
1771 priority = PR_DISPLAY; //display
1772 } else {
1773 priority = PR_SCRIPT; //run scripts and display
1775 } else {
1776 //dead actors are always visible on the map, but run no scripts
1777 if (stance == IE_ANI_TWITCH || stance == IE_ANI_DIE) {
1778 priority = PR_DISPLAY;
1779 } else {
1780 //isvisible flag is false (visibilitymap) here,
1781 //coz we want to reactivate creatures that
1782 //just became visible
1783 if (IsVisible(actor->Pos, false) && actor->Schedule(gametime, false) ) {
1784 priority = PR_SCRIPT; //run scripts and display, activated now
1785 //more like activate!
1786 actor->Activate();
1787 ActorSpottedByPlayer(actor);
1788 } else {
1789 priority = PR_IGNORE;
1794 //we ignore priority 2
1795 if (priority>=PR_IGNORE) continue;
1797 queue[priority][Qcount[priority]] = actor;
1798 Qcount[priority]++;
1802 //the original qsort implementation was flawed
1803 void Map::SortQueues()
1805 for (int q=0;q<QUEUE_COUNT;q++) {
1806 Actor **baseline=queue[q];
1807 int n = Qcount[q];
1808 int i = n/2;
1809 int parent, child;
1810 Actor * tmp;
1812 for (;;) {
1813 if (i>0) {
1814 i--;
1815 tmp = baseline[i];
1816 } else {
1817 n--;
1818 if (n<=0) break; //breaking loop
1819 tmp = baseline[n];
1820 baseline[n] = baseline[0];
1822 parent = i;
1823 child = i*2+1;
1824 while(child<n) {
1825 int chp = child+1;
1826 if (chp<n && baseline[chp]->Pos.y < baseline[child]->Pos.y) {
1827 child=chp;
1829 if (baseline[child]->Pos.y<tmp->Pos.y) {
1830 baseline[parent] = baseline[child];
1831 parent = child;
1832 child = parent*2+1;
1833 } else
1834 break;
1836 baseline[parent]=tmp;
1841 void Map::AddProjectile(Projectile* pro, const Point &source, ieWord actorID)
1843 proIterator iter;
1845 pro->MoveTo(this,source);
1846 pro->SetTarget(actorID);
1847 int height = pro->GetHeight();
1848 for(iter=projectiles.begin();iter!=projectiles.end() && (*iter)->GetHeight()<height; iter++) ;
1849 projectiles.insert(iter, pro);
1852 //adding projectile in order, based on its height parameter
1853 void Map::AddProjectile(Projectile* pro, const Point &source, const Point &dest)
1855 proIterator iter;
1857 pro->MoveTo(this,source);
1858 pro->SetTarget(dest);
1859 int height = pro->GetHeight();
1860 for(iter=projectiles.begin();iter!=projectiles.end() && (*iter)->GetHeight()<height; iter++) ;
1861 projectiles.insert(iter, pro);
1864 //returns the longest duration of the VVC cell named 'resource' (if it exists)
1865 //if P is empty, the position won't be checked
1866 ieDword Map::HasVVCCell(const ieResRef resource, const Point &p)
1868 scaIterator iter;
1869 ieDword ret = 0;
1871 for(iter=vvcCells.begin();iter!=vvcCells.end(); iter++) {
1872 if (!p.isempty()) {
1873 if ((*iter)->XPos!=p.x) continue;
1874 if ((*iter)->YPos!=p.y) continue;
1876 if (strnicmp(resource, (*iter)->ResName, sizeof(ieResRef) )) continue;
1877 ieDword tmp = (*iter)->GetSequenceDuration(AI_UPDATE_TIME)-(*iter)->GetCurrentFrame();
1878 if (tmp>ret) {
1879 ret = tmp;
1882 return ret;
1885 //adding videocell in order, based on its height parameter
1886 void Map::AddVVCell(ScriptedAnimation* vvc)
1888 scaIterator iter;
1890 for(iter=vvcCells.begin();iter!=vvcCells.end() && (*iter)->ZPos<vvc->ZPos; iter++) ;
1891 vvcCells.insert(iter, vvc);
1894 AreaAnimation* Map::GetAnimation(const char* Name)
1896 aniIterator iter;
1898 for(iter=animations.begin();iter!=animations.end();iter++) {
1899 AreaAnimation *anim = *iter;
1901 if (anim->Name && (strnicmp( anim->Name, Name, 32 ) == 0)) {
1902 return anim;
1905 return NULL;
1908 Spawn *Map::AddSpawn(char* Name, int XPos, int YPos, ieResRef *creatures, unsigned int count)
1910 Spawn* sp = new Spawn();
1911 strnspccpy(sp->Name, Name, 32);
1912 if (count>MAX_RESCOUNT) {
1913 count=MAX_RESCOUNT;
1915 sp->Pos.x = (ieWord) XPos;
1916 sp->Pos.y = (ieWord) YPos;
1917 sp->Count = count;
1918 sp->Creatures = (ieResRef *) calloc( count, sizeof(ieResRef) );
1919 for( unsigned int i=0;i<count;i++) {
1920 strnlwrcpy(sp->Creatures[i],creatures[i],8);
1922 spawns.push_back( sp );
1923 return sp;
1926 void Map::AddEntrance(char* Name, int XPos, int YPos, short Face)
1928 Entrance* ent = new Entrance();
1929 strncpy( ent->Name, Name, 32 );
1930 ent->Pos.x = (ieWord) XPos;
1931 ent->Pos.y = (ieWord) YPos;
1932 ent->Face = (ieWord) Face;
1933 entrances.push_back( ent );
1936 Entrance* Map::GetEntrance(const char* Name)
1938 size_t i=entrances.size();
1939 while (i--) {
1940 Entrance *e = entrances[i];
1942 if (strnicmp( e->Name, Name, 32 ) == 0) {
1943 return e;
1946 return NULL;
1949 bool Map::HasActor(Actor *actor)
1951 size_t i=actors.size();
1952 while (i--) {
1953 if (actors[i] == actor) {
1954 return true;
1957 return false;
1960 void Map::RemoveActor(Actor* actor)
1962 size_t i=actors.size();
1963 while (i--) {
1964 if (actors[i] == actor) {
1965 //BlockSearchMap(actor->Pos, actor->size, PATH_MAP_FREE);
1966 ClearSearchMapFor(actor);
1967 actors.erase( actors.begin()+i );
1968 return;
1971 printMessage("Map","RemoveActor: actor not found?",YELLOW);
1974 //returns true if none of the partymembers are on the map
1975 bool Map::CanFree()
1977 size_t i=actors.size();
1978 while (i--) {
1979 if (actors[i]->InParty) {
1980 return false;
1983 if (actors[i]->GetInternalFlag()&(IF_ACTIVE|IF_USEEXIT) ) {
1984 return false;
1987 //we expect the area to be swapped out, so we simply remove the corpses now
1988 PurgeArea(false);
1989 return true;
1992 void Map::DebugDump(bool show_actors) const
1994 printf( "DebugDump of Area %s:\n", scriptName );
1995 printf( "OutDoor: %s\n", YESNO(AreaType & AT_OUTDOOR ) );
1996 printf( "Day/Night: %s\n", YESNO(AreaType & AT_DAYNIGHT ) );
1997 printf( "Extended night: %s\n", YESNO(AreaType & AT_EXTENDED_NIGHT ) );
1998 printf( "Weather: %s\n", YESNO(AreaType & AT_WEATHER ) );
1999 printf( "Area Type: %d\n", AreaType & (AT_CITY|AT_FOREST|AT_DUNGEON) );
2000 printf( "Can rest: %s\n", YESNO(AreaType & AT_CAN_REST) );
2002 if (show_actors) {
2003 printf("\n");
2004 size_t i = actors.size();
2005 while (i--) {
2006 if (!(actors[i]->GetInternalFlag()&(IF_JUSTDIED|IF_REALLYDIED))) {
2007 printf("Actor: %s at %d.%d\n", actors[i]->GetName(1), actors[i]->Pos.x, actors[i]->Pos.y);
2013 /******************************************************************************/
2015 void Map::Leveldown(unsigned int px, unsigned int py,
2016 unsigned int& level, Point &n, unsigned int& diff)
2018 int pos;
2019 unsigned int nlevel;
2021 if (( px >= Width ) || ( py >= Height )) {
2022 return;
2023 } //walked off the map
2024 pos = py * Width + px;
2025 nlevel = MapSet[pos];
2026 if (!nlevel) {
2027 return;
2028 } //not even considered
2029 if (level <= nlevel) {
2030 return;
2032 unsigned int ndiff = level - nlevel;
2033 if (ndiff > diff) {
2034 level = nlevel;
2035 diff = ndiff;
2036 n.x = (ieWord) px;
2037 n.y = (ieWord) py;
2041 void Map::SetupNode(unsigned int x, unsigned int y, unsigned int size, unsigned int Cost)
2043 unsigned int pos;
2045 if (( x >= Width ) || ( y >= Height )) {
2046 return;
2048 pos = y * Width + x;
2049 if (MapSet[pos]) {
2050 return;
2052 if (GetBlocked(x*16+8,y*12+6,size)) {
2053 MapSet[pos] = 65535;
2054 return;
2056 MapSet[pos] = (ieWord) Cost;
2057 InternalStack.push( ( x << 16 ) | y );
2060 bool Map::AdjustPositionX(Point &goal, unsigned int radius)
2062 unsigned int minx = 0;
2063 if ((unsigned int) goal.x > radius)
2064 minx = goal.x - radius;
2065 unsigned int maxx = goal.x + radius + 1;
2066 if (maxx > Width)
2067 maxx = Width;
2069 for (unsigned int scanx = minx; scanx < maxx; scanx++) {
2070 if ((unsigned int) goal.y >= radius) {
2071 if (GetBlocked( scanx, goal.y - radius ) & PATH_MAP_PASSABLE) {
2072 goal.x = (ieWord) scanx;
2073 goal.y = (ieWord) (goal.y - radius);
2074 return true;
2077 if (goal.y + radius < Height) {
2078 if (GetBlocked( scanx, goal.y + radius ) & PATH_MAP_PASSABLE) {
2079 goal.x = (ieWord) scanx;
2080 goal.y = (ieWord) (goal.y + radius);
2081 return true;
2085 return false;
2088 bool Map::AdjustPositionY(Point &goal, unsigned int radius)
2090 unsigned int miny = 0;
2091 if ((unsigned int) goal.y > radius)
2092 miny = goal.y - radius;
2093 unsigned int maxy = goal.y + radius + 1;
2094 if (maxy > Height)
2095 maxy = Height;
2096 for (unsigned int scany = miny; scany < maxy; scany++) {
2097 if ((unsigned int) goal.x >= radius) {
2098 if (GetBlocked( goal.x - radius, scany ) & PATH_MAP_PASSABLE) {
2099 goal.x = (ieWord) (goal.x - radius);
2100 goal.y = (ieWord) scany;
2101 return true;
2104 if (goal.x + radius < Width) {
2105 if (GetBlocked( goal.x + radius, scany ) & PATH_MAP_PASSABLE) {
2106 goal.x = (ieWord) (goal.x + radius);
2107 goal.y = (ieWord) scany;
2108 return true;
2112 return false;
2115 void Map::AdjustPosition(Point &goal, unsigned int radius)
2117 unsigned int maxr = Width;
2118 if (maxr < Height) {
2119 maxr = Height;
2121 if ((unsigned int) goal.x > Width) {
2122 goal.x = (ieWord) Width;
2124 if ((unsigned int) goal.y > Height) {
2125 goal.y = (ieWord) Height;
2128 for (; radius < maxr; radius++) {
2129 //lets make it slightly random where the actor will appear
2130 if (rand()&1) {
2131 if (AdjustPositionX(goal, radius)) {
2132 return;
2134 if (AdjustPositionY(goal, radius)) {
2135 return;
2137 } else {
2138 if (AdjustPositionY(goal, radius)) {
2139 return;
2141 if (AdjustPositionX(goal, radius)) {
2142 return;
2148 //run away from dX, dY (ie.: find the best path of limited length that brings us the farthest from dX, dY)
2149 PathNode* Map::RunAway(const Point &s, const Point &d, unsigned int size, unsigned int PathLen, int flags)
2151 Point start(s.x/16, s.y/12);
2152 Point goal (d.x/16, d.y/12);
2153 unsigned int dist;
2155 //MapSet entries are made of 16 bits
2156 if (PathLen>65535) {
2157 PathLen = 65535;
2160 memset( MapSet, 0, Width * Height * sizeof( unsigned short ) );
2161 while (InternalStack.size())
2162 InternalStack.pop();
2164 if (!( GetBlocked( start.x, start.y) & PATH_MAP_PASSABLE )) {
2165 AdjustPosition( start );
2167 unsigned int pos = ( start.x << 16 ) | start.y;
2168 InternalStack.push( pos );
2169 MapSet[start.y * Width + start.x] = 1;
2170 dist = 0;
2171 Point best = start;
2172 while (InternalStack.size()) {
2173 pos = InternalStack.front();
2174 InternalStack.pop();
2175 unsigned int x = pos >> 16;
2176 unsigned int y = pos & 0xffff;
2177 long tx = ( x - goal.x );
2178 long ty = ( y - goal.y );
2179 unsigned int distance = (unsigned int) sqrt( ( double ) ( tx* tx + ty* ty ) );
2180 if (dist<distance) {
2181 best.x=(ieWord) x;
2182 best.y=(ieWord) y;
2183 dist=distance;
2186 unsigned int Cost = MapSet[y * Width + x] + NormalCost;
2187 if (Cost > PathLen) {
2188 //printf("Path not found!\n");
2189 break;
2191 SetupNode( x - 1, y - 1, size, Cost );
2192 SetupNode( x + 1, y - 1, size, Cost );
2193 SetupNode( x + 1, y + 1, size, Cost );
2194 SetupNode( x - 1, y + 1, size, Cost );
2196 Cost += AdditionalCost;
2197 SetupNode( x, y - 1, size, Cost );
2198 SetupNode( x + 1, y, size, Cost );
2199 SetupNode( x, y + 1, size, Cost );
2200 SetupNode( x - 1, y, size, Cost );
2203 //find path backwards from best to start
2204 PathNode* StartNode = new PathNode;
2205 PathNode* Return = StartNode;
2206 StartNode->Next = NULL;
2207 StartNode->x = best.x;
2208 StartNode->y = best.y;
2209 if (flags) {
2210 StartNode->orient = GetOrient( start, best );
2211 } else {
2212 StartNode->orient = GetOrient( best, start );
2214 Point p = best;
2215 unsigned int pos2 = start.y * Width + start.x;
2216 while (( pos = p.y * Width + p.x ) != pos2) {
2217 Return = new PathNode;
2218 StartNode->Parent = Return;
2219 Return->Next = StartNode;
2220 StartNode = Return;
2221 unsigned int level = MapSet[pos];
2222 unsigned int diff = 0;
2223 Point n;
2224 Leveldown( p.x, p.y + 1, level, n, diff );
2225 Leveldown( p.x + 1, p.y, level, n, diff );
2226 Leveldown( p.x - 1, p.y, level, n, diff );
2227 Leveldown( p.x, p.y - 1, level, n, diff );
2228 Leveldown( p.x - 1, p.y + 1, level, n, diff );
2229 Leveldown( p.x + 1, p.y + 1, level, n, diff );
2230 Leveldown( p.x + 1, p.y - 1, level, n, diff );
2231 Leveldown( p.x - 1, p.y - 1, level, n, diff );
2232 Return->x = n.x;
2233 Return->y = n.y;
2235 if (flags) {
2236 Return->orient = GetOrient( p, n );
2237 } else {
2238 Return->orient = GetOrient( n, p );
2240 p = n;
2241 if (!diff) {
2242 break;
2245 Return->Parent = NULL;
2246 return Return;
2249 bool Map::TargetUnreachable(const Point &s, const Point &d, unsigned int size)
2251 Point start( s.x/16, s.y/12 );
2252 Point goal ( d.x/16, d.y/12 );
2253 memset( MapSet, 0, Width * Height * sizeof( unsigned short ) );
2254 while (InternalStack.size())
2255 InternalStack.pop();
2257 if (GetBlocked( d.x, d.y, size )) {
2258 return true;
2260 if (GetBlocked( s.x, s.y, size )) {
2261 return true;
2264 unsigned int pos = ( goal.x << 16 ) | goal.y;
2265 unsigned int pos2 = ( start.x << 16 ) | start.y;
2266 InternalStack.push( pos );
2267 MapSet[goal.y * Width + goal.x] = 1;
2269 while (InternalStack.size() && pos!=pos2) {
2270 pos = InternalStack.front();
2271 InternalStack.pop();
2272 unsigned int x = pos >> 16;
2273 unsigned int y = pos & 0xffff;
2275 SetupNode( x - 1, y - 1, size, 1 );
2276 SetupNode( x + 1, y - 1, size, 1 );
2277 SetupNode( x + 1, y + 1, size, 1 );
2278 SetupNode( x - 1, y + 1, size, 1 );
2279 SetupNode( x, y - 1, size, 1 );
2280 SetupNode( x + 1, y, size, 1 );
2281 SetupNode( x, y + 1, size, 1 );
2282 SetupNode( x - 1, y, size, 1 );
2284 return pos!=pos2;
2287 /* Use this function when you target something by a straight line projectile (like a lightning bolt, arrow, etc)
2290 PathNode* Map::GetLine(const Point &start, const Point &dest, int flags)
2292 int Orientation = GetOrient(start, dest);
2293 return GetLine(start, dest, 1, Orientation, flags);
2296 PathNode* Map::GetLine(const Point &start, int Steps, int Orientation, int flags)
2298 Point dest=start;
2300 unsigned int st = Steps>=MaxVisibility?MaxVisibility-1:Steps;
2301 int p = VisibilityPerimeter*Orientation/MAX_ORIENT;
2302 dest.x += VisibilityMasks[st][p].x;
2303 dest.y += VisibilityMasks[st][p].y;
2304 //FIXME: calculate dest based on distance and orientation
2305 return GetLine(start, dest, Steps, Orientation, flags);
2308 PathNode* Map::GetLine(const Point &start, const Point &dest, int Speed, int Orientation, int flags)
2310 PathNode* StartNode = new PathNode;
2311 PathNode *Return = StartNode;
2312 StartNode->Next = NULL;
2313 StartNode->Parent = NULL;
2314 StartNode->x = start.x;
2315 StartNode->y = start.y;
2316 StartNode->orient = Orientation;
2318 int Count = 0;
2319 int Max = Distance(start,dest);
2320 for (int Steps = 0; Steps<Max; Steps++) {
2321 if (!Count) {
2322 StartNode->Next = new PathNode;
2323 StartNode->Next->Parent = StartNode;
2324 StartNode = StartNode->Next;
2325 StartNode->Next = NULL;
2326 Count=Speed;
2327 } else {
2328 Count--;
2331 Point p;
2332 p.x = (ieWord) start.x + ((dest.x - start.x) * Steps / Max);
2333 p.y = (ieWord) start.y + ((dest.y - start.y) * Steps / Max);
2335 //the path ends here as it would go off the screen, causing problems
2336 //maybe there is a better way, but i needed a quick hack to fix
2337 //the crash in projectiles
2338 if ((signed) p.x<0 || (signed) p.y<0) {
2339 return Return;
2341 if ((ieWord) p.x>Width*16 || (ieWord) p.y>Height*12) {
2342 return Return;
2345 StartNode->x = p.x;
2346 StartNode->y = p.y;
2347 StartNode->orient = Orientation;
2348 bool wall = !( GetBlocked( p ) & PATH_MAP_PASSABLE );
2349 if (wall) switch (flags) {
2350 case GL_REBOUND:
2351 Orientation = (Orientation + 8) &15;
2352 //recalculate dest (mirror it)
2353 break;
2354 case GL_PASS:
2355 break;
2356 default: //premature end
2357 return Return;
2361 return Return;
2365 * find a path from start to goal, ending at the specified distance from the
2366 * target (the goal must be in sight of the end, if 'sight' is specified)
2368 * if you don't need to find an optimal path near the goal then use FindPath
2369 * instead, but don't change this one without testing with combat and dialog,
2370 * you can't predict the goal point for those, you *must* path!
2372 PathNode* Map::FindPathNear(const Point &s, const Point &d, unsigned int size, unsigned int MinDistance, bool sight)
2374 // adjust the start/goal points to be searchmap locations
2375 Point start( s.x/16, s.y/12 );
2376 Point goal ( d.x/16, d.y/12 );
2377 Point orig_goal = goal;
2379 // re-initialise the path finding structures
2380 memset( MapSet, 0, Width * Height * sizeof( unsigned short ) );
2381 while (InternalStack.size())
2382 InternalStack.pop();
2384 // set the start point in the path finding structures
2385 unsigned int pos2 = ( goal.x << 16 ) | goal.y;
2386 unsigned int pos = ( start.x << 16 ) | start.y;
2387 InternalStack.push( pos );
2388 MapSet[start.y * Width + start.x] = 1;
2390 unsigned int squaredmindistance = MinDistance * MinDistance;
2391 bool found_path = false;
2392 while (InternalStack.size()) {
2393 pos = InternalStack.front();
2394 InternalStack.pop();
2395 unsigned int x = pos >> 16;
2396 unsigned int y = pos & 0xffff;
2398 if (pos == pos2) {
2399 // we got all the way to the target!
2400 found_path = true;
2401 break;
2402 } else if (MinDistance) {
2403 /* check minimum distance:
2404 * as an obvious optimisation we only check squared distance: this is a
2405 * possible overestimate since the sqrt Distance() rounds down
2406 * (some other optimisations could be made here, but you'd be better off
2407 * fixing the pathfinder to do A* properly)
2408 * caller should have already done PersonalDistance adjustments, this is
2409 * simply between the specified points
2412 int distx = (x*16 + 8) - d.x;
2413 int disty = (y*12 + 6) - d.y;
2414 if ((unsigned int)(distx*distx + disty*disty) <= squaredmindistance) {
2415 // we are within the minimum distance of the goal
2416 Point ourpos(x*16 + 8, y*12 + 6);
2417 // sight check is *slow* :(
2418 if (!sight || IsVisible(ourpos, d)) {
2419 // we got all the way to a suitable goal!
2420 goal = Point(x, y);
2421 found_path = true;
2422 break;
2427 unsigned int Cost = MapSet[y * Width + x] + NormalCost;
2428 if (Cost > 65500) {
2429 // cost is far too high, no path found
2430 break;
2433 // diagonal movements
2434 SetupNode( x - 1, y - 1, size, Cost );
2435 SetupNode( x + 1, y - 1, size, Cost );
2436 SetupNode( x + 1, y + 1, size, Cost );
2437 SetupNode( x - 1, y + 1, size, Cost );
2439 // direct movements
2440 Cost += AdditionalCost;
2441 SetupNode( x, y - 1, size, Cost );
2442 SetupNode( x + 1, y, size, Cost );
2443 SetupNode( x, y + 1, size, Cost );
2444 SetupNode( x - 1, y, size, Cost );
2447 // find path from goal to start
2448 PathNode* StartNode = new PathNode;
2449 PathNode* Return = StartNode;
2450 StartNode->Next = NULL;
2451 StartNode->Parent = NULL;
2452 if (!found_path) {
2453 // this is not really great, we should be finding the path that
2454 // went nearest to where we wanted
2455 StartNode->x = start.x;
2456 StartNode->y = start.y;
2457 StartNode->orient = GetOrient( goal, start );
2458 return Return;
2460 StartNode->x = goal.x;
2461 StartNode->y = goal.y;
2462 bool fixup_orient = false;
2463 if (orig_goal != goal) {
2464 StartNode->orient = GetOrient( orig_goal, goal );
2465 } else {
2466 // we pathed all the way to original goal!
2467 // we don't know correct orientation until we find previous step
2468 fixup_orient = true;
2469 StartNode->orient = GetOrient( goal, start );
2471 Point p = goal;
2472 pos2 = start.y * Width + start.x;
2473 while (( pos = p.y * Width + p.x ) != pos2) {
2474 unsigned int level = MapSet[pos];
2475 unsigned int diff = 0;
2476 Point n;
2477 Leveldown( p.x, p.y + 1, level, n, diff );
2478 Leveldown( p.x + 1, p.y, level, n, diff );
2479 Leveldown( p.x - 1, p.y, level, n, diff );
2480 Leveldown( p.x, p.y - 1, level, n, diff );
2481 Leveldown( p.x - 1, p.y + 1, level, n, diff );
2482 Leveldown( p.x + 1, p.y + 1, level, n, diff );
2483 Leveldown( p.x + 1, p.y - 1, level, n, diff );
2484 Leveldown( p.x - 1, p.y - 1, level, n, diff );
2485 if (!diff)
2486 return Return;
2488 if (fixup_orient) {
2489 // don't change orientation at end of path? this seems best
2490 StartNode->orient = GetOrient( p, n );
2493 Return = new PathNode;
2494 Return->Next = StartNode;
2495 Return->Next->Parent = Return;
2496 StartNode = Return;
2498 StartNode->x = n.x;
2499 StartNode->y = n.y;
2500 StartNode->orient = GetOrient( p, n );
2501 p = n;
2504 return Return;
2507 PathNode* Map::FindPath(const Point &s, const Point &d, unsigned int size, int MinDistance)
2509 Point start( s.x/16, s.y/12 );
2510 Point goal ( d.x/16, d.y/12 );
2511 memset( MapSet, 0, Width * Height * sizeof( unsigned short ) );
2512 while (InternalStack.size())
2513 InternalStack.pop();
2515 if (GetBlocked( d.x, d.y, size )) {
2516 AdjustPosition( goal );
2518 unsigned int pos = ( goal.x << 16 ) | goal.y;
2519 unsigned int pos2 = ( start.x << 16 ) | start.y;
2520 InternalStack.push( pos );
2521 MapSet[goal.y * Width + goal.x] = 1;
2523 while (InternalStack.size()) {
2524 pos = InternalStack.front();
2525 InternalStack.pop();
2526 unsigned int x = pos >> 16;
2527 unsigned int y = pos & 0xffff;
2529 if (pos == pos2) {
2530 //We've found _a_ path
2531 //printf("GOAL!!!\n");
2532 break;
2534 unsigned int Cost = MapSet[y * Width + x] + NormalCost;
2535 if (Cost > 65500) {
2536 //printf("Path not found!\n");
2537 break;
2539 SetupNode( x - 1, y - 1, size, Cost );
2540 SetupNode( x + 1, y - 1, size, Cost );
2541 SetupNode( x + 1, y + 1, size, Cost );
2542 SetupNode( x - 1, y + 1, size, Cost );
2544 Cost += AdditionalCost;
2545 SetupNode( x, y - 1, size, Cost );
2546 SetupNode( x + 1, y, size, Cost );
2547 SetupNode( x, y + 1, size, Cost );
2548 SetupNode( x - 1, y, size, Cost );
2551 //find path from start to goal
2552 PathNode* StartNode = new PathNode;
2553 PathNode* Return = StartNode;
2554 StartNode->Next = NULL;
2555 StartNode->Parent = NULL;
2556 StartNode->x = start.x;
2557 StartNode->y = start.y;
2558 StartNode->orient = GetOrient( goal, start );
2559 if (pos != pos2) {
2560 return Return;
2562 Point p = start;
2563 pos2 = goal.y * Width + goal.x;
2564 while (( pos = p.y * Width + p.x ) != pos2) {
2565 StartNode->Next = new PathNode;
2566 StartNode->Next->Parent = StartNode;
2567 StartNode = StartNode->Next;
2568 StartNode->Next = NULL;
2569 unsigned int level = MapSet[pos];
2570 unsigned int diff = 0;
2571 Point n;
2572 Leveldown( p.x, p.y + 1, level, n, diff );
2573 Leveldown( p.x + 1, p.y, level, n, diff );
2574 Leveldown( p.x - 1, p.y, level, n, diff );
2575 Leveldown( p.x, p.y - 1, level, n, diff );
2576 Leveldown( p.x - 1, p.y + 1, level, n, diff );
2577 Leveldown( p.x + 1, p.y + 1, level, n, diff );
2578 Leveldown( p.x + 1, p.y - 1, level, n, diff );
2579 Leveldown( p.x - 1, p.y - 1, level, n, diff );
2580 if (!diff)
2581 return Return;
2582 StartNode->x = n.x;
2583 StartNode->y = n.y;
2584 StartNode->orient = GetOrient( n, p );
2585 p = n;
2587 //stepping back on the calculated path
2588 if (MinDistance) {
2589 while (StartNode->Parent) {
2590 Point tar;
2592 tar.x=StartNode->Parent->x*16;
2593 tar.y=StartNode->Parent->y*12;
2594 int dist = Distance(tar,d);
2595 if (dist+14>=MinDistance) {
2596 break;
2598 StartNode = StartNode->Parent;
2599 delete StartNode->Next;
2600 StartNode->Next = NULL;
2603 return Return;
2606 //single point visible or not (visible/exploredbitmap)
2607 //if explored = true then explored otherwise currently visible
2608 bool Map::IsVisible(const Point &pos, int explored)
2610 if (!VisibleBitmap)
2611 return false;
2612 int sX=pos.x/32;
2613 int sY=pos.y/32;
2614 if (sX<0) return false;
2615 if (sY<0) return false;
2616 int w = TMap->XCellCount * 2 + LargeFog;
2617 int h = TMap->YCellCount * 2 + LargeFog;
2618 if (sX>=w) return false;
2619 if (sY>=h) return false;
2620 int b0 = (sY * w) + sX;
2621 int by = b0/8;
2622 int bi = 1<<(b0%8);
2624 if (explored) return (ExploredBitmap[by] & bi)!=0;
2625 return (VisibleBitmap[by] & bi)!=0;
2628 //point a is visible from point b (searchmap)
2629 bool Map::IsVisible(const Point &s, const Point &d)
2631 int sX=s.x/16;
2632 int sY=s.y/12;
2633 int dX=d.x/16;
2634 int dY=d.y/12;
2635 int diffx = sX - dX;
2636 int diffy = sY - dY;
2638 // we basically draw a 'line' from (sX, sY) to (dX, dY)
2639 // we want to move along the larger axis, to make sure we don't miss anything
2640 if (abs( diffx ) >= abs( diffy )) {
2641 // (sX - startX)/elevationy = (sX - startX)/fabs(diffx) * diffy
2642 double elevationy = fabs((double)diffx ) / diffy;
2643 if (sX > dX) {
2644 // right to left
2645 for (int startx = sX; startx >= dX; startx--) {
2646 // sX - startx >= 0, so subtract (due to sign of diffy)
2647 //if (GetBlocked( startx, sY - ( int ) ( ( sX - startx ) / elevationy ) ) & PATH_MAP_NO_SEE)
2648 if (GetBlocked( startx, sY - ( int ) ( ( sX - startx ) / elevationy ) ) & PATH_MAP_SIDEWALL)
2649 return false;
2651 } else {
2652 // left to right
2653 for (int startx = sX; startx <= dX; startx++) {
2654 // sX - startx <= 0, so add (due to sign of diffy)
2655 //if (GetBlocked( startx, sY + ( int ) ( ( sX - startx ) / elevationy ) ) & PATH_MAP_NO_SEE)
2656 if (GetBlocked( startx, sY + ( int ) ( ( sX - startx ) / elevationy ) ) & PATH_MAP_SIDEWALL)
2657 return false;
2660 } else {
2661 // (sY - startY)/elevationx = (sY - startY)/fabs(diffy) * diffx
2662 double elevationx = fabs((double)diffy ) / diffx;
2663 if (sY > dY) {
2664 // bottom to top
2665 for (int starty = sY; starty >= dY; starty--) {
2666 // sY - starty >= 0, so subtract (due to sign of diffx)
2667 //if (GetBlocked( sX - ( int ) ( ( sY - starty ) / elevationx ), starty ) & PATH_MAP_NO_SEE)
2668 if (GetBlocked( sX - ( int ) ( ( sY - starty ) / elevationx ), starty ) & PATH_MAP_SIDEWALL)
2669 return false;
2671 } else {
2672 // top to bottom
2673 for (int starty = sY; starty <= dY; starty++) {
2674 // sY - starty <= 0, so add (due to sign of diffx)
2675 //if (GetBlocked( sX + ( int ) ( ( sY - starty ) / elevationx ), starty ) & PATH_MAP_NO_SEE)
2676 if (GetBlocked( sX + ( int ) ( ( sY - starty ) / elevationx ), starty ) & PATH_MAP_SIDEWALL)
2677 return false;
2681 return true;
2684 //returns direction of area boundary, returns -1 if it isn't a boundary
2685 int Map::WhichEdge(const Point &s)
2687 unsigned int sX=s.x/16;
2688 unsigned int sY=s.y/12;
2689 if (!(GetBlocked( sX, sY )&PATH_MAP_TRAVEL)) {
2690 printMessage("Map"," ",YELLOW);
2691 printf("This isn't a travel region [%d.%d]?\n",sX, sY);
2692 return -1;
2694 sX*=Height;
2695 sY*=Width;
2696 if (sX>sY) { //north or east
2697 if (Width*Height>sX+sY) { //
2698 return WMP_NORTH;
2700 return WMP_EAST;
2702 //south or west
2703 if (Width*Height<sX+sY) { //
2704 return WMP_SOUTH;
2706 return WMP_WEST;
2709 //--------ambients----------------
2710 void Map::SetupAmbients()
2712 AmbientMgr *ambim = core->GetAudioDrv()->GetAmbientMgr();
2713 if (!ambim) return;
2714 ambim->reset();
2715 ambim->setAmbients( ambients );
2717 //--------mapnotes----------------
2718 //text must be a pointer we can claim ownership of
2719 void Map::AddMapNote(const Point &point, int color, char *text, ieStrRef strref)
2721 MapNote *mn = new MapNote;
2723 mn->strref = strref;
2724 mn->Pos = point;
2725 mn->color = (ieWord) color;
2726 mn->text = text;
2727 RemoveMapNote(point); //delete previous mapnote
2728 mapnotes.push_back(mn);
2731 void Map::RemoveMapNote(const Point &point)
2733 size_t i = mapnotes.size();
2734 while (i--) {
2735 if ((point.x==mapnotes[i]->Pos.x) &&
2736 (point.y==mapnotes[i]->Pos.y)) {
2737 delete mapnotes[i];
2738 mapnotes.erase(mapnotes.begin()+i);
2743 MapNote *Map::GetMapNote(const Point &point)
2745 size_t i = mapnotes.size();
2746 while (i--) {
2747 if (Distance(point, mapnotes[i]->Pos) < 10 ) {
2748 return mapnotes[i];
2751 return NULL;
2753 //--------spawning------------------
2754 void Map::LoadIniSpawn()
2756 INISpawn = new IniSpawn(this);
2757 INISpawn->InitSpawn(WEDResRef);
2760 void Map::SpawnCreature(const Point &pos, const char *CreName, int radius)
2762 SpawnGroup *sg=NULL;
2763 Actor *creature;
2764 void* lookup;
2765 if ( !Spawns.Lookup( CreName, lookup) ) {
2766 creature = gamedata->GetCreature(CreName);
2767 if ( creature ) {
2768 AddActor(creature);
2769 creature->SetPosition( pos, true, radius );
2770 creature->RefreshEffects(NULL);
2772 return;
2774 sg = (SpawnGroup*)lookup;
2775 unsigned int count = 0;
2776 int amount = core->GetGame()->GetPartyLevel(true);
2777 // if the difficulty is too high, distribute it equally over all the
2778 // critters and summon as many as the summed difficulty allows
2779 if (amount - (signed)sg->Level < 0) {
2780 unsigned int share = sg->Level/sg->Count;
2781 amount -= share;
2782 if (amount < 0) {
2783 // a single critter is also too powerful
2784 return;
2786 while (amount >= 0) {
2787 count++;
2788 amount -= share;
2790 } else {
2791 count = sg->Count;
2794 while ( count-- ) {
2795 creature = gamedata->GetCreature(sg->ResRefs[count]);
2796 if ( creature ) {
2797 AddActor(creature);
2798 creature->SetPosition( pos, true, radius );
2799 creature->RefreshEffects(NULL);
2804 void Map::TriggerSpawn(Spawn *spawn)
2806 //is it still active
2807 if (!spawn->Enabled) {
2808 return;
2810 //check schedule
2811 ieDword bit = 1<<((core->GetGame()->GameTime/AI_UPDATE_TIME)%7200/300);
2812 if (!(spawn->appearance & bit)) {
2813 return;
2816 //check day or night chance
2817 if (rand()%100>spawn->DayChance) {
2818 return;
2820 // the difficulty check is done in SpawnCreature
2821 //create spawns
2822 for(unsigned int i = 0;i<spawn->Count;i++) {
2823 SpawnCreature(spawn->Pos, spawn->Creatures[i], 0);
2825 //disable spawnpoint
2826 spawn->Enabled = 0;
2829 //--------restheader----------------
2831 Every spawn has a difficulty associated with it. For CREs this is the xp stat
2832 and for groups it's the value in the difficulty row.
2833 For every spawn, the difficulty sum of all spawns up to now (including the
2834 current) is compared against (party level * rest header difficulty). If it's
2835 greater, the spawning is aborted. If all the other conditions are true, at
2836 least one creature is summoned, regardless the difficulty cap.
2838 bool Map::Rest(const Point &pos, int hours, int day)
2840 if (!RestHeader.CreatureNum || !RestHeader.Enabled || !RestHeader.Maximum) {
2841 return false;
2844 //based on ingame timer
2845 int chance=day?RestHeader.DayChance:RestHeader.NightChance;
2846 int spawncount = 1;
2847 int spawnamount = core->GetGame()->GetPartyLevel(true) * RestHeader.Difficulty;
2848 if (spawnamount < 1) spawnamount = 1;
2849 for (int i=0;i<hours;i++) {
2850 if ( rand()%100<chance ) {
2851 int idx = rand()%RestHeader.CreatureNum;
2852 Actor *creature = gamedata->GetCreature(RestHeader.CreResRef[idx]);
2853 if (!creature) continue;
2854 // ensure a minimum power level, since many creatures have this as 0
2855 int cpl = creature->Modified[IE_XP] ? creature->Modified[IE_XP] : 1;
2857 displaymsg->DisplayString( RestHeader.Strref[idx], 0x00404000, IE_STR_SOUND );
2858 while (spawnamount > 0 && spawncount <= RestHeader.Maximum) {
2859 SpawnCreature(pos, RestHeader.CreResRef[idx], 20);
2860 spawnamount -= cpl;
2861 spawncount++;
2863 return true;
2866 return false;
2869 //--------explored bitmap-----------
2870 int Map::GetExploredMapSize() const
2872 int x = TMap->XCellCount*2;
2873 int y = TMap->YCellCount*2;
2874 if (LargeFog) {
2875 x++;
2876 y++;
2878 return (x*y+7)/8;
2881 void Map::Explore(int setreset)
2883 memset (ExploredBitmap, setreset, GetExploredMapSize() );
2886 void Map::SetMapVisibility(int setreset)
2888 memset( VisibleBitmap, setreset, GetExploredMapSize() );
2891 // x, y are in tile coordinates
2892 void Map::ExploreTile(const Point &pos)
2894 int h = TMap->YCellCount * 2 + LargeFog;
2895 int y = pos.y/32;
2896 if (y < 0 || y >= h)
2897 return;
2899 int w = TMap->XCellCount * 2 + LargeFog;
2900 int x = pos.x/32;
2901 if (x < 0 || x >= w)
2902 return;
2904 int b0 = (y * w) + x;
2905 int by = b0/8;
2906 int bi = 1<<(b0%8);
2908 ExploredBitmap[by] |= bi;
2909 VisibleBitmap[by] |= bi;
2912 void Map::ExploreMapChunk(const Point &Pos, int range, int los)
2914 Point Tile;
2916 if (range>MaxVisibility) {
2917 range=MaxVisibility;
2919 int p=VisibilityPerimeter;
2920 while (p--) {
2921 int Pass = 2;
2922 bool block = false;
2923 bool sidewall = false ;
2924 for (int i=0;i<range;i++) {
2925 Tile.x = Pos.x+VisibilityMasks[i][p].x;
2926 Tile.y = Pos.y+VisibilityMasks[i][p].y;
2928 if (los) {
2929 if (!block) {
2930 int type = GetBlocked(Tile);
2931 if (type & PATH_MAP_NO_SEE) {
2932 block=true;
2933 } else if (type & PATH_MAP_SIDEWALL) {
2934 sidewall = true;
2935 } else if (sidewall)
2937 block=true ;
2940 if (block) {
2941 Pass--;
2942 if (!Pass) break;
2945 ExploreTile(Tile);
2950 void Map::UpdateFog()
2952 if (!(core->FogOfWar&FOG_DRAWFOG) ) {
2953 SetMapVisibility( -1 );
2954 return;
2957 SetMapVisibility( 0 );
2958 for (unsigned int e = 0; e<actors.size(); e++) {
2959 Actor *actor = actors[e];
2960 if (!actor->Modified[ IE_EXPLORE ] ) continue;
2961 int state = actor->Modified[IE_STATE_ID];
2962 if (state & STATE_CANTSEE) continue;
2963 int vis2 = actor->Modified[IE_VISUALRANGE];
2964 if ((state&STATE_BLIND) || (vis2<2)) vis2=2; //can see only themselves
2965 ExploreMapChunk (actor->Pos, vis2+actor->GetAnims()->GetCircleSize(), 1);
2966 Spawn *sp = GetSpawnRadius(actor->Pos, SPAWN_RANGE); //30 * 12
2967 if (sp) {
2968 TriggerSpawn(sp);
2973 //Valid values are - PATH_MAP_FREE, PATH_MAP_PC, PATH_MAP_NPC
2974 void Map::BlockSearchMap(const Point &Pos, unsigned int size, unsigned int value)
2976 // We block a circle of radius size-1 around (px,py)
2977 // Note that this does not exactly match BG2. BG2's approximations of
2978 // these circles are slightly different for sizes 6 and up.
2980 // Note: this is a larger circle than the one tested in GetBlocked.
2981 // This means that an actor can get closer to a wall than to another
2982 // actor. This matches the behaviour of the original BG2.
2984 if (size > MAX_CIRCLESIZE) size = MAX_CIRCLESIZE;
2985 if (size < 2) size = 2;
2986 unsigned int ppx = Pos.x/16;
2987 unsigned int ppy = Pos.y/12;
2988 unsigned int r=(size-1)*(size-1)+1;
2989 if (size == 1) r = 0;
2990 for (unsigned int i=0; i<size; i++) {
2991 for (unsigned int j=0; j<size; j++) {
2992 if (i*i+j*j <= r) {
2993 unsigned char tmp;
2995 tmp = SearchMap->GetAt(ppx+i,ppy+j)&PATH_MAP_NOTACTOR;
2996 SearchMap->SetAt(ppx+i,ppy+j,tmp|value);
2998 tmp = SearchMap->GetAt(ppx+i,ppy-j)&PATH_MAP_NOTACTOR;
2999 SearchMap->SetAt(ppx+i,ppy-j,tmp|value);
3001 tmp = SearchMap->GetAt(ppx-i,ppy+j)&PATH_MAP_NOTACTOR;
3002 SearchMap->SetAt(ppx-i,ppy+j,tmp|value);
3004 tmp = SearchMap->GetAt(ppx-i,ppy-j)&PATH_MAP_NOTACTOR;
3005 SearchMap->SetAt(ppx-i,ppy-j,tmp|value);
3011 Spawn* Map::GetSpawn(const char* Name)
3013 for (size_t i = 0; i < spawns.size(); i++) {
3014 Spawn* sp = spawns[i];
3016 if (stricmp( sp->Name, Name ) == 0)
3017 return sp;
3019 return NULL;
3022 Spawn *Map::GetSpawnRadius(const Point &point, unsigned int radius)
3024 for (size_t i = 0; i < spawns.size(); i++) {
3025 Spawn* sp = spawns[i];
3027 if (Distance(point, sp->Pos)<radius) {
3028 return sp;
3031 return NULL;
3034 int Map::ConsolidateContainers()
3036 int itemcount = 0;
3037 int containercount = (int) TMap->GetContainerCount();
3038 while (containercount--) {
3039 Container * c = TMap->GetContainer( containercount);
3041 if (TMap->CleanupContainer(c) ) {
3042 continue;
3044 itemcount += c->inventory.GetSlotCount();
3046 return itemcount;
3049 //Pos could be [-1,-1] in which case it copies the ground piles to their
3050 //original position in the second area
3051 void Map::CopyGroundPiles(Map *othermap, const Point &Pos)
3053 int containercount = (int) TMap->GetContainerCount();
3054 while (containercount--) {
3055 Container * c = TMap->GetContainer( containercount);
3056 if (c->Type==IE_CONTAINER_PILE) {
3057 //creating (or grabbing) the container in the other map at the given position
3058 Container *othercontainer;
3059 if (Pos.isempty()) {
3060 othercontainer = othermap->GetPile(c->Pos);
3061 } else {
3062 othercontainer = othermap->GetPile(Pos);
3064 //transfer the pile to the other container
3065 unsigned int i=c->inventory.GetSlotCount();
3066 while (i--) {
3067 CREItem *item = c->RemoveItem(i, 0);
3068 othercontainer->AddItem(item);
3074 void Map::MoveVisibleGroundPiles(const Point &Pos)
3076 //creating the container at the given position
3077 Container *othercontainer;
3078 othercontainer = GetPile(Pos);
3080 int containercount = (int) TMap->GetContainerCount();
3081 while (containercount--) {
3082 Container * c = TMap->GetContainer( containercount);
3083 if (c->Type==IE_CONTAINER_PILE && IsVisible(c->Pos, true)) {
3084 //transfer the pile to the other container
3085 unsigned int i=c->inventory.GetSlotCount();
3086 while (i--) {
3087 CREItem *item = c->RemoveItem(i, 0);
3088 othercontainer->AddItem(item);
3094 Container *Map::GetPile(Point position)
3096 Point tmp[4];
3097 char heapname[32];
3099 //converting to search square
3100 position.x=position.x/16;
3101 position.y=position.y/12;
3102 sprintf(heapname,"heap_%hd.%hd",position.x,position.y);
3103 //pixel position is centered on search square
3104 position.x=position.x*16+8;
3105 position.y=position.y*12+6;
3106 Container *container = TMap->GetContainer(position,IE_CONTAINER_PILE);
3107 if (!container) {
3108 //bounding box covers the search square
3109 tmp[0].x=position.x-8;
3110 tmp[0].y=position.y-6;
3111 tmp[1].x=position.x+8;
3112 tmp[1].y=position.y-6;
3113 tmp[2].x=position.x+8;
3114 tmp[2].y=position.y+6;
3115 tmp[3].x=position.x-8;
3116 tmp[3].y=position.y+6;
3117 Gem_Polygon* outline = new Gem_Polygon( tmp, 4 );
3118 container = AddContainer(heapname, IE_CONTAINER_PILE, outline);
3119 container->Pos=position;
3121 return container;
3124 void Map::AddItemToLocation(const Point &position, CREItem *item)
3126 Container *container = GetPile(position);
3127 container->AddItem(item);
3130 Container* Map::AddContainer(const char* Name, unsigned short Type,
3131 Gem_Polygon* outline)
3133 Container* c = new Container();
3134 c->SetScriptName( Name );
3135 c->Type = Type;
3136 c->outline = outline;
3137 c->SetMap(this);
3138 TMap->AddContainer( c );
3139 return c;
3142 int Map::GetCursor( const Point &p)
3144 if (!IsVisible( p, true ) ) {
3145 return IE_CURSOR_INVALID;
3147 switch (GetBlocked( p ) & (PATH_MAP_PASSABLE|PATH_MAP_TRAVEL)) {
3148 case 0:
3149 return IE_CURSOR_BLOCKED;
3150 case PATH_MAP_PASSABLE:
3151 return IE_CURSOR_WALK;
3152 default:
3153 return IE_CURSOR_TRAVEL;
3157 bool Map::HasWeather()
3159 if ((AreaType & (AT_WEATHER|AT_OUTDOOR) ) != (AT_WEATHER|AT_OUTDOOR) ) {
3160 return false;
3162 return true;
3165 int Map::GetWeather()
3167 if (Rain>=core->Roll(1,100,0) ) {
3168 if (Lightning>=core->Roll(1,100,0) ) {
3169 return WB_LIGHTNING|WB_RAIN;
3171 return WB_RAIN;
3173 if (Snow>=core->Roll(1,100,0) ) {
3174 return WB_SNOW;
3176 if (Fog>=core->Roll(1,100,0) ) {
3177 return WB_FOG;
3179 return WB_NORMAL;
3182 void Map::FadeSparkle(const Point &pos, bool forced)
3184 spaIterator iter;
3186 for(iter=particles.begin(); iter!=particles.end();iter++) {
3187 if ((*iter)->MatchPos(pos) ) {
3188 if (forced) {
3189 //particles.erase(iter);
3190 (*iter)->SetPhase(P_EMPTY);
3191 } else {
3192 (*iter)->SetPhase(P_FADE);
3194 return;
3199 void Map::Sparkle(ieDword duration, ieDword color, ieDword type, const Point &pos, unsigned int FragAnimID)
3201 int style, path, grow, size, width, ttl;
3203 //the high word is ignored in the original engine (compatibility hack)
3204 switch(type&0xffff) {
3205 case SPARKLE_SHOWER: //simple falling sparks
3206 path = SP_PATH_FALL;
3207 grow = SP_SPAWN_FULL;
3208 size = 100;
3209 width = 40;
3210 ttl = duration;
3211 break;
3212 case SPARKLE_PUFF:
3213 path = SP_PATH_FOUNT; //sparks go up and down
3214 grow = SP_SPAWN_SOME;
3215 size = 40;
3216 width = 40;
3217 ttl = core->GetGame()->GameTime+25;
3218 break;
3219 case SPARKLE_EXPLOSION: //this isn't in the original engine, but it is a nice effect to have
3220 path = SP_PATH_EXPL;
3221 grow = SP_SPAWN_SOME;
3222 size = 40;
3223 width = 40;
3224 ttl = core->GetGame()->GameTime+25;
3225 break;
3226 default:
3227 path = SP_PATH_FLIT;
3228 grow = SP_SPAWN_SOME;
3229 size = 100;
3230 width = 40;
3231 ttl = duration;
3232 break;
3234 Particles *sparkles = new Particles(size);
3235 sparkles->SetOwner(this);
3236 sparkles->SetRegion(pos.x-width/2, pos.y-30, width, 30);
3237 sparkles->SetTimeToLive(ttl);
3239 if (FragAnimID) {
3240 style = SP_TYPE_BITMAP;
3241 sparkles->SetBitmap(FragAnimID);
3243 else {
3244 style = SP_TYPE_POINT;
3246 sparkles->SetType(style, path, grow);
3247 sparkles->SetColor(color);
3248 sparkles->SetPhase(P_GROW);
3250 spaIterator iter;
3251 for(iter=particles.begin(); (iter!=particles.end()) && ((*iter)->GetHeight()<pos.y); iter++) ;
3252 particles.insert(iter, sparkles);
3255 //remove flags from actor if it has left the trigger area it had last entered
3256 void Map::ClearTrap(Actor *actor, ieDword InTrap)
3258 InfoPoint *trap = TMap->GetInfoPoint(InTrap);
3259 if (!trap) {
3260 actor->SetInTrap(0);
3261 } else {
3262 if(!trap->outline->PointIn(actor->Pos)) {
3263 actor->SetInTrap(0);
3268 void Map::SetTrackString(ieStrRef strref, int flg, int difficulty)
3270 trackString = strref;
3271 trackFlag = flg;
3272 trackDiff = (ieWord) difficulty;
3275 bool Map::DisplayTrackString(Actor *target)
3277 // this stat isn't saved
3278 // according to the HoW manual the chance of success is:
3279 // +5% for every three levels and +5% per point of wisdom
3280 int skill = target->GetStat(IE_TRACKING);
3281 skill += (target->GetStat(IE_LEVEL)/3)*5 + target->GetStat(IE_WIS)*5;
3282 if (core->Roll(1, 100, trackDiff) > skill) {
3283 displaymsg->DisplayConstantStringName(STR_TRACKINGFAILED, 0xd7d7be, target);
3284 return true;
3286 if (trackFlag) {
3287 char * str = core->GetString( trackString);
3288 core->GetTokenDictionary()->SetAt( "CREATURE", str);
3289 displaymsg->DisplayConstantStringName(STR_TRACKING, 0xd7d7be, target);
3290 return false;
3292 displaymsg->DisplayStringName(trackString, 0xd7d7be, target, 0);
3293 return false;
3296 // returns a lightness level in the range of [0-100]
3297 // since the lightmap is much smaller than the area, we need to interpolate
3298 unsigned int Map::GetLightLevel(const Point &Pos)
3300 Color c = LightMap->GetPixel(Pos.x/16, Pos.y/12);
3301 // at night/dusk/dawn the lightmap color is adjusted by the color overlay. (Only get's darker.)
3302 const Color *tint = core->GetGame()->GetGlobalTint();
3303 if (tint) {
3304 return ((c.r-tint->r)*114 + (c.g-tint->g)*587 + (c.b-tint->b)*299)/2550;
3306 return (c.r*114+c.g*587+c.b*299)/2550;
3309 ////////////////////AreaAnimation//////////////////
3310 //Area animation
3312 AreaAnimation::AreaAnimation()
3314 animation=NULL;
3315 animcount=0;
3316 palette=NULL;
3317 covers=NULL;
3320 AreaAnimation::~AreaAnimation()
3322 for(int i=0;i<animcount;i++) {
3323 if (animation[i]) {
3324 delete (animation[i]);
3327 free(animation);
3328 gamedata->FreePalette(palette, PaletteRef);
3329 if (covers) {
3330 for(int i=0;i<animcount;i++) {
3331 delete covers[i];
3333 free (covers);
3337 Animation *AreaAnimation::GetAnimationPiece(AnimationFactory *af, int animCycle)
3339 Animation *anim = af->GetCycle( ( unsigned char ) animCycle );
3340 if (!anim)
3341 anim = af->GetCycle( 0 );
3342 if (!anim) {
3343 printf("Cannot load animation: %s\n", BAM);
3344 return NULL;
3346 //this will make the animation stop when the game is stopped
3347 //a possible gemrb feature to have this flag settable in .are
3348 anim->gameAnimation = true;
3349 anim->pos = frame;
3350 anim->Flags = Flags;
3351 anim->x = Pos.x;
3352 anim->y = Pos.y;
3353 if (anim->Flags&A_ANI_MIRROR) {
3354 anim->MirrorAnimation();
3357 return anim;
3360 void AreaAnimation::InitAnimation()
3362 AnimationFactory* af = ( AnimationFactory* )
3363 gamedata->GetFactoryResource( BAM, IE_BAM_CLASS_ID );
3364 if (!af) {
3365 printf("Cannot load animation: %s\n", BAM);
3366 return;
3369 //freeing up the previous animation
3370 for(int i=0;i<animcount;i++) {
3371 if (animation[i]) {
3372 delete (animation[i]);
3375 free(animation);
3377 if (Flags & A_ANI_ALLCYCLES) {
3378 animcount = (int) af->GetCycleCount();
3380 animation = (Animation **) malloc(animcount * sizeof(Animation *) );
3381 for(int j=0;j<animcount;j++) {
3382 animation[j]=GetAnimationPiece(af, j);
3384 } else {
3385 animcount = 1;
3386 animation = (Animation **) malloc( sizeof(Animation *) );
3387 animation[0]=GetAnimationPiece(af, sequence);
3389 if (Flags & A_ANI_PALETTE) {
3390 SetPalette(PaletteRef);
3392 if (Flags&A_ANI_BLEND) {
3393 BlendAnimation();
3397 void AreaAnimation::SetPalette(ieResRef Pal)
3399 Flags |= A_ANI_PALETTE;
3400 gamedata->FreePalette(palette, PaletteRef);
3401 strnlwrcpy(PaletteRef, Pal, 8);
3402 palette = gamedata->GetPalette(PaletteRef);
3403 if (Flags&A_ANI_BLEND) {
3404 //re-blending after palette change
3405 BlendAnimation();
3409 void AreaAnimation::BlendAnimation()
3411 //Warning! This function will modify a shared palette
3412 if (!palette) {
3413 // CHECKME: what should we do here? Currently copying palette
3414 // from first frame of first animation
3416 if (animcount == 0 || !animation[0]) return;
3417 Sprite2D* spr = animation[0]->GetFrame(0);
3418 if (!spr) return;
3419 palette = spr->GetPalette()->Copy();
3420 PaletteRef[0] = 0;
3422 palette->CreateShadedAlphaChannel();
3425 bool AreaAnimation::Schedule(ieDword gametime)
3427 if (!(Flags&A_ANI_ACTIVE) ) {
3428 return false;
3431 //check for schedule
3432 ieDword bit = 1<<((gametime/AI_UPDATE_TIME)%7200/300);
3433 if (appearance & bit) {
3434 return true;
3436 return false;
3439 void AreaAnimation::Draw(const Region &screen, Map *area)
3441 int ac=animcount;
3442 Video* video = core->GetVideoDriver();
3444 //always draw the animation tinted because tint is also used for
3445 //transparency
3446 Color tint = {255,255,255,255-(ieByte) transparency};
3447 if ((Flags&A_ANI_NO_SHADOW)) {
3448 tint = area->LightMap->GetPixel( Pos.x / 16, Pos.y / 12);
3449 tint.a = 255-(ieByte) transparency;
3451 if (!(Flags&A_ANI_NO_WALL)) {
3452 if (!covers) {
3453 covers=(SpriteCover **) calloc( animcount, sizeof(SpriteCover *) );
3456 ac=animcount;
3457 while (ac--) {
3458 Animation *anim = animation[ac];
3459 Sprite2D *frame = anim->NextFrame();
3460 if(covers) {
3461 if(!covers[ac] || !covers[ac]->Covers(Pos.x, Pos.y, frame->XPos, frame->YPos, frame->Width, frame->Height)) {
3462 delete covers[ac];
3463 covers[ac] = area->BuildSpriteCover(Pos.x, Pos.y, -anim->animArea.x,
3464 -anim->animArea.y, anim->animArea.w, anim->animArea.h, 0);
3467 video->BlitGameSprite( frame, Pos.x + screen.x, Pos.y + screen.y,
3468 BLIT_TINTED, tint, covers?covers[ac]:0, palette, &screen );
3472 //change the tileset if needed and possible, return true if changed
3473 bool Map::ChangeMap(bool day_or_night)
3475 //no need of change if the area is not extended night
3476 if (! (AreaType&AT_EXTENDED_NIGHT)) return false;
3477 //no need of change if the area already has the right tilemap
3478 if ((DayNight == day_or_night) && GetTileMap()) return false;
3480 PluginHolder<MapMgr> mM(IE_ARE_CLASS_ID);
3481 //no need to open and read the .are file again
3482 //using the ARE class for this because ChangeMap is similar to LoadMap
3483 //it loads the lightmap and the minimap too, besides swapping the tileset
3484 mM->ChangeMap(this, day_or_night);
3485 return true;
3488 void Map::SeeSpellCast(Scriptable *caster, ieDword spell)
3490 if (caster->Type!=ST_ACTOR) {
3491 return;
3494 LastCasterSeen = ((Actor *) caster)->GetID();
3495 LastSpellSeen = spell;
3497 size_t i = actors.size();
3498 while (i--) {
3499 Actor* witness = actors[i];
3500 if (CanSee(witness, caster, true, 0)) {
3501 witness->LastSpellSeen=LastSpellSeen;
3502 witness->LastCasterSeen=LastCasterSeen;