Split GameScript into subdirectory.
[gemrb.git] / gemrb / core / Map.cpp
blob067cc6630bcf610adc1c434f4c52ff893c248e48
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 "PathFinder.h"
38 #include "Projectile.h"
39 #include "ScriptedAnimation.h"
40 #include "TileMap.h"
41 #include "Video.h"
42 #include "WorldMap.h"
43 #include "strrefs.h"
44 #include "GameScript/GSUtils.h"
45 #include "GUI/GameControl.h"
47 #include <cmath>
48 #include <cassert>
50 #define YESNO(x) ( (x)?"Yes":"No")
52 // TODO: fix this hardcoded resource reference
53 static ieResRef PortalResRef={"EF03TPR3"};
54 static unsigned int PortalTime = 15;
55 static unsigned int MAX_CIRCLESIZE = 8;
56 static int MaxVisibility = 30;
57 static int VisibilityPerimeter; //calculated from MaxVisibility
58 static int NormalCost = 10;
59 static int AdditionalCost = 4;
60 static unsigned char Passable[16] = {
61 4, 1, 1, 1, 1, 1, 1, 1, 0, 1, 8, 0, 0, 0, 3, 1
63 static Point **VisibilityMasks=NULL;
65 static bool PathFinderInited = false;
66 static Variables Spawns;
67 static int LargeFog;
68 static ieWord globalActorCounter;
70 void ReleaseSpawnGroup(void *poi)
72 delete (SpawnGroup *) poi;
75 void Map::ReleaseMemory()
77 if (VisibilityMasks) {
78 for (int i=0;i<MaxVisibility;i++) {
79 free(VisibilityMasks[i]);
81 free(VisibilityMasks);
82 VisibilityMasks=NULL;
85 Spawns.RemoveAll(ReleaseSpawnGroup);
86 PathFinderInited = false;
89 inline static AnimationObjectType SelectObject(Actor *actor, int q, AreaAnimation *a, ScriptedAnimation *sca, Particles *spark, Projectile *pro)
91 int actorh;
92 if (actor) {
93 actorh = actor->Pos.y;
94 if (q) actorh = 0;
95 } else {
96 actorh = 0x7fffffff;
99 int aah;
100 if (a) {
101 aah = a->Pos.y;//+a->height;
102 } else {
103 aah = 0x7fffffff;
106 int scah;
107 if (sca) {
108 scah = sca->YPos;//+sca->ZPos;
109 } else {
110 scah = 0x7fffffff;
113 int spah;
114 if (spark) {
115 //no idea if this should be plus or minus (or here at all)
116 spah = spark->GetHeight();//+spark->pos.h;
117 } else {
118 spah = 0x7fffffff;
121 int proh;
122 if (pro) {
123 proh = pro->GetHeight();
124 } else {
125 proh = 0x7fffffff;
128 if (proh<actorh && proh<scah && proh<aah && proh<spah) return AOT_PROJECTILE;
130 if (spah<actorh && spah<scah && spah<aah) return AOT_SPARK;
132 if (aah<actorh && aah<scah) return AOT_AREA;
134 if (scah<actorh) return AOT_SCRIPTED;
136 return AOT_ACTOR;
139 //returns true if creature must be embedded in the area
140 //npcs in saved game shouldn't be embedded either
141 inline static bool MustSave(Actor *actor)
143 if (actor->Persistent()) {
144 return false;
147 //check for familiars, summons?
148 return true;
151 //Preload spawn group entries (creature resrefs that reference groups of creatures)
152 void InitSpawnGroups()
154 ieResRef GroupName;
155 int i;
157 AutoTable tab("spawngrp");
159 Spawns.RemoveAll(NULL);
160 Spawns.SetType( GEM_VARIABLES_POINTER );
162 if (!tab)
163 return;
165 i=tab->GetColNamesCount();
166 while (i--) {
167 int j=tab->GetRowCount();
168 while (j--) {
169 const char *crename = tab->QueryField( j,i );
170 if (crename[0] != '*') break;
172 if (j>0) {
173 SpawnGroup *creatures = new SpawnGroup(j);
174 //difficulty
175 creatures->Level = (ieDword) atoi( tab->QueryField(0,i) );
176 for (;j;j--) {
177 strnlwrcpy( creatures->ResRefs[j-1], tab->QueryField(j,i), 8 );
179 strnlwrcpy( GroupName, tab->GetColumnName( i ), 8 );
180 Spawns.SetAt( GroupName, (void*) creatures );
185 //Preload the searchmap configuration
186 void InitPathFinder()
188 PathFinderInited = true;
189 AutoTable tm("pathfind");
190 if (tm) {
191 const char* poi;
193 for (int i = 0; i < 16; i++) {
194 poi = tm->QueryField( 0, i );
195 if (*poi != '*')
196 Passable[i] = atoi( poi );
198 poi = tm->QueryField( 1, 0 );
199 if (*poi != '*')
200 NormalCost = atoi( poi );
201 poi = tm->QueryField( 1, 1 );
202 if (*poi != '*')
203 AdditionalCost = atoi( poi );
207 void AddLOS(int destx, int desty, int slot)
209 for (int i=0;i<MaxVisibility;i++) {
210 int x=(destx*i+MaxVisibility/2)/MaxVisibility*16;
211 int y=(desty*i+MaxVisibility/2)/MaxVisibility*12;
212 if (LargeFog) {
213 x += 16;
214 y += 12;
216 VisibilityMasks[i][slot].x=(short) x;
217 VisibilityMasks[i][slot].y=(short) y;
221 void InitExplore()
223 LargeFog = !core->HasFeature(GF_SMALL_FOG);
225 //circle perimeter size for MaxVisibility
226 int x = MaxVisibility;
227 int y = 0;
228 int xc = 1 - ( 2 * MaxVisibility );
229 int yc = 1;
230 int re = 0;
231 VisibilityPerimeter = 0;
232 while (x>=y) {
233 VisibilityPerimeter+=8;
234 y++;
235 re += yc;
236 yc += 2;
237 if (( ( 2 * re ) + xc ) > 0) {
238 x--;
239 re += xc;
240 xc += 2;
244 int i;
245 VisibilityMasks = (Point **) malloc(MaxVisibility * sizeof(Point *) );
246 for (i=0;i<MaxVisibility;i++) {
247 VisibilityMasks[i] = (Point *) malloc(VisibilityPerimeter*sizeof(Point) );
250 x = MaxVisibility;
251 y = 0;
252 xc = 1 - ( 2 * MaxVisibility );
253 yc = 1;
254 re = 0;
255 VisibilityPerimeter = 0;
256 while (x>=y) {
257 AddLOS (x, y, VisibilityPerimeter++);
258 AddLOS (-x, y, VisibilityPerimeter++);
259 AddLOS (-x, -y, VisibilityPerimeter++);
260 AddLOS (x, -y, VisibilityPerimeter++);
261 AddLOS (y, x, VisibilityPerimeter++);
262 AddLOS (-y, x, VisibilityPerimeter++);
263 AddLOS (-y, -x, VisibilityPerimeter++);
264 AddLOS (y, -x, VisibilityPerimeter++);
265 y++;
266 re += yc;
267 yc += 2;
268 if (( ( 2 * re ) + xc ) > 0) {
269 x--;
270 re += xc;
271 xc += 2;
276 Map::Map(void)
277 : Scriptable( ST_AREA )
279 area=this;
280 TMap = NULL;
281 LightMap = NULL;
282 SearchMap = NULL;
283 HeightMap = NULL;
284 SmallMap = NULL;
285 MapSet = NULL;
286 Walls = NULL;
287 WallCount = 0;
288 queue[PR_SCRIPT] = NULL;
289 queue[PR_DISPLAY] = NULL;
290 INISpawn = NULL;
291 //no one needs this queue
292 //queue[PR_IGNORE] = NULL;
293 Qcount[PR_SCRIPT] = 0;
294 Qcount[PR_DISPLAY] = 0;
295 //no one needs this queue
296 //Qcount[PR_IGNORE] = 0;
297 lastActorCount[PR_SCRIPT] = 0;
298 lastActorCount[PR_DISPLAY] = 0;
299 //no one needs this
300 //lastActorCount[PR_IGNORE] = 0;
301 if (!PathFinderInited) {
302 InitPathFinder();
303 InitSpawnGroups();
304 InitExplore();
305 globalActorCounter = 0;
307 ExploredBitmap = NULL;
308 VisibleBitmap = NULL;
309 version = 0;
310 localActorCounter = 0;
311 MasterArea = core->GetGame()->MasterArea(scriptName);
314 Map::~Map(void)
316 unsigned int i;
318 free( MapSet );
319 delete TMap;
320 delete INISpawn;
321 aniIterator aniidx;
322 for (aniidx = animations.begin(); aniidx != animations.end(); aniidx++) {
323 delete (*aniidx);
326 for (i = 0; i < actors.size(); i++) {
327 Actor* a = actors[i];
328 //don't delete NPC/PC
329 if (a && !a->Persistent() ) {
330 delete a;
334 for (i = 0; i < entrances.size(); i++) {
335 delete entrances[i];
337 for (i = 0; i < spawns.size(); i++) {
338 delete spawns[i];
340 delete LightMap;
341 delete SearchMap;
342 delete HeightMap;
343 core->GetVideoDriver()->FreeSprite( SmallMap );
344 for (i = 0; i < QUEUE_COUNT; i++) {
345 free(queue[i]);
346 queue[i] = NULL;
349 proIterator pri;
351 for (pri = projectiles.begin(); pri != projectiles.end(); pri++) {
352 delete (*pri);
355 scaIterator sci;
357 for (sci = vvcCells.begin(); sci != vvcCells.end(); sci++) {
358 delete (*sci);
361 spaIterator spi;
363 for (spi = particles.begin(); spi != particles.end(); spi++) {
364 delete (*spi);
367 for (i = 0; i < ambients.size(); i++) {
368 delete ambients[i];
370 for (i = 0; i < mapnotes.size(); i++) {
371 delete mapnotes[i];
374 //malloc-d in AREImp
375 free( ExploredBitmap );
376 free( VisibleBitmap );
377 if (Walls) {
378 for(i=0;i<WallCount;i++) {
379 delete Walls[i];
381 free( Walls );
383 WallCount=0;
386 void Map::ChangeTileMap(Image* lm, Sprite2D* sm)
388 delete LightMap;
389 core->GetVideoDriver()->FreeSprite(SmallMap);
391 LightMap = lm;
392 SmallMap = sm;
394 TMap->UpdateDoors();
397 void Map::AddTileMap(TileMap* tm, Image* lm, Bitmap* sr, Sprite2D* sm, Bitmap* hm)
399 // CHECKME: leaks? Should the old TMap, LightMap, etc... be freed?
400 TMap = tm;
401 LightMap = lm;
402 SearchMap = sr;
403 HeightMap = hm;
404 SmallMap = sm;
405 Width = (unsigned int) (TMap->XCellCount * 4);
406 Height = (unsigned int) (( TMap->YCellCount * 64 ) / 12);
407 //Filling Matrices
408 MapSet = (unsigned short *) malloc(sizeof(unsigned short) * Width * Height);
409 //converting searchmap to internal format
410 int y=SearchMap->GetHeight();
411 while(y--) {
412 int x=SearchMap->GetWidth();
413 while(x--) {
414 SearchMap->SetAt(x,y,Passable[SearchMap->GetAt(x,y)&PATH_MAP_AREAMASK]);
419 void Map::MoveToNewArea(const char *area, const char *entrance, unsigned int direction, int EveryOne, Actor *actor)
421 char command[256];
423 //change loader MOS image here
424 //check worldmap entry, if that doesn't contain anything,
425 //make a random pick
427 if (EveryOne==CT_WHOLE) {
428 core->GetGameControl()->AutoSave();
430 Game* game = core->GetGame();
431 Map* map = game->GetMap(area, false);
432 if (!map) {
433 printMessage("Map", " ", LIGHT_RED);
434 printf("Invalid map: %s\n",area);
435 command[0]=0;
436 return;
438 Entrance* ent = map->GetEntrance( entrance );
439 int X,Y, face;
440 if (!ent) {
441 // no entrance found, try using direction flags
443 face = -1; // should this be handled per-case?
445 // ok, so the original engine tries these in a different order
446 // (north first, then south) but it doesn't seem to matter
447 if (direction & 0x1) { // north
448 X = map->TMap->XCellCount * 32;
449 Y = 0;
450 } else if (direction & 0x2) { // east
451 X = map->TMap->XCellCount * 64;
452 Y = map->TMap->YCellCount * 32;
453 } else if (direction & 0x4) { // south
454 X = map->TMap->XCellCount * 32;
455 Y = map->TMap->YCellCount * 64;
456 } else if (direction & 0x8) { // west
457 X = 0;
458 Y = map->TMap->YCellCount * 32;
459 } else {
460 // crashes in original engine
461 printMessage("Map", " ", YELLOW);
462 printf( "WARNING!!! EntryPoint '%s' does not exist and direction %d is invalid\n", entrance, direction );
463 X = map->TMap->XCellCount * 64;
464 Y = map->TMap->YCellCount * 64;
466 } else {
467 X = ent->Pos.x;
468 Y = ent->Pos.y;
469 face = ent->Face;
471 //LeaveArea is the same in ALL engine versions
472 sprintf(command, "LeaveArea(\"%s\",[%d.%d],%d)", area, X, Y, face);
474 if (EveryOne&CT_GO_CLOSER) {
475 int i=game->GetPartySize(false);
476 while (i--) {
477 Actor *pc = game->GetPC(i,false);
478 if (pc->GetCurrentArea()==this) {
479 pc->ClearPath();
480 pc->ClearActions();
481 pc->AddAction( GenerateAction( command ) );
482 pc->ProcessActions(true);
485 return;
487 if (EveryOne&CT_SELECTED) {
488 int i=game->GetPartySize(false);
489 while (i--) {
490 Actor *pc = game->GetPC(i,false);
492 if (!pc->IsSelected()) {
493 continue;
495 if (pc->GetCurrentArea()==this) {
496 pc->ClearPath();
497 pc->ClearActions();
498 pc->AddAction( GenerateAction( command ) );
499 pc->ProcessActions(true);
502 return;
505 actor->ClearPath();
506 actor->ClearActions();
507 actor->AddAction( GenerateAction( command ) );
508 actor->ProcessActions(true);
511 void Map::UseExit(Actor *actor, InfoPoint *ip)
513 Game *game=core->GetGame();
515 int EveryOne = ip->CheckTravel(actor);
516 switch(EveryOne) {
517 case CT_GO_CLOSER:
518 displaymsg->DisplayConstantString(STR_WHOLEPARTY,0xffffff); //white
519 if (game->EveryoneStopped()) {
520 ip->Flags&=~TRAP_RESET; //exit triggered
522 return;
523 //no ingame message for these events
524 case CT_CANTMOVE: case CT_SELECTED:
525 return;
526 case CT_ACTIVE: case CT_WHOLE: case CT_MOVE_SELECTED:
527 break;
530 actor->UseExit(false);
531 if (ip->Destination[0] != 0) {
532 // 0 here is direction, can infopoints specify that or is an entrance always provided?
533 MoveToNewArea(ip->Destination, ip->EntranceName, 0, EveryOne, actor);
534 return;
536 if (ip->Scripts[0]) {
537 ip->LastTriggerObject = ip->LastTrigger = ip->LastEntered = actor->GetID();
538 ip->ExecuteScript( 1 );
539 ip->ProcessActions(true);
543 //Draw two overlapped animations to achieve the original effect
544 //PlayOnce makes sure that if we stop drawing them, they will go away
545 void Map::DrawPortal(InfoPoint *ip, int enable)
547 ieDword gotportal = HasVVCCell(PortalResRef, ip->Pos);
549 if (enable) {
550 if (gotportal>PortalTime) return;
551 ScriptedAnimation *sca = gamedata->GetScriptedAnimation(PortalResRef, false);
552 if (sca) {
553 sca->SetBlend();
554 sca->PlayOnce();
555 sca->XPos=ip->Pos.x;
556 sca->YPos=ip->Pos.y;
557 sca->ZPos=gotportal;
558 AddVVCell(sca);
560 return;
564 void Map::UpdateScripts()
566 bool has_pcs = false;
567 size_t i=actors.size();
568 while (i--) {
569 if (actors[i]->InParty) {
570 has_pcs = true;
571 break;
575 // if masterarea, then we allow 'any' actors
576 // if not masterarea, we allow only players
577 // if (!GetActorCount(MasterArea) ) {
578 // fuzzie changed this because the previous code was wrong
579 // (GetActorCount(false) returns only non-PCs) - it is not
580 // well-tested so feel free to change if there are problems
581 // (for example, the CanFree seems like it would be needed to
582 // check for any running scripts, such as following, but it seems
583 // to work ok anyway in my testing - if you change it you probably
584 // also want to change the actor updating code below so it doesn't
585 // add new actions while we are trying to get rid of the area!)
586 if (!has_pcs && !(MasterArea && actors.size()) /*&& !CanFree()*/) {
587 return;
590 // fuzzie added this check because some area scripts (eg, AR1600 when
591 // escaping Brynnlaw) were executing after they were meant to be done,
592 // and this seems the nicest way of handling that for now - it's quite
593 // possibly wrong (so if you have problems, revert this and find
594 // another way)
595 if (has_pcs) {
596 //Run the Map Script
597 ExecuteScript( 1 );
600 //Execute Pending Actions
601 //if it is only here, then the drawing will fail
602 ProcessActions(false);
604 // If scripts frozen, return.
605 // This fixes starting a new IWD game. The above ProcessActions pauses the
606 // game for a textscreen, but one of the actor->ProcessActions calls
607 // below starts a cutscene, hiding the mouse. - wjp, 20060805
608 if (core->GetGameControl()->GetDialogueFlags() & DF_FREEZE_SCRIPTS) return;
610 //Run actor scripts (only for 0 priority)
611 int q=Qcount[PR_SCRIPT];
613 Game *game = core->GetGame();
614 Actor *timestop_owner = game->timestop_owner;
615 bool timestop = game->timestop_end>game->GameTime;
617 // this is silly, the speed should be pre-calculated somewhere
618 //int *actor_speeds = (int *)calloc(Qcount[PR_SCRIPT], sizeof(int));
620 //bool *no_more_steps_for_actor = (bool *)calloc(Qcount[PR_SCRIPT], sizeof(bool));
622 while (q--) {
623 Actor* actor = queue[PR_SCRIPT][q];
624 //actor just moved away, don't run its script from this side
625 if (actor->GetCurrentArea()!=this) {
626 actor->no_more_steps = true;
627 continue;
629 if (timestop && actor!=timestop_owner && actor->Modified[IE_DISABLETIMESTOP] ) {
630 actor->no_more_steps = true;
631 continue;
634 //Avenger moved this here from ApplyAllEffects (this one modifies the effect queue)
635 //.. but then fuzzie moved this here from UpdateActorState, because otherwise
636 //immobile actors (see check below) never become mobile again!
637 actor->fxqueue.Cleanup();
639 //if the actor is immobile, don't run the scripts
640 if (!game->StateOverrideFlag && !game->StateOverrideTime) {
641 if (actor->Immobile()) {
642 actor->no_more_steps = true;
643 continue;
646 actor->no_more_steps = false;
649 * we run scripts all at once because one of the actions in ProcessActions
650 * might remove us from a cutscene and then bad things can happen when
651 * scripts are queued unexpectedly (such as an ogre in a cutscene -> dialog
652 * -> cutscene transition in the first bg1 cutscene exploiting the race
653 * condition to murder player1) - it is entirely possible that we should be
654 * doing this differently (for example by storing the cutscene state at the
655 * start of this function, or by changing the cutscene state at a later
656 * point, etc), but i did it this way for now because it seems least painful
657 * and we should probably be staggering the script executions anyway
659 actor->ExecuteScript( MAX_SCRIPTS );
663 q=Qcount[PR_SCRIPT];
664 while (q--) {
665 Actor* actor = queue[PR_SCRIPT][q];
666 if (actor->no_more_steps) continue;
668 actor->ProcessActions(false);
670 actor->UpdateActorState(game->GameTime);
672 actor->inventory.CalculateWeight();
673 actor->SetBase( IE_ENCUMBRANCE, actor->inventory.GetWeight() );
675 //TODO:calculate actor speed!
676 int speed = (int) actor->GetStat(IE_MOVEMENTRATE);
677 if (speed) {
678 speed = 1500/speed;
680 if (core->GetResDataINI()) {
681 ieDword animid = actor->BaseStats[IE_ANIMATION_ID];
682 if (core->HasFeature(GF_ONE_BYTE_ANIMID)) {
683 animid = animid & 0xff;
685 if (animid < (ieDword)CharAnimations::GetAvatarsCount()) {
686 AvatarStruct *avatar = CharAnimations::GetAvatarStruct(animid);
687 if (avatar->RunScale && (actor->GetInternalFlag() & IF_RUNNING)) {
688 speed = avatar->RunScale;
689 } else if (avatar->WalkScale) {
690 speed = avatar->WalkScale;
691 } else {
692 //printf("no walkscale for anim %d!\n", actor->BaseStats[IE_ANIMATION_ID]);
696 actor->speed = speed;
699 // We need to step through the list of actors until all of them are done
700 // taking steps.
701 bool more_steps = true;
702 ieDword time = game->Ticks; // make sure everything moves at the same time
703 while (more_steps) {
704 more_steps = false;
706 q=Qcount[PR_SCRIPT];
707 while (q--) {
708 Actor* actor = queue[PR_SCRIPT][q];
709 if (actor->no_more_steps) continue;
711 // try to exclude actors which only just died
712 // (shouldn't we not be stepping actors which don't have a path anyway?)
713 // following fails on Immobile creatures, don't think it's a problem, but replace with next line if it is
714 if (!actor->ValidTarget(GA_NO_DEAD)) continue;
715 //if (actor->GetStat(IE_STATE_ID)&STATE_DEAD || actor->GetInternalFlag() & IF_JUSTDIED) continue;
717 actor->no_more_steps = DoStepForActor(actor, actor->speed, time);
718 if (!actor->no_more_steps) more_steps = true;
722 //Check if we need to start some door scripts
723 int doorCount = 0;
724 while (true) {
725 Door* door = TMap->GetDoor( doorCount++ );
726 if (!door)
727 break;
728 if (door->Scripts[0])
729 door->ExecuteScript( 1 );
730 //Execute Pending Actions
731 door->ProcessActions(false);
734 //Check if we need to start some container scripts
735 int containerCount = 0;
736 while (true) {
737 Container* container = TMap->GetContainer( containerCount++ );
738 if (!container)
739 break;
740 if (container->Scripts[0])
741 container->ExecuteScript( 1 );
742 //Execute Pending Actions
743 container->ProcessActions(false);
746 //Check if we need to start some trap scripts
747 int ipCount = 0;
748 while (true) {
749 //For each InfoPoint in the map
750 InfoPoint* ip = TMap->GetInfoPoint( ipCount++ );
751 if (!ip)
752 break;
753 //If this InfoPoint has no script and it is not a Travel Trigger, skip it
754 bool wasActive = (ip->Scripts[0] || ( ip->Type == ST_TRAVEL ));
755 // InfoPoints of all types don't run scripts if TRAP_DEACTIVATED is set
756 // (eg, TriggerActivation changes this, see lightning room from SoA)
757 if (wasActive)
758 wasActive = !(ip->Flags&TRAP_DEACTIVATED);
760 //If this InfoPoint is a Switch Trigger
761 if (ip->Type == ST_TRIGGER) {
762 //Check if this InfoPoint was activated
763 if (ip->LastTrigger) {
764 if (wasActive) {
765 //Run the InfoPoint script
766 ip->ExecuteScript( 1 );
769 //Execute Pending Actions
770 ip->ProcessActions(false);
771 continue;
774 if (ip->IsPortal()) {
775 DrawPortal(ip, ip->Trapped&PORTAL_TRAVEL);
778 if (wasActive) {
779 q=Qcount[PR_SCRIPT];
780 while (q--) {
781 Actor* actor = queue[PR_SCRIPT][q];
782 if (ip->Type == ST_PROXIMITY) {
783 if(ip->Entered(actor)) {
784 //if trap triggered, then mark actor
785 actor->SetInTrap(ipCount);
787 } else {
788 //ST_TRAVEL
789 //don't move if doing something else
790 // added CurrentAction as part of blocking action fixes
791 if (actor->CannotPassEntrance() ) {
792 continue;
794 //this is needed, otherwise the travel
795 //trigger would be activated anytime
796 //Well, i don't know why is it here, but lets try this
797 if (ip->Entered(actor)) {
798 UseExit(actor, ip);
804 if (wasActive) {
805 ip->ExecuteScript( 1 );
807 //Execute Pending Actions
808 ip->ProcessActions(false);
812 bool Map::DoStepForActor(Actor *actor, int speed, ieDword time) {
813 bool no_more_steps = true;
815 if (actor->BlocksSearchMap()) {
816 ClearSearchMapFor(actor);
818 PathNode * step = actor->GetNextStep();
819 if (step && step->Next) {
820 //we should actually wait for a short time and check then
821 if (GetBlocked(step->Next->x*16+8,step->Next->y*12+6,actor->size)) {
822 actor->NewPath();
826 if (!(actor->GetBase(IE_STATE_ID)&STATE_CANTMOVE) ) {
827 if (!actor->Immobile()) {
828 no_more_steps = actor->DoStep( speed, time );
829 if (actor->BlocksSearchMap()) {
830 BlockSearchMap( actor->Pos, actor->size, actor->InParty?PATH_MAP_PC:PATH_MAP_NPC);
835 return no_more_steps;
838 void Map::ClearSearchMapFor( Movable *actor ) {
839 Actor** nearActors = GetAllActorsInRadius(actor->Pos, GA_NO_DEAD, MAX_CIRCLE_SIZE*2*16);
840 BlockSearchMap( actor->Pos, actor->size, PATH_MAP_FREE);
842 // Restore the searchmap areas of any nearby actors that could
843 // have been cleared by this BlockSearchMap(..., 0).
844 // (Necessary since blocked areas of actors may overlap.)
845 int i=0;
846 while(nearActors[i]!=NULL) {
847 if(nearActors[i]!=actor && nearActors[i]->BlocksSearchMap())
848 BlockSearchMap( nearActors[i]->Pos, nearActors[i]->size, nearActors[i]->InParty?PATH_MAP_PC:PATH_MAP_NPC);
849 ++i;
851 free(nearActors);
854 void Map::DrawHighlightables( Region screen )
856 Region vp = core->GetVideoDriver()->GetViewport();
857 unsigned int i = 0;
858 Container *c;
860 while ( (c = TMap->GetContainer(i++))!=NULL ) {
861 Color tint = LightMap->GetPixel( c->Pos.x / 16, c->Pos.y / 12);
862 tint.a = 255;
864 if (c->Highlight) {
865 if (c->Type==IE_CONTAINER_PILE) {
866 Color tint = LightMap->GetPixel( c->Pos.x / 16, c->Pos.y / 12);
867 tint.a = 255;
868 c->DrawPile(true, screen, tint);
869 } else {
870 c->DrawOutline();
872 } else if (c->Type==IE_CONTAINER_PILE) {
873 if (c->outline->BBox.InsideRegion( vp )) {
874 c->DrawPile(false, screen, tint);
879 Door *d;
880 i = 0;
881 while ( (d = TMap->GetDoor(i++))!=NULL ) {
882 if (d->Highlight) d->DrawOutline();
885 InfoPoint *p;
886 i = 0;
887 while ( (p = TMap->GetInfoPoint(i++))!=NULL ) {
888 if (p->Highlight) p->DrawOutline();
892 Actor *Map::GetNextActor(int &q, int &index)
894 retry:
895 switch(q) {
896 case PR_SCRIPT:
897 if (index--)
898 return queue[q][index];
899 q--;
900 return NULL;
901 case PR_DISPLAY:
902 if (index--)
903 return queue[q][index];
904 q--;
905 index = Qcount[q];
906 goto retry;
907 default:
908 return NULL;
912 AreaAnimation *Map::GetNextAreaAnimation(aniIterator &iter, ieDword gametime)
914 retry:
915 if (iter==animations.end()) {
916 return NULL;
918 AreaAnimation *a = *(iter++);
919 if (!a->Schedule(gametime) ) {
920 goto retry;
922 if (!IsVisible( a->Pos, !(a->Flags & A_ANI_NOT_IN_FOG)) ) {
923 goto retry;
925 return a;
928 Particles *Map::GetNextSpark(spaIterator &iter)
930 if (iter==particles.end()) {
931 return NULL;
933 return *iter;
936 //doesn't increase iterator, because we might need to erase it from the list
937 Projectile *Map::GetNextProjectile(proIterator &iter)
939 if (iter==projectiles.end()) {
940 return NULL;
942 return *iter;
945 Projectile *Map::GetNextTrap(proIterator &iter)
947 Projectile *pro;
949 do {
950 pro=GetNextProjectile(iter);
951 iter++;
952 //logic to determine dormant traps
953 //if (pro && pro->IsTrap()) break;
954 } while(pro);
955 return pro;
958 size_t Map::GetProjectileCount(proIterator &iter)
960 iter = projectiles.begin();
961 return projectiles.size();
964 ieDword Map::GetTrapCount(proIterator &iter)
966 ieDword cnt=0;
967 iter=projectiles.begin();
968 while(GetNextTrap(iter)) {
969 cnt++;
972 iter = projectiles.begin();
973 return cnt;
977 //doesn't increase iterator, because we might need to erase it from the list
978 ScriptedAnimation *Map::GetNextScriptedAnimation(scaIterator &iter)
980 if (iter==vvcCells.end()) {
981 return NULL;
983 return *iter;
986 static ieDword oldgametime = 0;
988 //Draw the game area (including overlays, actors, animations, weather)
989 void Map::DrawMap(Region screen)
991 if (!TMap) {
992 return;
994 Game *game = core->GetGame();
995 ieDword gametime = game->GameTime;
997 //area specific spawn.ini files (a PST feature)
998 if (INISpawn) {
999 INISpawn->CheckSpawn();
1002 int rain;
1003 if (HasWeather()) {
1004 //zero when the weather particles are all gone
1005 rain = game->weather->GetPhase()-P_EMPTY;
1006 } else {
1007 rain = 0;
1009 TMap->DrawOverlays( screen, rain );
1011 //Blit the Background Map Animations (before actors)
1012 Video* video = core->GetVideoDriver();
1014 //Draw Outlines
1015 DrawHighlightables( screen );
1017 Region vp = video->GetViewport();
1018 //if it is only here, then the scripting will fail?
1019 GenerateQueues();
1020 SortQueues();
1021 //drawing queues 1 and 0
1022 //starting with lower priority
1023 //so displayed, but inactive actors (dead) will be drawn over
1024 int q = PR_DISPLAY;
1025 int index = Qcount[q];
1026 Actor* actor = GetNextActor(q, index);
1027 aniIterator aniidx = animations.begin();
1028 scaIterator scaidx = vvcCells.begin();
1029 proIterator proidx = projectiles.begin();
1030 spaIterator spaidx = particles.begin();
1032 AreaAnimation *a = GetNextAreaAnimation(aniidx, gametime);
1033 ScriptedAnimation *sca = GetNextScriptedAnimation(scaidx);
1034 Projectile *pro = GetNextProjectile(proidx);
1035 Particles *spark = GetNextSpark(spaidx);
1037 while (actor || a || sca || spark || pro) {
1038 switch(SelectObject(actor,q,a,sca,spark,pro)) {
1039 case AOT_ACTOR:
1040 actor->Draw( screen );
1041 actor = GetNextActor(q, index);
1042 break;
1043 case AOT_AREA:
1044 //draw animation
1045 a->Draw( screen, this );
1046 a = GetNextAreaAnimation(aniidx,gametime);
1047 break;
1048 case AOT_SCRIPTED:
1050 Point Pos(0,0);
1052 Color tint = LightMap->GetPixel( sca->XPos / 16, sca->YPos / 12);
1053 tint.a = 255;
1054 bool endReached = sca->Draw(screen, Pos, tint, this, 0, -1);
1055 if (endReached) {
1056 delete( sca );
1057 scaidx=vvcCells.erase(scaidx);
1058 } else {
1059 scaidx++;
1062 sca = GetNextScriptedAnimation(scaidx);
1063 break;
1064 case AOT_PROJECTILE:
1066 int drawn;
1067 if (gametime>oldgametime) {
1068 drawn = pro->Update();
1069 } else {
1070 drawn = 1;
1072 if (drawn) {
1073 pro->Draw( screen );
1074 proidx++;
1075 } else {
1076 delete( pro );
1077 proidx = projectiles.erase(proidx);
1080 pro = GetNextProjectile(proidx);
1081 break;
1082 case AOT_SPARK:
1084 int drawn;
1085 if (gametime>oldgametime) {
1086 drawn = spark->Update();
1087 } else {
1088 drawn = 1;
1090 if (drawn) {
1091 spark->Draw( screen );
1092 spaidx++;
1093 } else {
1094 delete( spark );
1095 spaidx=particles.erase(spaidx);
1098 spark = GetNextSpark(spaidx);
1099 break;
1100 default:
1101 abort();
1105 if ((core->FogOfWar&FOG_DRAWSEARCHMAP) && SearchMap) {
1106 DrawSearchMap(screen);
1107 } else {
1108 if ((core->FogOfWar&FOG_DRAWFOG) && TMap) {
1109 TMap->DrawFogOfWar( ExploredBitmap, VisibleBitmap, screen );
1113 int ipCount = 0;
1114 while (true) {
1115 //For each InfoPoint in the map
1116 InfoPoint* ip = TMap->GetInfoPoint( ipCount++ );
1117 if (!ip)
1118 break;
1119 ip->DrawOverheadText(screen);
1122 int cnCount = 0;
1123 while (true) {
1124 //For each Container in the map
1125 Container* cn = TMap->GetContainer( cnCount++ );
1126 if (!cn)
1127 break;
1128 cn->DrawOverheadText(screen);
1131 int drCount = 0;
1132 while (true) {
1133 //For each Door in the map
1134 Door* dr = TMap->GetDoor( drCount++ );
1135 if (!dr)
1136 break;
1137 dr->DrawOverheadText(screen);
1140 size_t i = actors.size();
1141 while (i--) {
1142 //For each Actor present
1143 //This must go AFTER the fog!
1144 //(maybe we should be using the queue?)
1145 Actor* actor = actors[i];
1146 actor->DrawOverheadText(screen);
1149 oldgametime=gametime;
1152 void Map::DrawSearchMap(const Region &screen)
1154 Color inaccessible = { 128, 128, 128, 128 };
1155 Video *vid=core->GetVideoDriver();
1156 Region rgn=vid->GetViewport();
1157 Region block;
1159 block.w=16;
1160 block.h=12;
1161 int w = screen.w/16+2;
1162 int h = screen.h/12+2;
1164 for(int x=0;x<w;x++) {
1165 for(int y=0;y<h;y++) {
1166 if (!(GetBlocked(x+rgn.x/16, y+rgn.y/12) & PATH_MAP_PASSABLE) ) {
1167 block.x=screen.x+x*16-(rgn.x % 16);
1168 block.y=screen.y+y*12-(rgn.y % 12);
1169 vid->DrawRect(block,inaccessible);
1175 //adding animation in order, based on its height parameter
1176 void Map::AddAnimation(AreaAnimation* anim)
1178 //this hack is to make sure animations flagged with background
1179 //are always drawn first (-9999 seems sufficiently small)
1180 if (anim->Flags&A_ANI_BACKGROUND) {
1181 anim->height=-9999;
1184 aniIterator iter;
1185 for(iter=animations.begin(); (iter!=animations.end()) && ((*iter)->height<anim->height); iter++) ;
1186 animations.insert(iter, anim);
1188 Animation *a = anim->animation[0];
1189 anim->SetSpriteCover(BuildSpriteCover(anim->Pos.x, anim->Pos.y,-a->animArea.x,
1190 -a->animArea.y, a->animArea.w, a->animArea.h,0
1195 //reapplying all of the effects on the actors of this map
1196 //this might be unnecessary later
1197 void Map::UpdateEffects()
1199 size_t i = actors.size();
1200 while (i--) {
1201 actors[i]->RefreshEffects(NULL);
1205 void Map::Shout(Actor* actor, int shoutID, unsigned int radius)
1207 size_t i=actors.size();
1208 while (i--) {
1209 Actor *listener = actors[i];
1211 if (radius) {
1212 if (Distance(actor->Pos, listener->Pos)>radius) {
1213 continue;
1216 if (shoutID) {
1217 listener->LastHeard = actor->GetID();
1218 listener->LastShout = shoutID;
1219 } else {
1220 listener->LastHelp = actor->GetID();
1225 bool Map::AnyEnemyNearPoint(const Point &p)
1227 ieDword gametime = core->GetGame()->GameTime;
1228 size_t i = actors.size();
1229 while (i--) {
1230 Actor *actor = actors[i];
1232 if (actor->Schedule(gametime, true) ) {
1233 continue;
1235 if (Distance(actor->Pos, p) > SPAWN_RANGE) {
1236 continue;
1238 if (actor->GetStat(IE_EA)<EA_EVILCUTOFF) {
1239 continue;
1241 return true;
1243 return false;
1246 void Map::ActorSpottedByPlayer(Actor *actor)
1248 unsigned int animid;
1250 if(core->HasFeature(GF_HAS_BEASTS_INI)) {
1251 animid=actor->BaseStats[IE_ANIMATION_ID];
1252 if(core->HasFeature(GF_ONE_BYTE_ANIMID)) {
1253 animid&=0xff;
1255 if (animid < (ieDword)CharAnimations::GetAvatarsCount()) {
1256 AvatarStruct *avatar = CharAnimations::GetAvatarStruct(animid);
1257 core->GetGame()->SetBeastKnown(avatar->Bestiary);
1261 if (!(actor->GetInternalFlag()&IF_STOPATTACK)) {
1262 if (actor->Modified[IE_EA]>=EA_EVILCUTOFF) {
1263 core->Autopause(AP_ENEMY);
1268 void Map::AddActor(Actor* actor)
1270 //setting the current area for the actor as this one
1271 strnlwrcpy(actor->Area, scriptName, 8);
1272 //if actor->globalID was already set, don't change it
1273 actor->SetMap(this, ++localActorCounter,
1274 actor->globalID?actor->globalID:++globalActorCounter);
1275 actors.push_back( actor );
1276 //if a visible aggressive actor was put on the map, it is an autopause reason
1277 //guess game is always loaded? if not, then we'll crash
1278 ieDword gametime = core->GetGame()->GameTime;
1280 if (IsVisible(actor->Pos, false) && actor->Schedule(gametime, true) ) {
1281 ActorSpottedByPlayer(actor);
1283 if (actor->InParty && core->HasFeature(GF_AREA_VISITED_VAR)) {
1284 char key[32];
1285 snprintf(key, sizeof(key),"%s_visited", scriptName);
1286 core->GetGame()->locals->SetAt(key, 1);
1291 bool Map::AnyPCSeesEnemy()
1293 ieDword gametime = core->GetGame()->GameTime;
1294 size_t i = actors.size();
1295 while (i--) {
1296 Actor* actor = actors[i];
1297 if (actor->Modified[IE_EA]>=EA_EVILCUTOFF) {
1298 if (IsVisible(actor->Pos, false) && actor->Schedule(gametime, true) ) {
1299 return true;
1303 return false;
1306 void Map::DeleteActor(int i)
1308 Actor *actor = actors[i];
1310 Game *game = core->GetGame();
1311 game->LeaveParty( actor );
1312 game->DelNPC( game->InStore(actor) );
1314 ClearSearchMapFor(actor);
1316 actors.erase( actors.begin()+i );
1317 delete actor;
1320 Actor* Map::GetActorByGlobalID(ieDword objectID)
1322 if (!objectID) {
1323 return NULL;
1325 //truncation is intentional
1326 ieWord globalID = (ieWord) objectID;
1327 size_t i = actors.size();
1328 while (i--) {
1329 Actor* actor = actors[i];
1331 if (actor->globalID==globalID) {
1332 return actor;
1335 return NULL;
1338 /** flags:
1339 GA_SELECT 16 - unselectable actors don't play
1340 GA_NO_DEAD 32 - dead actors don't play
1341 GA_POINT 64 - not actor specific
1342 GA_NO_HIDDEN 128 - hidden actors don't play
1344 Actor* Map::GetActor(const Point &p, int flags)
1346 ieDword gametime = core->GetGame()->GameTime;
1347 size_t i = actors.size();
1348 while (i--) {
1349 Actor* actor = actors[i];
1351 if (!actor->IsOver( p ))
1352 continue;
1353 if (!actor->ValidTarget(flags) ) {
1354 continue;
1356 if (!actor->Schedule(gametime, true) ) {
1357 continue;
1359 return actor;
1361 return NULL;
1364 Actor* Map::GetActorInRadius(const Point &p, int flags, unsigned int radius)
1366 ieDword gametime = core->GetGame()->GameTime;
1367 size_t i = actors.size();
1368 while (i--) {
1369 Actor* actor = actors[i];
1371 if (PersonalDistance( p, actor ) > radius)
1372 continue;
1373 if (!actor->ValidTarget(flags) ) {
1374 continue;
1376 if (!actor->Schedule(gametime, true) ) {
1377 continue;
1379 return actor;
1381 return NULL;
1384 Actor **Map::GetAllActorsInRadius(const Point &p, int flags, unsigned int radius)
1386 ieDword count = 1;
1387 size_t i;
1389 ieDword gametime = core->GetGame()->GameTime;
1390 i = actors.size();
1391 while (i--) {
1392 Actor* actor = actors[i];
1394 if (PersonalDistance( p, actor ) > radius)
1395 continue;
1396 if (!actor->ValidTarget(flags) ) {
1397 continue;
1399 if (!actor->Schedule(gametime, true) ) {
1400 continue;
1402 count++;
1405 Actor **ret = (Actor **) malloc( sizeof(Actor*) * count);
1406 i = actors.size();
1407 int j = 0;
1408 while (i--) {
1409 Actor* actor = actors[i];
1411 if (PersonalDistance( p, actor ) > radius)
1412 continue;
1413 if (!actor->ValidTarget(flags) ) {
1414 continue;
1416 if (!actor->Schedule(gametime, true) ) {
1417 continue;
1419 ret[j++]=actor;
1422 ret[j]=NULL;
1423 return ret;
1427 Actor* Map::GetActor(const char* Name, int flags)
1429 size_t i = actors.size();
1430 while (i--) {
1431 Actor* actor = actors[i];
1432 if (strnicmp( actor->GetScriptName(), Name, 32 ) == 0) {
1433 if (!actor->ValidTarget(flags) ) {
1434 return NULL;
1436 return actor;
1439 return NULL;
1442 int Map::GetActorCount(bool any) const
1444 if (any) {
1445 return (int) actors.size();
1447 int ret = 0;
1448 size_t i=actors.size();
1449 while (i--) {
1450 if (MustSave(actors[i])) {
1451 ret++;
1454 return ret;
1457 void Map::JumpActors(bool jump)
1459 size_t i = actors.size();
1460 while (i--) {
1461 Actor* actor = actors[i];
1462 if (actor->Modified[IE_DONOTJUMP]&DNJ_JUMP) {
1463 if (jump) {
1464 actor->FixPosition();
1466 actor->SetBase(IE_DONOTJUMP,0);
1471 //before writing the area out, perform some cleanups
1472 void Map::PurgeArea(bool items)
1474 InternalFlags |= IF_JUSTDIED; //area marked for swapping out
1476 //1. remove dead actors without 'keep corpse' flag
1477 int i=(int) actors.size();
1478 while (i--) {
1479 Actor *ac = actors[i];
1481 if (ac->Modified[IE_STATE_ID]&STATE_NOSAVE) {
1482 if (ac->Modified[IE_MC_FLAGS] & MC_KEEP_CORPSE) {
1483 continue;
1485 delete ac;
1486 actors.erase( actors.begin()+i );
1489 //2. remove any non critical items
1490 if (items) {
1491 i=(int) TMap->GetContainerCount();
1492 while (i--) {
1493 Container *c = TMap->GetContainer(i);
1494 unsigned int j=c->inventory.GetSlotCount();
1495 while (j--) {
1496 CREItem *itemslot = c->inventory.GetSlotItem(j);
1497 if (itemslot->Flags&IE_INV_ITEM_CRITICAL) {
1498 continue;
1501 TMap->CleanupContainer(c);
1506 Actor* Map::GetActor(int index, bool any)
1508 if (any) {
1509 return actors[index];
1511 unsigned int i=0;
1512 while (i<actors.size() ) {
1513 Actor *ac = actors[i++];
1514 if (MustSave(ac) ) {
1515 if (!index--) {
1516 return ac;
1520 return NULL;
1523 Actor* Map::GetActorByDialog(const char *resref)
1525 size_t i = actors.size();
1526 while (i--) {
1527 Actor* actor = actors[i];
1528 //if a busy or hostile actor shouldn't be found
1529 //set this to GD_CHECK
1530 if (strnicmp( actor->GetDialog(GD_NORMAL), resref, 8 ) == 0) {
1531 return actor;
1534 return NULL;
1537 //this function finds an actor by its original resref (not correct yet)
1538 Actor* Map::GetActorByResource(const char *resref)
1540 size_t i = actors.size();
1541 while (i--) {
1542 Actor* actor = actors[i];
1543 if (strnicmp( actor->GetScriptName(), resref, 8 ) == 0) { //temporarily!
1544 return actor;
1547 return NULL;
1550 int Map::GetActorInRect(Actor**& actorlist, Region& rgn, bool onlyparty)
1552 actorlist = ( Actor * * ) malloc( actors.size() * sizeof( Actor * ) );
1553 int count = 0;
1554 size_t i = actors.size();
1555 while (i--) {
1556 Actor* actor = actors[i];
1557 //use this function only for party?
1558 if (onlyparty && actor->GetStat(IE_EA)>EA_CHARMED) {
1559 continue;
1561 if (!actor->ValidTarget(GA_SELECT|GA_NO_DEAD) )
1562 continue;
1563 if ((actor->Pos.x<rgn.x) || (actor->Pos.y<rgn.y))
1564 continue;
1565 if ((actor->Pos.x>rgn.x+rgn.w) || (actor->Pos.y>rgn.y+rgn.h) )
1566 continue;
1567 actorlist[count++] = actor;
1569 actorlist = ( Actor * * ) realloc( actorlist, count * sizeof( Actor * ) );
1570 return count;
1573 void Map::PlayAreaSong(int SongType, bool restart)
1575 //Ok, we use a non constant pointer here, so it is easy to disable
1576 //a faulty music list on the fly. I don't want to add a method just for that
1577 //crap when we already have that pointer at hand!
1578 char* poi = core->GetMusicPlaylist( SongHeader.SongList[SongType] );
1579 if (!poi) return;
1580 if (!restart && core->GetMusicMgr()->CurrentPlayList(poi)) return;
1581 int ret = core->GetMusicMgr()->SwitchPlayList( poi, true );
1582 //Here we disable the faulty musiclist entry
1583 if (ret) {
1584 //Apparently, the playlist manager prefers a *
1585 *poi='*';
1589 unsigned char Map::GetBlocked(unsigned int x, unsigned int y)
1591 unsigned char ret = SearchMap->GetAt( x, y );
1592 if (ret&(PATH_MAP_DOOR_TRANSPARENT|PATH_MAP_ACTOR)) {
1593 ret&=~PATH_MAP_PASSABLE;
1595 if (ret&PATH_MAP_DOOR_OPAQUE) {
1596 ret=PATH_MAP_SIDEWALL;
1598 return ret;
1601 bool Map::GetBlocked(unsigned int px, unsigned int py, unsigned int size)
1603 // We check a circle of radius size-2 around (px,py)
1604 // Note that this does not exactly match BG2. BG2's approximations of
1605 // these circles are slightly different for sizes 7 and up.
1607 if (size > MAX_CIRCLESIZE) size = MAX_CIRCLESIZE;
1608 if (size < 2) size = 2;
1610 unsigned int ppx = px/16;
1611 unsigned int ppy = py/12;
1612 unsigned int r=(size-2)*(size-2)+1;
1613 if (size == 2) r = 0;
1614 for (unsigned int i=0; i<size-1; i++) {
1615 for (unsigned int j=0; j<size-1; j++) {
1616 if (i*i+j*j <= r) {
1617 if (!(GetBlocked(ppx+i,ppy+j)&PATH_MAP_PASSABLE)) return true;
1618 if (!(GetBlocked(ppx+i,ppy-j)&PATH_MAP_PASSABLE)) return true;
1619 if (!(GetBlocked(ppx-i,ppy+j)&PATH_MAP_PASSABLE)) return true;
1620 if (!(GetBlocked(ppx-i,ppy-j)&PATH_MAP_PASSABLE)) return true;
1624 return false;
1627 unsigned char Map::GetBlocked(const Point &c)
1629 return GetBlocked(c.x/16, c.y/12);
1632 //flags:0 - never dither (full cover)
1633 // 1 - dither if polygon wants it
1634 // 2 - always dither
1636 SpriteCover* Map::BuildSpriteCover(int x, int y, int xpos, int ypos,
1637 unsigned int width, unsigned int height, int flags)
1639 SpriteCover* sc = new SpriteCover;
1640 sc->worldx = x;
1641 sc->worldy = y;
1642 sc->XPos = xpos;
1643 sc->YPos = ypos;
1644 sc->Width = width;
1645 sc->Height = height;
1647 Video* video = core->GetVideoDriver();
1648 video->InitSpriteCover(sc, flags);
1650 unsigned int wpcount = GetWallCount();
1651 unsigned int i;
1653 for (i = 0; i < wpcount; ++i)
1655 Wall_Polygon* wp = GetWallGroup(i);
1656 if (!wp) continue;
1657 if (!wp->PointCovered(x, y)) continue;
1659 video->AddPolygonToSpriteCover(sc, wp);
1662 return sc;
1665 void Map::ActivateWallgroups(unsigned int baseindex, unsigned int count, int flg)
1667 unsigned int i;
1669 if (!Walls) {
1670 return;
1672 for(i=baseindex; i < baseindex+count; ++i) {
1673 Wall_Polygon* wp = GetWallGroup(i);
1674 if (!wp)
1675 continue;
1676 ieDword value=wp->GetPolygonFlag();
1677 if (flg)
1678 value&=~WF_DISABLED;
1679 else
1680 value|=WF_DISABLED;
1681 wp->SetPolygonFlag(value);
1683 //all actors will have to generate a new spritecover
1684 i=(int) actors.size();
1685 while(i--) {
1686 actors[i]->SetSpriteCover(NULL);
1691 //this function determines actor drawing order
1692 //it should be extended to wallgroups, animations, effects!
1693 void Map::GenerateQueues()
1695 int priority;
1697 unsigned int i=(unsigned int) actors.size();
1698 for (priority=0;priority<QUEUE_COUNT;priority++) {
1699 if (lastActorCount[priority] != i) {
1700 if (queue[priority]) {
1701 free(queue[priority]);
1702 queue[priority] = NULL;
1704 queue[priority] = (Actor **) calloc( i, sizeof(Actor *) );
1705 lastActorCount[priority] = i;
1707 Qcount[priority] = 0;
1710 ieDword gametime = core->GetGame()->GameTime;
1711 while (i--) {
1712 Actor* actor = actors[i];
1714 if (actor->CheckOnDeath()) {
1715 DeleteActor( i );
1716 continue;
1719 ieDword stance = actor->GetStance();
1720 ieDword internalFlag = actor->GetInternalFlag();
1722 if (internalFlag&IF_ACTIVE) {
1723 if ((stance == IE_ANI_TWITCH) && (internalFlag&IF_IDLE) ) {
1724 priority = PR_DISPLAY; //display
1725 } else {
1726 priority = PR_SCRIPT; //run scripts and display
1728 } else {
1729 //dead actors are always visible on the map, but run no scripts
1730 if (stance == IE_ANI_TWITCH || stance == IE_ANI_DIE) {
1731 priority = PR_DISPLAY;
1732 } else {
1733 //isvisible flag is false (visibilitymap) here,
1734 //coz we want to reactivate creatures that
1735 //just became visible
1736 if (IsVisible(actor->Pos, false) && actor->Schedule(gametime, false) ) {
1737 priority = PR_SCRIPT; //run scripts and display, activated now
1738 //more like activate!
1739 actor->Activate();
1740 ActorSpottedByPlayer(actor);
1741 } else {
1742 priority = PR_IGNORE;
1747 //we ignore priority 2
1748 if (priority>=PR_IGNORE) continue;
1750 queue[priority][Qcount[priority]] = actor;
1751 Qcount[priority]++;
1755 //the original qsort implementation was flawed
1756 void Map::SortQueues()
1758 for (int q=0;q<QUEUE_COUNT;q++) {
1759 Actor **baseline=queue[q];
1760 int n = Qcount[q];
1761 int i = n/2;
1762 int parent, child;
1763 Actor * tmp;
1765 for (;;) {
1766 if (i>0) {
1767 i--;
1768 tmp = baseline[i];
1769 } else {
1770 n--;
1771 if (n<=0) break; //breaking loop
1772 tmp = baseline[n];
1773 baseline[n] = baseline[0];
1775 parent = i;
1776 child = i*2+1;
1777 while(child<n) {
1778 int chp = child+1;
1779 if (chp<n && baseline[chp]->Pos.y < baseline[child]->Pos.y) {
1780 child=chp;
1782 if (baseline[child]->Pos.y<tmp->Pos.y) {
1783 baseline[parent] = baseline[child];
1784 parent = child;
1785 child = parent*2+1;
1786 } else
1787 break;
1789 baseline[parent]=tmp;
1794 void Map::AddProjectile(Projectile* pro, const Point &source, ieWord actorID)
1796 proIterator iter;
1798 pro->MoveTo(this,source);
1799 pro->SetTarget(actorID);
1800 int height = pro->GetHeight();
1801 for(iter=projectiles.begin();iter!=projectiles.end() && (*iter)->GetHeight()<height; iter++) ;
1802 projectiles.insert(iter, pro);
1805 //adding projectile in order, based on its height parameter
1806 void Map::AddProjectile(Projectile* pro, const Point &source, const Point &dest)
1808 proIterator iter;
1810 pro->MoveTo(this,source);
1811 pro->SetTarget(dest);
1812 int height = pro->GetHeight();
1813 for(iter=projectiles.begin();iter!=projectiles.end() && (*iter)->GetHeight()<height; iter++) ;
1814 projectiles.insert(iter, pro);
1817 //returns the longest duration of the VVC cell named 'resource' (if it exists)
1818 //if P is empty, the position won't be checked
1819 ieDword Map::HasVVCCell(const ieResRef resource, const Point &p)
1821 scaIterator iter;
1822 ieDword ret = 0;
1824 for(iter=vvcCells.begin();iter!=vvcCells.end(); iter++) {
1825 if (!p.isempty()) {
1826 if ((*iter)->XPos!=p.x) continue;
1827 if ((*iter)->YPos!=p.y) continue;
1829 if (strnicmp(resource, (*iter)->ResName, sizeof(ieResRef) )) continue;
1830 ieDword tmp = (*iter)->GetSequenceDuration(15)-(*iter)->GetCurrentFrame();
1831 if (tmp>ret) {
1832 ret = tmp;
1835 return ret;
1838 //adding videocell in order, based on its height parameter
1839 void Map::AddVVCell(ScriptedAnimation* vvc)
1841 scaIterator iter;
1843 for(iter=vvcCells.begin();iter!=vvcCells.end() && (*iter)->ZPos<vvc->ZPos; iter++) ;
1844 vvcCells.insert(iter, vvc);
1847 AreaAnimation* Map::GetAnimation(const char* Name)
1849 aniIterator iter;
1851 for(iter=animations.begin();iter!=animations.end();iter++) {
1852 AreaAnimation *anim = *iter;
1854 if (anim->Name && (strnicmp( anim->Name, Name, 32 ) == 0)) {
1855 return anim;
1858 return NULL;
1861 Spawn *Map::AddSpawn(char* Name, int XPos, int YPos, ieResRef *creatures, unsigned int count)
1863 Spawn* sp = new Spawn();
1864 strnspccpy(sp->Name, Name, 32);
1865 if (count>MAX_RESCOUNT) {
1866 count=MAX_RESCOUNT;
1868 sp->Pos.x = (ieWord) XPos;
1869 sp->Pos.y = (ieWord) YPos;
1870 sp->Count = count;
1871 sp->Creatures = (ieResRef *) calloc( count, sizeof(ieResRef) );
1872 for( unsigned int i=0;i<count;i++) {
1873 strnlwrcpy(sp->Creatures[i],creatures[i],8);
1875 spawns.push_back( sp );
1876 return sp;
1879 void Map::AddEntrance(char* Name, int XPos, int YPos, short Face)
1881 Entrance* ent = new Entrance();
1882 strncpy( ent->Name, Name, 32 );
1883 ent->Pos.x = (ieWord) XPos;
1884 ent->Pos.y = (ieWord) YPos;
1885 ent->Face = (ieWord) Face;
1886 entrances.push_back( ent );
1889 Entrance* Map::GetEntrance(const char* Name)
1891 size_t i=entrances.size();
1892 while (i--) {
1893 Entrance *e = entrances[i];
1895 if (strnicmp( e->Name, Name, 32 ) == 0) {
1896 return e;
1899 return NULL;
1902 bool Map::HasActor(Actor *actor)
1904 size_t i=actors.size();
1905 while (i--) {
1906 if (actors[i] == actor) {
1907 return true;
1910 return false;
1913 void Map::RemoveActor(Actor* actor)
1915 size_t i=actors.size();
1916 while (i--) {
1917 if (actors[i] == actor) {
1918 //BlockSearchMap(actor->Pos, actor->size, PATH_MAP_FREE);
1919 ClearSearchMapFor(actor);
1920 actors.erase( actors.begin()+i );
1921 return;
1924 printMessage("Map","RemoveActor: actor not found?",YELLOW);
1927 //returns true if none of the partymembers are on the map
1928 bool Map::CanFree()
1930 size_t i=actors.size();
1931 while (i--) {
1932 if (actors[i]->InParty) {
1933 return false;
1936 if (actors[i]->GetInternalFlag()&(IF_ACTIVE|IF_USEEXIT) ) {
1937 return false;
1940 //we expect the area to be swapped out, so we simply remove the corpses now
1941 PurgeArea(false);
1942 return true;
1945 void Map::DebugDump(bool show_actors) const
1947 printf( "DebugDump of Area %s:\n", scriptName );
1948 printf( "OutDoor: %s\n", YESNO(AreaType & AT_OUTDOOR ) );
1949 printf( "Day/Night: %s\n", YESNO(AreaType & AT_DAYNIGHT ) );
1950 printf( "Extended night: %s\n", YESNO(AreaType & AT_EXTENDED_NIGHT ) );
1951 printf( "Weather: %s\n", YESNO(AreaType & AT_WEATHER ) );
1952 printf( "Area Type: %d\n", AreaType & (AT_CITY|AT_FOREST|AT_DUNGEON) );
1953 printf( "Can rest: %s\n", YESNO(AreaType & AT_CAN_REST) );
1955 if (show_actors) {
1956 printf("\n");
1957 size_t i = actors.size();
1958 while (i--) {
1959 if (!(actors[i]->GetInternalFlag()&(IF_JUSTDIED|IF_REALLYDIED))) {
1960 printf("Actor: %s at %d.%d\n", actors[i]->GetName(1), actors[i]->Pos.x, actors[i]->Pos.y);
1966 /******************************************************************************/
1968 void Map::Leveldown(unsigned int px, unsigned int py,
1969 unsigned int& level, Point &n, unsigned int& diff)
1971 int pos;
1972 unsigned int nlevel;
1974 if (( px >= Width ) || ( py >= Height )) {
1975 return;
1976 } //walked off the map
1977 pos = py * Width + px;
1978 nlevel = MapSet[pos];
1979 if (!nlevel) {
1980 return;
1981 } //not even considered
1982 if (level <= nlevel) {
1983 return;
1985 unsigned int ndiff = level - nlevel;
1986 if (ndiff > diff) {
1987 level = nlevel;
1988 diff = ndiff;
1989 n.x = (ieWord) px;
1990 n.y = (ieWord) py;
1994 void Map::SetupNode(unsigned int x, unsigned int y, unsigned int size, unsigned int Cost)
1996 unsigned int pos;
1998 if (( x >= Width ) || ( y >= Height )) {
1999 return;
2001 pos = y * Width + x;
2002 if (MapSet[pos]) {
2003 return;
2005 if (GetBlocked(x*16+8,y*12+6,size)) {
2006 MapSet[pos] = 65535;
2007 return;
2009 MapSet[pos] = (ieWord) Cost;
2010 InternalStack.push( ( x << 16 ) | y );
2013 bool Map::AdjustPositionX(Point &goal, unsigned int radius)
2015 unsigned int minx = 0;
2016 if ((unsigned int) goal.x > radius)
2017 minx = goal.x - radius;
2018 unsigned int maxx = goal.x + radius + 1;
2019 if (maxx > Width)
2020 maxx = Width;
2022 for (unsigned int scanx = minx; scanx < maxx; scanx++) {
2023 if ((unsigned int) goal.y >= radius) {
2024 if (GetBlocked( scanx, goal.y - radius ) & PATH_MAP_PASSABLE) {
2025 goal.x = (ieWord) scanx;
2026 goal.y = (ieWord) (goal.y - radius);
2027 return true;
2030 if (goal.y + radius < Height) {
2031 if (GetBlocked( scanx, goal.y + radius ) & PATH_MAP_PASSABLE) {
2032 goal.x = (ieWord) scanx;
2033 goal.y = (ieWord) (goal.y + radius);
2034 return true;
2038 return false;
2041 bool Map::AdjustPositionY(Point &goal, unsigned int radius)
2043 unsigned int miny = 0;
2044 if ((unsigned int) goal.y > radius)
2045 miny = goal.y - radius;
2046 unsigned int maxy = goal.y + radius + 1;
2047 if (maxy > Height)
2048 maxy = Height;
2049 for (unsigned int scany = miny; scany < maxy; scany++) {
2050 if ((unsigned int) goal.x >= radius) {
2051 if (GetBlocked( goal.x - radius, scany ) & PATH_MAP_PASSABLE) {
2052 goal.x = (ieWord) (goal.x - radius);
2053 goal.y = (ieWord) scany;
2054 return true;
2057 if (goal.x + radius < Width) {
2058 if (GetBlocked( goal.x + radius, scany ) & PATH_MAP_PASSABLE) {
2059 goal.x = (ieWord) (goal.x + radius);
2060 goal.y = (ieWord) scany;
2061 return true;
2065 return false;
2068 void Map::AdjustPosition(Point &goal, unsigned int radius)
2070 unsigned int maxr = Width;
2071 if (maxr < Height) {
2072 maxr = Height;
2074 if ((unsigned int) goal.x > Width) {
2075 goal.x = (ieWord) Width;
2077 if ((unsigned int) goal.y > Height) {
2078 goal.y = (ieWord) Height;
2081 for (; radius < maxr; radius++) {
2082 //lets make it slightly random where the actor will appear
2083 if (rand()&1) {
2084 if (AdjustPositionX(goal, radius)) {
2085 return;
2087 if (AdjustPositionY(goal, radius)) {
2088 return;
2090 } else {
2091 if (AdjustPositionY(goal, radius)) {
2092 return;
2094 if (AdjustPositionX(goal, radius)) {
2095 return;
2101 //run away from dX, dY (ie.: find the best path of limited length that brings us the farthest from dX, dY)
2102 PathNode* Map::RunAway(const Point &s, const Point &d, unsigned int size, unsigned int PathLen, int flags)
2104 Point start(s.x/16, s.y/12);
2105 Point goal (d.x/16, d.y/12);
2106 unsigned int dist;
2108 //MapSet entries are made of 16 bits
2109 if (PathLen>65535) {
2110 PathLen = 65535;
2113 memset( MapSet, 0, Width * Height * sizeof( unsigned short ) );
2114 while (InternalStack.size())
2115 InternalStack.pop();
2117 if (!( GetBlocked( start.x, start.y) & PATH_MAP_PASSABLE )) {
2118 AdjustPosition( start );
2120 unsigned int pos = ( start.x << 16 ) | start.y;
2121 InternalStack.push( pos );
2122 MapSet[start.y * Width + start.x] = 1;
2123 dist = 0;
2124 Point best = start;
2125 while (InternalStack.size()) {
2126 pos = InternalStack.front();
2127 InternalStack.pop();
2128 unsigned int x = pos >> 16;
2129 unsigned int y = pos & 0xffff;
2130 long tx = ( x - goal.x );
2131 long ty = ( y - goal.y );
2132 unsigned int distance = (unsigned int) sqrt( ( double ) ( tx* tx + ty* ty ) );
2133 if (dist<distance) {
2134 best.x=(ieWord) x;
2135 best.y=(ieWord) y;
2136 dist=distance;
2139 unsigned int Cost = MapSet[y * Width + x] + NormalCost;
2140 if (Cost > PathLen) {
2141 //printf("Path not found!\n");
2142 break;
2144 SetupNode( x - 1, y - 1, size, Cost );
2145 SetupNode( x + 1, y - 1, size, Cost );
2146 SetupNode( x + 1, y + 1, size, Cost );
2147 SetupNode( x - 1, y + 1, size, Cost );
2149 Cost += AdditionalCost;
2150 SetupNode( x, y - 1, size, Cost );
2151 SetupNode( x + 1, y, size, Cost );
2152 SetupNode( x, y + 1, size, Cost );
2153 SetupNode( x - 1, y, size, Cost );
2156 //find path backwards from best to start
2157 PathNode* StartNode = new PathNode;
2158 PathNode* Return = StartNode;
2159 StartNode->Next = NULL;
2160 StartNode->x = best.x;
2161 StartNode->y = best.y;
2162 if (flags) {
2163 StartNode->orient = GetOrient( start, best );
2164 } else {
2165 StartNode->orient = GetOrient( best, start );
2167 Point p = best;
2168 unsigned int pos2 = start.y * Width + start.x;
2169 while (( pos = p.y * Width + p.x ) != pos2) {
2170 Return = new PathNode;
2171 StartNode->Parent = Return;
2172 Return->Next = StartNode;
2173 StartNode = Return;
2174 unsigned int level = MapSet[pos];
2175 unsigned int diff = 0;
2176 Point n;
2177 Leveldown( p.x, p.y + 1, level, n, diff );
2178 Leveldown( p.x + 1, p.y, level, n, diff );
2179 Leveldown( p.x - 1, p.y, level, n, diff );
2180 Leveldown( p.x, p.y - 1, level, n, diff );
2181 Leveldown( p.x - 1, p.y + 1, level, n, diff );
2182 Leveldown( p.x + 1, p.y + 1, level, n, diff );
2183 Leveldown( p.x + 1, p.y - 1, level, n, diff );
2184 Leveldown( p.x - 1, p.y - 1, level, n, diff );
2185 Return->x = n.x;
2186 Return->y = n.y;
2188 if (flags) {
2189 Return->orient = GetOrient( p, n );
2190 } else {
2191 Return->orient = GetOrient( n, p );
2193 p = n;
2194 if (!diff) {
2195 break;
2198 Return->Parent = NULL;
2199 return Return;
2202 bool Map::TargetUnreachable(const Point &s, const Point &d, unsigned int size)
2204 Point start( s.x/16, s.y/12 );
2205 Point goal ( d.x/16, d.y/12 );
2206 memset( MapSet, 0, Width * Height * sizeof( unsigned short ) );
2207 while (InternalStack.size())
2208 InternalStack.pop();
2210 if (GetBlocked( d.x, d.y, size )) {
2211 return true;
2213 if (GetBlocked( s.x, s.y, size )) {
2214 return true;
2217 unsigned int pos = ( goal.x << 16 ) | goal.y;
2218 unsigned int pos2 = ( start.x << 16 ) | start.y;
2219 InternalStack.push( pos );
2220 MapSet[goal.y * Width + goal.x] = 1;
2222 while (InternalStack.size() && pos!=pos2) {
2223 pos = InternalStack.front();
2224 InternalStack.pop();
2225 unsigned int x = pos >> 16;
2226 unsigned int y = pos & 0xffff;
2228 SetupNode( x - 1, y - 1, size, 1 );
2229 SetupNode( x + 1, y - 1, size, 1 );
2230 SetupNode( x + 1, y + 1, size, 1 );
2231 SetupNode( x - 1, y + 1, size, 1 );
2232 SetupNode( x, y - 1, size, 1 );
2233 SetupNode( x + 1, y, size, 1 );
2234 SetupNode( x, y + 1, size, 1 );
2235 SetupNode( x - 1, y, size, 1 );
2237 return pos!=pos2;
2240 /* Use this function when you target something by a straight line projectile (like a lightning bolt, arrow, etc)
2243 PathNode* Map::GetLine(const Point &start, const Point &dest, int flags)
2245 int Orientation = GetOrient(start, dest);
2246 return GetLine(start, dest, 1, Orientation, flags);
2249 PathNode* Map::GetLine(const Point &start, int Steps, int Orientation, int flags)
2251 Point dest=start;
2253 unsigned int st = Steps>=MaxVisibility?MaxVisibility-1:Steps;
2254 int p = VisibilityPerimeter*Orientation/MAX_ORIENT;
2255 dest.x += VisibilityMasks[st][p].x;
2256 dest.y += VisibilityMasks[st][p].y;
2257 //FIXME: calculate dest based on distance and orientation
2258 return GetLine(start, dest, Steps, Orientation, flags);
2261 PathNode* Map::GetLine(const Point &start, const Point &dest, int Speed, int Orientation, int flags)
2263 PathNode* StartNode = new PathNode;
2264 PathNode *Return = StartNode;
2265 StartNode->Next = NULL;
2266 StartNode->Parent = NULL;
2267 StartNode->x = start.x;
2268 StartNode->y = start.y;
2269 StartNode->orient = Orientation;
2271 int Count = 0;
2272 int Max = Distance(start,dest);
2273 for (int Steps = 0; Steps<Max; Steps++) {
2274 if (!Count) {
2275 StartNode->Next = new PathNode;
2276 StartNode->Next->Parent = StartNode;
2277 StartNode = StartNode->Next;
2278 StartNode->Next = NULL;
2279 Count=Speed;
2280 } else {
2281 Count--;
2284 Point p;
2285 p.x = (ieWord) start.x + ((dest.x - start.x) * Steps / Max);
2286 p.y = (ieWord) start.y + ((dest.y - start.y) * Steps / Max);
2288 //the path ends here as it would go off the screen, causing problems
2289 //maybe there is a better way, but i needed a quick hack to fix
2290 //the crash in projectiles
2291 if ((signed) p.x<0 || (signed) p.y<0) {
2292 return Return;
2294 if ((ieWord) p.x>Width*16 || (ieWord) p.y>Height*12) {
2295 return Return;
2298 StartNode->x = p.x;
2299 StartNode->y = p.y;
2300 StartNode->orient = Orientation;
2301 bool wall = !( GetBlocked( p ) & PATH_MAP_PASSABLE );
2302 if (wall) switch (flags) {
2303 case GL_REBOUND:
2304 Orientation = (Orientation + 8) &15;
2305 //recalculate dest (mirror it)
2306 break;
2307 case GL_PASS:
2308 break;
2309 default: //premature end
2310 return Return;
2314 return Return;
2318 * find a path from start to goal, ending at the specified distance from the
2319 * target (the goal must be in sight of the end, if 'sight' is specified)
2321 * if you don't need to find an optimal path near the goal then use FindPath
2322 * instead, but don't change this one without testing with combat and dialog,
2323 * you can't predict the goal point for those, you *must* path!
2325 PathNode* Map::FindPathNear(const Point &s, const Point &d, unsigned int size, unsigned int MinDistance, bool sight)
2327 // adjust the start/goal points to be searchmap locations
2328 Point start( s.x/16, s.y/12 );
2329 Point goal ( d.x/16, d.y/12 );
2330 Point orig_goal = goal;
2332 // re-initialise the path finding structures
2333 memset( MapSet, 0, Width * Height * sizeof( unsigned short ) );
2334 while (InternalStack.size())
2335 InternalStack.pop();
2337 // set the start point in the path finding structures
2338 unsigned int pos2 = ( goal.x << 16 ) | goal.y;
2339 unsigned int pos = ( start.x << 16 ) | start.y;
2340 InternalStack.push( pos );
2341 MapSet[start.y * Width + start.x] = 1;
2343 unsigned int squaredmindistance = MinDistance * MinDistance;
2344 bool found_path = false;
2345 while (InternalStack.size()) {
2346 pos = InternalStack.front();
2347 InternalStack.pop();
2348 unsigned int x = pos >> 16;
2349 unsigned int y = pos & 0xffff;
2351 if (pos == pos2) {
2352 // we got all the way to the target!
2353 found_path = true;
2354 break;
2355 } else if (MinDistance) {
2356 /* check minimum distance:
2357 * as an obvious optimisation we only check squared distance: this is a
2358 * possible overestimate since the sqrt Distance() rounds down
2359 * (some other optimisations could be made here, but you'd be better off
2360 * fixing the pathfinder to do A* properly)
2361 * caller should have already done PersonalDistance adjustments, this is
2362 * simply between the specified points
2365 int distx = (x - orig_goal.x)*16;
2366 int disty = (y - orig_goal.y)*12;
2367 if ((unsigned int)(distx*distx + disty*disty) <= squaredmindistance) {
2368 // we are within the minimum distance of the goal
2369 Point ourpos(x*16 + 8, y*12 + 6);
2370 // sight check is *slow* :(
2371 if (!sight || IsVisible(ourpos, d)) {
2372 // we got all the way to a suitable goal!
2373 goal = Point(x, y);
2374 found_path = true;
2375 break;
2380 unsigned int Cost = MapSet[y * Width + x] + NormalCost;
2381 if (Cost > 65500) {
2382 // cost is far too high, no path found
2383 break;
2386 // diagonal movements
2387 SetupNode( x - 1, y - 1, size, Cost );
2388 SetupNode( x + 1, y - 1, size, Cost );
2389 SetupNode( x + 1, y + 1, size, Cost );
2390 SetupNode( x - 1, y + 1, size, Cost );
2392 // direct movements
2393 Cost += AdditionalCost;
2394 SetupNode( x, y - 1, size, Cost );
2395 SetupNode( x + 1, y, size, Cost );
2396 SetupNode( x, y + 1, size, Cost );
2397 SetupNode( x - 1, y, size, Cost );
2400 // find path from goal to start
2401 PathNode* StartNode = new PathNode;
2402 PathNode* Return = StartNode;
2403 StartNode->Next = NULL;
2404 StartNode->Parent = NULL;
2405 if (!found_path) {
2406 // this is not really great, we should be finding the path that
2407 // went nearest to where we wanted
2408 StartNode->x = start.x;
2409 StartNode->y = start.y;
2410 StartNode->orient = GetOrient( goal, start );
2411 return Return;
2413 StartNode->x = goal.x;
2414 StartNode->y = goal.y;
2415 bool fixup_orient = false;
2416 if (orig_goal != goal) {
2417 StartNode->orient = GetOrient( orig_goal, goal );
2418 } else {
2419 // we pathed all the way to original goal!
2420 // we don't know correct orientation until we find previous step
2421 fixup_orient = true;
2422 StartNode->orient = GetOrient( goal, start );
2424 Point p = goal;
2425 pos2 = start.y * Width + start.x;
2426 while (( pos = p.y * Width + p.x ) != pos2) {
2427 unsigned int level = MapSet[pos];
2428 unsigned int diff = 0;
2429 Point n;
2430 Leveldown( p.x, p.y + 1, level, n, diff );
2431 Leveldown( p.x + 1, p.y, level, n, diff );
2432 Leveldown( p.x - 1, p.y, level, n, diff );
2433 Leveldown( p.x, p.y - 1, level, n, diff );
2434 Leveldown( p.x - 1, p.y + 1, level, n, diff );
2435 Leveldown( p.x + 1, p.y + 1, level, n, diff );
2436 Leveldown( p.x + 1, p.y - 1, level, n, diff );
2437 Leveldown( p.x - 1, p.y - 1, level, n, diff );
2438 if (!diff)
2439 return Return;
2441 if (fixup_orient) {
2442 // don't change orientation at end of path? this seems best
2443 StartNode->orient = GetOrient( p, n );
2446 Return = new PathNode;
2447 Return->Next = StartNode;
2448 Return->Next->Parent = Return;
2449 StartNode = Return;
2451 StartNode->x = n.x;
2452 StartNode->y = n.y;
2453 StartNode->orient = GetOrient( p, n );
2454 p = n;
2457 return Return;
2460 PathNode* Map::FindPath(const Point &s, const Point &d, unsigned int size, int MinDistance)
2462 Point start( s.x/16, s.y/12 );
2463 Point goal ( d.x/16, d.y/12 );
2464 memset( MapSet, 0, Width * Height * sizeof( unsigned short ) );
2465 while (InternalStack.size())
2466 InternalStack.pop();
2468 if (GetBlocked( d.x, d.y, size )) {
2469 AdjustPosition( goal );
2471 unsigned int pos = ( goal.x << 16 ) | goal.y;
2472 unsigned int pos2 = ( start.x << 16 ) | start.y;
2473 InternalStack.push( pos );
2474 MapSet[goal.y * Width + goal.x] = 1;
2476 while (InternalStack.size()) {
2477 pos = InternalStack.front();
2478 InternalStack.pop();
2479 unsigned int x = pos >> 16;
2480 unsigned int y = pos & 0xffff;
2482 if (pos == pos2) {
2483 //We've found _a_ path
2484 //printf("GOAL!!!\n");
2485 break;
2487 unsigned int Cost = MapSet[y * Width + x] + NormalCost;
2488 if (Cost > 65500) {
2489 //printf("Path not found!\n");
2490 break;
2492 SetupNode( x - 1, y - 1, size, Cost );
2493 SetupNode( x + 1, y - 1, size, Cost );
2494 SetupNode( x + 1, y + 1, size, Cost );
2495 SetupNode( x - 1, y + 1, size, Cost );
2497 Cost += AdditionalCost;
2498 SetupNode( x, y - 1, size, Cost );
2499 SetupNode( x + 1, y, size, Cost );
2500 SetupNode( x, y + 1, size, Cost );
2501 SetupNode( x - 1, y, size, Cost );
2504 //find path from start to goal
2505 PathNode* StartNode = new PathNode;
2506 PathNode* Return = StartNode;
2507 StartNode->Next = NULL;
2508 StartNode->Parent = NULL;
2509 StartNode->x = start.x;
2510 StartNode->y = start.y;
2511 StartNode->orient = GetOrient( goal, start );
2512 if (pos != pos2) {
2513 return Return;
2515 Point p = start;
2516 pos2 = goal.y * Width + goal.x;
2517 while (( pos = p.y * Width + p.x ) != pos2) {
2518 StartNode->Next = new PathNode;
2519 StartNode->Next->Parent = StartNode;
2520 StartNode = StartNode->Next;
2521 StartNode->Next = NULL;
2522 unsigned int level = MapSet[pos];
2523 unsigned int diff = 0;
2524 Point n;
2525 Leveldown( p.x, p.y + 1, level, n, diff );
2526 Leveldown( p.x + 1, p.y, level, n, diff );
2527 Leveldown( p.x - 1, p.y, level, n, diff );
2528 Leveldown( p.x, p.y - 1, level, n, diff );
2529 Leveldown( p.x - 1, p.y + 1, level, n, diff );
2530 Leveldown( p.x + 1, p.y + 1, level, n, diff );
2531 Leveldown( p.x + 1, p.y - 1, level, n, diff );
2532 Leveldown( p.x - 1, p.y - 1, level, n, diff );
2533 if (!diff)
2534 return Return;
2535 StartNode->x = n.x;
2536 StartNode->y = n.y;
2537 StartNode->orient = GetOrient( n, p );
2538 p = n;
2540 //stepping back on the calculated path
2541 if (MinDistance) {
2542 while (StartNode->Parent) {
2543 Point tar;
2545 tar.x=StartNode->Parent->x*16;
2546 tar.y=StartNode->Parent->y*12;
2547 int dist = Distance(tar,d);
2548 if (dist+14>=MinDistance) {
2549 break;
2551 StartNode = StartNode->Parent;
2552 delete StartNode->Next;
2553 StartNode->Next = NULL;
2556 return Return;
2559 //single point visible or not (visible/exploredbitmap)
2560 //if explored = true then explored otherwise currently visible
2561 bool Map::IsVisible(const Point &pos, int explored)
2563 if (!VisibleBitmap)
2564 return false;
2565 int sX=pos.x/32;
2566 int sY=pos.y/32;
2567 if (sX<0) return false;
2568 if (sY<0) return false;
2569 int w = TMap->XCellCount * 2 + LargeFog;
2570 int h = TMap->YCellCount * 2 + LargeFog;
2571 if (sX>=w) return false;
2572 if (sY>=h) return false;
2573 int b0 = (sY * w) + sX;
2574 int by = b0/8;
2575 int bi = 1<<(b0%8);
2577 if (explored) return (ExploredBitmap[by] & bi)!=0;
2578 return (VisibleBitmap[by] & bi)!=0;
2581 //point a is visible from point b (searchmap)
2582 bool Map::IsVisible(const Point &s, const Point &d)
2584 int sX=s.x/16;
2585 int sY=s.y/12;
2586 int dX=d.x/16;
2587 int dY=d.y/12;
2588 int diffx = sX - dX;
2589 int diffy = sY - dY;
2590 if (abs( diffx ) >= abs( diffy )) {
2591 //vertical
2592 double elevationy = fabs((double)diffx ) / diffy;
2593 if (sX > dX) {
2594 for (int startx = sX; startx > dX; startx--) {
2595 if (GetBlocked( startx, sY - ( int ) ( ( sX - startx ) / elevationy ) ) & PATH_MAP_NO_SEE)
2596 return false;
2598 } else {
2599 for (int startx = sX; startx < dX; startx++) {
2600 if (GetBlocked( startx, sY + ( int ) ( ( sX - startx ) / elevationy ) ) & PATH_MAP_NO_SEE)
2601 return false;
2604 } else {
2605 double elevationx = fabs((double)diffy ) / diffx;
2606 if (sY > dY) {
2607 for (int starty = sY; starty > dY; starty--) {
2608 if (GetBlocked( sX - ( int ) ( ( sY - starty ) / elevationx ), starty ) & PATH_MAP_NO_SEE)
2609 return false;
2611 } else {
2612 for (int starty = sY; starty < dY; starty++) {
2613 if (GetBlocked( sX + ( int ) ( ( sY - starty ) / elevationx ), starty ) & PATH_MAP_NO_SEE)
2614 return false;
2618 return true;
2621 //returns direction of area boundary, returns -1 if it isn't a boundary
2622 int Map::WhichEdge(const Point &s)
2624 unsigned int sX=s.x/16;
2625 unsigned int sY=s.y/12;
2626 if (!(GetBlocked( sX, sY )&PATH_MAP_TRAVEL)) {
2627 printMessage("Map"," ",YELLOW);
2628 printf("This isn't a travel region [%d.%d]?\n",sX, sY);
2629 return -1;
2631 sX*=Height;
2632 sY*=Width;
2633 if (sX>sY) { //north or east
2634 if (Width*Height>sX+sY) { //
2635 return WMP_NORTH;
2637 return WMP_EAST;
2639 //south or west
2640 if (Width*Height<sX+sY) { //
2641 return WMP_SOUTH;
2643 return WMP_WEST;
2646 //--------ambients----------------
2647 void Map::SetupAmbients()
2649 AmbientMgr *ambim = core->GetAudioDrv()->GetAmbientMgr();
2650 if (!ambim) return;
2651 ambim->reset();
2652 ambim->setAmbients( ambients );
2654 //--------mapnotes----------------
2655 //text must be a pointer we can claim ownership of
2656 void Map::AddMapNote(const Point &point, int color, char *text, ieStrRef strref)
2658 MapNote *mn = new MapNote;
2660 mn->strref = strref;
2661 mn->Pos = point;
2662 mn->color = (ieWord) color;
2663 mn->text = text;
2664 RemoveMapNote(point); //delete previous mapnote
2665 mapnotes.push_back(mn);
2668 void Map::RemoveMapNote(const Point &point)
2670 size_t i = mapnotes.size();
2671 while (i--) {
2672 if ((point.x==mapnotes[i]->Pos.x) &&
2673 (point.y==mapnotes[i]->Pos.y)) {
2674 delete mapnotes[i];
2675 mapnotes.erase(mapnotes.begin()+i);
2680 MapNote *Map::GetMapNote(const Point &point)
2682 size_t i = mapnotes.size();
2683 while (i--) {
2684 if (Distance(point, mapnotes[i]->Pos) < 10 ) {
2685 return mapnotes[i];
2688 return NULL;
2690 //--------spawning------------------
2691 void Map::LoadIniSpawn()
2693 INISpawn = new IniSpawn(this);
2694 INISpawn->InitSpawn(WEDResRef);
2697 void Map::SpawnCreature(const Point &pos, const char *CreName, int radius)
2699 SpawnGroup *sg=NULL;
2700 Actor *creature;
2701 void* lookup;
2702 if ( !Spawns.Lookup( CreName, lookup) ) {
2703 creature = gamedata->GetCreature(CreName);
2704 if ( creature ) {
2705 AddActor(creature);
2706 creature->SetPosition( pos, true, radius );
2707 creature->RefreshEffects(NULL);
2709 return;
2711 sg = (SpawnGroup*)lookup;
2712 unsigned int count = 0;
2713 int amount = core->GetGame()->GetPartyLevel(true);
2714 // if the difficulty is too high, distribute it equally over all the
2715 // critters and summon as many as the summed difficulty allows
2716 if (amount - (signed)sg->Level < 0) {
2717 unsigned int share = sg->Level/sg->Count;
2718 amount -= share;
2719 if (amount < 0) {
2720 // a single critter is also too powerful
2721 return;
2723 while (amount >= 0) {
2724 count++;
2725 amount -= share;
2727 } else {
2728 count = sg->Count;
2731 while ( count-- ) {
2732 creature = gamedata->GetCreature(sg->ResRefs[count]);
2733 if ( creature ) {
2734 AddActor(creature);
2735 creature->SetPosition( pos, true, radius );
2736 creature->RefreshEffects(NULL);
2741 void Map::TriggerSpawn(Spawn *spawn)
2743 //is it still active
2744 if (!spawn->Enabled) {
2745 return;
2747 //check schedule
2748 ieDword bit = 1<<((core->GetGame()->GameTime/AI_UPDATE_TIME)%7200/300);
2749 if (!(spawn->appearance & bit)) {
2750 return;
2753 //check day or night chance
2754 if (rand()%100>spawn->DayChance) {
2755 return;
2757 // the difficulty check is done in SpawnCreature
2758 //create spawns
2759 for(unsigned int i = 0;i<spawn->Count;i++) {
2760 SpawnCreature(spawn->Pos, spawn->Creatures[i], 0);
2762 //disable spawnpoint
2763 spawn->Enabled = 0;
2766 //--------restheader----------------
2768 Every spawn has a difficulty associated with it. For CREs this is the xp stat
2769 and for groups it's the value in the difficulty row.
2770 For every spawn, the difficulty sum of all spawns up to now (including the
2771 current) is compared against (party level * rest header difficulty). If it's
2772 greater, the spawning is aborted. If all the other conditions are true, at
2773 least one creature is summoned, regardless the difficulty cap.
2775 bool Map::Rest(const Point &pos, int hours, int day)
2777 if (!RestHeader.CreatureNum || !RestHeader.Enabled || !RestHeader.Maximum) {
2778 return false;
2781 //based on ingame timer
2782 int chance=day?RestHeader.DayChance:RestHeader.NightChance;
2783 int spawncount = 1;
2784 int spawnamount = core->GetGame()->GetPartyLevel(true) * RestHeader.Difficulty;
2785 if (spawnamount < 1) spawnamount = 1;
2786 for (int i=0;i<hours;i++) {
2787 if ( rand()%100<chance ) {
2788 int idx = rand()%RestHeader.CreatureNum;
2789 Actor *creature = gamedata->GetCreature(RestHeader.CreResRef[idx]);
2790 if (!creature) continue;
2791 // ensure a minimum power level, since many creatures have this as 0
2792 int cpl = creature->Modified[IE_XP] ? creature->Modified[IE_XP] : 1;
2794 displaymsg->DisplayString( RestHeader.Strref[idx], 0x00404000, IE_STR_SOUND );
2795 while (spawnamount > 0 && spawncount <= RestHeader.Maximum) {
2796 SpawnCreature(pos, RestHeader.CreResRef[idx], 20);
2797 spawnamount -= cpl;
2798 spawncount++;
2800 return true;
2803 return false;
2806 //--------explored bitmap-----------
2807 int Map::GetExploredMapSize() const
2809 int x = TMap->XCellCount*2;
2810 int y = TMap->YCellCount*2;
2811 if (LargeFog) {
2812 x++;
2813 y++;
2815 return (x*y+7)/8;
2818 void Map::Explore(int setreset)
2820 memset (ExploredBitmap, setreset, GetExploredMapSize() );
2823 void Map::SetMapVisibility(int setreset)
2825 memset( VisibleBitmap, setreset, GetExploredMapSize() );
2828 // x, y are in tile coordinates
2829 void Map::ExploreTile(const Point &pos)
2831 int h = TMap->YCellCount * 2 + LargeFog;
2832 int y = pos.y/32;
2833 if (y < 0 || y >= h)
2834 return;
2836 int w = TMap->XCellCount * 2 + LargeFog;
2837 int x = pos.x/32;
2838 if (x < 0 || x >= w)
2839 return;
2841 int b0 = (y * w) + x;
2842 int by = b0/8;
2843 int bi = 1<<(b0%8);
2845 ExploredBitmap[by] |= bi;
2846 VisibleBitmap[by] |= bi;
2849 void Map::ExploreMapChunk(const Point &Pos, int range, int los)
2851 Point Tile;
2853 if (range>MaxVisibility) {
2854 range=MaxVisibility;
2856 int p=VisibilityPerimeter;
2857 while (p--) {
2858 int Pass = 2;
2859 bool block = false;
2860 bool sidewall = false ;
2861 for (int i=0;i<range;i++) {
2862 Tile.x = Pos.x+VisibilityMasks[i][p].x;
2863 Tile.y = Pos.y+VisibilityMasks[i][p].y;
2865 if (los) {
2866 if (!block) {
2867 int type = GetBlocked(Tile);
2868 if (type & PATH_MAP_NO_SEE) {
2869 block=true;
2870 } else if (type & PATH_MAP_SIDEWALL) {
2871 sidewall = true;
2872 } else if (sidewall)
2874 block=true ;
2877 if (block) {
2878 Pass--;
2879 if (!Pass) break;
2882 ExploreTile(Tile);
2887 void Map::UpdateFog()
2889 if (!(core->FogOfWar&FOG_DRAWFOG) ) {
2890 SetMapVisibility( -1 );
2891 return;
2894 SetMapVisibility( 0 );
2895 for (unsigned int e = 0; e<actors.size(); e++) {
2896 Actor *actor = actors[e];
2897 if (!actor->Modified[ IE_EXPLORE ] ) continue;
2898 int state = actor->Modified[IE_STATE_ID];
2899 if (state & STATE_CANTSEE) continue;
2900 int vis2 = actor->Modified[IE_VISUALRANGE];
2901 if ((state&STATE_BLIND) || (vis2<2)) vis2=2; //can see only themselves
2902 ExploreMapChunk (actor->Pos, vis2, 1);
2903 Spawn *sp = GetSpawnRadius(actor->Pos, SPAWN_RANGE); //30 * 12
2904 if (sp) {
2905 TriggerSpawn(sp);
2910 //Valid values are - PATH_MAP_FREE, PATH_MAP_PC, PATH_MAP_NPC
2911 void Map::BlockSearchMap(const Point &Pos, unsigned int size, unsigned int value)
2913 // We block a circle of radius size-1 around (px,py)
2914 // Note that this does not exactly match BG2. BG2's approximations of
2915 // these circles are slightly different for sizes 6 and up.
2917 // Note: this is a larger circle than the one tested in GetBlocked.
2918 // This means that an actor can get closer to a wall than to another
2919 // actor. This matches the behaviour of the original BG2.
2921 if (size > MAX_CIRCLESIZE) size = MAX_CIRCLESIZE;
2922 if (size < 2) size = 2;
2923 unsigned int ppx = Pos.x/16;
2924 unsigned int ppy = Pos.y/12;
2925 unsigned int r=(size-1)*(size-1)+1;
2926 if (size == 1) r = 0;
2927 for (unsigned int i=0; i<size; i++) {
2928 for (unsigned int j=0; j<size; j++) {
2929 if (i*i+j*j <= r) {
2930 unsigned char tmp;
2932 tmp = SearchMap->GetAt(ppx+i,ppy+j)&PATH_MAP_NOTACTOR;
2933 SearchMap->SetAt(ppx+i,ppy+j,tmp|value);
2935 tmp = SearchMap->GetAt(ppx+i,ppy-j)&PATH_MAP_NOTACTOR;
2936 SearchMap->SetAt(ppx+i,ppy-j,tmp|value);
2938 tmp = SearchMap->GetAt(ppx-i,ppy+j)&PATH_MAP_NOTACTOR;
2939 SearchMap->SetAt(ppx-i,ppy+j,tmp|value);
2941 tmp = SearchMap->GetAt(ppx-i,ppy-j)&PATH_MAP_NOTACTOR;
2942 SearchMap->SetAt(ppx-i,ppy-j,tmp|value);
2948 Spawn* Map::GetSpawn(const char* Name)
2950 for (size_t i = 0; i < spawns.size(); i++) {
2951 Spawn* sp = spawns[i];
2953 if (stricmp( sp->Name, Name ) == 0)
2954 return sp;
2956 return NULL;
2959 Spawn *Map::GetSpawnRadius(const Point &point, unsigned int radius)
2961 for (size_t i = 0; i < spawns.size(); i++) {
2962 Spawn* sp = spawns[i];
2964 if (Distance(point, sp->Pos)<radius) {
2965 return sp;
2968 return NULL;
2971 int Map::ConsolidateContainers()
2973 int itemcount = 0;
2974 int containercount = (int) TMap->GetContainerCount();
2975 while (containercount--) {
2976 Container * c = TMap->GetContainer( containercount);
2978 if (TMap->CleanupContainer(c) ) {
2979 continue;
2981 itemcount += c->inventory.GetSlotCount();
2983 return itemcount;
2986 //Pos could be [-1,-1] in which case it copies the ground piles to their
2987 //original position in the second area
2988 void Map::CopyGroundPiles(Map *othermap, const Point &Pos)
2990 int containercount = (int) TMap->GetContainerCount();
2991 while (containercount--) {
2992 Container * c = TMap->GetContainer( containercount);
2993 if (c->Type==IE_CONTAINER_PILE) {
2994 //creating (or grabbing) the container in the other map at the given position
2995 Container *othercontainer;
2996 if (Pos.isempty()) {
2997 othercontainer = othermap->GetPile(c->Pos);
2998 } else {
2999 othercontainer = othermap->GetPile(Pos);
3001 //transfer the pile to the other container
3002 unsigned int i=c->inventory.GetSlotCount();
3003 while (i--) {
3004 CREItem *item = c->RemoveItem(i, 0);
3005 othercontainer->AddItem(item);
3011 Container *Map::GetPile(Point position)
3013 Point tmp[4];
3014 char heapname[32];
3016 //converting to search square
3017 position.x=position.x/16;
3018 position.y=position.y/12;
3019 sprintf(heapname,"heap_%hd.%hd",position.x,position.y);
3020 //pixel position is centered on search square
3021 position.x=position.x*16+8;
3022 position.y=position.y*12+6;
3023 Container *container = TMap->GetContainer(position,IE_CONTAINER_PILE);
3024 if (!container) {
3025 //bounding box covers the search square
3026 tmp[0].x=position.x-8;
3027 tmp[0].y=position.y-6;
3028 tmp[1].x=position.x+8;
3029 tmp[1].y=position.y-6;
3030 tmp[2].x=position.x+8;
3031 tmp[2].y=position.y+6;
3032 tmp[3].x=position.x-8;
3033 tmp[3].y=position.y+6;
3034 Gem_Polygon* outline = new Gem_Polygon( tmp, 4 );
3035 container = AddContainer(heapname, IE_CONTAINER_PILE, outline);
3036 container->Pos=position;
3038 return container;
3041 void Map::AddItemToLocation(const Point &position, CREItem *item)
3043 Container *container = GetPile(position);
3044 container->AddItem(item);
3047 Container* Map::AddContainer(const char* Name, unsigned short Type,
3048 Gem_Polygon* outline)
3050 Container* c = new Container();
3051 c->SetScriptName( Name );
3052 c->Type = Type;
3053 c->outline = outline;
3054 c->SetMap(this);
3055 TMap->AddContainer( c );
3056 return c;
3059 int Map::GetCursor( const Point &p)
3061 if (!IsVisible( p, true ) ) {
3062 return IE_CURSOR_INVALID;
3064 switch (GetBlocked( p ) & (PATH_MAP_PASSABLE|PATH_MAP_TRAVEL)) {
3065 case 0:
3066 return IE_CURSOR_BLOCKED;
3067 case PATH_MAP_PASSABLE:
3068 return IE_CURSOR_WALK;
3069 default:
3070 return IE_CURSOR_TRAVEL;
3074 bool Map::HasWeather()
3076 if ((AreaType & (AT_WEATHER|AT_OUTDOOR) ) != (AT_WEATHER|AT_OUTDOOR) ) {
3077 return false;
3079 return true;
3082 int Map::GetWeather()
3084 if (Rain>=core->Roll(1,100,0) ) {
3085 if (Lightning>=core->Roll(1,100,0) ) {
3086 return WB_LIGHTNING|WB_RAIN;
3088 return WB_RAIN;
3090 if (Snow>=core->Roll(1,100,0) ) {
3091 return WB_SNOW;
3093 if (Fog>=core->Roll(1,100,0) ) {
3094 return WB_FOG;
3096 return WB_NORMAL;
3099 void Map::FadeSparkle(const Point &pos, bool forced)
3101 spaIterator iter;
3103 for(iter=particles.begin(); iter!=particles.end();iter++) {
3104 if ((*iter)->MatchPos(pos) ) {
3105 if (forced) {
3106 //particles.erase(iter);
3107 (*iter)->SetPhase(P_EMPTY);
3108 } else {
3109 (*iter)->SetPhase(P_FADE);
3111 return;
3116 void Map::Sparkle(ieDword color, ieDword type, const Point &pos, unsigned int FragAnimID)
3118 int style, path, grow, size, width;
3120 //the high word is ignored in the original engine (compatibility hack)
3121 switch(type&0xffff) {
3122 case SPARKLE_SHOWER: //simple falling sparks
3123 path = SP_PATH_FALL;
3124 grow = SP_SPAWN_FULL;
3125 size = 100;
3126 width = 40;
3127 break;
3128 case SPARKLE_PUFF:
3129 path = SP_PATH_FOUNT; //sparks go up and down
3130 grow = SP_SPAWN_FULL;
3131 size = 100;
3132 width = 40;
3133 break;
3134 case SPARKLE_EXPLOSION: //this isn't in the original engine, but it is a nice effect to have
3135 path = SP_PATH_EXPL;
3136 grow = SP_SPAWN_FULL;
3137 size = 10;
3138 width = 140;
3139 break;
3140 default:
3141 path = SP_PATH_FLIT;
3142 grow = SP_SPAWN_SOME;
3143 size = 100;
3144 width = 40;
3145 break;
3147 Particles *sparkles = new Particles(size);
3148 sparkles->SetOwner(this);
3149 sparkles->SetRegion(pos.x-width/2, pos.y-80, width, 80);
3151 if (FragAnimID) {
3152 style = SP_TYPE_BITMAP;
3153 sparkles->SetBitmap(FragAnimID);
3155 else {
3156 style = SP_TYPE_POINT;
3158 sparkles->SetType(style, path, grow);
3159 sparkles->SetColor(color);
3160 sparkles->SetPhase(P_GROW);
3161 printf("sparkle: %d %d\n", color, type);
3162 printf("Position: %d.%d\n", pos.x,pos.y);
3164 //AddParticle(sparkles, pos);
3165 spaIterator iter;
3166 for(iter=particles.begin(); (iter!=particles.end()) && ((*iter)->GetHeight()<pos.y); iter++) ;
3167 particles.insert(iter, sparkles);
3170 //remove flags from actor if it has left the trigger area it had last entered
3171 void Map::ClearTrap(Actor *actor, ieDword InTrap)
3173 InfoPoint *trap = TMap->GetInfoPoint(InTrap);
3174 if (!trap) {
3175 actor->SetInTrap(0);
3176 } else {
3177 if(!trap->outline->PointIn(actor->Pos)) {
3178 actor->SetInTrap(0);
3183 void Map::SetTrackString(ieStrRef strref, int flg, int difficulty)
3185 trackString = strref;
3186 trackFlag = flg;
3187 trackDiff = (ieWord) difficulty;
3190 bool Map::DisplayTrackString(Actor *target)
3192 // this stat isn't saved
3193 // according to the HoW manual the chance of success is:
3194 // +5% for every three levels and +5% per point of wisdom
3195 int skill = target->GetStat(IE_TRACKING);
3196 skill += (target->GetStat(IE_LEVEL)/3)*5 + target->GetStat(IE_WIS)*5;
3197 if (core->Roll(1, 100, trackDiff) > skill) {
3198 displaymsg->DisplayConstantStringName(STR_TRACKINGFAILED, 0xd7d7be, target);
3199 return true;
3201 if (trackFlag) {
3202 char * str = core->GetString( trackString);
3203 core->GetTokenDictionary()->SetAt( "CREATURE", str);
3204 displaymsg->DisplayConstantStringName(STR_TRACKING, 0xd7d7be, target);
3205 return false;
3207 displaymsg->DisplayStringName(trackString, 0xd7d7be, target, 0);
3208 return false;
3211 // returns a lightness level in the range of [0-100]
3212 // since the lightmap is much smaller than the area, we need to interpolate
3213 unsigned int Map::GetLightLevel(const Point &Pos)
3215 Color c = LightMap->GetPixel(Pos.x/16, Pos.y/12);
3216 // at night/dusk/dawn the lightmap color is adjusted by the color overlay. (Only get's darker.)
3217 const Color *tint = core->GetGame()->GetGlobalTint();
3218 if (tint) {
3219 return ((c.r-tint->r)*114 + (c.g-tint->g)*587 + (c.b-tint->b)*299)/2550;
3221 return (c.r*114+c.g*587+c.b*299)/2550;
3224 ////////////////////AreaAnimation//////////////////
3225 //Area animation
3227 AreaAnimation::AreaAnimation()
3229 animation=NULL;
3230 animcount=0;
3231 palette=NULL;
3232 covers=NULL;
3235 AreaAnimation::~AreaAnimation()
3237 for(int i=0;i<animcount;i++) {
3238 if (animation[i]) {
3239 delete (animation[i]);
3242 free(animation);
3243 gamedata->FreePalette(palette, PaletteRef);
3244 if (covers) {
3245 for(int i=0;i<animcount;i++) {
3246 delete covers[i];
3248 free (covers);
3252 Animation *AreaAnimation::GetAnimationPiece(AnimationFactory *af, int animCycle)
3254 Animation *anim = af->GetCycle( ( unsigned char ) animCycle );
3255 if (!anim)
3256 anim = af->GetCycle( 0 );
3257 if (!anim) {
3258 printf("Cannot load animation: %s\n", BAM);
3259 return NULL;
3261 //this will make the animation stop when the game is stopped
3262 //a possible gemrb feature to have this flag settable in .are
3263 anim->gameAnimation = true;
3264 anim->pos = frame;
3265 anim->Flags = Flags;
3266 anim->x = Pos.x;
3267 anim->y = Pos.y;
3268 if (anim->Flags&A_ANI_MIRROR) {
3269 anim->MirrorAnimation();
3272 return anim;
3275 void AreaAnimation::InitAnimation()
3277 AnimationFactory* af = ( AnimationFactory* )
3278 gamedata->GetFactoryResource( BAM, IE_BAM_CLASS_ID );
3279 if (!af) {
3280 printf("Cannot load animation: %s\n", BAM);
3281 return;
3284 //freeing up the previous animation
3285 for(int i=0;i<animcount;i++) {
3286 if (animation[i]) {
3287 delete (animation[i]);
3290 free(animation);
3292 if (Flags & A_ANI_ALLCYCLES) {
3293 animcount = (int) af->GetCycleCount();
3295 animation = (Animation **) malloc(animcount * sizeof(Animation *) );
3296 for(int j=0;j<animcount;j++) {
3297 animation[j]=GetAnimationPiece(af, j);
3299 } else {
3300 animcount = 1;
3301 animation = (Animation **) malloc( sizeof(Animation *) );
3302 animation[0]=GetAnimationPiece(af, sequence);
3304 if (Flags & A_ANI_PALETTE) {
3305 SetPalette(PaletteRef);
3307 if (Flags&A_ANI_BLEND) {
3308 BlendAnimation();
3312 void AreaAnimation::SetPalette(ieResRef Pal)
3314 Flags |= A_ANI_PALETTE;
3315 gamedata->FreePalette(palette, PaletteRef);
3316 strnlwrcpy(PaletteRef, Pal, 8);
3317 palette = gamedata->GetPalette(PaletteRef);
3318 if (Flags&A_ANI_BLEND) {
3319 //re-blending after palette change
3320 BlendAnimation();
3324 void AreaAnimation::BlendAnimation()
3326 //Warning! This function will modify a shared palette
3327 if (!palette) {
3328 // CHECKME: what should we do here? Currently copying palette
3329 // from first frame of first animation
3331 if (animcount == 0 || !animation[0]) return;
3332 Sprite2D* spr = animation[0]->GetFrame(0);
3333 if (!spr) return;
3334 palette = spr->GetPalette()->Copy();
3335 PaletteRef[0] = 0;
3337 palette->CreateShadedAlphaChannel();
3340 bool AreaAnimation::Schedule(ieDword gametime)
3342 if (!(Flags&A_ANI_ACTIVE) ) {
3343 return false;
3346 //check for schedule
3347 ieDword bit = 1<<((gametime/AI_UPDATE_TIME)%7200/300);
3348 if (appearance & bit) {
3349 return true;
3351 return false;
3354 void AreaAnimation::Draw(const Region &screen, Map *area)
3356 int ac=animcount;
3357 Video* video = core->GetVideoDriver();
3359 //always draw the animation tinted because tint is also used for
3360 //transparency
3361 Color tint = {255,255,255,255-(ieByte) transparency};
3362 if ((Flags&A_ANI_NO_SHADOW)) {
3363 tint = area->LightMap->GetPixel( Pos.x / 16, Pos.y / 12);
3364 tint.a = 255-(ieByte) transparency;
3366 if (!(Flags&A_ANI_NO_WALL)) {
3367 if (!covers) {
3368 covers=(SpriteCover **) calloc( animcount, sizeof(SpriteCover *) );
3371 ac=animcount;
3372 while (ac--) {
3373 Animation *anim = animation[ac];
3374 Sprite2D *frame = anim->NextFrame();
3375 if(covers) {
3376 if(!covers[ac] || !covers[ac]->Covers(Pos.x, Pos.y, frame->XPos, frame->YPos, frame->Width, frame->Height)) {
3377 delete covers[ac];
3378 covers[ac] = area->BuildSpriteCover(Pos.x, Pos.y, -anim->animArea.x,
3379 -anim->animArea.y, anim->animArea.w, anim->animArea.h, 0);
3382 video->BlitGameSprite( frame, Pos.x + screen.x, Pos.y + screen.y,
3383 BLIT_TINTED, tint, covers?covers[ac]:0, palette, &screen );
3387 //change the tileset if needed and possible, return true if changed
3388 bool Map::ChangeMap(bool day_or_night)
3390 //no need of change if the area is not extended night
3391 if (! (AreaType&AT_EXTENDED_NIGHT)) return false;
3392 //no need of change if the area already has the right tilemap
3393 if ((DayNight == day_or_night) && GetTileMap()) return false;
3395 PluginHolder<MapMgr> mM(IE_ARE_CLASS_ID);
3396 //no need to open and read the .are file again
3397 //using the ARE class for this because ChangeMap is similar to LoadMap
3398 //it loads the lightmap and the minimap too, besides swapping the tileset
3399 mM->ChangeMap(this, day_or_night);
3400 return true;
3403 void Map::SeeSpellCast(Scriptable *caster, ieDword spell)
3405 if (caster->Type!=ST_ACTOR) {
3406 return;
3409 LastCasterSeen = ((Actor *) caster)->GetID();
3410 LastSpellSeen = spell;
3412 size_t i = actors.size();
3413 while (i--) {
3414 Actor* witness = actors[i];
3415 if (CanSee(witness, caster, true, 0)) {
3416 witness->LastSpellSeen=LastSpellSeen;
3417 witness->LastCasterSeen=LastCasterSeen;