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 "Particles.h"
38 #include "PathFinder.h"
39 #include "Projectile.h"
40 #include "ScriptedAnimation.h"
45 #include "GameScript/GSUtils.h"
46 #include "GUI/GameControl.h"
51 #define YESNO(x) ( (x)?"Yes":"No")
53 // TODO: fix this hardcoded resource reference
54 static ieResRef PortalResRef
={"EF03TPR3"};
55 static unsigned int PortalTime
= 15;
56 static unsigned int MAX_CIRCLESIZE
= 8;
57 static int MaxVisibility
= 30;
58 static int VisibilityPerimeter
; //calculated from MaxVisibility
59 static int NormalCost
= 10;
60 static int AdditionalCost
= 4;
61 static unsigned char Passable
[16] = {
62 4, 1, 1, 1, 1, 1, 1, 1, 0, 1, 8, 0, 0, 0, 3, 1
64 static Point
**VisibilityMasks
=NULL
;
66 static bool PathFinderInited
= false;
67 static Variables Spawns
;
69 static ieWord globalActorCounter
;
71 void ReleaseSpawnGroup(void *poi
)
73 delete (SpawnGroup
*) poi
;
76 void Map::ReleaseMemory()
78 if (VisibilityMasks
) {
79 for (int i
=0;i
<MaxVisibility
;i
++) {
80 free(VisibilityMasks
[i
]);
82 free(VisibilityMasks
);
86 Spawns
.RemoveAll(ReleaseSpawnGroup
);
87 PathFinderInited
= false;
90 inline static AnimationObjectType
SelectObject(Actor
*actor
, int q
, AreaAnimation
*a
, ScriptedAnimation
*sca
, Particles
*spark
, Projectile
*pro
)
94 actorh
= actor
->Pos
.y
;
102 aah
= a
->Pos
.y
;//+a->height;
109 scah
= sca
->YPos
;//+sca->ZPos;
116 //no idea if this should be plus or minus (or here at all)
117 spah
= spark
->GetHeight();//+spark->pos.h;
124 proh
= pro
->GetHeight();
129 if (proh
<actorh
&& proh
<scah
&& proh
<aah
&& proh
<spah
) return AOT_PROJECTILE
;
131 if (spah
<actorh
&& spah
<scah
&& spah
<aah
) return AOT_SPARK
;
133 if (aah
<actorh
&& aah
<scah
) return AOT_AREA
;
135 if (scah
<actorh
) return AOT_SCRIPTED
;
140 //returns true if creature must be embedded in the area
141 //npcs in saved game shouldn't be embedded either
142 inline static bool MustSave(Actor
*actor
)
144 if (actor
->Persistent()) {
148 //check for familiars, summons?
152 //Preload spawn group entries (creature resrefs that reference groups of creatures)
153 void InitSpawnGroups()
158 AutoTable
tab("spawngrp");
160 Spawns
.RemoveAll(NULL
);
161 Spawns
.SetType( GEM_VARIABLES_POINTER
);
166 i
=tab
->GetColNamesCount();
168 int j
=tab
->GetRowCount();
170 const char *crename
= tab
->QueryField( j
,i
);
171 if (crename
[0] != '*') break;
174 SpawnGroup
*creatures
= new SpawnGroup(j
);
176 creatures
->Level
= (ieDword
) atoi( tab
->QueryField(0,i
) );
178 strnlwrcpy( creatures
->ResRefs
[j
-1], tab
->QueryField(j
,i
), 8 );
180 strnlwrcpy( GroupName
, tab
->GetColumnName( i
), 8 );
181 Spawns
.SetAt( GroupName
, (void*) creatures
);
186 //Preload the searchmap configuration
187 void InitPathFinder()
189 PathFinderInited
= true;
190 AutoTable
tm("pathfind");
194 for (int i
= 0; i
< 16; i
++) {
195 poi
= tm
->QueryField( 0, i
);
197 Passable
[i
] = atoi( poi
);
199 poi
= tm
->QueryField( 1, 0 );
201 NormalCost
= atoi( poi
);
202 poi
= tm
->QueryField( 1, 1 );
204 AdditionalCost
= atoi( poi
);
208 void AddLOS(int destx
, int desty
, int slot
)
210 for (int i
=0;i
<MaxVisibility
;i
++) {
211 int x
=(destx
*i
+MaxVisibility
/2)/MaxVisibility
*16;
212 int y
=(desty
*i
+MaxVisibility
/2)/MaxVisibility
*12;
217 VisibilityMasks
[i
][slot
].x
=(short) x
;
218 VisibilityMasks
[i
][slot
].y
=(short) y
;
224 LargeFog
= !core
->HasFeature(GF_SMALL_FOG
);
226 //circle perimeter size for MaxVisibility
227 int x
= MaxVisibility
;
229 int xc
= 1 - ( 2 * MaxVisibility
);
232 VisibilityPerimeter
= 0;
234 VisibilityPerimeter
+=8;
238 if (( ( 2 * re
) + xc
) > 0) {
246 VisibilityMasks
= (Point
**) malloc(MaxVisibility
* sizeof(Point
*) );
247 for (i
=0;i
<MaxVisibility
;i
++) {
248 VisibilityMasks
[i
] = (Point
*) malloc(VisibilityPerimeter
*sizeof(Point
) );
253 xc
= 1 - ( 2 * MaxVisibility
);
256 VisibilityPerimeter
= 0;
258 AddLOS (x
, y
, VisibilityPerimeter
++);
259 AddLOS (-x
, y
, VisibilityPerimeter
++);
260 AddLOS (-x
, -y
, VisibilityPerimeter
++);
261 AddLOS (x
, -y
, VisibilityPerimeter
++);
262 AddLOS (y
, x
, VisibilityPerimeter
++);
263 AddLOS (-y
, x
, VisibilityPerimeter
++);
264 AddLOS (-y
, -x
, VisibilityPerimeter
++);
265 AddLOS (y
, -x
, VisibilityPerimeter
++);
269 if (( ( 2 * re
) + xc
) > 0) {
278 : Scriptable( ST_AREA
)
289 queue
[PR_SCRIPT
] = NULL
;
290 queue
[PR_DISPLAY
] = NULL
;
292 //no one needs this queue
293 //queue[PR_IGNORE] = NULL;
294 Qcount
[PR_SCRIPT
] = 0;
295 Qcount
[PR_DISPLAY
] = 0;
296 //no one needs this queue
297 //Qcount[PR_IGNORE] = 0;
298 lastActorCount
[PR_SCRIPT
] = 0;
299 lastActorCount
[PR_DISPLAY
] = 0;
301 //lastActorCount[PR_IGNORE] = 0;
302 if (!PathFinderInited
) {
306 globalActorCounter
= 0;
308 ExploredBitmap
= NULL
;
309 VisibleBitmap
= NULL
;
311 localActorCounter
= 0;
312 MasterArea
= core
->GetGame()->MasterArea(scriptName
);
323 for (aniidx
= animations
.begin(); aniidx
!= animations
.end(); aniidx
++) {
327 for (i
= 0; i
< actors
.size(); i
++) {
328 Actor
* a
= actors
[i
];
329 //don't delete NPC/PC
330 if (a
&& !a
->Persistent() ) {
335 for (i
= 0; i
< entrances
.size(); i
++) {
338 for (i
= 0; i
< spawns
.size(); i
++) {
344 core
->GetVideoDriver()->FreeSprite( SmallMap
);
345 for (i
= 0; i
< QUEUE_COUNT
; i
++) {
352 for (pri
= projectiles
.begin(); pri
!= projectiles
.end(); pri
++) {
358 for (sci
= vvcCells
.begin(); sci
!= vvcCells
.end(); sci
++) {
364 for (spi
= particles
.begin(); spi
!= particles
.end(); spi
++) {
368 for (i
= 0; i
< ambients
.size(); i
++) {
371 for (i
= 0; i
< mapnotes
.size(); i
++) {
376 free( ExploredBitmap
);
377 free( VisibleBitmap
);
379 for(i
=0;i
<WallCount
;i
++) {
387 void Map::ChangeTileMap(Image
* lm
, Sprite2D
* sm
)
390 core
->GetVideoDriver()->FreeSprite(SmallMap
);
398 void Map::AddTileMap(TileMap
* tm
, Image
* lm
, Bitmap
* sr
, Sprite2D
* sm
, Bitmap
* hm
)
400 // CHECKME: leaks? Should the old TMap, LightMap, etc... be freed?
406 Width
= (unsigned int) (TMap
->XCellCount
* 4);
407 Height
= (unsigned int) (( TMap
->YCellCount
* 64 ) / 12);
409 MapSet
= (unsigned short *) malloc(sizeof(unsigned short) * Width
* Height
);
410 //converting searchmap to internal format
411 int y
=SearchMap
->GetHeight();
413 int x
=SearchMap
->GetWidth();
415 SearchMap
->SetAt(x
,y
,Passable
[SearchMap
->GetAt(x
,y
)&PATH_MAP_AREAMASK
]);
420 void Map::MoveToNewArea(const char *area
, const char *entrance
, unsigned int direction
, int EveryOne
, Actor
*actor
)
424 //change loader MOS image here
425 //check worldmap entry, if that doesn't contain anything,
428 if (EveryOne
==CT_WHOLE
) {
429 core
->GetGameControl()->AutoSave();
431 Game
* game
= core
->GetGame();
432 Map
* map
= game
->GetMap(area
, false);
434 printMessage("Map", " ", LIGHT_RED
);
435 printf("Invalid map: %s\n",area
);
439 Entrance
* ent
= map
->GetEntrance( entrance
);
442 // no entrance found, try using direction flags
444 face
= -1; // should this be handled per-case?
446 // ok, so the original engine tries these in a different order
447 // (north first, then south) but it doesn't seem to matter
448 if (direction
& 0x1) { // north
449 X
= map
->TMap
->XCellCount
* 32;
451 } else if (direction
& 0x2) { // east
452 X
= map
->TMap
->XCellCount
* 64;
453 Y
= map
->TMap
->YCellCount
* 32;
454 } else if (direction
& 0x4) { // south
455 X
= map
->TMap
->XCellCount
* 32;
456 Y
= map
->TMap
->YCellCount
* 64;
457 } else if (direction
& 0x8) { // west
459 Y
= map
->TMap
->YCellCount
* 32;
461 // crashes in original engine
462 printMessage("Map", " ", YELLOW
);
463 printf( "WARNING!!! EntryPoint '%s' does not exist and direction %d is invalid\n", entrance
, direction
);
464 X
= map
->TMap
->XCellCount
* 64;
465 Y
= map
->TMap
->YCellCount
* 64;
472 //LeaveArea is the same in ALL engine versions
473 sprintf(command
, "LeaveArea(\"%s\",[%d.%d],%d)", area
, X
, Y
, face
);
475 if (EveryOne
&CT_GO_CLOSER
) {
476 int i
=game
->GetPartySize(false);
478 Actor
*pc
= game
->GetPC(i
,false);
479 if (pc
->GetCurrentArea()==this) {
482 pc
->AddAction( GenerateAction( command
) );
483 pc
->ProcessActions(true);
488 if (EveryOne
&CT_SELECTED
) {
489 int i
=game
->GetPartySize(false);
491 Actor
*pc
= game
->GetPC(i
,false);
493 if (!pc
->IsSelected()) {
496 if (pc
->GetCurrentArea()==this) {
499 pc
->AddAction( GenerateAction( command
) );
500 pc
->ProcessActions(true);
507 actor
->ClearActions();
508 actor
->AddAction( GenerateAction( command
) );
509 actor
->ProcessActions(true);
512 void Map::UseExit(Actor
*actor
, InfoPoint
*ip
)
514 Game
*game
=core
->GetGame();
516 int EveryOne
= ip
->CheckTravel(actor
);
519 displaymsg
->DisplayConstantString(STR_WHOLEPARTY
,0xffffff); //white
520 if (game
->EveryoneStopped()) {
521 ip
->Flags
&=~TRAP_RESET
; //exit triggered
524 //no ingame message for these events
525 case CT_CANTMOVE
: case CT_SELECTED
:
527 case CT_ACTIVE
: case CT_WHOLE
: case CT_MOVE_SELECTED
:
531 actor
->UseExit(false);
532 if (ip
->Destination
[0] != 0) {
533 // 0 here is direction, can infopoints specify that or is an entrance always provided?
534 MoveToNewArea(ip
->Destination
, ip
->EntranceName
, 0, EveryOne
, actor
);
537 if (ip
->Scripts
[0]) {
538 ip
->LastTriggerObject
= ip
->LastTrigger
= ip
->LastEntered
= actor
->GetID();
539 ip
->ExecuteScript( 1 );
540 ip
->ProcessActions(true);
544 //Draw two overlapped animations to achieve the original effect
545 //PlayOnce makes sure that if we stop drawing them, they will go away
546 void Map::DrawPortal(InfoPoint
*ip
, int enable
)
548 ieDword gotportal
= HasVVCCell(PortalResRef
, ip
->Pos
);
551 if (gotportal
>PortalTime
) return;
552 ScriptedAnimation
*sca
= gamedata
->GetScriptedAnimation(PortalResRef
, false);
565 void Map::UpdateScripts()
567 bool has_pcs
= false;
568 size_t i
=actors
.size();
570 if (actors
[i
]->InParty
) {
576 // if masterarea, then we allow 'any' actors
577 // if not masterarea, we allow only players
578 // if (!GetActorCount(MasterArea) ) {
579 // fuzzie changed this because the previous code was wrong
580 // (GetActorCount(false) returns only non-PCs) - it is not
581 // well-tested so feel free to change if there are problems
582 // (for example, the CanFree seems like it would be needed to
583 // check for any running scripts, such as following, but it seems
584 // to work ok anyway in my testing - if you change it you probably
585 // also want to change the actor updating code below so it doesn't
586 // add new actions while we are trying to get rid of the area!)
587 if (!has_pcs
&& !(MasterArea
&& actors
.size()) /*&& !CanFree()*/) {
591 // fuzzie added this check because some area scripts (eg, AR1600 when
592 // escaping Brynnlaw) were executing after they were meant to be done,
593 // and this seems the nicest way of handling that for now - it's quite
594 // possibly wrong (so if you have problems, revert this and find
601 //Execute Pending Actions
602 //if it is only here, then the drawing will fail
603 ProcessActions(false);
605 // If scripts frozen, return.
606 // This fixes starting a new IWD game. The above ProcessActions pauses the
607 // game for a textscreen, but one of the actor->ProcessActions calls
608 // below starts a cutscene, hiding the mouse. - wjp, 20060805
609 if (core
->GetGameControl()->GetDialogueFlags() & DF_FREEZE_SCRIPTS
) return;
611 //Run actor scripts (only for 0 priority)
612 int q
=Qcount
[PR_SCRIPT
];
614 Game
*game
= core
->GetGame();
615 Actor
*timestop_owner
= game
->timestop_owner
;
616 bool timestop
= game
->timestop_end
>game
->GameTime
;
618 // this is silly, the speed should be pre-calculated somewhere
619 //int *actor_speeds = (int *)calloc(Qcount[PR_SCRIPT], sizeof(int));
621 //bool *no_more_steps_for_actor = (bool *)calloc(Qcount[PR_SCRIPT], sizeof(bool));
624 Actor
* actor
= queue
[PR_SCRIPT
][q
];
625 //actor just moved away, don't run its script from this side
626 if (actor
->GetCurrentArea()!=this) {
627 actor
->no_more_steps
= true;
630 if (timestop
&& actor
!=timestop_owner
&& actor
->Modified
[IE_DISABLETIMESTOP
] ) {
631 actor
->no_more_steps
= true;
635 //Avenger moved this here from ApplyAllEffects (this one modifies the effect queue)
636 //.. but then fuzzie moved this here from UpdateActorState, because otherwise
637 //immobile actors (see check below) never become mobile again!
638 actor
->fxqueue
.Cleanup();
640 //if the actor is immobile, don't run the scripts
641 if (!game
->StateOverrideFlag
&& !game
->StateOverrideTime
) {
642 if (actor
->Immobile()) {
643 actor
->no_more_steps
= true;
647 actor
->no_more_steps
= false;
650 * we run scripts all at once because one of the actions in ProcessActions
651 * might remove us from a cutscene and then bad things can happen when
652 * scripts are queued unexpectedly (such as an ogre in a cutscene -> dialog
653 * -> cutscene transition in the first bg1 cutscene exploiting the race
654 * condition to murder player1) - it is entirely possible that we should be
655 * doing this differently (for example by storing the cutscene state at the
656 * start of this function, or by changing the cutscene state at a later
657 * point, etc), but i did it this way for now because it seems least painful
658 * and we should probably be staggering the script executions anyway
660 actor
->ExecuteScript( MAX_SCRIPTS
);
666 Actor
* actor
= queue
[PR_SCRIPT
][q
];
667 if (actor
->no_more_steps
) continue;
669 actor
->ProcessActions(false);
671 actor
->UpdateActorState(game
->GameTime
);
673 actor
->inventory
.CalculateWeight();
674 actor
->SetBase( IE_ENCUMBRANCE
, actor
->inventory
.GetWeight() );
676 //TODO:calculate actor speed!
677 int speed
= (int) actor
->GetStat(IE_MOVEMENTRATE
);
681 if (core
->GetResDataINI()) {
682 ieDword animid
= actor
->BaseStats
[IE_ANIMATION_ID
];
683 if (core
->HasFeature(GF_ONE_BYTE_ANIMID
)) {
684 animid
= animid
& 0xff;
686 if (animid
< (ieDword
)CharAnimations::GetAvatarsCount()) {
687 AvatarStruct
*avatar
= CharAnimations::GetAvatarStruct(animid
);
688 if (avatar
->RunScale
&& (actor
->GetInternalFlag() & IF_RUNNING
)) {
689 speed
= avatar
->RunScale
;
690 } else if (avatar
->WalkScale
) {
691 speed
= avatar
->WalkScale
;
693 //printf("no walkscale for anim %d!\n", actor->BaseStats[IE_ANIMATION_ID]);
697 actor
->speed
= speed
;
700 // We need to step through the list of actors until all of them are done
702 bool more_steps
= true;
703 ieDword time
= game
->Ticks
; // make sure everything moves at the same time
709 Actor
* actor
= queue
[PR_SCRIPT
][q
];
710 if (actor
->no_more_steps
) continue;
712 // try to exclude actors which only just died
713 // (shouldn't we not be stepping actors which don't have a path anyway?)
714 // following fails on Immobile creatures, don't think it's a problem, but replace with next line if it is
715 if (!actor
->ValidTarget(GA_NO_DEAD
)) continue;
716 //if (actor->GetStat(IE_STATE_ID)&STATE_DEAD || actor->GetInternalFlag() & IF_JUSTDIED) continue;
718 actor
->no_more_steps
= DoStepForActor(actor
, actor
->speed
, time
);
719 if (!actor
->no_more_steps
) more_steps
= true;
723 //Check if we need to start some door scripts
726 Door
* door
= TMap
->GetDoor( doorCount
++ );
729 if (door
->Scripts
[0])
730 door
->ExecuteScript( 1 );
731 //Execute Pending Actions
732 door
->ProcessActions(false);
735 //Check if we need to start some container scripts
736 int containerCount
= 0;
738 Container
* container
= TMap
->GetContainer( containerCount
++ );
741 if (container
->Scripts
[0])
742 container
->ExecuteScript( 1 );
743 //Execute Pending Actions
744 container
->ProcessActions(false);
747 //Check if we need to start some trap scripts
750 //For each InfoPoint in the map
751 InfoPoint
* ip
= TMap
->GetInfoPoint( ipCount
++ );
754 //If this InfoPoint has no script and it is not a Travel Trigger, skip it
755 bool wasActive
= (ip
->Scripts
[0] || ( ip
->Type
== ST_TRAVEL
));
756 // InfoPoints of all types don't run scripts if TRAP_DEACTIVATED is set
757 // (eg, TriggerActivation changes this, see lightning room from SoA)
759 wasActive
= !(ip
->Flags
&TRAP_DEACTIVATED
);
761 //If this InfoPoint is a Switch Trigger
762 if (ip
->Type
== ST_TRIGGER
) {
763 //Check if this InfoPoint was activated
764 if (ip
->LastTrigger
) {
766 //Run the InfoPoint script
767 ip
->ExecuteScript( 1 );
770 //Execute Pending Actions
771 ip
->ProcessActions(false);
775 if (ip
->IsPortal()) {
776 DrawPortal(ip
, ip
->Trapped
&PORTAL_TRAVEL
);
782 Actor
* actor
= queue
[PR_SCRIPT
][q
];
783 if (ip
->Type
== ST_PROXIMITY
) {
784 if(ip
->Entered(actor
)) {
785 //if trap triggered, then mark actor
786 actor
->SetInTrap(ipCount
);
790 //don't move if doing something else
791 // added CurrentAction as part of blocking action fixes
792 if (actor
->CannotPassEntrance() ) {
795 //this is needed, otherwise the travel
796 //trigger would be activated anytime
797 //Well, i don't know why is it here, but lets try this
798 if (ip
->Entered(actor
)) {
806 ip
->ExecuteScript( 1 );
808 //Execute Pending Actions
809 ip
->ProcessActions(false);
813 bool Map::DoStepForActor(Actor
*actor
, int speed
, ieDword time
) {
814 bool no_more_steps
= true;
816 if (actor
->BlocksSearchMap()) {
817 ClearSearchMapFor(actor
);
819 PathNode
* step
= actor
->GetNextStep();
820 if (step
&& step
->Next
) {
821 //we should actually wait for a short time and check then
822 if (GetBlocked(step
->Next
->x
*16+8,step
->Next
->y
*12+6,actor
->size
)) {
827 if (!(actor
->GetBase(IE_STATE_ID
)&STATE_CANTMOVE
) ) {
828 if (!actor
->Immobile()) {
829 no_more_steps
= actor
->DoStep( speed
, time
);
830 if (actor
->BlocksSearchMap()) {
831 BlockSearchMap( actor
->Pos
, actor
->size
, actor
->InParty
?PATH_MAP_PC
:PATH_MAP_NPC
);
836 return no_more_steps
;
839 void Map::ClearSearchMapFor( Movable
*actor
) {
840 Actor
** nearActors
= GetAllActorsInRadius(actor
->Pos
, GA_NO_DEAD
, MAX_CIRCLE_SIZE
*2*16);
841 BlockSearchMap( actor
->Pos
, actor
->size
, PATH_MAP_FREE
);
843 // Restore the searchmap areas of any nearby actors that could
844 // have been cleared by this BlockSearchMap(..., 0).
845 // (Necessary since blocked areas of actors may overlap.)
847 while(nearActors
[i
]!=NULL
) {
848 if(nearActors
[i
]!=actor
&& nearActors
[i
]->BlocksSearchMap())
849 BlockSearchMap( nearActors
[i
]->Pos
, nearActors
[i
]->size
, nearActors
[i
]->InParty
?PATH_MAP_PC
:PATH_MAP_NPC
);
855 void Map::DrawHighlightables( Region screen
)
857 Region vp
= core
->GetVideoDriver()->GetViewport();
861 while ( (c
= TMap
->GetContainer(i
++))!=NULL
) {
862 Color tint
= LightMap
->GetPixel( c
->Pos
.x
/ 16, c
->Pos
.y
/ 12);
866 if (c
->Type
==IE_CONTAINER_PILE
) {
867 Color tint
= LightMap
->GetPixel( c
->Pos
.x
/ 16, c
->Pos
.y
/ 12);
869 c
->DrawPile(true, screen
, tint
);
873 } else if (c
->Type
==IE_CONTAINER_PILE
) {
874 if (c
->outline
->BBox
.InsideRegion( vp
)) {
875 c
->DrawPile(false, screen
, tint
);
882 while ( (d
= TMap
->GetDoor(i
++))!=NULL
) {
883 if (d
->Highlight
) d
->DrawOutline();
888 while ( (p
= TMap
->GetInfoPoint(i
++))!=NULL
) {
889 if (p
->Highlight
) p
->DrawOutline();
893 Actor
*Map::GetNextActor(int &q
, int &index
)
899 return queue
[q
][index
];
904 return queue
[q
][index
];
913 AreaAnimation
*Map::GetNextAreaAnimation(aniIterator
&iter
, ieDword gametime
)
916 if (iter
==animations
.end()) {
919 AreaAnimation
*a
= *(iter
++);
920 if (!a
->Schedule(gametime
) ) {
923 if (!IsVisible( a
->Pos
, !(a
->Flags
& A_ANI_NOT_IN_FOG
)) ) {
929 Particles
*Map::GetNextSpark(spaIterator
&iter
)
931 if (iter
==particles
.end()) {
937 //doesn't increase iterator, because we might need to erase it from the list
938 Projectile
*Map::GetNextProjectile(proIterator
&iter
)
940 if (iter
==projectiles
.end()) {
946 Projectile
*Map::GetNextTrap(proIterator
&iter
)
951 pro
=GetNextProjectile(iter
);
953 //logic to determine dormant traps
954 //if (pro && pro->IsTrap()) break;
959 size_t Map::GetProjectileCount(proIterator
&iter
)
961 iter
= projectiles
.begin();
962 return projectiles
.size();
965 ieDword
Map::GetTrapCount(proIterator
&iter
)
968 iter
=projectiles
.begin();
969 while(GetNextTrap(iter
)) {
973 iter
= projectiles
.begin();
978 //doesn't increase iterator, because we might need to erase it from the list
979 ScriptedAnimation
*Map::GetNextScriptedAnimation(scaIterator
&iter
)
981 if (iter
==vvcCells
.end()) {
987 static ieDword oldgametime
= 0;
989 //Draw the game area (including overlays, actors, animations, weather)
990 void Map::DrawMap(Region screen
)
995 Game
*game
= core
->GetGame();
996 ieDword gametime
= game
->GameTime
;
998 //area specific spawn.ini files (a PST feature)
1000 INISpawn
->CheckSpawn();
1005 //zero when the weather particles are all gone
1006 rain
= game
->weather
->GetPhase()-P_EMPTY
;
1010 TMap
->DrawOverlays( screen
, rain
);
1012 //Blit the Background Map Animations (before actors)
1013 Video
* video
= core
->GetVideoDriver();
1016 DrawHighlightables( screen
);
1018 Region vp
= video
->GetViewport();
1019 //if it is only here, then the scripting will fail?
1022 //drawing queues 1 and 0
1023 //starting with lower priority
1024 //so displayed, but inactive actors (dead) will be drawn over
1026 int index
= Qcount
[q
];
1027 Actor
* actor
= GetNextActor(q
, index
);
1028 aniIterator aniidx
= animations
.begin();
1029 scaIterator scaidx
= vvcCells
.begin();
1030 proIterator proidx
= projectiles
.begin();
1031 spaIterator spaidx
= particles
.begin();
1033 AreaAnimation
*a
= GetNextAreaAnimation(aniidx
, gametime
);
1034 ScriptedAnimation
*sca
= GetNextScriptedAnimation(scaidx
);
1035 Projectile
*pro
= GetNextProjectile(proidx
);
1036 Particles
*spark
= GetNextSpark(spaidx
);
1038 while (actor
|| a
|| sca
|| spark
|| pro
) {
1039 switch(SelectObject(actor
,q
,a
,sca
,spark
,pro
)) {
1041 actor
->Draw( screen
);
1042 actor
= GetNextActor(q
, index
);
1046 a
->Draw( screen
, this );
1047 a
= GetNextAreaAnimation(aniidx
,gametime
);
1053 Color tint
= LightMap
->GetPixel( sca
->XPos
/ 16, sca
->YPos
/ 12);
1055 bool endReached
= sca
->Draw(screen
, Pos
, tint
, this, 0, -1);
1058 scaidx
=vvcCells
.erase(scaidx
);
1063 sca
= GetNextScriptedAnimation(scaidx
);
1065 case AOT_PROJECTILE
:
1068 if (gametime
>oldgametime
) {
1069 drawn
= pro
->Update();
1074 pro
->Draw( screen
);
1078 proidx
= projectiles
.erase(proidx
);
1081 pro
= GetNextProjectile(proidx
);
1086 if (gametime
>oldgametime
) {
1087 drawn
= spark
->Update();
1092 spark
->Draw( screen
);
1096 spaidx
=particles
.erase(spaidx
);
1099 spark
= GetNextSpark(spaidx
);
1106 if ((core
->FogOfWar
&FOG_DRAWSEARCHMAP
) && SearchMap
) {
1107 DrawSearchMap(screen
);
1109 if ((core
->FogOfWar
&FOG_DRAWFOG
) && TMap
) {
1110 TMap
->DrawFogOfWar( ExploredBitmap
, VisibleBitmap
, screen
);
1116 //For each InfoPoint in the map
1117 InfoPoint
* ip
= TMap
->GetInfoPoint( ipCount
++ );
1120 ip
->DrawOverheadText(screen
);
1125 //For each Container in the map
1126 Container
* cn
= TMap
->GetContainer( cnCount
++ );
1129 cn
->DrawOverheadText(screen
);
1134 //For each Door in the map
1135 Door
* dr
= TMap
->GetDoor( drCount
++ );
1138 dr
->DrawOverheadText(screen
);
1141 size_t i
= actors
.size();
1143 //For each Actor present
1144 //This must go AFTER the fog!
1145 //(maybe we should be using the queue?)
1146 Actor
* actor
= actors
[i
];
1147 actor
->DrawOverheadText(screen
);
1150 oldgametime
=gametime
;
1153 void Map::DrawSearchMap(const Region
&screen
)
1155 Color inaccessible
= { 128, 128, 128, 128 };
1156 Color impassible
= { 128, 64, 64, 128 }; // red-ish
1157 Color sidewall
= { 64, 64, 128, 128 }; // blue-ish
1158 Video
*vid
=core
->GetVideoDriver();
1159 Region rgn
=vid
->GetViewport();
1164 int w
= screen
.w
/16+2;
1165 int h
= screen
.h
/12+2;
1167 for(int x
=0;x
<w
;x
++) {
1168 for(int y
=0;y
<h
;y
++) {
1169 unsigned char blockvalue
= GetBlocked(x
+rgn
.x
/16, y
+rgn
.y
/12);
1170 if (!(blockvalue
& PATH_MAP_PASSABLE
)) {
1171 block
.x
=screen
.x
+x
*16-(rgn
.x
% 16);
1172 block
.y
=screen
.y
+y
*12-(rgn
.y
% 12);
1173 if (blockvalue
== PATH_MAP_IMPASSABLE
) { // 0
1174 vid
->DrawRect(block
,impassible
);
1175 } else if (blockvalue
& PATH_MAP_SIDEWALL
) {
1176 vid
->DrawRect(block
,sidewall
);
1178 vid
->DrawRect(block
,inaccessible
);
1185 //adding animation in order, based on its height parameter
1186 void Map::AddAnimation(AreaAnimation
* anim
)
1188 //this hack is to make sure animations flagged with background
1189 //are always drawn first (-9999 seems sufficiently small)
1190 if (anim
->Flags
&A_ANI_BACKGROUND
) {
1195 for(iter
=animations
.begin(); (iter
!=animations
.end()) && ((*iter
)->height
<anim
->height
); iter
++) ;
1196 animations
.insert(iter
, anim
);
1198 Animation *a = anim->animation[0];
1199 anim->SetSpriteCover(BuildSpriteCover(anim->Pos.x, anim->Pos.y,-a->animArea.x,
1200 -a->animArea.y, a->animArea.w, a->animArea.h,0
1205 //reapplying all of the effects on the actors of this map
1206 //this might be unnecessary later
1207 void Map::UpdateEffects()
1209 size_t i
= actors
.size();
1211 actors
[i
]->RefreshEffects(NULL
);
1215 void Map::Shout(Actor
* actor
, int shoutID
, unsigned int radius
)
1217 size_t i
=actors
.size();
1219 Actor
*listener
= actors
[i
];
1222 if (Distance(actor
->Pos
, listener
->Pos
)>radius
) {
1227 listener
->LastHeard
= actor
->GetID();
1228 listener
->LastShout
= shoutID
;
1230 listener
->LastHelp
= actor
->GetID();
1235 bool Map::AnyEnemyNearPoint(const Point
&p
)
1237 ieDword gametime
= core
->GetGame()->GameTime
;
1238 size_t i
= actors
.size();
1240 Actor
*actor
= actors
[i
];
1242 if (actor
->Schedule(gametime
, true) ) {
1245 if (Distance(actor
->Pos
, p
) > SPAWN_RANGE
) {
1248 if (actor
->GetStat(IE_EA
)<EA_EVILCUTOFF
) {
1256 void Map::ActorSpottedByPlayer(Actor
*actor
)
1258 unsigned int animid
;
1260 if(core
->HasFeature(GF_HAS_BEASTS_INI
)) {
1261 animid
=actor
->BaseStats
[IE_ANIMATION_ID
];
1262 if(core
->HasFeature(GF_ONE_BYTE_ANIMID
)) {
1265 if (animid
< (ieDword
)CharAnimations::GetAvatarsCount()) {
1266 AvatarStruct
*avatar
= CharAnimations::GetAvatarStruct(animid
);
1267 core
->GetGame()->SetBeastKnown(avatar
->Bestiary
);
1271 if (!(actor
->GetInternalFlag()&IF_STOPATTACK
)) {
1272 if (actor
->Modified
[IE_EA
]>=EA_EVILCUTOFF
) {
1273 core
->Autopause(AP_ENEMY
);
1278 void Map::AddActor(Actor
* actor
)
1280 //setting the current area for the actor as this one
1281 strnlwrcpy(actor
->Area
, scriptName
, 8);
1282 //if actor->globalID was already set, don't change it
1283 actor
->SetMap(this, ++localActorCounter
,
1284 actor
->globalID
?actor
->globalID
:++globalActorCounter
);
1285 actors
.push_back( actor
);
1286 //if a visible aggressive actor was put on the map, it is an autopause reason
1287 //guess game is always loaded? if not, then we'll crash
1288 ieDword gametime
= core
->GetGame()->GameTime
;
1290 if (IsVisible(actor
->Pos
, false) && actor
->Schedule(gametime
, true) ) {
1291 ActorSpottedByPlayer(actor
);
1293 if (actor
->InParty
&& core
->HasFeature(GF_AREA_VISITED_VAR
)) {
1295 snprintf(key
, sizeof(key
),"%s_visited", scriptName
);
1296 core
->GetGame()->locals
->SetAt(key
, 1);
1301 bool Map::AnyPCSeesEnemy()
1303 ieDword gametime
= core
->GetGame()->GameTime
;
1304 size_t i
= actors
.size();
1306 Actor
* actor
= actors
[i
];
1307 if (actor
->Modified
[IE_EA
]>=EA_EVILCUTOFF
) {
1308 if (IsVisible(actor
->Pos
, false) && actor
->Schedule(gametime
, true) ) {
1316 //Make an actor gone for (almost) good
1317 //If the actor was in the party, it will be moved to the npc storage
1318 //If the actor is in the NPC storage, its area and some other fields
1319 //that are needed for proper reentry will be zeroed out
1320 //If the actor isn't in the NPC storage, it is destructed
1321 void Map::DeleteActor(int i
)
1323 Actor
*actor
= actors
[i
];
1325 Game
*game
= core
->GetGame();
1326 //this makes sure that a PC will be demoted to NPC
1327 game
->LeaveParty( actor
);
1328 //this frees up the spot under the feet circle
1329 ClearSearchMapFor( actor
);
1330 //remove the area reference from the actor
1331 actor
->SetMap(NULL
,0,0);
1332 //don't destroy the object in case it is a persistent object
1333 //otherwise there is a dead reference causing a crash on save
1334 if (!game
->InStore(actor
) ) {
1338 //remove the actor from the area's actor list
1339 actors
.erase( actors
.begin()+i
);
1342 Actor
* Map::GetActorByGlobalID(ieDword objectID
)
1347 //truncation is intentional
1348 ieWord globalID
= (ieWord
) objectID
;
1349 size_t i
= actors
.size();
1351 Actor
* actor
= actors
[i
];
1353 if (actor
->globalID
==globalID
) {
1361 GA_SELECT 16 - unselectable actors don't play
1362 GA_NO_DEAD 32 - dead actors don't play
1363 GA_POINT 64 - not actor specific
1364 GA_NO_HIDDEN 128 - hidden actors don't play
1366 Actor
* Map::GetActor(const Point
&p
, int flags
)
1368 ieDword gametime
= core
->GetGame()->GameTime
;
1369 size_t i
= actors
.size();
1371 Actor
* actor
= actors
[i
];
1373 if (!actor
->IsOver( p
))
1375 if (!actor
->ValidTarget(flags
) ) {
1378 if (!actor
->Schedule(gametime
, true) ) {
1386 Actor
* Map::GetActorInRadius(const Point
&p
, int flags
, unsigned int radius
)
1388 ieDword gametime
= core
->GetGame()->GameTime
;
1389 size_t i
= actors
.size();
1391 Actor
* actor
= actors
[i
];
1393 if (PersonalDistance( p
, actor
) > radius
)
1395 if (!actor
->ValidTarget(flags
) ) {
1398 if (!actor
->Schedule(gametime
, true) ) {
1406 Actor
**Map::GetAllActorsInRadius(const Point
&p
, int flags
, unsigned int radius
)
1411 ieDword gametime
= core
->GetGame()->GameTime
;
1414 Actor
* actor
= actors
[i
];
1416 if (PersonalDistance( p
, actor
) > radius
)
1418 if (!actor
->ValidTarget(flags
) ) {
1421 if (!actor
->Schedule(gametime
, true) ) {
1427 Actor
**ret
= (Actor
**) malloc( sizeof(Actor
*) * count
);
1431 Actor
* actor
= actors
[i
];
1433 if (PersonalDistance( p
, actor
) > radius
)
1435 if (!actor
->ValidTarget(flags
) ) {
1438 if (!actor
->Schedule(gametime
, true) ) {
1449 Actor
* Map::GetActor(const char* Name
, int flags
)
1451 size_t i
= actors
.size();
1453 Actor
* actor
= actors
[i
];
1454 if (strnicmp( actor
->GetScriptName(), Name
, 32 ) == 0) {
1455 if (!actor
->ValidTarget(flags
) ) {
1464 int Map::GetActorCount(bool any
) const
1467 return (int) actors
.size();
1470 size_t i
=actors
.size();
1472 if (MustSave(actors
[i
])) {
1479 void Map::JumpActors(bool jump
)
1481 size_t i
= actors
.size();
1483 Actor
* actor
= actors
[i
];
1484 if (actor
->Modified
[IE_DONOTJUMP
]&DNJ_JUMP
) {
1486 actor
->FixPosition();
1488 actor
->SetBase(IE_DONOTJUMP
,0);
1493 //before writing the area out, perform some cleanups
1494 void Map::PurgeArea(bool items
)
1496 InternalFlags
|= IF_JUSTDIED
; //area marked for swapping out
1498 //1. remove dead actors without 'keep corpse' flag
1499 int i
=(int) actors
.size();
1501 Actor
*ac
= actors
[i
];
1503 if (ac
->Modified
[IE_STATE_ID
]&STATE_NOSAVE
) {
1504 if (ac
->Modified
[IE_MC_FLAGS
] & MC_KEEP_CORPSE
) {
1507 //don't delete persistent actors
1508 if (ac
->Persistent()) {
1511 //even if you delete it, be very careful!
1515 //2. remove any non critical items
1517 i
=(int) TMap
->GetContainerCount();
1519 Container
*c
= TMap
->GetContainer(i
);
1520 unsigned int j
=c
->inventory
.GetSlotCount();
1522 CREItem
*itemslot
= c
->inventory
.GetSlotItem(j
);
1523 if (itemslot
->Flags
&IE_INV_ITEM_CRITICAL
) {
1527 TMap
->CleanupContainer(c
);
1532 Actor
* Map::GetActor(int index
, bool any
)
1535 return actors
[index
];
1538 while (i
<actors
.size() ) {
1539 Actor
*ac
= actors
[i
++];
1540 if (MustSave(ac
) ) {
1549 Actor
* Map::GetActorByDialog(const char *resref
)
1551 size_t i
= actors
.size();
1553 Actor
* actor
= actors
[i
];
1554 //if a busy or hostile actor shouldn't be found
1555 //set this to GD_CHECK
1556 if (strnicmp( actor
->GetDialog(GD_NORMAL
), resref
, 8 ) == 0) {
1563 //this function finds an actor by its original resref (not correct yet)
1564 Actor
* Map::GetActorByResource(const char *resref
)
1566 size_t i
= actors
.size();
1568 Actor
* actor
= actors
[i
];
1569 if (strnicmp( actor
->GetScriptName(), resref
, 8 ) == 0) { //temporarily!
1576 Actor
* Map::GetActorByScriptName(const char *name
)
1578 size_t i
= actors
.size();
1580 Actor
* actor
= actors
[i
];
1581 if (strnicmp( actor
->GetScriptName(), name
, 8 ) == 0) {
1588 int Map::GetActorInRect(Actor
**& actorlist
, Region
& rgn
, bool onlyparty
)
1590 actorlist
= ( Actor
* * ) malloc( actors
.size() * sizeof( Actor
* ) );
1592 size_t i
= actors
.size();
1594 Actor
* actor
= actors
[i
];
1595 //use this function only for party?
1596 if (onlyparty
&& actor
->GetStat(IE_EA
)>EA_CHARMED
) {
1599 // this is called by non-selection code..
1600 if (onlyparty
&& !actor
->ValidTarget(GA_SELECT
))
1602 if (!actor
->ValidTarget(GA_NO_DEAD
) )
1604 if ((actor
->Pos
.x
<rgn
.x
) || (actor
->Pos
.y
<rgn
.y
))
1606 if ((actor
->Pos
.x
>rgn
.x
+rgn
.w
) || (actor
->Pos
.y
>rgn
.y
+rgn
.h
) )
1608 actorlist
[count
++] = actor
;
1610 actorlist
= ( Actor
* * ) realloc( actorlist
, count
* sizeof( Actor
* ) );
1614 void Map::PlayAreaSong(int SongType
, bool restart
, bool hard
)
1616 //Ok, we use a non constant pointer here, so it is easy to disable
1617 //a faulty music list on the fly. I don't want to add a method just for that
1618 //crap when we already have that pointer at hand!
1619 char* poi
= core
->GetMusicPlaylist( SongHeader
.SongList
[SongType
] );
1622 //check if restart needed (either forced or the current song is different)
1623 if (!restart
&& core
->GetMusicMgr()->CurrentPlayList(poi
)) return;
1624 int ret
= core
->GetMusicMgr()->SwitchPlayList( poi
, hard
);
1625 //Here we disable the faulty musiclist entry
1627 //Apparently, the playlist manager prefers a *
1631 if (SongType
== SONG_BATTLE
) {
1632 core
->GetGame()->CombatCounter
= 150;
1636 unsigned char Map::GetBlocked(unsigned int x
, unsigned int y
)
1638 unsigned char ret
= SearchMap
->GetAt( x
, y
);
1639 if (ret
&(PATH_MAP_DOOR_TRANSPARENT
|PATH_MAP_ACTOR
)) {
1640 ret
&=~PATH_MAP_PASSABLE
;
1642 if (ret
&PATH_MAP_DOOR_OPAQUE
) {
1643 ret
=PATH_MAP_SIDEWALL
;
1648 bool Map::GetBlocked(unsigned int px
, unsigned int py
, unsigned int size
)
1650 // We check a circle of radius size-2 around (px,py)
1651 // Note that this does not exactly match BG2. BG2's approximations of
1652 // these circles are slightly different for sizes 7 and up.
1654 if (size
> MAX_CIRCLESIZE
) size
= MAX_CIRCLESIZE
;
1655 if (size
< 2) size
= 2;
1657 unsigned int ppx
= px
/16;
1658 unsigned int ppy
= py
/12;
1659 unsigned int r
=(size
-2)*(size
-2)+1;
1660 if (size
== 2) r
= 0;
1661 for (unsigned int i
=0; i
<size
-1; i
++) {
1662 for (unsigned int j
=0; j
<size
-1; j
++) {
1664 if (!(GetBlocked(ppx
+i
,ppy
+j
)&PATH_MAP_PASSABLE
)) return true;
1665 if (!(GetBlocked(ppx
+i
,ppy
-j
)&PATH_MAP_PASSABLE
)) return true;
1666 if (!(GetBlocked(ppx
-i
,ppy
+j
)&PATH_MAP_PASSABLE
)) return true;
1667 if (!(GetBlocked(ppx
-i
,ppy
-j
)&PATH_MAP_PASSABLE
)) return true;
1674 unsigned char Map::GetBlocked(const Point
&c
)
1676 return GetBlocked(c
.x
/16, c
.y
/12);
1679 //flags:0 - never dither (full cover)
1680 // 1 - dither if polygon wants it
1681 // 2 - always dither
1683 SpriteCover
* Map::BuildSpriteCover(int x
, int y
, int xpos
, int ypos
,
1684 unsigned int width
, unsigned int height
, int flags
)
1686 SpriteCover
* sc
= new SpriteCover
;
1692 sc
->Height
= height
;
1694 Video
* video
= core
->GetVideoDriver();
1695 video
->InitSpriteCover(sc
, flags
);
1697 unsigned int wpcount
= GetWallCount();
1700 for (i
= 0; i
< wpcount
; ++i
)
1702 Wall_Polygon
* wp
= GetWallGroup(i
);
1704 if (!wp
->PointCovered(x
, y
)) continue;
1706 video
->AddPolygonToSpriteCover(sc
, wp
);
1712 void Map::ActivateWallgroups(unsigned int baseindex
, unsigned int count
, int flg
)
1719 for(i
=baseindex
; i
< baseindex
+count
; ++i
) {
1720 Wall_Polygon
* wp
= GetWallGroup(i
);
1723 ieDword value
=wp
->GetPolygonFlag();
1725 value
&=~WF_DISABLED
;
1728 wp
->SetPolygonFlag(value
);
1730 //all actors will have to generate a new spritecover
1731 i
=(int) actors
.size();
1733 actors
[i
]->SetSpriteCover(NULL
);
1738 //this function determines actor drawing order
1739 //it should be extended to wallgroups, animations, effects!
1740 void Map::GenerateQueues()
1744 unsigned int i
=(unsigned int) actors
.size();
1745 for (priority
=0;priority
<QUEUE_COUNT
;priority
++) {
1746 if (lastActorCount
[priority
] != i
) {
1747 if (queue
[priority
]) {
1748 free(queue
[priority
]);
1749 queue
[priority
] = NULL
;
1751 queue
[priority
] = (Actor
**) calloc( i
, sizeof(Actor
*) );
1752 lastActorCount
[priority
] = i
;
1754 Qcount
[priority
] = 0;
1757 ieDword gametime
= core
->GetGame()->GameTime
;
1759 Actor
* actor
= actors
[i
];
1761 if (actor
->CheckOnDeath()) {
1766 ieDword stance
= actor
->GetStance();
1767 ieDword internalFlag
= actor
->GetInternalFlag();
1769 if (internalFlag
&IF_ACTIVE
) {
1770 if ((stance
== IE_ANI_TWITCH
) && (internalFlag
&IF_IDLE
) ) {
1771 priority
= PR_DISPLAY
; //display
1773 priority
= PR_SCRIPT
; //run scripts and display
1776 //dead actors are always visible on the map, but run no scripts
1777 if (stance
== IE_ANI_TWITCH
|| stance
== IE_ANI_DIE
) {
1778 priority
= PR_DISPLAY
;
1780 //isvisible flag is false (visibilitymap) here,
1781 //coz we want to reactivate creatures that
1782 //just became visible
1783 if (IsVisible(actor
->Pos
, false) && actor
->Schedule(gametime
, false) ) {
1784 priority
= PR_SCRIPT
; //run scripts and display, activated now
1785 //more like activate!
1787 ActorSpottedByPlayer(actor
);
1789 priority
= PR_IGNORE
;
1794 //we ignore priority 2
1795 if (priority
>=PR_IGNORE
) continue;
1797 queue
[priority
][Qcount
[priority
]] = actor
;
1802 //the original qsort implementation was flawed
1803 void Map::SortQueues()
1805 for (int q
=0;q
<QUEUE_COUNT
;q
++) {
1806 Actor
**baseline
=queue
[q
];
1818 if (n
<=0) break; //breaking loop
1820 baseline
[n
] = baseline
[0];
1826 if (chp
<n
&& baseline
[chp
]->Pos
.y
< baseline
[child
]->Pos
.y
) {
1829 if (baseline
[child
]->Pos
.y
<tmp
->Pos
.y
) {
1830 baseline
[parent
] = baseline
[child
];
1836 baseline
[parent
]=tmp
;
1841 void Map::AddProjectile(Projectile
* pro
, const Point
&source
, ieWord actorID
)
1845 pro
->MoveTo(this,source
);
1846 pro
->SetTarget(actorID
);
1847 int height
= pro
->GetHeight();
1848 for(iter
=projectiles
.begin();iter
!=projectiles
.end() && (*iter
)->GetHeight()<height
; iter
++) ;
1849 projectiles
.insert(iter
, pro
);
1852 //adding projectile in order, based on its height parameter
1853 void Map::AddProjectile(Projectile
* pro
, const Point
&source
, const Point
&dest
)
1857 pro
->MoveTo(this,source
);
1858 pro
->SetTarget(dest
);
1859 int height
= pro
->GetHeight();
1860 for(iter
=projectiles
.begin();iter
!=projectiles
.end() && (*iter
)->GetHeight()<height
; iter
++) ;
1861 projectiles
.insert(iter
, pro
);
1864 //returns the longest duration of the VVC cell named 'resource' (if it exists)
1865 //if P is empty, the position won't be checked
1866 ieDword
Map::HasVVCCell(const ieResRef resource
, const Point
&p
)
1871 for(iter
=vvcCells
.begin();iter
!=vvcCells
.end(); iter
++) {
1873 if ((*iter
)->XPos
!=p
.x
) continue;
1874 if ((*iter
)->YPos
!=p
.y
) continue;
1876 if (strnicmp(resource
, (*iter
)->ResName
, sizeof(ieResRef
) )) continue;
1877 ieDword tmp
= (*iter
)->GetSequenceDuration(AI_UPDATE_TIME
)-(*iter
)->GetCurrentFrame();
1885 //adding videocell in order, based on its height parameter
1886 void Map::AddVVCell(ScriptedAnimation
* vvc
)
1890 for(iter
=vvcCells
.begin();iter
!=vvcCells
.end() && (*iter
)->ZPos
<vvc
->ZPos
; iter
++) ;
1891 vvcCells
.insert(iter
, vvc
);
1894 AreaAnimation
* Map::GetAnimation(const char* Name
)
1898 for(iter
=animations
.begin();iter
!=animations
.end();iter
++) {
1899 AreaAnimation
*anim
= *iter
;
1901 if (anim
->Name
&& (strnicmp( anim
->Name
, Name
, 32 ) == 0)) {
1908 Spawn
*Map::AddSpawn(char* Name
, int XPos
, int YPos
, ieResRef
*creatures
, unsigned int count
)
1910 Spawn
* sp
= new Spawn();
1911 strnspccpy(sp
->Name
, Name
, 32);
1912 if (count
>MAX_RESCOUNT
) {
1915 sp
->Pos
.x
= (ieWord
) XPos
;
1916 sp
->Pos
.y
= (ieWord
) YPos
;
1918 sp
->Creatures
= (ieResRef
*) calloc( count
, sizeof(ieResRef
) );
1919 for( unsigned int i
=0;i
<count
;i
++) {
1920 strnlwrcpy(sp
->Creatures
[i
],creatures
[i
],8);
1922 spawns
.push_back( sp
);
1926 void Map::AddEntrance(char* Name
, int XPos
, int YPos
, short Face
)
1928 Entrance
* ent
= new Entrance();
1929 strncpy( ent
->Name
, Name
, 32 );
1930 ent
->Pos
.x
= (ieWord
) XPos
;
1931 ent
->Pos
.y
= (ieWord
) YPos
;
1932 ent
->Face
= (ieWord
) Face
;
1933 entrances
.push_back( ent
);
1936 Entrance
* Map::GetEntrance(const char* Name
)
1938 size_t i
=entrances
.size();
1940 Entrance
*e
= entrances
[i
];
1942 if (strnicmp( e
->Name
, Name
, 32 ) == 0) {
1949 bool Map::HasActor(Actor
*actor
)
1951 size_t i
=actors
.size();
1953 if (actors
[i
] == actor
) {
1960 void Map::RemoveActor(Actor
* actor
)
1962 size_t i
=actors
.size();
1964 if (actors
[i
] == actor
) {
1965 //BlockSearchMap(actor->Pos, actor->size, PATH_MAP_FREE);
1966 ClearSearchMapFor(actor
);
1967 actors
.erase( actors
.begin()+i
);
1971 printMessage("Map","RemoveActor: actor not found?",YELLOW
);
1974 //returns true if none of the partymembers are on the map
1977 size_t i
=actors
.size();
1979 if (actors
[i
]->InParty
) {
1983 if (actors
[i
]->GetInternalFlag()&(IF_ACTIVE
|IF_USEEXIT
) ) {
1987 //we expect the area to be swapped out, so we simply remove the corpses now
1992 void Map::DebugDump(bool show_actors
) const
1994 printf( "DebugDump of Area %s:\n", scriptName
);
1995 printf( "OutDoor: %s\n", YESNO(AreaType
& AT_OUTDOOR
) );
1996 printf( "Day/Night: %s\n", YESNO(AreaType
& AT_DAYNIGHT
) );
1997 printf( "Extended night: %s\n", YESNO(AreaType
& AT_EXTENDED_NIGHT
) );
1998 printf( "Weather: %s\n", YESNO(AreaType
& AT_WEATHER
) );
1999 printf( "Area Type: %d\n", AreaType
& (AT_CITY
|AT_FOREST
|AT_DUNGEON
) );
2000 printf( "Can rest: %s\n", YESNO(AreaType
& AT_CAN_REST
) );
2004 size_t i
= actors
.size();
2006 if (!(actors
[i
]->GetInternalFlag()&(IF_JUSTDIED
|IF_REALLYDIED
))) {
2007 printf("Actor: %s at %d.%d\n", actors
[i
]->GetName(1), actors
[i
]->Pos
.x
, actors
[i
]->Pos
.y
);
2013 /******************************************************************************/
2015 void Map::Leveldown(unsigned int px
, unsigned int py
,
2016 unsigned int& level
, Point
&n
, unsigned int& diff
)
2019 unsigned int nlevel
;
2021 if (( px
>= Width
) || ( py
>= Height
)) {
2023 } //walked off the map
2024 pos
= py
* Width
+ px
;
2025 nlevel
= MapSet
[pos
];
2028 } //not even considered
2029 if (level
<= nlevel
) {
2032 unsigned int ndiff
= level
- nlevel
;
2041 void Map::SetupNode(unsigned int x
, unsigned int y
, unsigned int size
, unsigned int Cost
)
2045 if (( x
>= Width
) || ( y
>= Height
)) {
2048 pos
= y
* Width
+ x
;
2052 if (GetBlocked(x
*16+8,y
*12+6,size
)) {
2053 MapSet
[pos
] = 65535;
2056 MapSet
[pos
] = (ieWord
) Cost
;
2057 InternalStack
.push( ( x
<< 16 ) | y
);
2060 bool Map::AdjustPositionX(Point
&goal
, unsigned int radius
)
2062 unsigned int minx
= 0;
2063 if ((unsigned int) goal
.x
> radius
)
2064 minx
= goal
.x
- radius
;
2065 unsigned int maxx
= goal
.x
+ radius
+ 1;
2069 for (unsigned int scanx
= minx
; scanx
< maxx
; scanx
++) {
2070 if ((unsigned int) goal
.y
>= radius
) {
2071 if (GetBlocked( scanx
, goal
.y
- radius
) & PATH_MAP_PASSABLE
) {
2072 goal
.x
= (ieWord
) scanx
;
2073 goal
.y
= (ieWord
) (goal
.y
- radius
);
2077 if (goal
.y
+ radius
< Height
) {
2078 if (GetBlocked( scanx
, goal
.y
+ radius
) & PATH_MAP_PASSABLE
) {
2079 goal
.x
= (ieWord
) scanx
;
2080 goal
.y
= (ieWord
) (goal
.y
+ radius
);
2088 bool Map::AdjustPositionY(Point
&goal
, unsigned int radius
)
2090 unsigned int miny
= 0;
2091 if ((unsigned int) goal
.y
> radius
)
2092 miny
= goal
.y
- radius
;
2093 unsigned int maxy
= goal
.y
+ radius
+ 1;
2096 for (unsigned int scany
= miny
; scany
< maxy
; scany
++) {
2097 if ((unsigned int) goal
.x
>= radius
) {
2098 if (GetBlocked( goal
.x
- radius
, scany
) & PATH_MAP_PASSABLE
) {
2099 goal
.x
= (ieWord
) (goal
.x
- radius
);
2100 goal
.y
= (ieWord
) scany
;
2104 if (goal
.x
+ radius
< Width
) {
2105 if (GetBlocked( goal
.x
+ radius
, scany
) & PATH_MAP_PASSABLE
) {
2106 goal
.x
= (ieWord
) (goal
.x
+ radius
);
2107 goal
.y
= (ieWord
) scany
;
2115 void Map::AdjustPosition(Point
&goal
, unsigned int radius
)
2117 unsigned int maxr
= Width
;
2118 if (maxr
< Height
) {
2121 if ((unsigned int) goal
.x
> Width
) {
2122 goal
.x
= (ieWord
) Width
;
2124 if ((unsigned int) goal
.y
> Height
) {
2125 goal
.y
= (ieWord
) Height
;
2128 for (; radius
< maxr
; radius
++) {
2129 //lets make it slightly random where the actor will appear
2131 if (AdjustPositionX(goal
, radius
)) {
2134 if (AdjustPositionY(goal
, radius
)) {
2138 if (AdjustPositionY(goal
, radius
)) {
2141 if (AdjustPositionX(goal
, radius
)) {
2148 //run away from dX, dY (ie.: find the best path of limited length that brings us the farthest from dX, dY)
2149 PathNode
* Map::RunAway(const Point
&s
, const Point
&d
, unsigned int size
, unsigned int PathLen
, int flags
)
2151 Point
start(s
.x
/16, s
.y
/12);
2152 Point
goal (d
.x
/16, d
.y
/12);
2155 //MapSet entries are made of 16 bits
2156 if (PathLen
>65535) {
2160 memset( MapSet
, 0, Width
* Height
* sizeof( unsigned short ) );
2161 while (InternalStack
.size())
2162 InternalStack
.pop();
2164 if (!( GetBlocked( start
.x
, start
.y
) & PATH_MAP_PASSABLE
)) {
2165 AdjustPosition( start
);
2167 unsigned int pos
= ( start
.x
<< 16 ) | start
.y
;
2168 InternalStack
.push( pos
);
2169 MapSet
[start
.y
* Width
+ start
.x
] = 1;
2172 while (InternalStack
.size()) {
2173 pos
= InternalStack
.front();
2174 InternalStack
.pop();
2175 unsigned int x
= pos
>> 16;
2176 unsigned int y
= pos
& 0xffff;
2177 long tx
= ( x
- goal
.x
);
2178 long ty
= ( y
- goal
.y
);
2179 unsigned int distance
= (unsigned int) sqrt( ( double ) ( tx
* tx
+ ty
* ty
) );
2180 if (dist
<distance
) {
2186 unsigned int Cost
= MapSet
[y
* Width
+ x
] + NormalCost
;
2187 if (Cost
> PathLen
) {
2188 //printf("Path not found!\n");
2191 SetupNode( x
- 1, y
- 1, size
, Cost
);
2192 SetupNode( x
+ 1, y
- 1, size
, Cost
);
2193 SetupNode( x
+ 1, y
+ 1, size
, Cost
);
2194 SetupNode( x
- 1, y
+ 1, size
, Cost
);
2196 Cost
+= AdditionalCost
;
2197 SetupNode( x
, y
- 1, size
, Cost
);
2198 SetupNode( x
+ 1, y
, size
, Cost
);
2199 SetupNode( x
, y
+ 1, size
, Cost
);
2200 SetupNode( x
- 1, y
, size
, Cost
);
2203 //find path backwards from best to start
2204 PathNode
* StartNode
= new PathNode
;
2205 PathNode
* Return
= StartNode
;
2206 StartNode
->Next
= NULL
;
2207 StartNode
->x
= best
.x
;
2208 StartNode
->y
= best
.y
;
2210 StartNode
->orient
= GetOrient( start
, best
);
2212 StartNode
->orient
= GetOrient( best
, start
);
2215 unsigned int pos2
= start
.y
* Width
+ start
.x
;
2216 while (( pos
= p
.y
* Width
+ p
.x
) != pos2
) {
2217 Return
= new PathNode
;
2218 StartNode
->Parent
= Return
;
2219 Return
->Next
= StartNode
;
2221 unsigned int level
= MapSet
[pos
];
2222 unsigned int diff
= 0;
2224 Leveldown( p
.x
, p
.y
+ 1, level
, n
, diff
);
2225 Leveldown( p
.x
+ 1, p
.y
, level
, n
, diff
);
2226 Leveldown( p
.x
- 1, p
.y
, level
, n
, diff
);
2227 Leveldown( p
.x
, p
.y
- 1, level
, n
, diff
);
2228 Leveldown( p
.x
- 1, p
.y
+ 1, level
, n
, diff
);
2229 Leveldown( p
.x
+ 1, p
.y
+ 1, level
, n
, diff
);
2230 Leveldown( p
.x
+ 1, p
.y
- 1, level
, n
, diff
);
2231 Leveldown( p
.x
- 1, p
.y
- 1, level
, n
, diff
);
2236 Return
->orient
= GetOrient( p
, n
);
2238 Return
->orient
= GetOrient( n
, p
);
2245 Return
->Parent
= NULL
;
2249 bool Map::TargetUnreachable(const Point
&s
, const Point
&d
, unsigned int size
)
2251 Point
start( s
.x
/16, s
.y
/12 );
2252 Point
goal ( d
.x
/16, d
.y
/12 );
2253 memset( MapSet
, 0, Width
* Height
* sizeof( unsigned short ) );
2254 while (InternalStack
.size())
2255 InternalStack
.pop();
2257 if (GetBlocked( d
.x
, d
.y
, size
)) {
2260 if (GetBlocked( s
.x
, s
.y
, size
)) {
2264 unsigned int pos
= ( goal
.x
<< 16 ) | goal
.y
;
2265 unsigned int pos2
= ( start
.x
<< 16 ) | start
.y
;
2266 InternalStack
.push( pos
);
2267 MapSet
[goal
.y
* Width
+ goal
.x
] = 1;
2269 while (InternalStack
.size() && pos
!=pos2
) {
2270 pos
= InternalStack
.front();
2271 InternalStack
.pop();
2272 unsigned int x
= pos
>> 16;
2273 unsigned int y
= pos
& 0xffff;
2275 SetupNode( x
- 1, y
- 1, size
, 1 );
2276 SetupNode( x
+ 1, y
- 1, size
, 1 );
2277 SetupNode( x
+ 1, y
+ 1, size
, 1 );
2278 SetupNode( x
- 1, y
+ 1, size
, 1 );
2279 SetupNode( x
, y
- 1, size
, 1 );
2280 SetupNode( x
+ 1, y
, size
, 1 );
2281 SetupNode( x
, y
+ 1, size
, 1 );
2282 SetupNode( x
- 1, y
, size
, 1 );
2287 /* Use this function when you target something by a straight line projectile (like a lightning bolt, arrow, etc)
2290 PathNode
* Map::GetLine(const Point
&start
, const Point
&dest
, int flags
)
2292 int Orientation
= GetOrient(start
, dest
);
2293 return GetLine(start
, dest
, 1, Orientation
, flags
);
2296 PathNode
* Map::GetLine(const Point
&start
, int Steps
, int Orientation
, int flags
)
2300 unsigned int st
= Steps
>=MaxVisibility
?MaxVisibility
-1:Steps
;
2301 int p
= VisibilityPerimeter
*Orientation
/MAX_ORIENT
;
2302 dest
.x
+= VisibilityMasks
[st
][p
].x
;
2303 dest
.y
+= VisibilityMasks
[st
][p
].y
;
2304 //FIXME: calculate dest based on distance and orientation
2305 return GetLine(start
, dest
, Steps
, Orientation
, flags
);
2308 PathNode
* Map::GetLine(const Point
&start
, const Point
&dest
, int Speed
, int Orientation
, int flags
)
2310 PathNode
* StartNode
= new PathNode
;
2311 PathNode
*Return
= StartNode
;
2312 StartNode
->Next
= NULL
;
2313 StartNode
->Parent
= NULL
;
2314 StartNode
->x
= start
.x
;
2315 StartNode
->y
= start
.y
;
2316 StartNode
->orient
= Orientation
;
2319 int Max
= Distance(start
,dest
);
2320 for (int Steps
= 0; Steps
<Max
; Steps
++) {
2322 StartNode
->Next
= new PathNode
;
2323 StartNode
->Next
->Parent
= StartNode
;
2324 StartNode
= StartNode
->Next
;
2325 StartNode
->Next
= NULL
;
2332 p
.x
= (ieWord
) start
.x
+ ((dest
.x
- start
.x
) * Steps
/ Max
);
2333 p
.y
= (ieWord
) start
.y
+ ((dest
.y
- start
.y
) * Steps
/ Max
);
2335 //the path ends here as it would go off the screen, causing problems
2336 //maybe there is a better way, but i needed a quick hack to fix
2337 //the crash in projectiles
2338 if ((signed) p
.x
<0 || (signed) p
.y
<0) {
2341 if ((ieWord
) p
.x
>Width
*16 || (ieWord
) p
.y
>Height
*12) {
2347 StartNode
->orient
= Orientation
;
2348 bool wall
= !( GetBlocked( p
) & PATH_MAP_PASSABLE
);
2349 if (wall
) switch (flags
) {
2351 Orientation
= (Orientation
+ 8) &15;
2352 //recalculate dest (mirror it)
2356 default: //premature end
2365 * find a path from start to goal, ending at the specified distance from the
2366 * target (the goal must be in sight of the end, if 'sight' is specified)
2368 * if you don't need to find an optimal path near the goal then use FindPath
2369 * instead, but don't change this one without testing with combat and dialog,
2370 * you can't predict the goal point for those, you *must* path!
2372 PathNode
* Map::FindPathNear(const Point
&s
, const Point
&d
, unsigned int size
, unsigned int MinDistance
, bool sight
)
2374 // adjust the start/goal points to be searchmap locations
2375 Point
start( s
.x
/16, s
.y
/12 );
2376 Point
goal ( d
.x
/16, d
.y
/12 );
2377 Point orig_goal
= goal
;
2379 // re-initialise the path finding structures
2380 memset( MapSet
, 0, Width
* Height
* sizeof( unsigned short ) );
2381 while (InternalStack
.size())
2382 InternalStack
.pop();
2384 // set the start point in the path finding structures
2385 unsigned int pos2
= ( goal
.x
<< 16 ) | goal
.y
;
2386 unsigned int pos
= ( start
.x
<< 16 ) | start
.y
;
2387 InternalStack
.push( pos
);
2388 MapSet
[start
.y
* Width
+ start
.x
] = 1;
2390 unsigned int squaredmindistance
= MinDistance
* MinDistance
;
2391 bool found_path
= false;
2392 while (InternalStack
.size()) {
2393 pos
= InternalStack
.front();
2394 InternalStack
.pop();
2395 unsigned int x
= pos
>> 16;
2396 unsigned int y
= pos
& 0xffff;
2399 // we got all the way to the target!
2402 } else if (MinDistance
) {
2403 /* check minimum distance:
2404 * as an obvious optimisation we only check squared distance: this is a
2405 * possible overestimate since the sqrt Distance() rounds down
2406 * (some other optimisations could be made here, but you'd be better off
2407 * fixing the pathfinder to do A* properly)
2408 * caller should have already done PersonalDistance adjustments, this is
2409 * simply between the specified points
2412 int distx
= (x
*16 + 8) - d
.x
;
2413 int disty
= (y
*12 + 6) - d
.y
;
2414 if ((unsigned int)(distx
*distx
+ disty
*disty
) <= squaredmindistance
) {
2415 // we are within the minimum distance of the goal
2416 Point
ourpos(x
*16 + 8, y
*12 + 6);
2417 // sight check is *slow* :(
2418 if (!sight
|| IsVisible(ourpos
, d
)) {
2419 // we got all the way to a suitable goal!
2427 unsigned int Cost
= MapSet
[y
* Width
+ x
] + NormalCost
;
2429 // cost is far too high, no path found
2433 // diagonal movements
2434 SetupNode( x
- 1, y
- 1, size
, Cost
);
2435 SetupNode( x
+ 1, y
- 1, size
, Cost
);
2436 SetupNode( x
+ 1, y
+ 1, size
, Cost
);
2437 SetupNode( x
- 1, y
+ 1, size
, Cost
);
2440 Cost
+= AdditionalCost
;
2441 SetupNode( x
, y
- 1, size
, Cost
);
2442 SetupNode( x
+ 1, y
, size
, Cost
);
2443 SetupNode( x
, y
+ 1, size
, Cost
);
2444 SetupNode( x
- 1, y
, size
, Cost
);
2447 // find path from goal to start
2448 PathNode
* StartNode
= new PathNode
;
2449 PathNode
* Return
= StartNode
;
2450 StartNode
->Next
= NULL
;
2451 StartNode
->Parent
= NULL
;
2453 // this is not really great, we should be finding the path that
2454 // went nearest to where we wanted
2455 StartNode
->x
= start
.x
;
2456 StartNode
->y
= start
.y
;
2457 StartNode
->orient
= GetOrient( goal
, start
);
2460 StartNode
->x
= goal
.x
;
2461 StartNode
->y
= goal
.y
;
2462 bool fixup_orient
= false;
2463 if (orig_goal
!= goal
) {
2464 StartNode
->orient
= GetOrient( orig_goal
, goal
);
2466 // we pathed all the way to original goal!
2467 // we don't know correct orientation until we find previous step
2468 fixup_orient
= true;
2469 StartNode
->orient
= GetOrient( goal
, start
);
2472 pos2
= start
.y
* Width
+ start
.x
;
2473 while (( pos
= p
.y
* Width
+ p
.x
) != pos2
) {
2474 unsigned int level
= MapSet
[pos
];
2475 unsigned int diff
= 0;
2477 Leveldown( p
.x
, p
.y
+ 1, level
, n
, diff
);
2478 Leveldown( p
.x
+ 1, p
.y
, level
, n
, diff
);
2479 Leveldown( p
.x
- 1, p
.y
, level
, n
, diff
);
2480 Leveldown( p
.x
, p
.y
- 1, level
, n
, diff
);
2481 Leveldown( p
.x
- 1, p
.y
+ 1, level
, n
, diff
);
2482 Leveldown( p
.x
+ 1, p
.y
+ 1, level
, n
, diff
);
2483 Leveldown( p
.x
+ 1, p
.y
- 1, level
, n
, diff
);
2484 Leveldown( p
.x
- 1, p
.y
- 1, level
, n
, diff
);
2489 // don't change orientation at end of path? this seems best
2490 StartNode
->orient
= GetOrient( p
, n
);
2493 Return
= new PathNode
;
2494 Return
->Next
= StartNode
;
2495 Return
->Next
->Parent
= Return
;
2500 StartNode
->orient
= GetOrient( p
, n
);
2507 PathNode
* Map::FindPath(const Point
&s
, const Point
&d
, unsigned int size
, int MinDistance
)
2509 Point
start( s
.x
/16, s
.y
/12 );
2510 Point
goal ( d
.x
/16, d
.y
/12 );
2511 memset( MapSet
, 0, Width
* Height
* sizeof( unsigned short ) );
2512 while (InternalStack
.size())
2513 InternalStack
.pop();
2515 if (GetBlocked( d
.x
, d
.y
, size
)) {
2516 AdjustPosition( goal
);
2518 unsigned int pos
= ( goal
.x
<< 16 ) | goal
.y
;
2519 unsigned int pos2
= ( start
.x
<< 16 ) | start
.y
;
2520 InternalStack
.push( pos
);
2521 MapSet
[goal
.y
* Width
+ goal
.x
] = 1;
2523 while (InternalStack
.size()) {
2524 pos
= InternalStack
.front();
2525 InternalStack
.pop();
2526 unsigned int x
= pos
>> 16;
2527 unsigned int y
= pos
& 0xffff;
2530 //We've found _a_ path
2531 //printf("GOAL!!!\n");
2534 unsigned int Cost
= MapSet
[y
* Width
+ x
] + NormalCost
;
2536 //printf("Path not found!\n");
2539 SetupNode( x
- 1, y
- 1, size
, Cost
);
2540 SetupNode( x
+ 1, y
- 1, size
, Cost
);
2541 SetupNode( x
+ 1, y
+ 1, size
, Cost
);
2542 SetupNode( x
- 1, y
+ 1, size
, Cost
);
2544 Cost
+= AdditionalCost
;
2545 SetupNode( x
, y
- 1, size
, Cost
);
2546 SetupNode( x
+ 1, y
, size
, Cost
);
2547 SetupNode( x
, y
+ 1, size
, Cost
);
2548 SetupNode( x
- 1, y
, size
, Cost
);
2551 //find path from start to goal
2552 PathNode
* StartNode
= new PathNode
;
2553 PathNode
* Return
= StartNode
;
2554 StartNode
->Next
= NULL
;
2555 StartNode
->Parent
= NULL
;
2556 StartNode
->x
= start
.x
;
2557 StartNode
->y
= start
.y
;
2558 StartNode
->orient
= GetOrient( goal
, start
);
2563 pos2
= goal
.y
* Width
+ goal
.x
;
2564 while (( pos
= p
.y
* Width
+ p
.x
) != pos2
) {
2565 StartNode
->Next
= new PathNode
;
2566 StartNode
->Next
->Parent
= StartNode
;
2567 StartNode
= StartNode
->Next
;
2568 StartNode
->Next
= NULL
;
2569 unsigned int level
= MapSet
[pos
];
2570 unsigned int diff
= 0;
2572 Leveldown( p
.x
, p
.y
+ 1, level
, n
, diff
);
2573 Leveldown( p
.x
+ 1, p
.y
, level
, n
, diff
);
2574 Leveldown( p
.x
- 1, p
.y
, level
, n
, diff
);
2575 Leveldown( p
.x
, p
.y
- 1, level
, n
, diff
);
2576 Leveldown( p
.x
- 1, p
.y
+ 1, level
, n
, diff
);
2577 Leveldown( p
.x
+ 1, p
.y
+ 1, level
, n
, diff
);
2578 Leveldown( p
.x
+ 1, p
.y
- 1, level
, n
, diff
);
2579 Leveldown( p
.x
- 1, p
.y
- 1, level
, n
, diff
);
2584 StartNode
->orient
= GetOrient( n
, p
);
2587 //stepping back on the calculated path
2589 while (StartNode
->Parent
) {
2592 tar
.x
=StartNode
->Parent
->x
*16;
2593 tar
.y
=StartNode
->Parent
->y
*12;
2594 int dist
= Distance(tar
,d
);
2595 if (dist
+14>=MinDistance
) {
2598 StartNode
= StartNode
->Parent
;
2599 delete StartNode
->Next
;
2600 StartNode
->Next
= NULL
;
2606 //single point visible or not (visible/exploredbitmap)
2607 //if explored = true then explored otherwise currently visible
2608 bool Map::IsVisible(const Point
&pos
, int explored
)
2614 if (sX
<0) return false;
2615 if (sY
<0) return false;
2616 int w
= TMap
->XCellCount
* 2 + LargeFog
;
2617 int h
= TMap
->YCellCount
* 2 + LargeFog
;
2618 if (sX
>=w
) return false;
2619 if (sY
>=h
) return false;
2620 int b0
= (sY
* w
) + sX
;
2624 if (explored
) return (ExploredBitmap
[by
] & bi
)!=0;
2625 return (VisibleBitmap
[by
] & bi
)!=0;
2628 //point a is visible from point b (searchmap)
2629 bool Map::IsVisible(const Point
&s
, const Point
&d
)
2635 int diffx
= sX
- dX
;
2636 int diffy
= sY
- dY
;
2638 // we basically draw a 'line' from (sX, sY) to (dX, dY)
2639 // we want to move along the larger axis, to make sure we don't miss anything
2640 if (abs( diffx
) >= abs( diffy
)) {
2641 // (sX - startX)/elevationy = (sX - startX)/fabs(diffx) * diffy
2642 double elevationy
= fabs((double)diffx
) / diffy
;
2645 for (int startx
= sX
; startx
>= dX
; startx
--) {
2646 // sX - startx >= 0, so subtract (due to sign of diffy)
2647 //if (GetBlocked( startx, sY - ( int ) ( ( sX - startx ) / elevationy ) ) & PATH_MAP_NO_SEE)
2648 if (GetBlocked( startx
, sY
- ( int ) ( ( sX
- startx
) / elevationy
) ) & PATH_MAP_SIDEWALL
)
2653 for (int startx
= sX
; startx
<= dX
; startx
++) {
2654 // sX - startx <= 0, so add (due to sign of diffy)
2655 //if (GetBlocked( startx, sY + ( int ) ( ( sX - startx ) / elevationy ) ) & PATH_MAP_NO_SEE)
2656 if (GetBlocked( startx
, sY
+ ( int ) ( ( sX
- startx
) / elevationy
) ) & PATH_MAP_SIDEWALL
)
2661 // (sY - startY)/elevationx = (sY - startY)/fabs(diffy) * diffx
2662 double elevationx
= fabs((double)diffy
) / diffx
;
2665 for (int starty
= sY
; starty
>= dY
; starty
--) {
2666 // sY - starty >= 0, so subtract (due to sign of diffx)
2667 //if (GetBlocked( sX - ( int ) ( ( sY - starty ) / elevationx ), starty ) & PATH_MAP_NO_SEE)
2668 if (GetBlocked( sX
- ( int ) ( ( sY
- starty
) / elevationx
), starty
) & PATH_MAP_SIDEWALL
)
2673 for (int starty
= sY
; starty
<= dY
; starty
++) {
2674 // sY - starty <= 0, so add (due to sign of diffx)
2675 //if (GetBlocked( sX + ( int ) ( ( sY - starty ) / elevationx ), starty ) & PATH_MAP_NO_SEE)
2676 if (GetBlocked( sX
+ ( int ) ( ( sY
- starty
) / elevationx
), starty
) & PATH_MAP_SIDEWALL
)
2684 //returns direction of area boundary, returns -1 if it isn't a boundary
2685 int Map::WhichEdge(const Point
&s
)
2687 unsigned int sX
=s
.x
/16;
2688 unsigned int sY
=s
.y
/12;
2689 if (!(GetBlocked( sX
, sY
)&PATH_MAP_TRAVEL
)) {
2690 printMessage("Map"," ",YELLOW
);
2691 printf("This isn't a travel region [%d.%d]?\n",sX
, sY
);
2696 if (sX
>sY
) { //north or east
2697 if (Width
*Height
>sX
+sY
) { //
2703 if (Width
*Height
<sX
+sY
) { //
2709 //--------ambients----------------
2710 void Map::SetupAmbients()
2712 AmbientMgr
*ambim
= core
->GetAudioDrv()->GetAmbientMgr();
2715 ambim
->setAmbients( ambients
);
2717 //--------mapnotes----------------
2718 //text must be a pointer we can claim ownership of
2719 void Map::AddMapNote(const Point
&point
, int color
, char *text
, ieStrRef strref
)
2721 MapNote
*mn
= new MapNote
;
2723 mn
->strref
= strref
;
2725 mn
->color
= (ieWord
) color
;
2727 RemoveMapNote(point
); //delete previous mapnote
2728 mapnotes
.push_back(mn
);
2731 void Map::RemoveMapNote(const Point
&point
)
2733 size_t i
= mapnotes
.size();
2735 if ((point
.x
==mapnotes
[i
]->Pos
.x
) &&
2736 (point
.y
==mapnotes
[i
]->Pos
.y
)) {
2738 mapnotes
.erase(mapnotes
.begin()+i
);
2743 MapNote
*Map::GetMapNote(const Point
&point
)
2745 size_t i
= mapnotes
.size();
2747 if (Distance(point
, mapnotes
[i
]->Pos
) < 10 ) {
2753 //--------spawning------------------
2754 void Map::LoadIniSpawn()
2756 INISpawn
= new IniSpawn(this);
2757 INISpawn
->InitSpawn(WEDResRef
);
2760 void Map::SpawnCreature(const Point
&pos
, const char *CreName
, int radius
)
2762 SpawnGroup
*sg
=NULL
;
2765 if ( !Spawns
.Lookup( CreName
, lookup
) ) {
2766 creature
= gamedata
->GetCreature(CreName
);
2769 creature
->SetPosition( pos
, true, radius
);
2770 creature
->RefreshEffects(NULL
);
2774 sg
= (SpawnGroup
*)lookup
;
2775 unsigned int count
= 0;
2776 int amount
= core
->GetGame()->GetPartyLevel(true);
2777 // if the difficulty is too high, distribute it equally over all the
2778 // critters and summon as many as the summed difficulty allows
2779 if (amount
- (signed)sg
->Level
< 0) {
2780 unsigned int share
= sg
->Level
/sg
->Count
;
2783 // a single critter is also too powerful
2786 while (amount
>= 0) {
2795 creature
= gamedata
->GetCreature(sg
->ResRefs
[count
]);
2798 creature
->SetPosition( pos
, true, radius
);
2799 creature
->RefreshEffects(NULL
);
2804 void Map::TriggerSpawn(Spawn
*spawn
)
2806 //is it still active
2807 if (!spawn
->Enabled
) {
2811 ieDword bit
= 1<<((core
->GetGame()->GameTime
/AI_UPDATE_TIME
)%7200/300);
2812 if (!(spawn
->appearance
& bit
)) {
2816 //check day or night chance
2817 if (rand()%100>spawn
->DayChance
) {
2820 // the difficulty check is done in SpawnCreature
2822 for(unsigned int i
= 0;i
<spawn
->Count
;i
++) {
2823 SpawnCreature(spawn
->Pos
, spawn
->Creatures
[i
], 0);
2825 //disable spawnpoint
2829 //--------restheader----------------
2831 Every spawn has a difficulty associated with it. For CREs this is the xp stat
2832 and for groups it's the value in the difficulty row.
2833 For every spawn, the difficulty sum of all spawns up to now (including the
2834 current) is compared against (party level * rest header difficulty). If it's
2835 greater, the spawning is aborted. If all the other conditions are true, at
2836 least one creature is summoned, regardless the difficulty cap.
2838 bool Map::Rest(const Point
&pos
, int hours
, int day
)
2840 if (!RestHeader
.CreatureNum
|| !RestHeader
.Enabled
|| !RestHeader
.Maximum
) {
2844 //based on ingame timer
2845 int chance
=day
?RestHeader
.DayChance
:RestHeader
.NightChance
;
2847 int spawnamount
= core
->GetGame()->GetPartyLevel(true) * RestHeader
.Difficulty
;
2848 if (spawnamount
< 1) spawnamount
= 1;
2849 for (int i
=0;i
<hours
;i
++) {
2850 if ( rand()%100<chance
) {
2851 int idx
= rand()%RestHeader
.CreatureNum
;
2852 Actor
*creature
= gamedata
->GetCreature(RestHeader
.CreResRef
[idx
]);
2853 if (!creature
) continue;
2854 // ensure a minimum power level, since many creatures have this as 0
2855 int cpl
= creature
->Modified
[IE_XP
] ? creature
->Modified
[IE_XP
] : 1;
2857 displaymsg
->DisplayString( RestHeader
.Strref
[idx
], 0x00404000, IE_STR_SOUND
);
2858 while (spawnamount
> 0 && spawncount
<= RestHeader
.Maximum
) {
2859 SpawnCreature(pos
, RestHeader
.CreResRef
[idx
], 20);
2869 //--------explored bitmap-----------
2870 int Map::GetExploredMapSize() const
2872 int x
= TMap
->XCellCount
*2;
2873 int y
= TMap
->YCellCount
*2;
2881 void Map::Explore(int setreset
)
2883 memset (ExploredBitmap
, setreset
, GetExploredMapSize() );
2886 void Map::SetMapVisibility(int setreset
)
2888 memset( VisibleBitmap
, setreset
, GetExploredMapSize() );
2891 // x, y are in tile coordinates
2892 void Map::ExploreTile(const Point
&pos
)
2894 int h
= TMap
->YCellCount
* 2 + LargeFog
;
2896 if (y
< 0 || y
>= h
)
2899 int w
= TMap
->XCellCount
* 2 + LargeFog
;
2901 if (x
< 0 || x
>= w
)
2904 int b0
= (y
* w
) + x
;
2908 ExploredBitmap
[by
] |= bi
;
2909 VisibleBitmap
[by
] |= bi
;
2912 void Map::ExploreMapChunk(const Point
&Pos
, int range
, int los
)
2916 if (range
>MaxVisibility
) {
2917 range
=MaxVisibility
;
2919 int p
=VisibilityPerimeter
;
2923 bool sidewall
= false ;
2924 for (int i
=0;i
<range
;i
++) {
2925 Tile
.x
= Pos
.x
+VisibilityMasks
[i
][p
].x
;
2926 Tile
.y
= Pos
.y
+VisibilityMasks
[i
][p
].y
;
2930 int type
= GetBlocked(Tile
);
2931 if (type
& PATH_MAP_NO_SEE
) {
2933 } else if (type
& PATH_MAP_SIDEWALL
) {
2935 } else if (sidewall
)
2950 void Map::UpdateFog()
2952 if (!(core
->FogOfWar
&FOG_DRAWFOG
) ) {
2953 SetMapVisibility( -1 );
2957 SetMapVisibility( 0 );
2958 for (unsigned int e
= 0; e
<actors
.size(); e
++) {
2959 Actor
*actor
= actors
[e
];
2960 if (!actor
->Modified
[ IE_EXPLORE
] ) continue;
2961 int state
= actor
->Modified
[IE_STATE_ID
];
2962 if (state
& STATE_CANTSEE
) continue;
2963 int vis2
= actor
->Modified
[IE_VISUALRANGE
];
2964 if ((state
&STATE_BLIND
) || (vis2
<2)) vis2
=2; //can see only themselves
2965 ExploreMapChunk (actor
->Pos
, vis2
+actor
->GetAnims()->GetCircleSize(), 1);
2966 Spawn
*sp
= GetSpawnRadius(actor
->Pos
, SPAWN_RANGE
); //30 * 12
2973 //Valid values are - PATH_MAP_FREE, PATH_MAP_PC, PATH_MAP_NPC
2974 void Map::BlockSearchMap(const Point
&Pos
, unsigned int size
, unsigned int value
)
2976 // We block a circle of radius size-1 around (px,py)
2977 // Note that this does not exactly match BG2. BG2's approximations of
2978 // these circles are slightly different for sizes 6 and up.
2980 // Note: this is a larger circle than the one tested in GetBlocked.
2981 // This means that an actor can get closer to a wall than to another
2982 // actor. This matches the behaviour of the original BG2.
2984 if (size
> MAX_CIRCLESIZE
) size
= MAX_CIRCLESIZE
;
2985 if (size
< 2) size
= 2;
2986 unsigned int ppx
= Pos
.x
/16;
2987 unsigned int ppy
= Pos
.y
/12;
2988 unsigned int r
=(size
-1)*(size
-1)+1;
2989 if (size
== 1) r
= 0;
2990 for (unsigned int i
=0; i
<size
; i
++) {
2991 for (unsigned int j
=0; j
<size
; j
++) {
2995 tmp
= SearchMap
->GetAt(ppx
+i
,ppy
+j
)&PATH_MAP_NOTACTOR
;
2996 SearchMap
->SetAt(ppx
+i
,ppy
+j
,tmp
|value
);
2998 tmp
= SearchMap
->GetAt(ppx
+i
,ppy
-j
)&PATH_MAP_NOTACTOR
;
2999 SearchMap
->SetAt(ppx
+i
,ppy
-j
,tmp
|value
);
3001 tmp
= SearchMap
->GetAt(ppx
-i
,ppy
+j
)&PATH_MAP_NOTACTOR
;
3002 SearchMap
->SetAt(ppx
-i
,ppy
+j
,tmp
|value
);
3004 tmp
= SearchMap
->GetAt(ppx
-i
,ppy
-j
)&PATH_MAP_NOTACTOR
;
3005 SearchMap
->SetAt(ppx
-i
,ppy
-j
,tmp
|value
);
3011 Spawn
* Map::GetSpawn(const char* Name
)
3013 for (size_t i
= 0; i
< spawns
.size(); i
++) {
3014 Spawn
* sp
= spawns
[i
];
3016 if (stricmp( sp
->Name
, Name
) == 0)
3022 Spawn
*Map::GetSpawnRadius(const Point
&point
, unsigned int radius
)
3024 for (size_t i
= 0; i
< spawns
.size(); i
++) {
3025 Spawn
* sp
= spawns
[i
];
3027 if (Distance(point
, sp
->Pos
)<radius
) {
3034 int Map::ConsolidateContainers()
3037 int containercount
= (int) TMap
->GetContainerCount();
3038 while (containercount
--) {
3039 Container
* c
= TMap
->GetContainer( containercount
);
3041 if (TMap
->CleanupContainer(c
) ) {
3044 itemcount
+= c
->inventory
.GetSlotCount();
3049 //Pos could be [-1,-1] in which case it copies the ground piles to their
3050 //original position in the second area
3051 void Map::CopyGroundPiles(Map
*othermap
, const Point
&Pos
)
3053 int containercount
= (int) TMap
->GetContainerCount();
3054 while (containercount
--) {
3055 Container
* c
= TMap
->GetContainer( containercount
);
3056 if (c
->Type
==IE_CONTAINER_PILE
) {
3057 //creating (or grabbing) the container in the other map at the given position
3058 Container
*othercontainer
;
3059 if (Pos
.isempty()) {
3060 othercontainer
= othermap
->GetPile(c
->Pos
);
3062 othercontainer
= othermap
->GetPile(Pos
);
3064 //transfer the pile to the other container
3065 unsigned int i
=c
->inventory
.GetSlotCount();
3067 CREItem
*item
= c
->RemoveItem(i
, 0);
3068 othercontainer
->AddItem(item
);
3074 void Map::MoveVisibleGroundPiles(const Point
&Pos
)
3076 //creating the container at the given position
3077 Container
*othercontainer
;
3078 othercontainer
= GetPile(Pos
);
3080 int containercount
= (int) TMap
->GetContainerCount();
3081 while (containercount
--) {
3082 Container
* c
= TMap
->GetContainer( containercount
);
3083 if (c
->Type
==IE_CONTAINER_PILE
&& IsVisible(c
->Pos
, true)) {
3084 //transfer the pile to the other container
3085 unsigned int i
=c
->inventory
.GetSlotCount();
3087 CREItem
*item
= c
->RemoveItem(i
, 0);
3088 othercontainer
->AddItem(item
);
3094 Container
*Map::GetPile(Point position
)
3099 //converting to search square
3100 position
.x
=position
.x
/16;
3101 position
.y
=position
.y
/12;
3102 sprintf(heapname
,"heap_%hd.%hd",position
.x
,position
.y
);
3103 //pixel position is centered on search square
3104 position
.x
=position
.x
*16+8;
3105 position
.y
=position
.y
*12+6;
3106 Container
*container
= TMap
->GetContainer(position
,IE_CONTAINER_PILE
);
3108 //bounding box covers the search square
3109 tmp
[0].x
=position
.x
-8;
3110 tmp
[0].y
=position
.y
-6;
3111 tmp
[1].x
=position
.x
+8;
3112 tmp
[1].y
=position
.y
-6;
3113 tmp
[2].x
=position
.x
+8;
3114 tmp
[2].y
=position
.y
+6;
3115 tmp
[3].x
=position
.x
-8;
3116 tmp
[3].y
=position
.y
+6;
3117 Gem_Polygon
* outline
= new Gem_Polygon( tmp
, 4 );
3118 container
= AddContainer(heapname
, IE_CONTAINER_PILE
, outline
);
3119 container
->Pos
=position
;
3124 void Map::AddItemToLocation(const Point
&position
, CREItem
*item
)
3126 Container
*container
= GetPile(position
);
3127 container
->AddItem(item
);
3130 Container
* Map::AddContainer(const char* Name
, unsigned short Type
,
3131 Gem_Polygon
* outline
)
3133 Container
* c
= new Container();
3134 c
->SetScriptName( Name
);
3136 c
->outline
= outline
;
3138 TMap
->AddContainer( c
);
3142 int Map::GetCursor( const Point
&p
)
3144 if (!IsVisible( p
, true ) ) {
3145 return IE_CURSOR_INVALID
;
3147 switch (GetBlocked( p
) & (PATH_MAP_PASSABLE
|PATH_MAP_TRAVEL
)) {
3149 return IE_CURSOR_BLOCKED
;
3150 case PATH_MAP_PASSABLE
:
3151 return IE_CURSOR_WALK
;
3153 return IE_CURSOR_TRAVEL
;
3157 bool Map::HasWeather()
3159 if ((AreaType
& (AT_WEATHER
|AT_OUTDOOR
) ) != (AT_WEATHER
|AT_OUTDOOR
) ) {
3165 int Map::GetWeather()
3167 if (Rain
>=core
->Roll(1,100,0) ) {
3168 if (Lightning
>=core
->Roll(1,100,0) ) {
3169 return WB_LIGHTNING
|WB_RAIN
;
3173 if (Snow
>=core
->Roll(1,100,0) ) {
3176 if (Fog
>=core
->Roll(1,100,0) ) {
3182 void Map::FadeSparkle(const Point
&pos
, bool forced
)
3186 for(iter
=particles
.begin(); iter
!=particles
.end();iter
++) {
3187 if ((*iter
)->MatchPos(pos
) ) {
3189 //particles.erase(iter);
3190 (*iter
)->SetPhase(P_EMPTY
);
3192 (*iter
)->SetPhase(P_FADE
);
3199 void Map::Sparkle(ieDword duration
, ieDword color
, ieDword type
, const Point
&pos
, unsigned int FragAnimID
)
3201 int style
, path
, grow
, size
, width
, ttl
;
3203 //the high word is ignored in the original engine (compatibility hack)
3204 switch(type
&0xffff) {
3205 case SPARKLE_SHOWER
: //simple falling sparks
3206 path
= SP_PATH_FALL
;
3207 grow
= SP_SPAWN_FULL
;
3213 path
= SP_PATH_FOUNT
; //sparks go up and down
3214 grow
= SP_SPAWN_SOME
;
3217 ttl
= core
->GetGame()->GameTime
+25;
3219 case SPARKLE_EXPLOSION
: //this isn't in the original engine, but it is a nice effect to have
3220 path
= SP_PATH_EXPL
;
3221 grow
= SP_SPAWN_SOME
;
3224 ttl
= core
->GetGame()->GameTime
+25;
3227 path
= SP_PATH_FLIT
;
3228 grow
= SP_SPAWN_SOME
;
3234 Particles
*sparkles
= new Particles(size
);
3235 sparkles
->SetOwner(this);
3236 sparkles
->SetRegion(pos
.x
-width
/2, pos
.y
-30, width
, 30);
3237 sparkles
->SetTimeToLive(ttl
);
3240 style
= SP_TYPE_BITMAP
;
3241 sparkles
->SetBitmap(FragAnimID
);
3244 style
= SP_TYPE_POINT
;
3246 sparkles
->SetType(style
, path
, grow
);
3247 sparkles
->SetColor(color
);
3248 sparkles
->SetPhase(P_GROW
);
3251 for(iter
=particles
.begin(); (iter
!=particles
.end()) && ((*iter
)->GetHeight()<pos
.y
); iter
++) ;
3252 particles
.insert(iter
, sparkles
);
3255 //remove flags from actor if it has left the trigger area it had last entered
3256 void Map::ClearTrap(Actor
*actor
, ieDword InTrap
)
3258 InfoPoint
*trap
= TMap
->GetInfoPoint(InTrap
);
3260 actor
->SetInTrap(0);
3262 if(!trap
->outline
->PointIn(actor
->Pos
)) {
3263 actor
->SetInTrap(0);
3268 void Map::SetTrackString(ieStrRef strref
, int flg
, int difficulty
)
3270 trackString
= strref
;
3272 trackDiff
= (ieWord
) difficulty
;
3275 bool Map::DisplayTrackString(Actor
*target
)
3277 // this stat isn't saved
3278 // according to the HoW manual the chance of success is:
3279 // +5% for every three levels and +5% per point of wisdom
3280 int skill
= target
->GetStat(IE_TRACKING
);
3281 skill
+= (target
->GetStat(IE_LEVEL
)/3)*5 + target
->GetStat(IE_WIS
)*5;
3282 if (core
->Roll(1, 100, trackDiff
) > skill
) {
3283 displaymsg
->DisplayConstantStringName(STR_TRACKINGFAILED
, 0xd7d7be, target
);
3287 char * str
= core
->GetString( trackString
);
3288 core
->GetTokenDictionary()->SetAt( "CREATURE", str
);
3289 displaymsg
->DisplayConstantStringName(STR_TRACKING
, 0xd7d7be, target
);
3292 displaymsg
->DisplayStringName(trackString
, 0xd7d7be, target
, 0);
3296 // returns a lightness level in the range of [0-100]
3297 // since the lightmap is much smaller than the area, we need to interpolate
3298 unsigned int Map::GetLightLevel(const Point
&Pos
)
3300 Color c
= LightMap
->GetPixel(Pos
.x
/16, Pos
.y
/12);
3301 // at night/dusk/dawn the lightmap color is adjusted by the color overlay. (Only get's darker.)
3302 const Color
*tint
= core
->GetGame()->GetGlobalTint();
3304 return ((c
.r
-tint
->r
)*114 + (c
.g
-tint
->g
)*587 + (c
.b
-tint
->b
)*299)/2550;
3306 return (c
.r
*114+c
.g
*587+c
.b
*299)/2550;
3309 ////////////////////AreaAnimation//////////////////
3312 AreaAnimation::AreaAnimation()
3320 AreaAnimation::~AreaAnimation()
3322 for(int i
=0;i
<animcount
;i
++) {
3324 delete (animation
[i
]);
3328 gamedata
->FreePalette(palette
, PaletteRef
);
3330 for(int i
=0;i
<animcount
;i
++) {
3337 Animation
*AreaAnimation::GetAnimationPiece(AnimationFactory
*af
, int animCycle
)
3339 Animation
*anim
= af
->GetCycle( ( unsigned char ) animCycle
);
3341 anim
= af
->GetCycle( 0 );
3343 printf("Cannot load animation: %s\n", BAM
);
3346 //this will make the animation stop when the game is stopped
3347 //a possible gemrb feature to have this flag settable in .are
3348 anim
->gameAnimation
= true;
3350 anim
->Flags
= Flags
;
3353 if (anim
->Flags
&A_ANI_MIRROR
) {
3354 anim
->MirrorAnimation();
3360 void AreaAnimation::InitAnimation()
3362 AnimationFactory
* af
= ( AnimationFactory
* )
3363 gamedata
->GetFactoryResource( BAM
, IE_BAM_CLASS_ID
);
3365 printf("Cannot load animation: %s\n", BAM
);
3369 //freeing up the previous animation
3370 for(int i
=0;i
<animcount
;i
++) {
3372 delete (animation
[i
]);
3377 if (Flags
& A_ANI_ALLCYCLES
) {
3378 animcount
= (int) af
->GetCycleCount();
3380 animation
= (Animation
**) malloc(animcount
* sizeof(Animation
*) );
3381 for(int j
=0;j
<animcount
;j
++) {
3382 animation
[j
]=GetAnimationPiece(af
, j
);
3386 animation
= (Animation
**) malloc( sizeof(Animation
*) );
3387 animation
[0]=GetAnimationPiece(af
, sequence
);
3389 if (Flags
& A_ANI_PALETTE
) {
3390 SetPalette(PaletteRef
);
3392 if (Flags
&A_ANI_BLEND
) {
3397 void AreaAnimation::SetPalette(ieResRef Pal
)
3399 Flags
|= A_ANI_PALETTE
;
3400 gamedata
->FreePalette(palette
, PaletteRef
);
3401 strnlwrcpy(PaletteRef
, Pal
, 8);
3402 palette
= gamedata
->GetPalette(PaletteRef
);
3403 if (Flags
&A_ANI_BLEND
) {
3404 //re-blending after palette change
3409 void AreaAnimation::BlendAnimation()
3411 //Warning! This function will modify a shared palette
3413 // CHECKME: what should we do here? Currently copying palette
3414 // from first frame of first animation
3416 if (animcount
== 0 || !animation
[0]) return;
3417 Sprite2D
* spr
= animation
[0]->GetFrame(0);
3419 palette
= spr
->GetPalette()->Copy();
3422 palette
->CreateShadedAlphaChannel();
3425 bool AreaAnimation::Schedule(ieDword gametime
)
3427 if (!(Flags
&A_ANI_ACTIVE
) ) {
3431 //check for schedule
3432 ieDword bit
= 1<<((gametime
/AI_UPDATE_TIME
)%7200/300);
3433 if (appearance
& bit
) {
3439 void AreaAnimation::Draw(const Region
&screen
, Map
*area
)
3442 Video
* video
= core
->GetVideoDriver();
3444 //always draw the animation tinted because tint is also used for
3446 Color tint
= {255,255,255,255-(ieByte
) transparency
};
3447 if ((Flags
&A_ANI_NO_SHADOW
)) {
3448 tint
= area
->LightMap
->GetPixel( Pos
.x
/ 16, Pos
.y
/ 12);
3449 tint
.a
= 255-(ieByte
) transparency
;
3451 if (!(Flags
&A_ANI_NO_WALL
)) {
3453 covers
=(SpriteCover
**) calloc( animcount
, sizeof(SpriteCover
*) );
3458 Animation
*anim
= animation
[ac
];
3459 Sprite2D
*frame
= anim
->NextFrame();
3461 if(!covers
[ac
] || !covers
[ac
]->Covers(Pos
.x
, Pos
.y
, frame
->XPos
, frame
->YPos
, frame
->Width
, frame
->Height
)) {
3463 covers
[ac
] = area
->BuildSpriteCover(Pos
.x
, Pos
.y
, -anim
->animArea
.x
,
3464 -anim
->animArea
.y
, anim
->animArea
.w
, anim
->animArea
.h
, 0);
3467 video
->BlitGameSprite( frame
, Pos
.x
+ screen
.x
, Pos
.y
+ screen
.y
,
3468 BLIT_TINTED
, tint
, covers
?covers
[ac
]:0, palette
, &screen
);
3472 //change the tileset if needed and possible, return true if changed
3473 bool Map::ChangeMap(bool day_or_night
)
3475 //no need of change if the area is not extended night
3476 if (! (AreaType
&AT_EXTENDED_NIGHT
)) return false;
3477 //no need of change if the area already has the right tilemap
3478 if ((DayNight
== day_or_night
) && GetTileMap()) return false;
3480 PluginHolder
<MapMgr
> mM(IE_ARE_CLASS_ID
);
3481 //no need to open and read the .are file again
3482 //using the ARE class for this because ChangeMap is similar to LoadMap
3483 //it loads the lightmap and the minimap too, besides swapping the tileset
3484 mM
->ChangeMap(this, day_or_night
);
3488 void Map::SeeSpellCast(Scriptable
*caster
, ieDword spell
)
3490 if (caster
->Type
!=ST_ACTOR
) {
3494 LastCasterSeen
= ((Actor
*) caster
)->GetID();
3495 LastSpellSeen
= spell
;
3497 size_t i
= actors
.size();
3499 Actor
* witness
= actors
[i
];
3500 if (CanSee(witness
, caster
, true, 0)) {
3501 witness
->LastSpellSeen
=LastSpellSeen
;
3502 witness
->LastCasterSeen
=LastCasterSeen
;