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 "Interface.h"
29 #include "PathFinder.h"
32 #include "AmbientMgr.h"
34 #include "ScriptedAnimation.h"
35 #include "Projectile.h"
41 #include "GameControl.h"
47 #define YESNO(x) ( (x)?"Yes":"No")
49 // TODO: fix this hardcoded resource reference
50 static ieResRef PortalResRef
={"EF03TPR3"};
51 static unsigned int PortalTime
= 15;
52 static unsigned int MAX_CIRCLESIZE
= 8;
53 static int MaxVisibility
= 30;
54 static int VisibilityPerimeter
; //calculated from MaxVisibility
55 static int NormalCost
= 10;
56 static int AdditionalCost
= 4;
57 static unsigned char Passable
[16] = {
58 4, 1, 1, 1, 1, 1, 1, 1, 0, 1, 8, 0, 0, 0, 3, 1
60 static Point
**VisibilityMasks
=NULL
;
62 static bool PathFinderInited
= false;
63 static Variables Spawns
;
65 static ieWord globalActorCounter
;
67 void ReleaseSpawnGroup(void *poi
)
69 delete (SpawnGroup
*) poi
;
72 void Map::ReleaseMemory()
74 if (VisibilityMasks
) {
75 for (int i
=0;i
<MaxVisibility
;i
++) {
76 free(VisibilityMasks
[i
]);
78 free(VisibilityMasks
);
82 Spawns
.RemoveAll(ReleaseSpawnGroup
);
83 PathFinderInited
= false;
86 inline static AnimationObjectType
SelectObject(Actor
*actor
, int q
, AreaAnimation
*a
, ScriptedAnimation
*sca
, Particles
*spark
, Projectile
*pro
)
90 actorh
= actor
->Pos
.y
;
98 aah
= a
->Pos
.y
;//+a->height;
105 scah
= sca
->YPos
;//+sca->ZPos;
112 //no idea if this should be plus or minus (or here at all)
113 spah
= spark
->GetHeight();//+spark->pos.h;
120 proh
= pro
->GetHeight();
125 if (proh
<actorh
&& proh
<scah
&& proh
<aah
&& proh
<spah
) return AOT_PROJECTILE
;
127 if (spah
<actorh
&& spah
<scah
&& spah
<aah
) return AOT_SPARK
;
129 if (aah
<actorh
&& aah
<scah
) return AOT_AREA
;
131 if (scah
<actorh
) return AOT_SCRIPTED
;
136 //returns true if creature must be embedded in the area
137 //npcs in saved game shouldn't be embedded either
138 inline static bool MustSave(Actor
*actor
)
140 if (actor
->Persistent()) {
144 //check for familiars, summons?
148 //Preload spawn group entries (creature resrefs that reference groups of creatures)
149 void InitSpawnGroups()
154 AutoTable
tab("spawngrp");
156 Spawns
.RemoveAll(NULL
);
157 Spawns
.SetType( GEM_VARIABLES_POINTER
);
162 i
=tab
->GetColNamesCount();
164 int j
=tab
->GetRowCount();
166 const char *crename
= tab
->QueryField( j
,i
);
167 if (crename
[0] != '*') break;
170 SpawnGroup
*creatures
= new SpawnGroup(j
);
172 creatures
->Level
= (ieDword
) atoi( tab
->QueryField(0,i
) );
174 strnlwrcpy( creatures
->ResRefs
[j
-1], tab
->QueryField(j
,i
), 8 );
176 strnlwrcpy( GroupName
, tab
->GetColumnName( i
), 8 );
177 Spawns
.SetAt( GroupName
, (void*) creatures
);
182 //Preload the searchmap configuration
183 void InitPathFinder()
185 PathFinderInited
= true;
186 AutoTable
tm("pathfind");
190 for (int i
= 0; i
< 16; i
++) {
191 poi
= tm
->QueryField( 0, i
);
193 Passable
[i
] = atoi( poi
);
195 poi
= tm
->QueryField( 1, 0 );
197 NormalCost
= atoi( poi
);
198 poi
= tm
->QueryField( 1, 1 );
200 AdditionalCost
= atoi( poi
);
204 void AddLOS(int destx
, int desty
, int slot
)
206 for (int i
=0;i
<MaxVisibility
;i
++) {
207 int x
=(destx
*i
+MaxVisibility
/2)/MaxVisibility
*16;
208 int y
=(desty
*i
+MaxVisibility
/2)/MaxVisibility
*12;
213 VisibilityMasks
[i
][slot
].x
=(short) x
;
214 VisibilityMasks
[i
][slot
].y
=(short) y
;
220 LargeFog
= !core
->HasFeature(GF_SMALL_FOG
);
222 //circle perimeter size for MaxVisibility
223 int x
= MaxVisibility
;
225 int xc
= 1 - ( 2 * MaxVisibility
);
228 VisibilityPerimeter
= 0;
230 VisibilityPerimeter
+=8;
234 if (( ( 2 * re
) + xc
) > 0) {
242 VisibilityMasks
= (Point
**) malloc(MaxVisibility
* sizeof(Point
*) );
243 for (i
=0;i
<MaxVisibility
;i
++) {
244 VisibilityMasks
[i
] = (Point
*) malloc(VisibilityPerimeter
*sizeof(Point
) );
249 xc
= 1 - ( 2 * MaxVisibility
);
252 VisibilityPerimeter
= 0;
254 AddLOS (x
, y
, VisibilityPerimeter
++);
255 AddLOS (-x
, y
, VisibilityPerimeter
++);
256 AddLOS (-x
, -y
, VisibilityPerimeter
++);
257 AddLOS (x
, -y
, VisibilityPerimeter
++);
258 AddLOS (y
, x
, VisibilityPerimeter
++);
259 AddLOS (-y
, x
, VisibilityPerimeter
++);
260 AddLOS (-y
, -x
, VisibilityPerimeter
++);
261 AddLOS (y
, -x
, VisibilityPerimeter
++);
265 if (( ( 2 * re
) + xc
) > 0) {
274 : Scriptable( ST_AREA
)
285 queue
[PR_SCRIPT
] = NULL
;
286 queue
[PR_DISPLAY
] = NULL
;
288 //no one needs this queue
289 //queue[PR_IGNORE] = NULL;
290 Qcount
[PR_SCRIPT
] = 0;
291 Qcount
[PR_DISPLAY
] = 0;
292 //no one needs this queue
293 //Qcount[PR_IGNORE] = 0;
294 lastActorCount
[PR_SCRIPT
] = 0;
295 lastActorCount
[PR_DISPLAY
] = 0;
297 //lastActorCount[PR_IGNORE] = 0;
298 if (!PathFinderInited
) {
302 globalActorCounter
= 0;
304 ExploredBitmap
= NULL
;
305 VisibleBitmap
= NULL
;
307 localActorCounter
= 0;
308 MasterArea
= core
->GetGame()->MasterArea(scriptName
);
319 for (aniidx
= animations
.begin(); aniidx
!= animations
.end(); aniidx
++) {
323 for (i
= 0; i
< actors
.size(); i
++) {
324 Actor
* a
= actors
[i
];
325 //don't delete NPC/PC
326 if (a
&& !a
->Persistent() ) {
331 for (i
= 0; i
< entrances
.size(); i
++) {
334 for (i
= 0; i
< spawns
.size(); i
++) {
340 core
->GetVideoDriver()->FreeSprite( SmallMap
);
341 for (i
= 0; i
< QUEUE_COUNT
; i
++) {
348 for (pri
= projectiles
.begin(); pri
!= projectiles
.end(); pri
++) {
354 for (sci
= vvcCells
.begin(); sci
!= vvcCells
.end(); sci
++) {
360 for (spi
= particles
.begin(); spi
!= particles
.end(); spi
++) {
364 for (i
= 0; i
< ambients
.size(); i
++) {
367 for (i
= 0; i
< mapnotes
.size(); i
++) {
372 free( ExploredBitmap
);
373 free( VisibleBitmap
);
375 for(i
=0;i
<WallCount
;i
++) {
383 void Map::ChangeTileMap(Image
* lm
, Sprite2D
* sm
)
386 core
->GetVideoDriver()->FreeSprite(SmallMap
);
394 void Map::AddTileMap(TileMap
* tm
, Image
* lm
, Bitmap
* sr
, Sprite2D
* sm
, Bitmap
* hm
)
396 // CHECKME: leaks? Should the old TMap, LightMap, etc... be freed?
402 Width
= (unsigned int) (TMap
->XCellCount
* 4);
403 Height
= (unsigned int) (( TMap
->YCellCount
* 64 ) / 12);
405 MapSet
= (unsigned short *) malloc(sizeof(unsigned short) * Width
* Height
);
406 //converting searchmap to internal format
407 int y
=SearchMap
->GetHeight();
409 int x
=SearchMap
->GetWidth();
411 SearchMap
->SetAt(x
,y
,Passable
[SearchMap
->GetAt(x
,y
)&PATH_MAP_AREAMASK
]);
416 void Map::MoveToNewArea(const char *area
, const char *entrance
, unsigned int direction
, int EveryOne
, Actor
*actor
)
420 //change loader MOS image here
421 //check worldmap entry, if that doesn't contain anything,
424 if (EveryOne
==CT_WHOLE
) {
425 core
->GetGameControl()->AutoSave();
427 Game
* game
= core
->GetGame();
428 Map
* map
= game
->GetMap(area
, false);
430 printMessage("Map", " ", LIGHT_RED
);
431 printf("Invalid map: %s\n",area
);
435 Entrance
* ent
= map
->GetEntrance( entrance
);
438 // no entrance found, try using direction flags
440 face
= -1; // should this be handled per-case?
442 // ok, so the original engine tries these in a different order
443 // (north first, then south) but it doesn't seem to matter
444 if (direction
& 0x1) { // north
445 X
= map
->TMap
->XCellCount
* 32;
447 } else if (direction
& 0x2) { // east
448 X
= map
->TMap
->XCellCount
* 64;
449 Y
= map
->TMap
->YCellCount
* 32;
450 } else if (direction
& 0x4) { // south
451 X
= map
->TMap
->XCellCount
* 32;
452 Y
= map
->TMap
->YCellCount
* 64;
453 } else if (direction
& 0x8) { // west
455 Y
= map
->TMap
->YCellCount
* 32;
457 // crashes in original engine
458 printMessage("Map", " ", YELLOW
);
459 printf( "WARNING!!! EntryPoint '%s' does not exist and direction %d is invalid\n", entrance
, direction
);
460 X
= map
->TMap
->XCellCount
* 64;
461 Y
= map
->TMap
->YCellCount
* 64;
468 //LeaveArea is the same in ALL engine versions
469 sprintf(command
, "LeaveArea(\"%s\",[%d.%d],%d)", area
, X
, Y
, face
);
471 if (EveryOne
&CT_GO_CLOSER
) {
472 int i
=game
->GetPartySize(false);
474 Actor
*pc
= game
->GetPC(i
,false);
475 if (pc
->GetCurrentArea()==this) {
478 pc
->AddAction( GenerateAction( command
) );
479 pc
->ProcessActions(true);
484 if (EveryOne
&CT_SELECTED
) {
485 int i
=game
->GetPartySize(false);
487 Actor
*pc
= game
->GetPC(i
,false);
489 if (!pc
->IsSelected()) {
492 if (pc
->GetCurrentArea()==this) {
495 pc
->AddAction( GenerateAction( command
) );
496 pc
->ProcessActions(true);
503 actor
->ClearActions();
504 actor
->AddAction( GenerateAction( command
) );
505 actor
->ProcessActions(true);
508 void Map::UseExit(Actor
*actor
, InfoPoint
*ip
)
510 Game
*game
=core
->GetGame();
512 int EveryOne
= ip
->CheckTravel(actor
);
515 core
->DisplayConstantString(STR_WHOLEPARTY
,0xffffff); //white
516 if (game
->EveryoneStopped()) {
517 ip
->Flags
&=~TRAP_RESET
; //exit triggered
520 //no ingame message for these events
521 case CT_CANTMOVE
: case CT_SELECTED
:
523 case CT_ACTIVE
: case CT_WHOLE
: case CT_MOVE_SELECTED
:
527 actor
->UseExit(false);
528 if (ip
->Destination
[0] != 0) {
529 // 0 here is direction, can infopoints specify that or is an entrance always provided?
530 MoveToNewArea(ip
->Destination
, ip
->EntranceName
, 0, EveryOne
, actor
);
533 if (ip
->Scripts
[0]) {
534 ip
->LastTriggerObject
= ip
->LastTrigger
= ip
->LastEntered
= actor
->GetID();
535 ip
->ExecuteScript( 1 );
536 ip
->ProcessActions(true);
540 //Draw two overlapped animations to achieve the original effect
541 //PlayOnce makes sure that if we stop drawing them, they will go away
542 void Map::DrawPortal(InfoPoint
*ip
, int enable
)
544 ieDword gotportal
= HasVVCCell(PortalResRef
, ip
->Pos
);
547 if (gotportal
>PortalTime
) return;
548 ScriptedAnimation
*sca
= gamedata
->GetScriptedAnimation(PortalResRef
, false);
561 void Map::UpdateScripts()
563 bool has_pcs
= false;
564 size_t i
=actors
.size();
566 if (actors
[i
]->InParty
) {
572 // if masterarea, then we allow 'any' actors
573 // if not masterarea, we allow only players
574 // if (!GetActorCount(MasterArea) ) {
575 // fuzzie changed this because the previous code was wrong
576 // (GetActorCount(false) returns only non-PCs) - it is not
577 // well-tested so feel free to change if there are problems
578 // (for example, the CanFree seems like it would be needed to
579 // check for any running scripts, such as following, but it seems
580 // to work ok anyway in my testing - if you change it you probably
581 // also want to change the actor updating code below so it doesn't
582 // add new actions while we are trying to get rid of the area!)
583 if (!has_pcs
&& !(MasterArea
&& actors
.size()) /*&& !CanFree()*/) {
587 // fuzzie added this check because some area scripts (eg, AR1600 when
588 // escaping Brynnlaw) were executing after they were meant to be done,
589 // and this seems the nicest way of handling that for now - it's quite
590 // possibly wrong (so if you have problems, revert this and find
597 //Execute Pending Actions
598 //if it is only here, then the drawing will fail
599 ProcessActions(false);
601 // If scripts frozen, return.
602 // This fixes starting a new IWD game. The above ProcessActions pauses the
603 // game for a textscreen, but one of the actor->ProcessActions calls
604 // below starts a cutscene, hiding the mouse. - wjp, 20060805
605 if (core
->GetGameControl()->GetDialogueFlags() & DF_FREEZE_SCRIPTS
) return;
607 //Run actor scripts (only for 0 priority)
608 int q
=Qcount
[PR_SCRIPT
];
610 Game
*game
= core
->GetGame();
611 Actor
*timestop_owner
= game
->timestop_owner
;
612 bool timestop
= game
->timestop_end
>game
->GameTime
;
614 // this is silly, the speed should be pre-calculated somewhere
615 //int *actor_speeds = (int *)calloc(Qcount[PR_SCRIPT], sizeof(int));
617 //bool *no_more_steps_for_actor = (bool *)calloc(Qcount[PR_SCRIPT], sizeof(bool));
620 Actor
* actor
= queue
[PR_SCRIPT
][q
];
621 //actor just moved away, don't run its script from this side
622 if (actor
->GetCurrentArea()!=this) {
623 actor
->no_more_steps
= true;
626 if (timestop
&& actor
!=timestop_owner
&& actor
->Modified
[IE_DISABLETIMESTOP
] ) {
627 actor
->no_more_steps
= true;
631 //Avenger moved this here from ApplyAllEffects (this one modifies the effect queue)
632 //.. but then fuzzie moved this here from UpdateActorState, because otherwise
633 //immobile actors (see check below) never become mobile again!
634 actor
->fxqueue
.Cleanup();
636 //if the actor is immobile, don't run the scripts
637 if (!game
->StateOverrideFlag
&& !game
->StateOverrideTime
) {
638 if (actor
->Immobile()) {
639 actor
->no_more_steps
= true;
643 actor
->no_more_steps
= false;
646 * we run scripts all at once because one of the actions in ProcessActions
647 * might remove us from a cutscene and then bad things can happen when
648 * scripts are queued unexpectedly (such as an ogre in a cutscene -> dialog
649 * -> cutscene transition in the first bg1 cutscene exploiting the race
650 * condition to murder player1) - it is entirely possible that we should be
651 * doing this differently (for example by storing the cutscene state at the
652 * start of this function, or by changing the cutscene state at a later
653 * point, etc), but i did it this way for now because it seems least painful
654 * and we should probably be staggering the script executions anyway
656 actor
->ExecuteScript( MAX_SCRIPTS
);
662 Actor
* actor
= queue
[PR_SCRIPT
][q
];
663 if (actor
->no_more_steps
) continue;
665 actor
->ProcessActions(false);
667 actor
->UpdateActorState(game
->GameTime
);
669 actor
->inventory
.CalculateWeight();
670 actor
->SetBase( IE_ENCUMBRANCE
, actor
->inventory
.GetWeight() );
672 //TODO:calculate actor speed!
673 int speed
= (int) actor
->GetStat(IE_MOVEMENTRATE
);
677 if (core
->GetResDataINI()) {
678 ieDword animid
= actor
->BaseStats
[IE_ANIMATION_ID
];
679 if (core
->HasFeature(GF_ONE_BYTE_ANIMID
)) {
680 animid
= animid
& 0xff;
682 if (animid
< (ieDword
)CharAnimations::GetAvatarsCount()) {
683 AvatarStruct
*avatar
= CharAnimations::GetAvatarStruct(animid
);
684 if (avatar
->RunScale
&& (actor
->GetInternalFlag() & IF_RUNNING
)) {
685 speed
= avatar
->RunScale
;
686 } else if (avatar
->WalkScale
) {
687 speed
= avatar
->WalkScale
;
689 //printf("no walkscale for anim %d!\n", actor->BaseStats[IE_ANIMATION_ID]);
693 actor
->speed
= speed
;
696 // We need to step through the list of actors until all of them are done
698 bool more_steps
= true;
699 ieDword time
= game
->Ticks
; // make sure everything moves at the same time
705 Actor
* actor
= queue
[PR_SCRIPT
][q
];
706 if (actor
->no_more_steps
) continue;
708 // try to exclude actors which only just died
709 // (shouldn't we not be stepping actors which don't have a path anyway?)
710 // following fails on Immobile creatures, don't think it's a problem, but replace with next line if it is
711 if (!actor
->ValidTarget(GA_NO_DEAD
)) continue;
712 //if (actor->GetStat(IE_STATE_ID)&STATE_DEAD || actor->GetInternalFlag() & IF_JUSTDIED) continue;
714 actor
->no_more_steps
= DoStepForActor(actor
, actor
->speed
, time
);
715 if (!actor
->no_more_steps
) more_steps
= true;
719 //Check if we need to start some door scripts
722 Door
* door
= TMap
->GetDoor( doorCount
++ );
725 if (door
->Scripts
[0])
726 door
->ExecuteScript( 1 );
727 //Execute Pending Actions
728 door
->ProcessActions(false);
731 //Check if we need to start some container scripts
732 int containerCount
= 0;
734 Container
* container
= TMap
->GetContainer( containerCount
++ );
737 if (container
->Scripts
[0])
738 container
->ExecuteScript( 1 );
739 //Execute Pending Actions
740 container
->ProcessActions(false);
743 //Check if we need to start some trap scripts
746 //For each InfoPoint in the map
747 InfoPoint
* ip
= TMap
->GetInfoPoint( ipCount
++ );
750 //If this InfoPoint has no script and it is not a Travel Trigger, skip it
751 bool wasActive
= (ip
->Scripts
[0] || ( ip
->Type
== ST_TRAVEL
));
752 // InfoPoints of all types don't run scripts if TRAP_DEACTIVATED is set
753 // (eg, TriggerActivation changes this, see lightning room from SoA)
755 wasActive
= !(ip
->Flags
&TRAP_DEACTIVATED
);
757 //If this InfoPoint is a Switch Trigger
758 if (ip
->Type
== ST_TRIGGER
) {
759 //Check if this InfoPoint was activated
760 if (ip
->LastTrigger
) {
762 //Run the InfoPoint script
763 ip
->ExecuteScript( 1 );
766 //Execute Pending Actions
767 ip
->ProcessActions(false);
771 if (ip
->IsPortal()) {
772 DrawPortal(ip
, ip
->Trapped
&PORTAL_TRAVEL
);
778 Actor
* actor
= queue
[PR_SCRIPT
][q
];
779 if (ip
->Type
== ST_PROXIMITY
) {
780 if(ip
->Entered(actor
)) {
781 //if trap triggered, then mark actor
782 actor
->SetInTrap(ipCount
);
786 //don't move if doing something else
787 // added CurrentAction as part of blocking action fixes
788 if (actor
->CannotPassEntrance() ) {
791 //this is needed, otherwise the travel
792 //trigger would be activated anytime
793 //Well, i don't know why is it here, but lets try this
794 if (ip
->Entered(actor
)) {
802 ip
->ExecuteScript( 1 );
804 //Execute Pending Actions
805 ip
->ProcessActions(false);
809 bool Map::DoStepForActor(Actor
*actor
, int speed
, ieDword time
) {
810 bool no_more_steps
= true;
812 if (actor
->BlocksSearchMap()) {
813 ClearSearchMapFor(actor
);
815 PathNode
* step
= actor
->GetNextStep();
816 if (step
&& step
->Next
) {
817 //we should actually wait for a short time and check then
818 if (GetBlocked(step
->Next
->x
*16+8,step
->Next
->y
*12+6,actor
->size
)) {
823 if (!(actor
->GetBase(IE_STATE_ID
)&STATE_CANTMOVE
) ) {
824 if (!actor
->Immobile()) {
825 no_more_steps
= actor
->DoStep( speed
, time
);
826 if (actor
->BlocksSearchMap()) {
827 BlockSearchMap( actor
->Pos
, actor
->size
, actor
->InParty
?PATH_MAP_PC
:PATH_MAP_NPC
);
832 return no_more_steps
;
835 void Map::ClearSearchMapFor( Movable
*actor
) {
836 Actor
** nearActors
= GetAllActorsInRadius(actor
->Pos
, GA_NO_DEAD
, MAX_CIRCLE_SIZE
*2*16);
837 BlockSearchMap( actor
->Pos
, actor
->size
, PATH_MAP_FREE
);
839 // Restore the searchmap areas of any nearby actors that could
840 // have been cleared by this BlockSearchMap(..., 0).
841 // (Necessary since blocked areas of actors may overlap.)
843 while(nearActors
[i
]!=NULL
) {
844 if(nearActors
[i
]!=actor
&& nearActors
[i
]->BlocksSearchMap())
845 BlockSearchMap( nearActors
[i
]->Pos
, nearActors
[i
]->size
, nearActors
[i
]->InParty
?PATH_MAP_PC
:PATH_MAP_NPC
);
851 void Map::DrawHighlightables( Region screen
)
853 Region vp
= core
->GetVideoDriver()->GetViewport();
857 while ( (c
= TMap
->GetContainer(i
++))!=NULL
) {
858 Color tint
= LightMap
->GetPixel( c
->Pos
.x
/ 16, c
->Pos
.y
/ 12);
862 if (c
->Type
==IE_CONTAINER_PILE
) {
863 Color tint
= LightMap
->GetPixel( c
->Pos
.x
/ 16, c
->Pos
.y
/ 12);
865 c
->DrawPile(true, screen
, tint
);
869 } else if (c
->Type
==IE_CONTAINER_PILE
) {
870 if (c
->outline
->BBox
.InsideRegion( vp
)) {
871 c
->DrawPile(false, screen
, tint
);
878 while ( (d
= TMap
->GetDoor(i
++))!=NULL
) {
879 if (d
->Highlight
) d
->DrawOutline();
884 while ( (p
= TMap
->GetInfoPoint(i
++))!=NULL
) {
885 if (p
->Highlight
) p
->DrawOutline();
889 Actor
*Map::GetNextActor(int &q
, int &index
)
895 return queue
[q
][index
];
900 return queue
[q
][index
];
909 AreaAnimation
*Map::GetNextAreaAnimation(aniIterator
&iter
, ieDword gametime
)
912 if (iter
==animations
.end()) {
915 AreaAnimation
*a
= *(iter
++);
916 if (!a
->Schedule(gametime
) ) {
919 if (!IsVisible( a
->Pos
, !(a
->Flags
& A_ANI_NOT_IN_FOG
)) ) {
925 Particles
*Map::GetNextSpark(spaIterator
&iter
)
927 if (iter
==particles
.end()) {
933 //doesn't increase iterator, because we might need to erase it from the list
934 Projectile
*Map::GetNextProjectile(proIterator
&iter
)
936 if (iter
==projectiles
.end()) {
942 Projectile
*Map::GetNextTrap(proIterator
&iter
)
947 pro
=GetNextProjectile(iter
);
949 //logic to determine dormant traps
950 //if (pro && pro->IsTrap()) break;
955 size_t Map::GetProjectileCount(proIterator
&iter
)
957 iter
= projectiles
.begin();
958 return projectiles
.size();
961 ieDword
Map::GetTrapCount(proIterator
&iter
)
964 iter
=projectiles
.begin();
965 while(GetNextTrap(iter
)) {
969 iter
= projectiles
.begin();
974 //doesn't increase iterator, because we might need to erase it from the list
975 ScriptedAnimation
*Map::GetNextScriptedAnimation(scaIterator
&iter
)
977 if (iter
==vvcCells
.end()) {
983 static ieDword oldgametime
= 0;
985 //Draw the game area (including overlays, actors, animations, weather)
986 void Map::DrawMap(Region screen
)
991 Game
*game
= core
->GetGame();
992 ieDword gametime
= game
->GameTime
;
994 //area specific spawn.ini files (a PST feature)
996 INISpawn
->CheckSpawn();
1001 //zero when the weather particles are all gone
1002 rain
= game
->weather
->GetPhase()-P_EMPTY
;
1006 TMap
->DrawOverlays( screen
, rain
);
1008 //Blit the Background Map Animations (before actors)
1009 Video
* video
= core
->GetVideoDriver();
1012 DrawHighlightables( screen
);
1014 Region vp
= video
->GetViewport();
1015 //if it is only here, then the scripting will fail?
1018 //drawing queues 1 and 0
1019 //starting with lower priority
1020 //so displayed, but inactive actors (dead) will be drawn over
1022 int index
= Qcount
[q
];
1023 Actor
* actor
= GetNextActor(q
, index
);
1024 aniIterator aniidx
= animations
.begin();
1025 scaIterator scaidx
= vvcCells
.begin();
1026 proIterator proidx
= projectiles
.begin();
1027 spaIterator spaidx
= particles
.begin();
1029 AreaAnimation
*a
= GetNextAreaAnimation(aniidx
, gametime
);
1030 ScriptedAnimation
*sca
= GetNextScriptedAnimation(scaidx
);
1031 Projectile
*pro
= GetNextProjectile(proidx
);
1032 Particles
*spark
= GetNextSpark(spaidx
);
1034 while (actor
|| a
|| sca
|| spark
|| pro
) {
1035 switch(SelectObject(actor
,q
,a
,sca
,spark
,pro
)) {
1037 actor
->Draw( screen
);
1038 actor
= GetNextActor(q
, index
);
1042 a
->Draw( screen
, this );
1043 a
= GetNextAreaAnimation(aniidx
,gametime
);
1049 Color tint
= LightMap
->GetPixel( sca
->XPos
/ 16, sca
->YPos
/ 12);
1051 bool endReached
= sca
->Draw(screen
, Pos
, tint
, this, 0, -1);
1054 scaidx
=vvcCells
.erase(scaidx
);
1059 sca
= GetNextScriptedAnimation(scaidx
);
1061 case AOT_PROJECTILE
:
1064 if (gametime
>oldgametime
) {
1065 drawn
= pro
->Update();
1070 pro
->Draw( screen
);
1074 proidx
= projectiles
.erase(proidx
);
1077 pro
= GetNextProjectile(proidx
);
1082 if (gametime
>oldgametime
) {
1083 drawn
= spark
->Update();
1088 spark
->Draw( screen
);
1092 spaidx
=particles
.erase(spaidx
);
1095 spark
= GetNextSpark(spaidx
);
1102 if ((core
->FogOfWar
&FOG_DRAWSEARCHMAP
) && SearchMap
) {
1103 DrawSearchMap(screen
);
1105 if ((core
->FogOfWar
&FOG_DRAWFOG
) && TMap
) {
1106 TMap
->DrawFogOfWar( ExploredBitmap
, VisibleBitmap
, screen
);
1112 //For each InfoPoint in the map
1113 InfoPoint
* ip
= TMap
->GetInfoPoint( ipCount
++ );
1116 ip
->DrawOverheadText(screen
);
1121 //For each Container in the map
1122 Container
* cn
= TMap
->GetContainer( cnCount
++ );
1125 cn
->DrawOverheadText(screen
);
1130 //For each Door in the map
1131 Door
* dr
= TMap
->GetDoor( drCount
++ );
1134 dr
->DrawOverheadText(screen
);
1137 size_t i
= actors
.size();
1139 //For each Actor present
1140 //This must go AFTER the fog!
1141 //(maybe we should be using the queue?)
1142 Actor
* actor
= actors
[i
];
1143 actor
->DrawOverheadText(screen
);
1146 oldgametime
=gametime
;
1149 void Map::DrawSearchMap(Region
&screen
)
1151 Color inaccessible
= { 128, 128, 128, 128 };
1152 Video
*vid
=core
->GetVideoDriver();
1153 Region rgn
=vid
->GetViewport();
1158 int w
= screen
.w
/16+2;
1159 int h
= screen
.h
/12+2;
1161 for(int x
=0;x
<w
;x
++) {
1162 for(int y
=0;y
<h
;y
++) {
1163 if (!(GetBlocked(x
+rgn
.x
/16, y
+rgn
.y
/12) & PATH_MAP_PASSABLE
) ) {
1164 block
.x
=screen
.x
+x
*16-(rgn
.x
% 16);
1165 block
.y
=screen
.y
+y
*12-(rgn
.y
% 12);
1166 vid
->DrawRect(block
,inaccessible
);
1172 //adding animation in order, based on its height parameter
1173 void Map::AddAnimation(AreaAnimation
* anim
)
1175 //this hack is to make sure animations flagged with background
1176 //are always drawn first (-9999 seems sufficiently small)
1177 if (anim
->Flags
&A_ANI_BACKGROUND
) {
1182 for(iter
=animations
.begin(); (iter
!=animations
.end()) && ((*iter
)->height
<anim
->height
); iter
++) ;
1183 animations
.insert(iter
, anim
);
1185 Animation *a = anim->animation[0];
1186 anim->SetSpriteCover(BuildSpriteCover(anim->Pos.x, anim->Pos.y,-a->animArea.x,
1187 -a->animArea.y, a->animArea.w, a->animArea.h,0
1192 //reapplying all of the effects on the actors of this map
1193 //this might be unnecessary later
1194 void Map::UpdateEffects()
1196 size_t i
= actors
.size();
1198 actors
[i
]->RefreshEffects(NULL
);
1202 void Map::Shout(Actor
* actor
, int shoutID
, unsigned int radius
)
1204 size_t i
=actors
.size();
1206 Actor
*listener
= actors
[i
];
1209 if (Distance(actor
->Pos
, listener
->Pos
)>radius
) {
1214 listener
->LastHeard
= actor
->GetID();
1215 listener
->LastShout
= shoutID
;
1217 listener
->LastHelp
= actor
->GetID();
1222 bool Map::AnyEnemyNearPoint(Point
&p
)
1224 ieDword gametime
= core
->GetGame()->GameTime
;
1225 size_t i
= actors
.size();
1227 Actor
*actor
= actors
[i
];
1229 if (actor
->Schedule(gametime
, true) ) {
1232 if (Distance(actor
->Pos
, p
) > SPAWN_RANGE
) {
1235 if (actor
->GetStat(IE_EA
)<EA_EVILCUTOFF
) {
1243 void Map::ActorSpottedByPlayer(Actor
*actor
)
1245 unsigned int animid
;
1247 if(core
->HasFeature(GF_HAS_BEASTS_INI
)) {
1248 animid
=actor
->BaseStats
[IE_ANIMATION_ID
];
1249 if(core
->HasFeature(GF_ONE_BYTE_ANIMID
)) {
1252 if (animid
< (ieDword
)CharAnimations::GetAvatarsCount()) {
1253 AvatarStruct
*avatar
= CharAnimations::GetAvatarStruct(animid
);
1254 core
->GetGame()->SetBeastKnown(avatar
->Bestiary
);
1258 if (!(actor
->GetInternalFlag()&IF_STOPATTACK
)) {
1259 if (actor
->Modified
[IE_EA
]>=EA_EVILCUTOFF
) {
1260 core
->Autopause(AP_ENEMY
);
1265 void Map::AddActor(Actor
* actor
)
1267 //setting the current area for the actor as this one
1268 strnlwrcpy(actor
->Area
, scriptName
, 8);
1269 //if actor->globalID was already set, don't change it
1270 actor
->SetMap(this, ++localActorCounter
,
1271 actor
->globalID
?actor
->globalID
:++globalActorCounter
);
1272 actors
.push_back( actor
);
1273 //if a visible aggressive actor was put on the map, it is an autopause reason
1274 //guess game is always loaded? if not, then we'll crash
1275 ieDword gametime
= core
->GetGame()->GameTime
;
1277 if (IsVisible(actor
->Pos
, false) && actor
->Schedule(gametime
, true) ) {
1278 ActorSpottedByPlayer(actor
);
1280 if (actor
->InParty
&& core
->HasFeature(GF_AREA_VISITED_VAR
)) {
1282 snprintf(key
, sizeof(key
),"%s_visited", scriptName
);
1283 core
->GetGame()->locals
->SetAt(key
, 1);
1288 bool Map::AnyPCSeesEnemy()
1290 ieDword gametime
= core
->GetGame()->GameTime
;
1291 size_t i
= actors
.size();
1293 Actor
* actor
= actors
[i
];
1294 if (actor
->Modified
[IE_EA
]>=EA_EVILCUTOFF
) {
1295 if (IsVisible(actor
->Pos
, false) && actor
->Schedule(gametime
, true) ) {
1303 void Map::DeleteActor(int i
)
1305 Actor
*actor
= actors
[i
];
1307 Game
*game
= core
->GetGame();
1308 game
->LeaveParty( actor
);
1309 game
->DelNPC( game
->InStore(actor
) );
1311 ClearSearchMapFor(actor
);
1313 actors
.erase( actors
.begin()+i
);
1317 Actor
* Map::GetActorByGlobalID(ieDword objectID
)
1322 //truncation is intentional
1323 ieWord globalID
= (ieWord
) objectID
;
1324 size_t i
= actors
.size();
1326 Actor
* actor
= actors
[i
];
1328 if (actor
->globalID
==globalID
) {
1336 GA_SELECT 16 - unselectable actors don't play
1337 GA_NO_DEAD 32 - dead actors don't play
1338 GA_POINT 64 - not actor specific
1339 GA_NO_HIDDEN 128 - hidden actors don't play
1341 Actor
* Map::GetActor(Point
&p
, int flags
)
1343 ieDword gametime
= core
->GetGame()->GameTime
;
1344 size_t i
= actors
.size();
1346 Actor
* actor
= actors
[i
];
1348 if (!actor
->IsOver( p
))
1350 if (!actor
->ValidTarget(flags
) ) {
1353 if (!actor
->Schedule(gametime
, true) ) {
1361 Actor
* Map::GetActorInRadius(Point
&p
, int flags
, unsigned int radius
)
1363 ieDword gametime
= core
->GetGame()->GameTime
;
1364 size_t i
= actors
.size();
1366 Actor
* actor
= actors
[i
];
1368 if (PersonalDistance( p
, actor
) > radius
)
1370 if (!actor
->ValidTarget(flags
) ) {
1373 if (!actor
->Schedule(gametime
, true) ) {
1381 Actor
**Map::GetAllActorsInRadius(Point
&p
, int flags
, unsigned int radius
)
1386 ieDword gametime
= core
->GetGame()->GameTime
;
1389 Actor
* actor
= actors
[i
];
1391 if (PersonalDistance( p
, actor
) > radius
)
1393 if (!actor
->ValidTarget(flags
) ) {
1396 if (!actor
->Schedule(gametime
, true) ) {
1402 Actor
**ret
= (Actor
**) malloc( sizeof(Actor
*) * count
);
1406 Actor
* actor
= actors
[i
];
1408 if (PersonalDistance( p
, actor
) > radius
)
1410 if (!actor
->ValidTarget(flags
) ) {
1413 if (!actor
->Schedule(gametime
, true) ) {
1424 Actor
* Map::GetActor(const char* Name
, int flags
)
1426 size_t i
= actors
.size();
1428 Actor
* actor
= actors
[i
];
1429 if (strnicmp( actor
->GetScriptName(), Name
, 32 ) == 0) {
1430 if (!actor
->ValidTarget(flags
) ) {
1439 int Map::GetActorCount(bool any
) const
1442 return (int) actors
.size();
1445 size_t i
=actors
.size();
1447 if (MustSave(actors
[i
])) {
1454 void Map::JumpActors(bool jump
)
1456 size_t i
= actors
.size();
1458 Actor
* actor
= actors
[i
];
1459 if (actor
->Modified
[IE_DONOTJUMP
]&DNJ_JUMP
) {
1461 actor
->FixPosition();
1463 actor
->SetBase(IE_DONOTJUMP
,0);
1468 //before writing the area out, perform some cleanups
1469 void Map::PurgeArea(bool items
)
1471 InternalFlags
|= IF_JUSTDIED
; //area marked for swapping out
1473 //1. remove dead actors without 'keep corpse' flag
1474 int i
=(int) actors
.size();
1476 Actor
*ac
= actors
[i
];
1478 if (ac
->Modified
[IE_STATE_ID
]&STATE_NOSAVE
) {
1479 if (ac
->Modified
[IE_MC_FLAGS
] & MC_KEEP_CORPSE
) {
1483 actors
.erase( actors
.begin()+i
);
1486 //2. remove any non critical items
1488 i
=(int) TMap
->GetContainerCount();
1490 Container
*c
= TMap
->GetContainer(i
);
1491 unsigned int j
=c
->inventory
.GetSlotCount();
1493 CREItem
*itemslot
= c
->inventory
.GetSlotItem(j
);
1494 if (itemslot
->Flags
&IE_INV_ITEM_CRITICAL
) {
1498 TMap
->CleanupContainer(c
);
1503 Actor
* Map::GetActor(int index
, bool any
)
1506 return actors
[index
];
1509 while (i
<actors
.size() ) {
1510 Actor
*ac
= actors
[i
++];
1511 if (MustSave(ac
) ) {
1520 Actor
* Map::GetActorByDialog(const char *resref
)
1522 size_t i
= actors
.size();
1524 Actor
* actor
= actors
[i
];
1525 //if a busy or hostile actor shouldn't be found
1526 //set this to GD_CHECK
1527 if (strnicmp( actor
->GetDialog(GD_NORMAL
), resref
, 8 ) == 0) {
1534 //this function finds an actor by its original resref (not correct yet)
1535 Actor
* Map::GetActorByResource(const char *resref
)
1537 size_t i
= actors
.size();
1539 Actor
* actor
= actors
[i
];
1540 if (strnicmp( actor
->GetScriptName(), resref
, 8 ) == 0) { //temporarily!
1547 int Map::GetActorInRect(Actor
**& actorlist
, Region
& rgn
, bool onlyparty
)
1549 actorlist
= ( Actor
* * ) malloc( actors
.size() * sizeof( Actor
* ) );
1551 size_t i
= actors
.size();
1553 Actor
* actor
= actors
[i
];
1554 //use this function only for party?
1555 if (onlyparty
&& actor
->GetStat(IE_EA
)>EA_CHARMED
) {
1558 if (!actor
->ValidTarget(GA_SELECT
|GA_NO_DEAD
) )
1560 if ((actor
->Pos
.x
<rgn
.x
) || (actor
->Pos
.y
<rgn
.y
))
1562 if ((actor
->Pos
.x
>rgn
.x
+rgn
.w
) || (actor
->Pos
.y
>rgn
.y
+rgn
.h
) )
1564 actorlist
[count
++] = actor
;
1566 actorlist
= ( Actor
* * ) realloc( actorlist
, count
* sizeof( Actor
* ) );
1570 void Map::PlayAreaSong(int SongType
, bool restart
)
1572 //Ok, we use a non constant pointer here, so it is easy to disable
1573 //a faulty music list on the fly. I don't want to add a method just for that
1574 //crap when we already have that pointer at hand!
1575 char* poi
= core
->GetMusicPlaylist( SongHeader
.SongList
[SongType
] );
1577 if (!restart
&& core
->GetMusicMgr()->CurrentPlayList(poi
)) return;
1578 int ret
= core
->GetMusicMgr()->SwitchPlayList( poi
, true );
1579 //Here we disable the faulty musiclist entry
1581 //Apparently, the playlist manager prefers a *
1586 unsigned char Map::GetBlocked(unsigned int x
, unsigned int y
)
1588 unsigned char ret
= SearchMap
->GetAt( x
, y
);
1589 if (ret
&(PATH_MAP_DOOR_TRANSPARENT
|PATH_MAP_ACTOR
)) {
1590 ret
&=~PATH_MAP_PASSABLE
;
1592 if (ret
&PATH_MAP_DOOR_OPAQUE
) {
1593 ret
=PATH_MAP_SIDEWALL
;
1598 bool Map::GetBlocked(unsigned int px
, unsigned int py
, unsigned int size
)
1600 // We check a circle of radius size-2 around (px,py)
1601 // Note that this does not exactly match BG2. BG2's approximations of
1602 // these circles are slightly different for sizes 7 and up.
1604 if (size
> MAX_CIRCLESIZE
) size
= MAX_CIRCLESIZE
;
1605 if (size
< 2) size
= 2;
1607 unsigned int ppx
= px
/16;
1608 unsigned int ppy
= py
/12;
1609 unsigned int r
=(size
-2)*(size
-2)+1;
1610 if (size
== 2) r
= 0;
1611 for (unsigned int i
=0; i
<size
-1; i
++) {
1612 for (unsigned int j
=0; j
<size
-1; j
++) {
1614 if (!(GetBlocked(ppx
+i
,ppy
+j
)&PATH_MAP_PASSABLE
)) return true;
1615 if (!(GetBlocked(ppx
+i
,ppy
-j
)&PATH_MAP_PASSABLE
)) return true;
1616 if (!(GetBlocked(ppx
-i
,ppy
+j
)&PATH_MAP_PASSABLE
)) return true;
1617 if (!(GetBlocked(ppx
-i
,ppy
-j
)&PATH_MAP_PASSABLE
)) return true;
1624 unsigned char Map::GetBlocked(Point
&c
)
1626 return GetBlocked(c
.x
/16, c
.y
/12);
1629 //flags:0 - never dither (full cover)
1630 // 1 - dither if polygon wants it
1631 // 2 - always dither
1633 SpriteCover
* Map::BuildSpriteCover(int x
, int y
, int xpos
, int ypos
,
1634 unsigned int width
, unsigned int height
, int flags
)
1636 SpriteCover
* sc
= new SpriteCover
;
1642 sc
->Height
= height
;
1644 Video
* video
= core
->GetVideoDriver();
1645 video
->InitSpriteCover(sc
, flags
);
1647 unsigned int wpcount
= GetWallCount();
1650 for (i
= 0; i
< wpcount
; ++i
)
1652 Wall_Polygon
* wp
= GetWallGroup(i
);
1654 if (!wp
->PointCovered(x
, y
)) continue;
1656 video
->AddPolygonToSpriteCover(sc
, wp
);
1662 void Map::ActivateWallgroups(unsigned int baseindex
, unsigned int count
, int flg
)
1669 for(i
=baseindex
; i
< baseindex
+count
; ++i
) {
1670 Wall_Polygon
* wp
= GetWallGroup(i
);
1673 ieDword value
=wp
->GetPolygonFlag();
1675 value
&=~WF_DISABLED
;
1678 wp
->SetPolygonFlag(value
);
1680 //all actors will have to generate a new spritecover
1681 i
=(int) actors
.size();
1683 actors
[i
]->SetSpriteCover(NULL
);
1688 //this function determines actor drawing order
1689 //it should be extended to wallgroups, animations, effects!
1690 void Map::GenerateQueues()
1694 unsigned int i
=(unsigned int) actors
.size();
1695 for (priority
=0;priority
<QUEUE_COUNT
;priority
++) {
1696 if (lastActorCount
[priority
] != i
) {
1697 if (queue
[priority
]) {
1698 free(queue
[priority
]);
1699 queue
[priority
] = NULL
;
1701 queue
[priority
] = (Actor
**) calloc( i
, sizeof(Actor
*) );
1702 lastActorCount
[priority
] = i
;
1704 Qcount
[priority
] = 0;
1707 ieDword gametime
= core
->GetGame()->GameTime
;
1709 Actor
* actor
= actors
[i
];
1711 if (actor
->CheckOnDeath()) {
1716 ieDword stance
= actor
->GetStance();
1717 ieDword internalFlag
= actor
->GetInternalFlag();
1719 if (internalFlag
&IF_ACTIVE
) {
1720 if ((stance
== IE_ANI_TWITCH
) && (internalFlag
&IF_IDLE
) ) {
1721 priority
= PR_DISPLAY
; //display
1723 priority
= PR_SCRIPT
; //run scripts and display
1726 //dead actors are always visible on the map, but run no scripts
1727 if (stance
== IE_ANI_TWITCH
|| stance
== IE_ANI_DIE
) {
1728 priority
= PR_DISPLAY
;
1730 //isvisible flag is false (visibilitymap) here,
1731 //coz we want to reactivate creatures that
1732 //just became visible
1733 if (IsVisible(actor
->Pos
, false) && actor
->Schedule(gametime
, false) ) {
1734 priority
= PR_SCRIPT
; //run scripts and display, activated now
1735 //more like activate!
1737 ActorSpottedByPlayer(actor
);
1739 priority
= PR_IGNORE
;
1744 //we ignore priority 2
1745 if (priority
>=PR_IGNORE
) continue;
1747 queue
[priority
][Qcount
[priority
]] = actor
;
1752 //the original qsort implementation was flawed
1753 void Map::SortQueues()
1755 for (int q
=0;q
<QUEUE_COUNT
;q
++) {
1756 Actor
**baseline
=queue
[q
];
1768 if (n
<=0) break; //breaking loop
1770 baseline
[n
] = baseline
[0];
1776 if (chp
<n
&& baseline
[chp
]->Pos
.y
< baseline
[child
]->Pos
.y
) {
1779 if (baseline
[child
]->Pos
.y
<tmp
->Pos
.y
) {
1780 baseline
[parent
] = baseline
[child
];
1786 baseline
[parent
]=tmp
;
1791 void Map::AddProjectile(Projectile
* pro
, Point
&source
, ieWord actorID
)
1795 pro
->MoveTo(this,source
);
1796 pro
->SetTarget(actorID
);
1797 int height
= pro
->GetHeight();
1798 for(iter
=projectiles
.begin();iter
!=projectiles
.end() && (*iter
)->GetHeight()<height
; iter
++) ;
1799 projectiles
.insert(iter
, pro
);
1802 //adding projectile in order, based on its height parameter
1803 void Map::AddProjectile(Projectile
* pro
, Point
&source
, Point
&dest
)
1807 pro
->MoveTo(this,source
);
1808 pro
->SetTarget(dest
);
1809 int height
= pro
->GetHeight();
1810 for(iter
=projectiles
.begin();iter
!=projectiles
.end() && (*iter
)->GetHeight()<height
; iter
++) ;
1811 projectiles
.insert(iter
, pro
);
1814 //returns the longest duration of the VVC cell named 'resource' (if it exists)
1815 //if P is empty, the position won't be checked
1816 ieDword
Map::HasVVCCell(const ieResRef resource
, Point
&p
)
1821 for(iter
=vvcCells
.begin();iter
!=vvcCells
.end(); iter
++) {
1823 if ((*iter
)->XPos
!=p
.x
) continue;
1824 if ((*iter
)->YPos
!=p
.y
) continue;
1826 if (strnicmp(resource
, (*iter
)->ResName
, sizeof(ieResRef
) )) continue;
1827 ieDword tmp
= (*iter
)->GetSequenceDuration(15)-(*iter
)->GetCurrentFrame();
1835 //adding videocell in order, based on its height parameter
1836 void Map::AddVVCell(ScriptedAnimation
* vvc
)
1840 for(iter
=vvcCells
.begin();iter
!=vvcCells
.end() && (*iter
)->ZPos
<vvc
->ZPos
; iter
++) ;
1841 vvcCells
.insert(iter
, vvc
);
1844 AreaAnimation
* Map::GetAnimation(const char* Name
)
1848 for(iter
=animations
.begin();iter
!=animations
.end();iter
++) {
1849 AreaAnimation
*anim
= *iter
;
1851 if (anim
->Name
&& (strnicmp( anim
->Name
, Name
, 32 ) == 0)) {
1858 Spawn
*Map::AddSpawn(char* Name
, int XPos
, int YPos
, ieResRef
*creatures
, unsigned int count
)
1860 Spawn
* sp
= new Spawn();
1861 strnspccpy(sp
->Name
, Name
, 32);
1862 if (count
>MAX_RESCOUNT
) {
1865 sp
->Pos
.x
= (ieWord
) XPos
;
1866 sp
->Pos
.y
= (ieWord
) YPos
;
1868 sp
->Creatures
= (ieResRef
*) calloc( count
, sizeof(ieResRef
) );
1869 for( unsigned int i
=0;i
<count
;i
++) {
1870 strnlwrcpy(sp
->Creatures
[i
],creatures
[i
],8);
1872 spawns
.push_back( sp
);
1876 void Map::AddEntrance(char* Name
, int XPos
, int YPos
, short Face
)
1878 Entrance
* ent
= new Entrance();
1879 strncpy( ent
->Name
, Name
, 32 );
1880 ent
->Pos
.x
= (ieWord
) XPos
;
1881 ent
->Pos
.y
= (ieWord
) YPos
;
1882 ent
->Face
= (ieWord
) Face
;
1883 entrances
.push_back( ent
);
1886 Entrance
* Map::GetEntrance(const char* Name
)
1888 size_t i
=entrances
.size();
1890 Entrance
*e
= entrances
[i
];
1892 if (strnicmp( e
->Name
, Name
, 32 ) == 0) {
1899 bool Map::HasActor(Actor
*actor
)
1901 size_t i
=actors
.size();
1903 if (actors
[i
] == actor
) {
1910 void Map::RemoveActor(Actor
* actor
)
1912 size_t i
=actors
.size();
1914 if (actors
[i
] == actor
) {
1915 //BlockSearchMap(actor->Pos, actor->size, PATH_MAP_FREE);
1916 ClearSearchMapFor(actor
);
1917 actors
.erase( actors
.begin()+i
);
1921 printMessage("Map","RemoveActor: actor not found?",YELLOW
);
1924 //returns true if none of the partymembers are on the map
1927 size_t i
=actors
.size();
1929 if (actors
[i
]->InParty
) {
1933 if (actors
[i
]->GetInternalFlag()&(IF_ACTIVE
|IF_USEEXIT
) ) {
1937 //we expect the area to be swapped out, so we simply remove the corpses now
1942 void Map::DebugDump(bool show_actors
)
1944 printf( "DebugDump of Area %s:\n", scriptName
);
1945 printf( "OutDoor: %s\n", YESNO(AreaType
& AT_OUTDOOR
) );
1946 printf( "Day/Night: %s\n", YESNO(AreaType
& AT_DAYNIGHT
) );
1947 printf( "Extended night: %s\n", YESNO(AreaType
& AT_EXTENDED_NIGHT
) );
1948 printf( "Weather: %s\n", YESNO(AreaType
& AT_WEATHER
) );
1949 printf( "Area Type: %d\n", AreaType
& (AT_CITY
|AT_FOREST
|AT_DUNGEON
) );
1950 printf( "Can rest: %s\n", YESNO(AreaType
& AT_CAN_REST
) );
1954 size_t i
= actors
.size();
1956 if (!(actors
[i
]->GetInternalFlag()&(IF_JUSTDIED
|IF_REALLYDIED
))) {
1957 printf("Actor: %s at %d.%d\n", actors
[i
]->GetName(1), actors
[i
]->Pos
.x
, actors
[i
]->Pos
.y
);
1963 /******************************************************************************/
1965 void Map::Leveldown(unsigned int px
, unsigned int py
,
1966 unsigned int& level
, Point
&n
, unsigned int& diff
)
1969 unsigned int nlevel
;
1971 if (( px
>= Width
) || ( py
>= Height
)) {
1973 } //walked off the map
1974 pos
= py
* Width
+ px
;
1975 nlevel
= MapSet
[pos
];
1978 } //not even considered
1979 if (level
<= nlevel
) {
1982 unsigned int ndiff
= level
- nlevel
;
1991 void Map::SetupNode(unsigned int x
, unsigned int y
, unsigned int size
, unsigned int Cost
)
1995 if (( x
>= Width
) || ( y
>= Height
)) {
1998 pos
= y
* Width
+ x
;
2002 if (GetBlocked(x
*16+8,y
*12+6,size
)) {
2003 MapSet
[pos
] = 65535;
2006 MapSet
[pos
] = (ieWord
) Cost
;
2007 InternalStack
.push( ( x
<< 16 ) | y
);
2010 bool Map::AdjustPositionX(Point
&goal
, unsigned int radius
)
2012 unsigned int minx
= 0;
2013 if ((unsigned int) goal
.x
> radius
)
2014 minx
= goal
.x
- radius
;
2015 unsigned int maxx
= goal
.x
+ radius
+ 1;
2019 for (unsigned int scanx
= minx
; scanx
< maxx
; scanx
++) {
2020 if ((unsigned int) goal
.y
>= radius
) {
2021 if (GetBlocked( scanx
, goal
.y
- radius
) & PATH_MAP_PASSABLE
) {
2022 goal
.x
= (ieWord
) scanx
;
2023 goal
.y
= (ieWord
) (goal
.y
- radius
);
2027 if (goal
.y
+ radius
< Height
) {
2028 if (GetBlocked( scanx
, goal
.y
+ radius
) & PATH_MAP_PASSABLE
) {
2029 goal
.x
= (ieWord
) scanx
;
2030 goal
.y
= (ieWord
) (goal
.y
+ radius
);
2038 bool Map::AdjustPositionY(Point
&goal
, unsigned int radius
)
2040 unsigned int miny
= 0;
2041 if ((unsigned int) goal
.y
> radius
)
2042 miny
= goal
.y
- radius
;
2043 unsigned int maxy
= goal
.y
+ radius
+ 1;
2046 for (unsigned int scany
= miny
; scany
< maxy
; scany
++) {
2047 if ((unsigned int) goal
.x
>= radius
) {
2048 if (GetBlocked( goal
.x
- radius
, scany
) & PATH_MAP_PASSABLE
) {
2049 goal
.x
= (ieWord
) (goal
.x
- radius
);
2050 goal
.y
= (ieWord
) scany
;
2054 if (goal
.x
+ radius
< Width
) {
2055 if (GetBlocked( goal
.x
+ radius
, scany
) & PATH_MAP_PASSABLE
) {
2056 goal
.x
= (ieWord
) (goal
.x
+ radius
);
2057 goal
.y
= (ieWord
) scany
;
2065 void Map::AdjustPosition(Point
&goal
, unsigned int radius
)
2067 unsigned int maxr
= Width
;
2068 if (maxr
< Height
) {
2071 if ((unsigned int) goal
.x
> Width
) {
2072 goal
.x
= (ieWord
) Width
;
2074 if ((unsigned int) goal
.y
> Height
) {
2075 goal
.y
= (ieWord
) Height
;
2078 for (; radius
< maxr
; radius
++) {
2079 //lets make it slightly random where the actor will appear
2081 if (AdjustPositionX(goal
, radius
)) {
2084 if (AdjustPositionY(goal
, radius
)) {
2088 if (AdjustPositionY(goal
, radius
)) {
2091 if (AdjustPositionX(goal
, radius
)) {
2098 //run away from dX, dY (ie.: find the best path of limited length that brings us the farthest from dX, dY)
2099 PathNode
* Map::RunAway(Point
&s
, Point
&d
, unsigned int size
, unsigned int PathLen
, int flags
)
2101 Point
start(s
.x
/16, s
.y
/12);
2102 Point
goal (d
.x
/16, d
.y
/12);
2105 //MapSet entries are made of 16 bits
2106 if (PathLen
>65535) {
2110 memset( MapSet
, 0, Width
* Height
* sizeof( unsigned short ) );
2111 while (InternalStack
.size())
2112 InternalStack
.pop();
2114 if (!( GetBlocked( start
.x
, start
.y
) & PATH_MAP_PASSABLE
)) {
2115 AdjustPosition( start
);
2117 unsigned int pos
= ( start
.x
<< 16 ) | start
.y
;
2118 InternalStack
.push( pos
);
2119 MapSet
[start
.y
* Width
+ start
.x
] = 1;
2122 while (InternalStack
.size()) {
2123 pos
= InternalStack
.front();
2124 InternalStack
.pop();
2125 unsigned int x
= pos
>> 16;
2126 unsigned int y
= pos
& 0xffff;
2127 long tx
= ( x
- goal
.x
);
2128 long ty
= ( y
- goal
.y
);
2129 unsigned int distance
= (unsigned int) sqrt( ( double ) ( tx
* tx
+ ty
* ty
) );
2130 if (dist
<distance
) {
2136 unsigned int Cost
= MapSet
[y
* Width
+ x
] + NormalCost
;
2137 if (Cost
> PathLen
) {
2138 //printf("Path not found!\n");
2141 SetupNode( x
- 1, y
- 1, size
, Cost
);
2142 SetupNode( x
+ 1, y
- 1, size
, Cost
);
2143 SetupNode( x
+ 1, y
+ 1, size
, Cost
);
2144 SetupNode( x
- 1, y
+ 1, size
, Cost
);
2146 Cost
+= AdditionalCost
;
2147 SetupNode( x
, y
- 1, size
, Cost
);
2148 SetupNode( x
+ 1, y
, size
, Cost
);
2149 SetupNode( x
, y
+ 1, size
, Cost
);
2150 SetupNode( x
- 1, y
, size
, Cost
);
2153 //find path backwards from best to start
2154 PathNode
* StartNode
= new PathNode
;
2155 PathNode
* Return
= StartNode
;
2156 StartNode
->Next
= NULL
;
2157 StartNode
->x
= best
.x
;
2158 StartNode
->y
= best
.y
;
2160 StartNode
->orient
= GetOrient( start
, best
);
2162 StartNode
->orient
= GetOrient( best
, start
);
2165 unsigned int pos2
= start
.y
* Width
+ start
.x
;
2166 while (( pos
= p
.y
* Width
+ p
.x
) != pos2
) {
2167 Return
= new PathNode
;
2168 StartNode
->Parent
= Return
;
2169 Return
->Next
= StartNode
;
2171 unsigned int level
= MapSet
[pos
];
2172 unsigned int diff
= 0;
2174 Leveldown( p
.x
, p
.y
+ 1, level
, n
, diff
);
2175 Leveldown( p
.x
+ 1, p
.y
, level
, n
, diff
);
2176 Leveldown( p
.x
- 1, p
.y
, level
, n
, diff
);
2177 Leveldown( p
.x
, p
.y
- 1, level
, n
, diff
);
2178 Leveldown( p
.x
- 1, p
.y
+ 1, level
, n
, diff
);
2179 Leveldown( p
.x
+ 1, p
.y
+ 1, level
, n
, diff
);
2180 Leveldown( p
.x
+ 1, p
.y
- 1, level
, n
, diff
);
2181 Leveldown( p
.x
- 1, p
.y
- 1, level
, n
, diff
);
2186 Return
->orient
= GetOrient( p
, n
);
2188 Return
->orient
= GetOrient( n
, p
);
2195 Return
->Parent
= NULL
;
2199 bool Map::TargetUnreachable(Point
&s
, Point
&d
, unsigned int size
)
2201 Point
start( s
.x
/16, s
.y
/12 );
2202 Point
goal ( d
.x
/16, d
.y
/12 );
2203 memset( MapSet
, 0, Width
* Height
* sizeof( unsigned short ) );
2204 while (InternalStack
.size())
2205 InternalStack
.pop();
2207 if (GetBlocked( d
.x
, d
.y
, size
)) {
2210 if (GetBlocked( s
.x
, s
.y
, size
)) {
2214 unsigned int pos
= ( goal
.x
<< 16 ) | goal
.y
;
2215 unsigned int pos2
= ( start
.x
<< 16 ) | start
.y
;
2216 InternalStack
.push( pos
);
2217 MapSet
[goal
.y
* Width
+ goal
.x
] = 1;
2219 while (InternalStack
.size() && pos
!=pos2
) {
2220 pos
= InternalStack
.front();
2221 InternalStack
.pop();
2222 unsigned int x
= pos
>> 16;
2223 unsigned int y
= pos
& 0xffff;
2225 SetupNode( x
- 1, y
- 1, size
, 1 );
2226 SetupNode( x
+ 1, y
- 1, size
, 1 );
2227 SetupNode( x
+ 1, y
+ 1, size
, 1 );
2228 SetupNode( x
- 1, y
+ 1, size
, 1 );
2229 SetupNode( x
, y
- 1, size
, 1 );
2230 SetupNode( x
+ 1, y
, size
, 1 );
2231 SetupNode( x
, y
+ 1, size
, 1 );
2232 SetupNode( x
- 1, y
, size
, 1 );
2237 /* Use this function when you target something by a straight line projectile (like a lightning bolt, arrow, etc)
2240 PathNode
* Map::GetLine(Point
&start
, Point
&dest
, int flags
)
2242 int Orientation
= GetOrient(start
, dest
);
2243 return GetLine(start
, dest
, 1, Orientation
, flags
);
2246 PathNode
* Map::GetLine(Point
&start
, int Steps
, int Orientation
, int flags
)
2250 unsigned int st
= Steps
>=MaxVisibility
?MaxVisibility
-1:Steps
;
2251 int p
= VisibilityPerimeter
*Orientation
/MAX_ORIENT
;
2252 dest
.x
+= VisibilityMasks
[st
][p
].x
;
2253 dest
.y
+= VisibilityMasks
[st
][p
].y
;
2254 //FIXME: calculate dest based on distance and orientation
2255 return GetLine(start
, dest
, Steps
, Orientation
, flags
);
2258 PathNode
* Map::GetLine(Point
&start
, Point
&dest
, int Speed
, int Orientation
, int flags
)
2260 PathNode
* StartNode
= new PathNode
;
2261 PathNode
*Return
= StartNode
;
2262 StartNode
->Next
= NULL
;
2263 StartNode
->Parent
= NULL
;
2264 StartNode
->x
= start
.x
;
2265 StartNode
->y
= start
.y
;
2266 StartNode
->orient
= Orientation
;
2269 int Max
= Distance(start
,dest
);
2270 for (int Steps
= 0; Steps
<Max
; Steps
++) {
2272 StartNode
->Next
= new PathNode
;
2273 StartNode
->Next
->Parent
= StartNode
;
2274 StartNode
= StartNode
->Next
;
2275 StartNode
->Next
= NULL
;
2282 p
.x
= (ieWord
) start
.x
+ ((dest
.x
- start
.x
) * Steps
/ Max
);
2283 p
.y
= (ieWord
) start
.y
+ ((dest
.y
- start
.y
) * Steps
/ Max
);
2285 //the path ends here as it would go off the screen, causing problems
2286 //maybe there is a better way, but i needed a quick hack to fix
2287 //the crash in projectiles
2288 if ((signed) p
.x
<0 || (signed) p
.y
<0) {
2291 if ((ieWord
) p
.x
>Width
*16 || (ieWord
) p
.y
>Height
*12) {
2297 StartNode
->orient
= Orientation
;
2298 bool wall
= !( GetBlocked( p
) & PATH_MAP_PASSABLE
);
2299 if (wall
) switch (flags
) {
2301 Orientation
= (Orientation
+ 8) &15;
2302 //recalculate dest (mirror it)
2306 default: //premature end
2315 * find a path from start to goal, ending at the specified distance from the
2316 * target (the goal must be in sight of the end, if 'sight' is specified)
2318 * if you don't need to find an optimal path near the goal then use FindPath
2319 * instead, but don't change this one without testing with combat and dialog,
2320 * you can't predict the goal point for those, you *must* path!
2322 PathNode
* Map::FindPathNear(const Point
&s
, const Point
&d
, unsigned int size
, unsigned int MinDistance
, bool sight
)
2324 // adjust the start/goal points to be searchmap locations
2325 Point
start( s
.x
/16, s
.y
/12 );
2326 Point
goal ( d
.x
/16, d
.y
/12 );
2327 Point orig_goal
= goal
;
2329 // re-initialise the path finding structures
2330 memset( MapSet
, 0, Width
* Height
* sizeof( unsigned short ) );
2331 while (InternalStack
.size())
2332 InternalStack
.pop();
2334 // set the start point in the path finding structures
2335 unsigned int pos2
= ( goal
.x
<< 16 ) | goal
.y
;
2336 unsigned int pos
= ( start
.x
<< 16 ) | start
.y
;
2337 InternalStack
.push( pos
);
2338 MapSet
[start
.y
* Width
+ start
.x
] = 1;
2340 unsigned int squaredmindistance
= MinDistance
* MinDistance
;
2341 bool found_path
= false;
2342 while (InternalStack
.size()) {
2343 pos
= InternalStack
.front();
2344 InternalStack
.pop();
2345 unsigned int x
= pos
>> 16;
2346 unsigned int y
= pos
& 0xffff;
2349 // we got all the way to the target!
2352 } else if (MinDistance
) {
2353 /* check minimum distance:
2354 * as an obvious optimisation we only check squared distance: this is a
2355 * possible overestimate since the sqrt Distance() rounds down
2356 * (some other optimisations could be made here, but you'd be better off
2357 * fixing the pathfinder to do A* properly)
2358 * caller should have already done PersonalDistance adjustments, this is
2359 * simply between the specified points
2362 int distx
= (x
- orig_goal
.x
)*16;
2363 int disty
= (y
- orig_goal
.y
)*12;
2364 if ((unsigned int)(distx
*distx
+ disty
*disty
) <= squaredmindistance
) {
2365 // we are within the minimum distance of the goal
2366 Point
ourpos(x
*16 + 8, y
*12 + 6);
2367 // sight check is *slow* :(
2368 if (!sight
|| IsVisible(ourpos
, d
)) {
2369 // we got all the way to a suitable goal!
2377 unsigned int Cost
= MapSet
[y
* Width
+ x
] + NormalCost
;
2379 // cost is far too high, no path found
2383 // diagonal movements
2384 SetupNode( x
- 1, y
- 1, size
, Cost
);
2385 SetupNode( x
+ 1, y
- 1, size
, Cost
);
2386 SetupNode( x
+ 1, y
+ 1, size
, Cost
);
2387 SetupNode( x
- 1, y
+ 1, size
, Cost
);
2390 Cost
+= AdditionalCost
;
2391 SetupNode( x
, y
- 1, size
, Cost
);
2392 SetupNode( x
+ 1, y
, size
, Cost
);
2393 SetupNode( x
, y
+ 1, size
, Cost
);
2394 SetupNode( x
- 1, y
, size
, Cost
);
2397 // find path from goal to start
2398 PathNode
* StartNode
= new PathNode
;
2399 PathNode
* Return
= StartNode
;
2400 StartNode
->Next
= NULL
;
2401 StartNode
->Parent
= NULL
;
2403 // this is not really great, we should be finding the path that
2404 // went nearest to where we wanted
2405 StartNode
->x
= start
.x
;
2406 StartNode
->y
= start
.y
;
2407 StartNode
->orient
= GetOrient( goal
, start
);
2410 StartNode
->x
= goal
.x
;
2411 StartNode
->y
= goal
.y
;
2412 bool fixup_orient
= false;
2413 if (orig_goal
!= goal
) {
2414 StartNode
->orient
= GetOrient( orig_goal
, goal
);
2416 // we pathed all the way to original goal!
2417 // we don't know correct orientation until we find previous step
2418 fixup_orient
= true;
2419 StartNode
->orient
= GetOrient( goal
, start
);
2422 pos2
= start
.y
* Width
+ start
.x
;
2423 while (( pos
= p
.y
* Width
+ p
.x
) != pos2
) {
2424 unsigned int level
= MapSet
[pos
];
2425 unsigned int diff
= 0;
2427 Leveldown( p
.x
, p
.y
+ 1, level
, n
, diff
);
2428 Leveldown( p
.x
+ 1, p
.y
, level
, n
, diff
);
2429 Leveldown( p
.x
- 1, p
.y
, level
, n
, diff
);
2430 Leveldown( p
.x
, p
.y
- 1, level
, n
, diff
);
2431 Leveldown( p
.x
- 1, p
.y
+ 1, level
, n
, diff
);
2432 Leveldown( p
.x
+ 1, p
.y
+ 1, level
, n
, diff
);
2433 Leveldown( p
.x
+ 1, p
.y
- 1, level
, n
, diff
);
2434 Leveldown( p
.x
- 1, p
.y
- 1, level
, n
, diff
);
2439 // don't change orientation at end of path? this seems best
2440 StartNode
->orient
= GetOrient( p
, n
);
2443 Return
= new PathNode
;
2444 Return
->Next
= StartNode
;
2445 Return
->Next
->Parent
= Return
;
2450 StartNode
->orient
= GetOrient( p
, n
);
2457 PathNode
* Map::FindPath(const Point
&s
, const Point
&d
, unsigned int size
, int MinDistance
)
2459 Point
start( s
.x
/16, s
.y
/12 );
2460 Point
goal ( d
.x
/16, d
.y
/12 );
2461 memset( MapSet
, 0, Width
* Height
* sizeof( unsigned short ) );
2462 while (InternalStack
.size())
2463 InternalStack
.pop();
2465 if (GetBlocked( d
.x
, d
.y
, size
)) {
2466 AdjustPosition( goal
);
2468 unsigned int pos
= ( goal
.x
<< 16 ) | goal
.y
;
2469 unsigned int pos2
= ( start
.x
<< 16 ) | start
.y
;
2470 InternalStack
.push( pos
);
2471 MapSet
[goal
.y
* Width
+ goal
.x
] = 1;
2473 while (InternalStack
.size()) {
2474 pos
= InternalStack
.front();
2475 InternalStack
.pop();
2476 unsigned int x
= pos
>> 16;
2477 unsigned int y
= pos
& 0xffff;
2480 //We've found _a_ path
2481 //printf("GOAL!!!\n");
2484 unsigned int Cost
= MapSet
[y
* Width
+ x
] + NormalCost
;
2486 //printf("Path not found!\n");
2489 SetupNode( x
- 1, y
- 1, size
, Cost
);
2490 SetupNode( x
+ 1, y
- 1, size
, Cost
);
2491 SetupNode( x
+ 1, y
+ 1, size
, Cost
);
2492 SetupNode( x
- 1, y
+ 1, size
, Cost
);
2494 Cost
+= AdditionalCost
;
2495 SetupNode( x
, y
- 1, size
, Cost
);
2496 SetupNode( x
+ 1, y
, size
, Cost
);
2497 SetupNode( x
, y
+ 1, size
, Cost
);
2498 SetupNode( x
- 1, y
, size
, Cost
);
2501 //find path from start to goal
2502 PathNode
* StartNode
= new PathNode
;
2503 PathNode
* Return
= StartNode
;
2504 StartNode
->Next
= NULL
;
2505 StartNode
->Parent
= NULL
;
2506 StartNode
->x
= start
.x
;
2507 StartNode
->y
= start
.y
;
2508 StartNode
->orient
= GetOrient( goal
, start
);
2513 pos2
= goal
.y
* Width
+ goal
.x
;
2514 while (( pos
= p
.y
* Width
+ p
.x
) != pos2
) {
2515 StartNode
->Next
= new PathNode
;
2516 StartNode
->Next
->Parent
= StartNode
;
2517 StartNode
= StartNode
->Next
;
2518 StartNode
->Next
= NULL
;
2519 unsigned int level
= MapSet
[pos
];
2520 unsigned int diff
= 0;
2522 Leveldown( p
.x
, p
.y
+ 1, level
, n
, diff
);
2523 Leveldown( p
.x
+ 1, p
.y
, level
, n
, diff
);
2524 Leveldown( p
.x
- 1, p
.y
, level
, n
, diff
);
2525 Leveldown( p
.x
, p
.y
- 1, level
, n
, diff
);
2526 Leveldown( p
.x
- 1, p
.y
+ 1, level
, n
, diff
);
2527 Leveldown( p
.x
+ 1, p
.y
+ 1, level
, n
, diff
);
2528 Leveldown( p
.x
+ 1, p
.y
- 1, level
, n
, diff
);
2529 Leveldown( p
.x
- 1, p
.y
- 1, level
, n
, diff
);
2534 StartNode
->orient
= GetOrient( n
, p
);
2537 //stepping back on the calculated path
2539 while (StartNode
->Parent
) {
2542 tar
.x
=StartNode
->Parent
->x
*16;
2543 tar
.y
=StartNode
->Parent
->y
*12;
2544 int dist
= Distance(tar
,d
);
2545 if (dist
+14>=MinDistance
) {
2548 StartNode
= StartNode
->Parent
;
2549 delete StartNode
->Next
;
2550 StartNode
->Next
= NULL
;
2556 //single point visible or not (visible/exploredbitmap)
2557 //if explored = true then explored otherwise currently visible
2558 bool Map::IsVisible(const Point
&pos
, int explored
)
2564 if (sX
<0) return false;
2565 if (sY
<0) return false;
2566 int w
= TMap
->XCellCount
* 2 + LargeFog
;
2567 int h
= TMap
->YCellCount
* 2 + LargeFog
;
2568 if (sX
>=w
) return false;
2569 if (sY
>=h
) return false;
2570 int b0
= (sY
* w
) + sX
;
2574 if (explored
) return (ExploredBitmap
[by
] & bi
)!=0;
2575 return (VisibleBitmap
[by
] & bi
)!=0;
2578 //point a is visible from point b (searchmap)
2579 bool Map::IsVisible(const Point
&s
, const Point
&d
)
2585 int diffx
= sX
- dX
;
2586 int diffy
= sY
- dY
;
2587 if (abs( diffx
) >= abs( diffy
)) {
2589 double elevationy
= fabs((double)diffx
) / diffy
;
2591 for (int startx
= sX
; startx
> dX
; startx
--) {
2592 if (GetBlocked( startx
, sY
- ( int ) ( ( sX
- startx
) / elevationy
) ) & PATH_MAP_NO_SEE
)
2596 for (int startx
= sX
; startx
< dX
; startx
++) {
2597 if (GetBlocked( startx
, sY
+ ( int ) ( ( sX
- startx
) / elevationy
) ) & PATH_MAP_NO_SEE
)
2602 double elevationx
= fabs((double)diffy
) / diffx
;
2604 for (int starty
= sY
; starty
> dY
; starty
--) {
2605 if (GetBlocked( sX
- ( int ) ( ( sY
- starty
) / elevationx
), starty
) & PATH_MAP_NO_SEE
)
2609 for (int starty
= sY
; starty
< dY
; starty
++) {
2610 if (GetBlocked( sX
+ ( int ) ( ( sY
- starty
) / elevationx
), starty
) & PATH_MAP_NO_SEE
)
2618 //returns direction of area boundary, returns -1 if it isn't a boundary
2619 int Map::WhichEdge(Point
&s
)
2621 unsigned int sX
=s
.x
/16;
2622 unsigned int sY
=s
.y
/12;
2623 if (!(GetBlocked( sX
, sY
)&PATH_MAP_TRAVEL
)) {
2624 printMessage("Map"," ",YELLOW
);
2625 printf("This isn't a travel region [%d.%d]?\n",sX
, sY
);
2630 if (sX
>sY
) { //north or east
2631 if (Width
*Height
>sX
+sY
) { //
2637 if (Width
*Height
<sX
+sY
) { //
2643 //--------ambients----------------
2644 void Map::SetupAmbients()
2646 AmbientMgr
*ambim
= core
->GetAudioDrv()->GetAmbientMgr();
2649 ambim
->setAmbients( ambients
);
2651 //--------mapnotes----------------
2652 //text must be a pointer we can claim ownership of
2653 void Map::AddMapNote(Point
&point
, int color
, char *text
, ieStrRef strref
)
2655 MapNote
*mn
= new MapNote
;
2657 mn
->strref
= strref
;
2659 mn
->color
= (ieWord
) color
;
2661 RemoveMapNote(point
); //delete previous mapnote
2662 mapnotes
.push_back(mn
);
2665 void Map::RemoveMapNote(Point
&point
)
2667 size_t i
= mapnotes
.size();
2669 if ((point
.x
==mapnotes
[i
]->Pos
.x
) &&
2670 (point
.y
==mapnotes
[i
]->Pos
.y
)) {
2672 mapnotes
.erase(mapnotes
.begin()+i
);
2677 MapNote
*Map::GetMapNote(Point
&point
)
2679 size_t i
= mapnotes
.size();
2681 if (Distance(point
, mapnotes
[i
]->Pos
) < 10 ) {
2687 //--------spawning------------------
2688 void Map::LoadIniSpawn()
2690 INISpawn
= new IniSpawn(this);
2691 INISpawn
->InitSpawn(WEDResRef
);
2694 void Map::SpawnCreature(Point
&pos
, const char *CreName
, int radius
)
2696 SpawnGroup
*sg
=NULL
;
2699 if ( !Spawns
.Lookup( CreName
, lookup
) ) {
2700 creature
= gamedata
->GetCreature(CreName
);
2703 creature
->SetPosition( pos
, true, radius
);
2704 creature
->RefreshEffects(NULL
);
2708 sg
= (SpawnGroup
*)lookup
;
2709 unsigned int count
= 0;
2710 int amount
= core
->GetGame()->GetPartyLevel(true);
2711 // if the difficulty is too high, distribute it equally over all the
2712 // critters and summon as many as the summed difficulty allows
2713 if (amount
- (signed)sg
->Level
< 0) {
2714 unsigned int share
= sg
->Level
/sg
->Count
;
2717 // a single critter is also too powerful
2720 while (amount
>= 0) {
2729 creature
= gamedata
->GetCreature(sg
->ResRefs
[count
]);
2732 creature
->SetPosition( pos
, true, radius
);
2733 creature
->RefreshEffects(NULL
);
2738 void Map::TriggerSpawn(Spawn
*spawn
)
2740 //is it still active
2741 if (!spawn
->Enabled
) {
2745 ieDword bit
= 1<<((core
->GetGame()->GameTime
/AI_UPDATE_TIME
)%7200/300);
2746 if (!(spawn
->appearance
& bit
)) {
2750 //check day or night chance
2751 if (rand()%100>spawn
->DayChance
) {
2754 // the difficulty check is done in SpawnCreature
2756 for(unsigned int i
= 0;i
<spawn
->Count
;i
++) {
2757 SpawnCreature(spawn
->Pos
, spawn
->Creatures
[i
], 0);
2759 //disable spawnpoint
2763 //--------restheader----------------
2765 Every spawn has a difficulty associated with it. For CREs this is the xp stat
2766 and for groups it's the value in the difficulty row.
2767 For every spawn, the difficulty sum of all spawns up to now (including the
2768 current) is compared against (party level * rest header difficulty). If it's
2769 greater, the spawning is aborted. If all the other conditions are true, at
2770 least one creature is summoned, regardless the difficulty cap.
2772 bool Map::Rest(Point
&pos
, int hours
, int day
)
2774 if (!RestHeader
.CreatureNum
|| !RestHeader
.Enabled
|| !RestHeader
.Maximum
) {
2778 //based on ingame timer
2779 int chance
=day
?RestHeader
.DayChance
:RestHeader
.NightChance
;
2781 int spawnamount
= core
->GetGame()->GetPartyLevel(true) * RestHeader
.Difficulty
;
2782 if (spawnamount
< 1) spawnamount
= 1;
2783 for (int i
=0;i
<hours
;i
++) {
2784 if ( rand()%100<chance
) {
2785 int idx
= rand()%RestHeader
.CreatureNum
;
2786 Actor
*creature
= gamedata
->GetCreature(RestHeader
.CreResRef
[idx
]);
2787 if (!creature
) continue;
2788 // ensure a minimum power level, since many creatures have this as 0
2789 int cpl
= creature
->Modified
[IE_XP
] ? creature
->Modified
[IE_XP
] : 1;
2791 core
->DisplayString( RestHeader
.Strref
[idx
], 0x00404000, IE_STR_SOUND
);
2792 while (spawnamount
> 0 && spawncount
<= RestHeader
.Maximum
) {
2793 SpawnCreature(pos
, RestHeader
.CreResRef
[idx
], 20);
2803 //--------explored bitmap-----------
2804 int Map::GetExploredMapSize() const
2806 int x
= TMap
->XCellCount
*2;
2807 int y
= TMap
->YCellCount
*2;
2815 void Map::Explore(int setreset
)
2817 memset (ExploredBitmap
, setreset
, GetExploredMapSize() );
2820 void Map::SetMapVisibility(int setreset
)
2822 memset( VisibleBitmap
, setreset
, GetExploredMapSize() );
2825 // x, y are in tile coordinates
2826 void Map::ExploreTile(Point
&pos
)
2828 int h
= TMap
->YCellCount
* 2 + LargeFog
;
2830 if (y
< 0 || y
>= h
)
2833 int w
= TMap
->XCellCount
* 2 + LargeFog
;
2835 if (x
< 0 || x
>= w
)
2838 int b0
= (y
* w
) + x
;
2842 ExploredBitmap
[by
] |= bi
;
2843 VisibleBitmap
[by
] |= bi
;
2846 void Map::ExploreMapChunk(Point
&Pos
, int range
, int los
)
2850 if (range
>MaxVisibility
) {
2851 range
=MaxVisibility
;
2853 int p
=VisibilityPerimeter
;
2857 bool sidewall
= false ;
2858 for (int i
=0;i
<range
;i
++) {
2859 Tile
.x
= Pos
.x
+VisibilityMasks
[i
][p
].x
;
2860 Tile
.y
= Pos
.y
+VisibilityMasks
[i
][p
].y
;
2864 int type
= GetBlocked(Tile
);
2865 if (type
& PATH_MAP_NO_SEE
) {
2867 } else if (type
& PATH_MAP_SIDEWALL
) {
2869 } else if (sidewall
)
2884 void Map::UpdateFog()
2886 if (!(core
->FogOfWar
&FOG_DRAWFOG
) ) {
2887 SetMapVisibility( -1 );
2891 SetMapVisibility( 0 );
2892 for (unsigned int e
= 0; e
<actors
.size(); e
++) {
2893 Actor
*actor
= actors
[e
];
2894 if (!actor
->Modified
[ IE_EXPLORE
] ) continue;
2895 int state
= actor
->Modified
[IE_STATE_ID
];
2896 if (state
& STATE_CANTSEE
) continue;
2897 int vis2
= actor
->Modified
[IE_VISUALRANGE
];
2898 if ((state
&STATE_BLIND
) || (vis2
<2)) vis2
=2; //can see only themselves
2899 ExploreMapChunk (actor
->Pos
, vis2
, 1);
2900 Spawn
*sp
= GetSpawnRadius(actor
->Pos
, SPAWN_RANGE
); //30 * 12
2907 //Valid values are - PATH_MAP_FREE, PATH_MAP_PC, PATH_MAP_NPC
2908 void Map::BlockSearchMap(Point
&Pos
, unsigned int size
, unsigned int value
)
2910 // We block a circle of radius size-1 around (px,py)
2911 // Note that this does not exactly match BG2. BG2's approximations of
2912 // these circles are slightly different for sizes 6 and up.
2914 // Note: this is a larger circle than the one tested in GetBlocked.
2915 // This means that an actor can get closer to a wall than to another
2916 // actor. This matches the behaviour of the original BG2.
2918 if (size
> MAX_CIRCLESIZE
) size
= MAX_CIRCLESIZE
;
2919 if (size
< 2) size
= 2;
2920 unsigned int ppx
= Pos
.x
/16;
2921 unsigned int ppy
= Pos
.y
/12;
2922 unsigned int r
=(size
-1)*(size
-1)+1;
2923 if (size
== 1) r
= 0;
2924 for (unsigned int i
=0; i
<size
; i
++) {
2925 for (unsigned int j
=0; j
<size
; j
++) {
2929 tmp
= SearchMap
->GetAt(ppx
+i
,ppy
+j
)&PATH_MAP_NOTACTOR
;
2930 SearchMap
->SetAt(ppx
+i
,ppy
+j
,tmp
|value
);
2932 tmp
= SearchMap
->GetAt(ppx
+i
,ppy
-j
)&PATH_MAP_NOTACTOR
;
2933 SearchMap
->SetAt(ppx
+i
,ppy
-j
,tmp
|value
);
2935 tmp
= SearchMap
->GetAt(ppx
-i
,ppy
+j
)&PATH_MAP_NOTACTOR
;
2936 SearchMap
->SetAt(ppx
-i
,ppy
+j
,tmp
|value
);
2938 tmp
= SearchMap
->GetAt(ppx
-i
,ppy
-j
)&PATH_MAP_NOTACTOR
;
2939 SearchMap
->SetAt(ppx
-i
,ppy
-j
,tmp
|value
);
2945 Spawn
* Map::GetSpawn(const char* Name
)
2947 for (size_t i
= 0; i
< spawns
.size(); i
++) {
2948 Spawn
* sp
= spawns
[i
];
2950 if (stricmp( sp
->Name
, Name
) == 0)
2956 Spawn
*Map::GetSpawnRadius(Point
&point
, unsigned int radius
)
2958 for (size_t i
= 0; i
< spawns
.size(); i
++) {
2959 Spawn
* sp
= spawns
[i
];
2961 if (Distance(point
, sp
->Pos
)<radius
) {
2968 int Map::ConsolidateContainers()
2971 int containercount
= (int) TMap
->GetContainerCount();
2972 while (containercount
--) {
2973 Container
* c
= TMap
->GetContainer( containercount
);
2975 if (TMap
->CleanupContainer(c
) ) {
2978 itemcount
+= c
->inventory
.GetSlotCount();
2983 //Pos could be [-1,-1] in which case it copies the ground piles to their
2984 //original position in the second area
2985 void Map::CopyGroundPiles(Map
*othermap
, Point
&Pos
)
2987 int containercount
= (int) TMap
->GetContainerCount();
2988 while (containercount
--) {
2989 Container
* c
= TMap
->GetContainer( containercount
);
2990 if (c
->Type
==IE_CONTAINER_PILE
) {
2991 //creating (or grabbing) the container in the other map at the given position
2992 Container
*othercontainer
;
2993 if (Pos
.isempty()) {
2994 othercontainer
= othermap
->GetPile(c
->Pos
);
2996 othercontainer
= othermap
->GetPile(Pos
);
2998 //transfer the pile to the other container
2999 unsigned int i
=c
->inventory
.GetSlotCount();
3001 CREItem
*item
= c
->RemoveItem(i
, 0);
3002 othercontainer
->AddItem(item
);
3008 Container
*Map::GetPile(Point
&position
)
3013 //converting to search square
3014 position
.x
=position
.x
/16;
3015 position
.y
=position
.y
/12;
3016 sprintf(heapname
,"heap_%hd.%hd",position
.x
,position
.y
);
3017 //pixel position is centered on search square
3018 position
.x
=position
.x
*16+8;
3019 position
.y
=position
.y
*12+6;
3020 Container
*container
= TMap
->GetContainer(position
,IE_CONTAINER_PILE
);
3022 //bounding box covers the search square
3023 tmp
[0].x
=position
.x
-8;
3024 tmp
[0].y
=position
.y
-6;
3025 tmp
[1].x
=position
.x
+8;
3026 tmp
[1].y
=position
.y
-6;
3027 tmp
[2].x
=position
.x
+8;
3028 tmp
[2].y
=position
.y
+6;
3029 tmp
[3].x
=position
.x
-8;
3030 tmp
[3].y
=position
.y
+6;
3031 Gem_Polygon
* outline
= new Gem_Polygon( tmp
, 4 );
3032 container
= AddContainer(heapname
, IE_CONTAINER_PILE
, outline
);
3033 container
->Pos
=position
;
3038 void Map::AddItemToLocation(Point
&position
, CREItem
*item
)
3040 Container
*container
= GetPile(position
);
3041 container
->AddItem(item
);
3044 Container
* Map::AddContainer(const char* Name
, unsigned short Type
,
3045 Gem_Polygon
* outline
)
3047 Container
* c
= new Container();
3048 c
->SetScriptName( Name
);
3050 c
->outline
= outline
;
3052 TMap
->AddContainer( c
);
3056 int Map::GetCursor( Point
&p
)
3058 if (!IsVisible( p
, true ) ) {
3059 return IE_CURSOR_INVALID
;
3061 switch (GetBlocked( p
) & (PATH_MAP_PASSABLE
|PATH_MAP_TRAVEL
)) {
3063 return IE_CURSOR_BLOCKED
;
3064 case PATH_MAP_PASSABLE
:
3065 return IE_CURSOR_WALK
;
3067 return IE_CURSOR_TRAVEL
;
3071 bool Map::HasWeather()
3073 if ((AreaType
& (AT_WEATHER
|AT_OUTDOOR
) ) != (AT_WEATHER
|AT_OUTDOOR
) ) {
3079 int Map::GetWeather()
3081 if (Rain
>=core
->Roll(1,100,0) ) {
3082 if (Lightning
>=core
->Roll(1,100,0) ) {
3083 return WB_LIGHTNING
|WB_RAIN
;
3087 if (Snow
>=core
->Roll(1,100,0) ) {
3090 if (Fog
>=core
->Roll(1,100,0) ) {
3096 void Map::FadeSparkle(Point
&pos
, bool forced
)
3100 for(iter
=particles
.begin(); iter
!=particles
.end();iter
++) {
3101 if ((*iter
)->MatchPos(pos
) ) {
3103 //particles.erase(iter);
3104 (*iter
)->SetPhase(P_EMPTY
);
3106 (*iter
)->SetPhase(P_FADE
);
3113 void Map::Sparkle(ieDword color
, ieDword type
, Point
&pos
, unsigned int FragAnimID
)
3115 int style
, path
, grow
, size
, width
;
3117 //the high word is ignored in the original engine (compatibility hack)
3118 switch(type
&0xffff) {
3119 case SPARKLE_SHOWER
: //simple falling sparks
3120 path
= SP_PATH_FALL
;
3121 grow
= SP_SPAWN_FULL
;
3126 path
= SP_PATH_FOUNT
; //sparks go up and down
3127 grow
= SP_SPAWN_FULL
;
3131 case SPARKLE_EXPLOSION
: //this isn't in the original engine, but it is a nice effect to have
3132 path
= SP_PATH_EXPL
;
3133 grow
= SP_SPAWN_FULL
;
3138 path
= SP_PATH_FLIT
;
3139 grow
= SP_SPAWN_SOME
;
3144 Particles
*sparkles
= new Particles(size
);
3145 sparkles
->SetOwner(this);
3146 sparkles
->SetRegion(pos
.x
-width
/2, pos
.y
-80, width
, 80);
3149 style
= SP_TYPE_BITMAP
;
3150 sparkles
->SetBitmap(FragAnimID
);
3153 style
= SP_TYPE_POINT
;
3155 sparkles
->SetType(style
, path
, grow
);
3156 sparkles
->SetColor(color
);
3157 sparkles
->SetPhase(P_GROW
);
3158 printf("sparkle: %d %d\n", color
, type
);
3159 printf("Position: %d.%d\n", pos
.x
,pos
.y
);
3161 //AddParticle(sparkles, pos);
3163 for(iter
=particles
.begin(); (iter
!=particles
.end()) && ((*iter
)->GetHeight()<pos
.y
); iter
++) ;
3164 particles
.insert(iter
, sparkles
);
3167 //remove flags from actor if it has left the trigger area it had last entered
3168 void Map::ClearTrap(Actor
*actor
, ieDword InTrap
)
3170 InfoPoint
*trap
= TMap
->GetInfoPoint(InTrap
);
3172 actor
->SetInTrap(0);
3174 if(!trap
->outline
->PointIn(actor
->Pos
)) {
3175 actor
->SetInTrap(0);
3180 void Map::SetTrackString(ieStrRef strref
, int flg
, int difficulty
)
3182 trackString
= strref
;
3184 trackDiff
= (ieWord
) difficulty
;
3187 bool Map::DisplayTrackString(Actor
*target
)
3189 // this stat isn't saved
3190 // according to the HoW manual the chance of success is:
3191 // +5% for every three levels and +5% per point of wisdom
3192 int skill
= target
->GetStat(IE_TRACKING
);
3193 skill
+= (target
->GetStat(IE_LEVEL
)/3)*5 + target
->GetStat(IE_WIS
)*5;
3194 if (core
->Roll(1, 100, trackDiff
) > skill
) {
3195 core
->DisplayConstantStringName(STR_TRACKINGFAILED
, 0xd7d7be, target
);
3199 char * str
= core
->GetString( trackString
);
3200 core
->GetTokenDictionary()->SetAt( "CREATURE", str
);
3201 core
->DisplayConstantStringName(STR_TRACKING
, 0xd7d7be, target
);
3204 core
->DisplayStringName(trackString
, 0xd7d7be, target
, 0);
3208 // returns a lightness level in the range of [0-100]
3209 // since the lightmap is much smaller than the area, we need to interpolate
3210 unsigned int Map::GetLightLevel(Point
&Pos
)
3212 Color c
= LightMap
->GetPixel(Pos
.x
/16, Pos
.y
/12);
3213 // at night/dusk/dawn the lightmap color is adjusted by the color overlay. (Only get's darker.)
3214 const Color
*tint
= core
->GetGame()->GetGlobalTint();
3216 return ((c
.r
-tint
->r
)*114 + (c
.g
-tint
->g
)*587 + (c
.b
-tint
->b
)*299)/2550;
3218 return (c
.r
*114+c
.g
*587+c
.b
*299)/2550;
3221 ////////////////////AreaAnimation//////////////////
3224 AreaAnimation::AreaAnimation()
3232 AreaAnimation::~AreaAnimation()
3234 for(int i
=0;i
<animcount
;i
++) {
3236 delete (animation
[i
]);
3240 gamedata
->FreePalette(palette
, PaletteRef
);
3242 for(int i
=0;i
<animcount
;i
++) {
3249 Animation
*AreaAnimation::GetAnimationPiece(AnimationFactory
*af
, int animCycle
)
3251 Animation
*anim
= af
->GetCycle( ( unsigned char ) animCycle
);
3253 anim
= af
->GetCycle( 0 );
3255 printf("Cannot load animation: %s\n", BAM
);
3258 //this will make the animation stop when the game is stopped
3259 //a possible gemrb feature to have this flag settable in .are
3260 anim
->gameAnimation
= true;
3262 anim
->Flags
= Flags
;
3265 if (anim
->Flags
&A_ANI_MIRROR
) {
3266 anim
->MirrorAnimation();
3272 void AreaAnimation::InitAnimation()
3274 AnimationFactory
* af
= ( AnimationFactory
* )
3275 gamedata
->GetFactoryResource( BAM
, IE_BAM_CLASS_ID
);
3277 printf("Cannot load animation: %s\n", BAM
);
3281 //freeing up the previous animation
3282 for(int i
=0;i
<animcount
;i
++) {
3284 delete (animation
[i
]);
3289 if (Flags
& A_ANI_ALLCYCLES
) {
3290 animcount
= (int) af
->GetCycleCount();
3292 animation
= (Animation
**) malloc(animcount
* sizeof(Animation
*) );
3293 for(int j
=0;j
<animcount
;j
++) {
3294 animation
[j
]=GetAnimationPiece(af
, j
);
3298 animation
= (Animation
**) malloc( sizeof(Animation
*) );
3299 animation
[0]=GetAnimationPiece(af
, sequence
);
3301 if (Flags
& A_ANI_PALETTE
) {
3302 SetPalette(PaletteRef
);
3304 if (Flags
&A_ANI_BLEND
) {
3309 void AreaAnimation::SetPalette(ieResRef Pal
)
3311 Flags
|= A_ANI_PALETTE
;
3312 gamedata
->FreePalette(palette
, PaletteRef
);
3313 strnlwrcpy(PaletteRef
, Pal
, 8);
3314 palette
= gamedata
->GetPalette(PaletteRef
);
3315 if (Flags
&A_ANI_BLEND
) {
3316 //re-blending after palette change
3321 void AreaAnimation::BlendAnimation()
3323 //Warning! This function will modify a shared palette
3325 // CHECKME: what should we do here? Currently copying palette
3326 // from first frame of first animation
3328 if (animcount
== 0 || !animation
[0]) return;
3329 Sprite2D
* spr
= animation
[0]->GetFrame(0);
3331 palette
= spr
->GetPalette()->Copy();
3334 palette
->CreateShadedAlphaChannel();
3337 bool AreaAnimation::Schedule(ieDword gametime
)
3339 if (!(Flags
&A_ANI_ACTIVE
) ) {
3343 //check for schedule
3344 ieDword bit
= 1<<((gametime
/AI_UPDATE_TIME
)%7200/300);
3345 if (appearance
& bit
) {
3351 void AreaAnimation::Draw(Region
&screen
, Map
*area
)
3354 Video
* video
= core
->GetVideoDriver();
3356 //always draw the animation tinted because tint is also used for
3358 Color tint
= {255,255,255,255-(ieByte
) transparency
};
3359 if ((Flags
&A_ANI_NO_SHADOW
)) {
3360 tint
= area
->LightMap
->GetPixel( Pos
.x
/ 16, Pos
.y
/ 12);
3361 tint
.a
= 255-(ieByte
) transparency
;
3363 if (!(Flags
&A_ANI_NO_WALL
)) {
3365 covers
=(SpriteCover
**) calloc( animcount
, sizeof(SpriteCover
*) );
3370 Animation
*anim
= animation
[ac
];
3371 Sprite2D
*frame
= anim
->NextFrame();
3373 if(!covers
[ac
] || !covers
[ac
]->Covers(Pos
.x
, Pos
.y
, frame
->XPos
, frame
->YPos
, frame
->Width
, frame
->Height
)) {
3375 covers
[ac
] = area
->BuildSpriteCover(Pos
.x
, Pos
.y
, -anim
->animArea
.x
,
3376 -anim
->animArea
.y
, anim
->animArea
.w
, anim
->animArea
.h
, 0);
3379 video
->BlitGameSprite( frame
, Pos
.x
+ screen
.x
, Pos
.y
+ screen
.y
,
3380 BLIT_TINTED
, tint
, covers
?covers
[ac
]:0, palette
, &screen
);
3384 //change the tileset if needed and possible, return true if changed
3385 bool Map::ChangeMap(bool day_or_night
)
3387 //no need of change if the area is not extended night
3388 if (! (AreaType
&AT_EXTENDED_NIGHT
)) return false;
3389 //no need of change if the area already has the right tilemap
3390 if ((DayNight
== day_or_night
) && GetTileMap()) return false;
3392 MapMgr
* mM
= ( MapMgr
* ) core
->GetInterface( IE_ARE_CLASS_ID
);
3393 //no need to open and read the .are file again
3394 //using the ARE class for this because ChangeMap is similar to LoadMap
3395 //it loads the lightmap and the minimap too, besides swapping the tileset
3396 mM
->ChangeMap(this, day_or_night
);
3401 void Map::SeeSpellCast(Scriptable
*caster
, ieDword spell
)
3403 if (caster
->Type
!=ST_ACTOR
) {
3407 LastCasterSeen
= ((Actor
*) caster
)->GetID();
3408 LastSpellSeen
= spell
;
3410 size_t i
= actors
.size();
3412 Actor
* witness
= actors
[i
];
3413 if (CanSee(witness
, caster
, true, 0)) {
3414 witness
->LastSpellSeen
=LastSpellSeen
;
3415 witness
->LastCasterSeen
=LastCasterSeen
;