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
28 #include "AmbientMgr.h"
30 #include "DisplayMessage.h"
33 #include "Interface.h"
37 #include "PathFinder.h"
38 #include "Projectile.h"
39 #include "ScriptedAnimation.h"
44 #include "GameScript/GSUtils.h"
45 #include "GUI/GameControl.h"
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
;
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
);
85 Spawns
.RemoveAll(ReleaseSpawnGroup
);
86 PathFinderInited
= false;
89 inline static AnimationObjectType
SelectObject(Actor
*actor
, int q
, AreaAnimation
*a
, ScriptedAnimation
*sca
, Particles
*spark
, Projectile
*pro
)
93 actorh
= actor
->Pos
.y
;
101 aah
= a
->Pos
.y
;//+a->height;
108 scah
= sca
->YPos
;//+sca->ZPos;
115 //no idea if this should be plus or minus (or here at all)
116 spah
= spark
->GetHeight();//+spark->pos.h;
123 proh
= pro
->GetHeight();
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
;
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()) {
147 //check for familiars, summons?
151 //Preload spawn group entries (creature resrefs that reference groups of creatures)
152 void InitSpawnGroups()
157 AutoTable
tab("spawngrp");
159 Spawns
.RemoveAll(NULL
);
160 Spawns
.SetType( GEM_VARIABLES_POINTER
);
165 i
=tab
->GetColNamesCount();
167 int j
=tab
->GetRowCount();
169 const char *crename
= tab
->QueryField( j
,i
);
170 if (crename
[0] != '*') break;
173 SpawnGroup
*creatures
= new SpawnGroup(j
);
175 creatures
->Level
= (ieDword
) atoi( tab
->QueryField(0,i
) );
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");
193 for (int i
= 0; i
< 16; i
++) {
194 poi
= tm
->QueryField( 0, i
);
196 Passable
[i
] = atoi( poi
);
198 poi
= tm
->QueryField( 1, 0 );
200 NormalCost
= atoi( poi
);
201 poi
= tm
->QueryField( 1, 1 );
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;
216 VisibilityMasks
[i
][slot
].x
=(short) x
;
217 VisibilityMasks
[i
][slot
].y
=(short) y
;
223 LargeFog
= !core
->HasFeature(GF_SMALL_FOG
);
225 //circle perimeter size for MaxVisibility
226 int x
= MaxVisibility
;
228 int xc
= 1 - ( 2 * MaxVisibility
);
231 VisibilityPerimeter
= 0;
233 VisibilityPerimeter
+=8;
237 if (( ( 2 * re
) + xc
) > 0) {
245 VisibilityMasks
= (Point
**) malloc(MaxVisibility
* sizeof(Point
*) );
246 for (i
=0;i
<MaxVisibility
;i
++) {
247 VisibilityMasks
[i
] = (Point
*) malloc(VisibilityPerimeter
*sizeof(Point
) );
252 xc
= 1 - ( 2 * MaxVisibility
);
255 VisibilityPerimeter
= 0;
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
++);
268 if (( ( 2 * re
) + xc
) > 0) {
277 : Scriptable( ST_AREA
)
288 queue
[PR_SCRIPT
] = NULL
;
289 queue
[PR_DISPLAY
] = 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;
300 //lastActorCount[PR_IGNORE] = 0;
301 if (!PathFinderInited
) {
305 globalActorCounter
= 0;
307 ExploredBitmap
= NULL
;
308 VisibleBitmap
= NULL
;
310 localActorCounter
= 0;
311 MasterArea
= core
->GetGame()->MasterArea(scriptName
);
322 for (aniidx
= animations
.begin(); aniidx
!= animations
.end(); 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() ) {
334 for (i
= 0; i
< entrances
.size(); i
++) {
337 for (i
= 0; i
< spawns
.size(); i
++) {
343 core
->GetVideoDriver()->FreeSprite( SmallMap
);
344 for (i
= 0; i
< QUEUE_COUNT
; i
++) {
351 for (pri
= projectiles
.begin(); pri
!= projectiles
.end(); pri
++) {
357 for (sci
= vvcCells
.begin(); sci
!= vvcCells
.end(); sci
++) {
363 for (spi
= particles
.begin(); spi
!= particles
.end(); spi
++) {
367 for (i
= 0; i
< ambients
.size(); i
++) {
370 for (i
= 0; i
< mapnotes
.size(); i
++) {
375 free( ExploredBitmap
);
376 free( VisibleBitmap
);
378 for(i
=0;i
<WallCount
;i
++) {
386 void Map::ChangeTileMap(Image
* lm
, Sprite2D
* sm
)
389 core
->GetVideoDriver()->FreeSprite(SmallMap
);
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?
405 Width
= (unsigned int) (TMap
->XCellCount
* 4);
406 Height
= (unsigned int) (( TMap
->YCellCount
* 64 ) / 12);
408 MapSet
= (unsigned short *) malloc(sizeof(unsigned short) * Width
* Height
);
409 //converting searchmap to internal format
410 int y
=SearchMap
->GetHeight();
412 int x
=SearchMap
->GetWidth();
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
)
423 //change loader MOS image here
424 //check worldmap entry, if that doesn't contain anything,
427 if (EveryOne
==CT_WHOLE
) {
428 core
->GetGameControl()->AutoSave();
430 Game
* game
= core
->GetGame();
431 Map
* map
= game
->GetMap(area
, false);
433 printMessage("Map", " ", LIGHT_RED
);
434 printf("Invalid map: %s\n",area
);
438 Entrance
* ent
= map
->GetEntrance( entrance
);
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;
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
458 Y
= map
->TMap
->YCellCount
* 32;
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;
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);
477 Actor
*pc
= game
->GetPC(i
,false);
478 if (pc
->GetCurrentArea()==this) {
481 pc
->AddAction( GenerateAction( command
) );
482 pc
->ProcessActions(true);
487 if (EveryOne
&CT_SELECTED
) {
488 int i
=game
->GetPartySize(false);
490 Actor
*pc
= game
->GetPC(i
,false);
492 if (!pc
->IsSelected()) {
495 if (pc
->GetCurrentArea()==this) {
498 pc
->AddAction( GenerateAction( command
) );
499 pc
->ProcessActions(true);
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
);
518 displaymsg
->DisplayConstantString(STR_WHOLEPARTY
,0xffffff); //white
519 if (game
->EveryoneStopped()) {
520 ip
->Flags
&=~TRAP_RESET
; //exit triggered
523 //no ingame message for these events
524 case CT_CANTMOVE
: case CT_SELECTED
:
526 case CT_ACTIVE
: case CT_WHOLE
: case CT_MOVE_SELECTED
:
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
);
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
);
550 if (gotportal
>PortalTime
) return;
551 ScriptedAnimation
*sca
= gamedata
->GetScriptedAnimation(PortalResRef
, false);
564 void Map::UpdateScripts()
566 bool has_pcs
= false;
567 size_t i
=actors
.size();
569 if (actors
[i
]->InParty
) {
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()*/) {
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
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));
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;
629 if (timestop
&& actor
!=timestop_owner
&& actor
->Modified
[IE_DISABLETIMESTOP
] ) {
630 actor
->no_more_steps
= true;
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;
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
);
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
);
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
;
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
701 bool more_steps
= true;
702 ieDword time
= game
->Ticks
; // make sure everything moves at the same time
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
725 Door
* door
= TMap
->GetDoor( doorCount
++ );
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;
737 Container
* container
= TMap
->GetContainer( containerCount
++ );
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
749 //For each InfoPoint in the map
750 InfoPoint
* ip
= TMap
->GetInfoPoint( ipCount
++ );
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)
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
) {
765 //Run the InfoPoint script
766 ip
->ExecuteScript( 1 );
769 //Execute Pending Actions
770 ip
->ProcessActions(false);
774 if (ip
->IsPortal()) {
775 DrawPortal(ip
, ip
->Trapped
&PORTAL_TRAVEL
);
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
);
789 //don't move if doing something else
790 // added CurrentAction as part of blocking action fixes
791 if (actor
->CannotPassEntrance() ) {
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
)) {
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
)) {
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.)
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
);
854 void Map::DrawHighlightables( Region screen
)
856 Region vp
= core
->GetVideoDriver()->GetViewport();
860 while ( (c
= TMap
->GetContainer(i
++))!=NULL
) {
861 Color tint
= LightMap
->GetPixel( c
->Pos
.x
/ 16, c
->Pos
.y
/ 12);
865 if (c
->Type
==IE_CONTAINER_PILE
) {
866 Color tint
= LightMap
->GetPixel( c
->Pos
.x
/ 16, c
->Pos
.y
/ 12);
868 c
->DrawPile(true, screen
, tint
);
872 } else if (c
->Type
==IE_CONTAINER_PILE
) {
873 if (c
->outline
->BBox
.InsideRegion( vp
)) {
874 c
->DrawPile(false, screen
, tint
);
881 while ( (d
= TMap
->GetDoor(i
++))!=NULL
) {
882 if (d
->Highlight
) d
->DrawOutline();
887 while ( (p
= TMap
->GetInfoPoint(i
++))!=NULL
) {
888 if (p
->Highlight
) p
->DrawOutline();
892 Actor
*Map::GetNextActor(int &q
, int &index
)
898 return queue
[q
][index
];
903 return queue
[q
][index
];
912 AreaAnimation
*Map::GetNextAreaAnimation(aniIterator
&iter
, ieDword gametime
)
915 if (iter
==animations
.end()) {
918 AreaAnimation
*a
= *(iter
++);
919 if (!a
->Schedule(gametime
) ) {
922 if (!IsVisible( a
->Pos
, !(a
->Flags
& A_ANI_NOT_IN_FOG
)) ) {
928 Particles
*Map::GetNextSpark(spaIterator
&iter
)
930 if (iter
==particles
.end()) {
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()) {
945 Projectile
*Map::GetNextTrap(proIterator
&iter
)
950 pro
=GetNextProjectile(iter
);
952 //logic to determine dormant traps
953 //if (pro && pro->IsTrap()) break;
958 size_t Map::GetProjectileCount(proIterator
&iter
)
960 iter
= projectiles
.begin();
961 return projectiles
.size();
964 ieDword
Map::GetTrapCount(proIterator
&iter
)
967 iter
=projectiles
.begin();
968 while(GetNextTrap(iter
)) {
972 iter
= projectiles
.begin();
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()) {
986 static ieDword oldgametime
= 0;
988 //Draw the game area (including overlays, actors, animations, weather)
989 void Map::DrawMap(Region screen
)
994 Game
*game
= core
->GetGame();
995 ieDword gametime
= game
->GameTime
;
997 //area specific spawn.ini files (a PST feature)
999 INISpawn
->CheckSpawn();
1004 //zero when the weather particles are all gone
1005 rain
= game
->weather
->GetPhase()-P_EMPTY
;
1009 TMap
->DrawOverlays( screen
, rain
);
1011 //Blit the Background Map Animations (before actors)
1012 Video
* video
= core
->GetVideoDriver();
1015 DrawHighlightables( screen
);
1017 Region vp
= video
->GetViewport();
1018 //if it is only here, then the scripting will fail?
1021 //drawing queues 1 and 0
1022 //starting with lower priority
1023 //so displayed, but inactive actors (dead) will be drawn over
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
)) {
1040 actor
->Draw( screen
);
1041 actor
= GetNextActor(q
, index
);
1045 a
->Draw( screen
, this );
1046 a
= GetNextAreaAnimation(aniidx
,gametime
);
1052 Color tint
= LightMap
->GetPixel( sca
->XPos
/ 16, sca
->YPos
/ 12);
1054 bool endReached
= sca
->Draw(screen
, Pos
, tint
, this, 0, -1);
1057 scaidx
=vvcCells
.erase(scaidx
);
1062 sca
= GetNextScriptedAnimation(scaidx
);
1064 case AOT_PROJECTILE
:
1067 if (gametime
>oldgametime
) {
1068 drawn
= pro
->Update();
1073 pro
->Draw( screen
);
1077 proidx
= projectiles
.erase(proidx
);
1080 pro
= GetNextProjectile(proidx
);
1085 if (gametime
>oldgametime
) {
1086 drawn
= spark
->Update();
1091 spark
->Draw( screen
);
1095 spaidx
=particles
.erase(spaidx
);
1098 spark
= GetNextSpark(spaidx
);
1105 if ((core
->FogOfWar
&FOG_DRAWSEARCHMAP
) && SearchMap
) {
1106 DrawSearchMap(screen
);
1108 if ((core
->FogOfWar
&FOG_DRAWFOG
) && TMap
) {
1109 TMap
->DrawFogOfWar( ExploredBitmap
, VisibleBitmap
, screen
);
1115 //For each InfoPoint in the map
1116 InfoPoint
* ip
= TMap
->GetInfoPoint( ipCount
++ );
1119 ip
->DrawOverheadText(screen
);
1124 //For each Container in the map
1125 Container
* cn
= TMap
->GetContainer( cnCount
++ );
1128 cn
->DrawOverheadText(screen
);
1133 //For each Door in the map
1134 Door
* dr
= TMap
->GetDoor( drCount
++ );
1137 dr
->DrawOverheadText(screen
);
1140 size_t i
= actors
.size();
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();
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
) {
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();
1201 actors
[i
]->RefreshEffects(NULL
);
1205 void Map::Shout(Actor
* actor
, int shoutID
, unsigned int radius
)
1207 size_t i
=actors
.size();
1209 Actor
*listener
= actors
[i
];
1212 if (Distance(actor
->Pos
, listener
->Pos
)>radius
) {
1217 listener
->LastHeard
= actor
->GetID();
1218 listener
->LastShout
= shoutID
;
1220 listener
->LastHelp
= actor
->GetID();
1225 bool Map::AnyEnemyNearPoint(const Point
&p
)
1227 ieDword gametime
= core
->GetGame()->GameTime
;
1228 size_t i
= actors
.size();
1230 Actor
*actor
= actors
[i
];
1232 if (actor
->Schedule(gametime
, true) ) {
1235 if (Distance(actor
->Pos
, p
) > SPAWN_RANGE
) {
1238 if (actor
->GetStat(IE_EA
)<EA_EVILCUTOFF
) {
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
)) {
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
)) {
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();
1296 Actor
* actor
= actors
[i
];
1297 if (actor
->Modified
[IE_EA
]>=EA_EVILCUTOFF
) {
1298 if (IsVisible(actor
->Pos
, false) && actor
->Schedule(gametime
, true) ) {
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
);
1320 Actor
* Map::GetActorByGlobalID(ieDword objectID
)
1325 //truncation is intentional
1326 ieWord globalID
= (ieWord
) objectID
;
1327 size_t i
= actors
.size();
1329 Actor
* actor
= actors
[i
];
1331 if (actor
->globalID
==globalID
) {
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();
1349 Actor
* actor
= actors
[i
];
1351 if (!actor
->IsOver( p
))
1353 if (!actor
->ValidTarget(flags
) ) {
1356 if (!actor
->Schedule(gametime
, true) ) {
1364 Actor
* Map::GetActorInRadius(const Point
&p
, int flags
, unsigned int radius
)
1366 ieDword gametime
= core
->GetGame()->GameTime
;
1367 size_t i
= actors
.size();
1369 Actor
* actor
= actors
[i
];
1371 if (PersonalDistance( p
, actor
) > radius
)
1373 if (!actor
->ValidTarget(flags
) ) {
1376 if (!actor
->Schedule(gametime
, true) ) {
1384 Actor
**Map::GetAllActorsInRadius(const Point
&p
, int flags
, unsigned int radius
)
1389 ieDword gametime
= core
->GetGame()->GameTime
;
1392 Actor
* actor
= actors
[i
];
1394 if (PersonalDistance( p
, actor
) > radius
)
1396 if (!actor
->ValidTarget(flags
) ) {
1399 if (!actor
->Schedule(gametime
, true) ) {
1405 Actor
**ret
= (Actor
**) malloc( sizeof(Actor
*) * count
);
1409 Actor
* actor
= actors
[i
];
1411 if (PersonalDistance( p
, actor
) > radius
)
1413 if (!actor
->ValidTarget(flags
) ) {
1416 if (!actor
->Schedule(gametime
, true) ) {
1427 Actor
* Map::GetActor(const char* Name
, int flags
)
1429 size_t i
= actors
.size();
1431 Actor
* actor
= actors
[i
];
1432 if (strnicmp( actor
->GetScriptName(), Name
, 32 ) == 0) {
1433 if (!actor
->ValidTarget(flags
) ) {
1442 int Map::GetActorCount(bool any
) const
1445 return (int) actors
.size();
1448 size_t i
=actors
.size();
1450 if (MustSave(actors
[i
])) {
1457 void Map::JumpActors(bool jump
)
1459 size_t i
= actors
.size();
1461 Actor
* actor
= actors
[i
];
1462 if (actor
->Modified
[IE_DONOTJUMP
]&DNJ_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();
1479 Actor
*ac
= actors
[i
];
1481 if (ac
->Modified
[IE_STATE_ID
]&STATE_NOSAVE
) {
1482 if (ac
->Modified
[IE_MC_FLAGS
] & MC_KEEP_CORPSE
) {
1486 actors
.erase( actors
.begin()+i
);
1489 //2. remove any non critical items
1491 i
=(int) TMap
->GetContainerCount();
1493 Container
*c
= TMap
->GetContainer(i
);
1494 unsigned int j
=c
->inventory
.GetSlotCount();
1496 CREItem
*itemslot
= c
->inventory
.GetSlotItem(j
);
1497 if (itemslot
->Flags
&IE_INV_ITEM_CRITICAL
) {
1501 TMap
->CleanupContainer(c
);
1506 Actor
* Map::GetActor(int index
, bool any
)
1509 return actors
[index
];
1512 while (i
<actors
.size() ) {
1513 Actor
*ac
= actors
[i
++];
1514 if (MustSave(ac
) ) {
1523 Actor
* Map::GetActorByDialog(const char *resref
)
1525 size_t i
= actors
.size();
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) {
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();
1542 Actor
* actor
= actors
[i
];
1543 if (strnicmp( actor
->GetScriptName(), resref
, 8 ) == 0) { //temporarily!
1550 int Map::GetActorInRect(Actor
**& actorlist
, Region
& rgn
, bool onlyparty
)
1552 actorlist
= ( Actor
* * ) malloc( actors
.size() * sizeof( Actor
* ) );
1554 size_t i
= actors
.size();
1556 Actor
* actor
= actors
[i
];
1557 //use this function only for party?
1558 if (onlyparty
&& actor
->GetStat(IE_EA
)>EA_CHARMED
) {
1561 if (!actor
->ValidTarget(GA_SELECT
|GA_NO_DEAD
) )
1563 if ((actor
->Pos
.x
<rgn
.x
) || (actor
->Pos
.y
<rgn
.y
))
1565 if ((actor
->Pos
.x
>rgn
.x
+rgn
.w
) || (actor
->Pos
.y
>rgn
.y
+rgn
.h
) )
1567 actorlist
[count
++] = actor
;
1569 actorlist
= ( Actor
* * ) realloc( actorlist
, count
* sizeof( Actor
* ) );
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
] );
1580 if (!restart
&& core
->GetMusicMgr()->CurrentPlayList(poi
)) return;
1581 int ret
= core
->GetMusicMgr()->SwitchPlayList( poi
, true );
1582 //Here we disable the faulty musiclist entry
1584 //Apparently, the playlist manager prefers a *
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
;
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
++) {
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;
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
;
1645 sc
->Height
= height
;
1647 Video
* video
= core
->GetVideoDriver();
1648 video
->InitSpriteCover(sc
, flags
);
1650 unsigned int wpcount
= GetWallCount();
1653 for (i
= 0; i
< wpcount
; ++i
)
1655 Wall_Polygon
* wp
= GetWallGroup(i
);
1657 if (!wp
->PointCovered(x
, y
)) continue;
1659 video
->AddPolygonToSpriteCover(sc
, wp
);
1665 void Map::ActivateWallgroups(unsigned int baseindex
, unsigned int count
, int flg
)
1672 for(i
=baseindex
; i
< baseindex
+count
; ++i
) {
1673 Wall_Polygon
* wp
= GetWallGroup(i
);
1676 ieDword value
=wp
->GetPolygonFlag();
1678 value
&=~WF_DISABLED
;
1681 wp
->SetPolygonFlag(value
);
1683 //all actors will have to generate a new spritecover
1684 i
=(int) actors
.size();
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()
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
;
1712 Actor
* actor
= actors
[i
];
1714 if (actor
->CheckOnDeath()) {
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
1726 priority
= PR_SCRIPT
; //run scripts and display
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
;
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!
1740 ActorSpottedByPlayer(actor
);
1742 priority
= PR_IGNORE
;
1747 //we ignore priority 2
1748 if (priority
>=PR_IGNORE
) continue;
1750 queue
[priority
][Qcount
[priority
]] = actor
;
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
];
1771 if (n
<=0) break; //breaking loop
1773 baseline
[n
] = baseline
[0];
1779 if (chp
<n
&& baseline
[chp
]->Pos
.y
< baseline
[child
]->Pos
.y
) {
1782 if (baseline
[child
]->Pos
.y
<tmp
->Pos
.y
) {
1783 baseline
[parent
] = baseline
[child
];
1789 baseline
[parent
]=tmp
;
1794 void Map::AddProjectile(Projectile
* pro
, const Point
&source
, ieWord actorID
)
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
)
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
)
1824 for(iter
=vvcCells
.begin();iter
!=vvcCells
.end(); iter
++) {
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();
1838 //adding videocell in order, based on its height parameter
1839 void Map::AddVVCell(ScriptedAnimation
* vvc
)
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
)
1851 for(iter
=animations
.begin();iter
!=animations
.end();iter
++) {
1852 AreaAnimation
*anim
= *iter
;
1854 if (anim
->Name
&& (strnicmp( anim
->Name
, Name
, 32 ) == 0)) {
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
) {
1868 sp
->Pos
.x
= (ieWord
) XPos
;
1869 sp
->Pos
.y
= (ieWord
) YPos
;
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
);
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();
1893 Entrance
*e
= entrances
[i
];
1895 if (strnicmp( e
->Name
, Name
, 32 ) == 0) {
1902 bool Map::HasActor(Actor
*actor
)
1904 size_t i
=actors
.size();
1906 if (actors
[i
] == actor
) {
1913 void Map::RemoveActor(Actor
* actor
)
1915 size_t i
=actors
.size();
1917 if (actors
[i
] == actor
) {
1918 //BlockSearchMap(actor->Pos, actor->size, PATH_MAP_FREE);
1919 ClearSearchMapFor(actor
);
1920 actors
.erase( actors
.begin()+i
);
1924 printMessage("Map","RemoveActor: actor not found?",YELLOW
);
1927 //returns true if none of the partymembers are on the map
1930 size_t i
=actors
.size();
1932 if (actors
[i
]->InParty
) {
1936 if (actors
[i
]->GetInternalFlag()&(IF_ACTIVE
|IF_USEEXIT
) ) {
1940 //we expect the area to be swapped out, so we simply remove the corpses now
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
) );
1957 size_t i
= actors
.size();
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
)
1972 unsigned int nlevel
;
1974 if (( px
>= Width
) || ( py
>= Height
)) {
1976 } //walked off the map
1977 pos
= py
* Width
+ px
;
1978 nlevel
= MapSet
[pos
];
1981 } //not even considered
1982 if (level
<= nlevel
) {
1985 unsigned int ndiff
= level
- nlevel
;
1994 void Map::SetupNode(unsigned int x
, unsigned int y
, unsigned int size
, unsigned int Cost
)
1998 if (( x
>= Width
) || ( y
>= Height
)) {
2001 pos
= y
* Width
+ x
;
2005 if (GetBlocked(x
*16+8,y
*12+6,size
)) {
2006 MapSet
[pos
] = 65535;
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;
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
);
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
);
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;
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
;
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
;
2068 void Map::AdjustPosition(Point
&goal
, unsigned int radius
)
2070 unsigned int maxr
= Width
;
2071 if (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
2084 if (AdjustPositionX(goal
, radius
)) {
2087 if (AdjustPositionY(goal
, radius
)) {
2091 if (AdjustPositionY(goal
, radius
)) {
2094 if (AdjustPositionX(goal
, radius
)) {
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);
2108 //MapSet entries are made of 16 bits
2109 if (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;
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
) {
2139 unsigned int Cost
= MapSet
[y
* Width
+ x
] + NormalCost
;
2140 if (Cost
> PathLen
) {
2141 //printf("Path not found!\n");
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
;
2163 StartNode
->orient
= GetOrient( start
, best
);
2165 StartNode
->orient
= GetOrient( best
, start
);
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
;
2174 unsigned int level
= MapSet
[pos
];
2175 unsigned int diff
= 0;
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
);
2189 Return
->orient
= GetOrient( p
, n
);
2191 Return
->orient
= GetOrient( n
, p
);
2198 Return
->Parent
= NULL
;
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
)) {
2213 if (GetBlocked( s
.x
, s
.y
, size
)) {
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 );
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
)
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
;
2272 int Max
= Distance(start
,dest
);
2273 for (int Steps
= 0; Steps
<Max
; Steps
++) {
2275 StartNode
->Next
= new PathNode
;
2276 StartNode
->Next
->Parent
= StartNode
;
2277 StartNode
= StartNode
->Next
;
2278 StartNode
->Next
= NULL
;
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) {
2294 if ((ieWord
) p
.x
>Width
*16 || (ieWord
) p
.y
>Height
*12) {
2300 StartNode
->orient
= Orientation
;
2301 bool wall
= !( GetBlocked( p
) & PATH_MAP_PASSABLE
);
2302 if (wall
) switch (flags
) {
2304 Orientation
= (Orientation
+ 8) &15;
2305 //recalculate dest (mirror it)
2309 default: //premature end
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;
2352 // we got all the way to the target!
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!
2380 unsigned int Cost
= MapSet
[y
* Width
+ x
] + NormalCost
;
2382 // cost is far too high, no path found
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
);
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
;
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
);
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
);
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
);
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;
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
);
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
;
2453 StartNode
->orient
= GetOrient( p
, n
);
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;
2483 //We've found _a_ path
2484 //printf("GOAL!!!\n");
2487 unsigned int Cost
= MapSet
[y
* Width
+ x
] + NormalCost
;
2489 //printf("Path not found!\n");
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
);
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;
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
);
2537 StartNode
->orient
= GetOrient( n
, p
);
2540 //stepping back on the calculated path
2542 while (StartNode
->Parent
) {
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
) {
2551 StartNode
= StartNode
->Parent
;
2552 delete StartNode
->Next
;
2553 StartNode
->Next
= NULL
;
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
)
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
;
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
)
2588 int diffx
= sX
- dX
;
2589 int diffy
= sY
- dY
;
2590 if (abs( diffx
) >= abs( diffy
)) {
2592 double elevationy
= fabs((double)diffx
) / diffy
;
2594 for (int startx
= sX
; startx
> dX
; startx
--) {
2595 if (GetBlocked( startx
, sY
- ( int ) ( ( sX
- startx
) / elevationy
) ) & PATH_MAP_NO_SEE
)
2599 for (int startx
= sX
; startx
< dX
; startx
++) {
2600 if (GetBlocked( startx
, sY
+ ( int ) ( ( sX
- startx
) / elevationy
) ) & PATH_MAP_NO_SEE
)
2605 double elevationx
= fabs((double)diffy
) / diffx
;
2607 for (int starty
= sY
; starty
> dY
; starty
--) {
2608 if (GetBlocked( sX
- ( int ) ( ( sY
- starty
) / elevationx
), starty
) & PATH_MAP_NO_SEE
)
2612 for (int starty
= sY
; starty
< dY
; starty
++) {
2613 if (GetBlocked( sX
+ ( int ) ( ( sY
- starty
) / elevationx
), starty
) & PATH_MAP_NO_SEE
)
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
);
2633 if (sX
>sY
) { //north or east
2634 if (Width
*Height
>sX
+sY
) { //
2640 if (Width
*Height
<sX
+sY
) { //
2646 //--------ambients----------------
2647 void Map::SetupAmbients()
2649 AmbientMgr
*ambim
= core
->GetAudioDrv()->GetAmbientMgr();
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
;
2662 mn
->color
= (ieWord
) color
;
2664 RemoveMapNote(point
); //delete previous mapnote
2665 mapnotes
.push_back(mn
);
2668 void Map::RemoveMapNote(const Point
&point
)
2670 size_t i
= mapnotes
.size();
2672 if ((point
.x
==mapnotes
[i
]->Pos
.x
) &&
2673 (point
.y
==mapnotes
[i
]->Pos
.y
)) {
2675 mapnotes
.erase(mapnotes
.begin()+i
);
2680 MapNote
*Map::GetMapNote(const Point
&point
)
2682 size_t i
= mapnotes
.size();
2684 if (Distance(point
, mapnotes
[i
]->Pos
) < 10 ) {
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
;
2702 if ( !Spawns
.Lookup( CreName
, lookup
) ) {
2703 creature
= gamedata
->GetCreature(CreName
);
2706 creature
->SetPosition( pos
, true, radius
);
2707 creature
->RefreshEffects(NULL
);
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
;
2720 // a single critter is also too powerful
2723 while (amount
>= 0) {
2732 creature
= gamedata
->GetCreature(sg
->ResRefs
[count
]);
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
) {
2748 ieDword bit
= 1<<((core
->GetGame()->GameTime
/AI_UPDATE_TIME
)%7200/300);
2749 if (!(spawn
->appearance
& bit
)) {
2753 //check day or night chance
2754 if (rand()%100>spawn
->DayChance
) {
2757 // the difficulty check is done in SpawnCreature
2759 for(unsigned int i
= 0;i
<spawn
->Count
;i
++) {
2760 SpawnCreature(spawn
->Pos
, spawn
->Creatures
[i
], 0);
2762 //disable spawnpoint
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
) {
2781 //based on ingame timer
2782 int chance
=day
?RestHeader
.DayChance
:RestHeader
.NightChance
;
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);
2806 //--------explored bitmap-----------
2807 int Map::GetExploredMapSize() const
2809 int x
= TMap
->XCellCount
*2;
2810 int y
= TMap
->YCellCount
*2;
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
;
2833 if (y
< 0 || y
>= h
)
2836 int w
= TMap
->XCellCount
* 2 + LargeFog
;
2838 if (x
< 0 || x
>= w
)
2841 int b0
= (y
* w
) + x
;
2845 ExploredBitmap
[by
] |= bi
;
2846 VisibleBitmap
[by
] |= bi
;
2849 void Map::ExploreMapChunk(const Point
&Pos
, int range
, int los
)
2853 if (range
>MaxVisibility
) {
2854 range
=MaxVisibility
;
2856 int p
=VisibilityPerimeter
;
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
;
2867 int type
= GetBlocked(Tile
);
2868 if (type
& PATH_MAP_NO_SEE
) {
2870 } else if (type
& PATH_MAP_SIDEWALL
) {
2872 } else if (sidewall
)
2887 void Map::UpdateFog()
2889 if (!(core
->FogOfWar
&FOG_DRAWFOG
) ) {
2890 SetMapVisibility( -1 );
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
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
++) {
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)
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
) {
2971 int Map::ConsolidateContainers()
2974 int containercount
= (int) TMap
->GetContainerCount();
2975 while (containercount
--) {
2976 Container
* c
= TMap
->GetContainer( containercount
);
2978 if (TMap
->CleanupContainer(c
) ) {
2981 itemcount
+= c
->inventory
.GetSlotCount();
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
);
2999 othercontainer
= othermap
->GetPile(Pos
);
3001 //transfer the pile to the other container
3002 unsigned int i
=c
->inventory
.GetSlotCount();
3004 CREItem
*item
= c
->RemoveItem(i
, 0);
3005 othercontainer
->AddItem(item
);
3011 Container
*Map::GetPile(Point position
)
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
);
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
;
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
);
3053 c
->outline
= outline
;
3055 TMap
->AddContainer( 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
)) {
3066 return IE_CURSOR_BLOCKED
;
3067 case PATH_MAP_PASSABLE
:
3068 return IE_CURSOR_WALK
;
3070 return IE_CURSOR_TRAVEL
;
3074 bool Map::HasWeather()
3076 if ((AreaType
& (AT_WEATHER
|AT_OUTDOOR
) ) != (AT_WEATHER
|AT_OUTDOOR
) ) {
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
;
3090 if (Snow
>=core
->Roll(1,100,0) ) {
3093 if (Fog
>=core
->Roll(1,100,0) ) {
3099 void Map::FadeSparkle(const Point
&pos
, bool forced
)
3103 for(iter
=particles
.begin(); iter
!=particles
.end();iter
++) {
3104 if ((*iter
)->MatchPos(pos
) ) {
3106 //particles.erase(iter);
3107 (*iter
)->SetPhase(P_EMPTY
);
3109 (*iter
)->SetPhase(P_FADE
);
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
;
3129 path
= SP_PATH_FOUNT
; //sparks go up and down
3130 grow
= SP_SPAWN_FULL
;
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
;
3141 path
= SP_PATH_FLIT
;
3142 grow
= SP_SPAWN_SOME
;
3147 Particles
*sparkles
= new Particles(size
);
3148 sparkles
->SetOwner(this);
3149 sparkles
->SetRegion(pos
.x
-width
/2, pos
.y
-80, width
, 80);
3152 style
= SP_TYPE_BITMAP
;
3153 sparkles
->SetBitmap(FragAnimID
);
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);
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
);
3175 actor
->SetInTrap(0);
3177 if(!trap
->outline
->PointIn(actor
->Pos
)) {
3178 actor
->SetInTrap(0);
3183 void Map::SetTrackString(ieStrRef strref
, int flg
, int difficulty
)
3185 trackString
= strref
;
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
);
3202 char * str
= core
->GetString( trackString
);
3203 core
->GetTokenDictionary()->SetAt( "CREATURE", str
);
3204 displaymsg
->DisplayConstantStringName(STR_TRACKING
, 0xd7d7be, target
);
3207 displaymsg
->DisplayStringName(trackString
, 0xd7d7be, target
, 0);
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();
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//////////////////
3227 AreaAnimation::AreaAnimation()
3235 AreaAnimation::~AreaAnimation()
3237 for(int i
=0;i
<animcount
;i
++) {
3239 delete (animation
[i
]);
3243 gamedata
->FreePalette(palette
, PaletteRef
);
3245 for(int i
=0;i
<animcount
;i
++) {
3252 Animation
*AreaAnimation::GetAnimationPiece(AnimationFactory
*af
, int animCycle
)
3254 Animation
*anim
= af
->GetCycle( ( unsigned char ) animCycle
);
3256 anim
= af
->GetCycle( 0 );
3258 printf("Cannot load animation: %s\n", BAM
);
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;
3265 anim
->Flags
= Flags
;
3268 if (anim
->Flags
&A_ANI_MIRROR
) {
3269 anim
->MirrorAnimation();
3275 void AreaAnimation::InitAnimation()
3277 AnimationFactory
* af
= ( AnimationFactory
* )
3278 gamedata
->GetFactoryResource( BAM
, IE_BAM_CLASS_ID
);
3280 printf("Cannot load animation: %s\n", BAM
);
3284 //freeing up the previous animation
3285 for(int i
=0;i
<animcount
;i
++) {
3287 delete (animation
[i
]);
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
);
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
) {
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
3324 void AreaAnimation::BlendAnimation()
3326 //Warning! This function will modify a shared 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);
3334 palette
= spr
->GetPalette()->Copy();
3337 palette
->CreateShadedAlphaChannel();
3340 bool AreaAnimation::Schedule(ieDword gametime
)
3342 if (!(Flags
&A_ANI_ACTIVE
) ) {
3346 //check for schedule
3347 ieDword bit
= 1<<((gametime
/AI_UPDATE_TIME
)%7200/300);
3348 if (appearance
& bit
) {
3354 void AreaAnimation::Draw(const Region
&screen
, Map
*area
)
3357 Video
* video
= core
->GetVideoDriver();
3359 //always draw the animation tinted because tint is also used for
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
)) {
3368 covers
=(SpriteCover
**) calloc( animcount
, sizeof(SpriteCover
*) );
3373 Animation
*anim
= animation
[ac
];
3374 Sprite2D
*frame
= anim
->NextFrame();
3376 if(!covers
[ac
] || !covers
[ac
]->Covers(Pos
.x
, Pos
.y
, frame
->XPos
, frame
->YPos
, frame
->Width
, frame
->Height
)) {
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
);
3403 void Map::SeeSpellCast(Scriptable
*caster
, ieDword spell
)
3405 if (caster
->Type
!=ST_ACTOR
) {
3409 LastCasterSeen
= ((Actor
*) caster
)->GetID();
3410 LastSpellSeen
= spell
;
3412 size_t i
= actors
.size();
3414 Actor
* witness
= actors
[i
];
3415 if (CanSee(witness
, caster
, true, 0)) {
3416 witness
->LastSpellSeen
=LastSpellSeen
;
3417 witness
->LastCasterSeen
=LastCasterSeen
;