1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 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 .gam (savegame) file in the engine
25 #include "defsounds.h"
29 #include "DisplayMessage.h"
31 #include "Interface.h"
34 #include "Particles.h"
35 #include "ScriptEngine.h"
36 #include "GameScript/GameScript.h"
37 #include "GUI/GameControl.h"
38 #include "System/DataStream.h"
40 #define MAX_MAPS_LOADED 1
42 Game::Game(void) : Scriptable( ST_GLOBAL
)
44 protagonist
= PM_YES
; //set it to 2 for iwd/iwd2 and 0 for pst
50 SelectedSingle
= 1; //the PC we are looking at (inventory, shop)
52 SetScript( core
->GlobalScript
, 0 );
56 CombatCounter
= 0; //stored here until we know better
57 StateOverrideTime
= 0;
58 StateOverrideFlag
= 0;
66 timestop_owner
= NULL
;
70 weather
= new Particles(200);
71 weather
->SetRegion(0, 0, core
->Width
, core
->Height
);
74 //loading master areas
76 if (table
.load("mastarea")) {
77 int i
= table
->GetRowCount();
80 char *tmp
= (char *) malloc(9);
81 strnuprcpy (tmp
,table
->QueryField(i
,0),8);
82 mastarea
.push_back( tmp
);
86 //loading rest/daylight switching movies (only bg2 has them)
87 memset(restmovies
,'*',sizeof(restmovies
));
88 memset(daymovies
,'*',sizeof(restmovies
));
89 memset(nightmovies
,'*',sizeof(restmovies
));
90 if (table
.load("restmov")) {
91 for(int i
=0;i
<8;i
++) {
92 strnuprcpy(restmovies
[i
],table
->QueryField(i
,0),8);
93 strnuprcpy(daymovies
[i
],table
->QueryField(i
,1),8);
94 strnuprcpy(nightmovies
[i
],table
->QueryField(i
,2),8);
98 interval
= 1000/AI_UPDATE_TIME
;
99 //FIXME:i'm not sure in this...
108 for (i
= 0; i
< Maps
.size(); i
++) {
111 for (i
= 0; i
< PCs
.size(); i
++) {
114 for (i
= 0; i
< NPCs
.size(); i
++) {
117 for (i
= 0; i
< mastarea
.size(); i
++) {
118 free ( mastarea
[i
] );
139 i
=savedpositions
.size();
141 free (savedpositions
[i
]);
144 i
=planepositions
.size();
146 free (planepositions
[i
]);
150 bool IsAlive(Actor
*pc
)
152 if (pc
->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
158 int Game::FindPlayer(unsigned int partyID
)
160 for (unsigned int slot
=0; slot
<PCs
.size(); slot
++) {
161 if (PCs
[slot
]->InParty
==partyID
) {
168 Actor
* Game::FindPC(unsigned int partyID
)
170 for (unsigned int slot
=0; slot
<PCs
.size(); slot
++) {
171 if (PCs
[slot
]->InParty
==partyID
) return PCs
[slot
];
176 Actor
* Game::FindPC(const char *scriptingname
)
178 for (unsigned int slot
=0; slot
<PCs
.size(); slot
++) {
179 if (strnicmp(PCs
[slot
]->GetScriptName(),scriptingname
,32)==0 ) {
186 Actor
* Game::FindNPC(unsigned int partyID
)
188 for (unsigned int slot
=0; slot
<NPCs
.size(); slot
++) {
189 if (NPCs
[slot
]->InParty
==partyID
) return NPCs
[slot
];
194 Actor
* Game::FindNPC(const char *scriptingname
)
196 for (unsigned int slot
=0; slot
<NPCs
.size(); slot
++) {
197 if (strnicmp(NPCs
[slot
]->GetScriptName(),scriptingname
,32)==0 )
205 Actor
* Game::GetPC(unsigned int slot
, bool onlyalive
)
207 if (slot
>= PCs
.size()) {
212 while(i
<PCs
.size() ) {
213 Actor
*ac
= PCs
[i
++];
226 int Game::InStore(Actor
* pc
) const
228 for (unsigned int i
= 0; i
< NPCs
.size(); i
++) {
236 int Game::InParty(Actor
* pc
) const
238 for (unsigned int i
= 0; i
< PCs
.size(); i
++) {
246 int Game::DelPC(unsigned int slot
, bool autoFree
)
248 if (slot
>= PCs
.size()) {
254 SelectActor(PCs
[slot
], false, SELECT_NORMAL
);
258 std::vector
< Actor
*>::iterator m
= PCs
.begin() + slot
;
263 int Game::DelNPC(unsigned int slot
, bool autoFree
)
265 if (slot
>= NPCs
.size()) {
272 delete( NPCs
[slot
] );
274 std::vector
< Actor
*>::iterator m
= NPCs
.begin() + slot
;
279 //i'm sure this could be faster
280 void Game::ConsolidateParty()
282 int max
= (int) PCs
.size();
283 std::vector
< Actor
*>::const_iterator m
;
284 for (int i
=1;i
<=max
;) {
285 if (FindPlayer(i
)==-1) {
287 for ( m
= PCs
.begin(); m
!= PCs
.end(); ++m
) {
288 if ( (*m
)->InParty
>i
) {
294 for ( m
= PCs
.begin(); m
!= PCs
.end(); ++m
) {
295 (*m
)->RefreshEffects(NULL
);
299 int Game::LeaveParty (Actor
* actor
)
301 core
->SetEventFlag(EF_PORTRAIT
);
302 actor
->CreateStats(); //create or update stats for leaving
303 actor
->SetBase(IE_EXPLORE
, 0);
305 SelectActor(actor
, false, SELECT_NORMAL
);
306 int slot
= InParty( actor
);
310 std::vector
< Actor
*>::iterator m
= PCs
.begin() + slot
;
313 ieDword id
= actor
->GetID();
314 for ( m
= PCs
.begin(); m
!= PCs
.end(); ++m
) {
315 (*m
)->PCStats
->LastLeft
= id
;
316 if ( (*m
)->InParty
>actor
->InParty
) {
320 //removing from party, but actor remains in 'game'
321 actor
->SetPersistent(0);
322 NPCs
.push_back( actor
);
324 if (core
->HasFeature( GF_HAS_DPLAYER
)) {
325 actor
->SetScript( "", SCR_DEFAULT
);
327 actor
->SetBase( IE_EA
, EA_NEUTRAL
);
328 return ( int ) NPCs
.size() - 1;
331 //determines if startpos.2da has rotation rows (it cannot have tutorial line)
332 bool Game::DetermineStartPosType(const TableMgr
*strta
)
334 if ((strta
->GetRowCount()>=6) && !stricmp(strta
->GetRowName(4),"START_ROT" ) )
341 #define PMODE_COUNT 3
343 void Game::InitActorPos(Actor
*actor
)
345 //start.2da row labels
346 const char *mode
[PMODE_COUNT
] = { "NORMAL", "TUTORIAL", "EXPANSION" };
348 unsigned int ip
= (unsigned int) (actor
->InParty
-1);
349 AutoTable
start("start");
350 AutoTable
strta("startpos");
351 // 0 - single player, 1 - tutorial, 2 - expansion
352 ieDword playmode
= 0;
353 core
->GetDictionary()->Lookup( "PlayMode", playmode
);
355 //Sometimes playmode is set to -1 (in pregenerate)
356 //normally execution shouldn't ever come here, but it actually does
357 //preventing problems by defaulting to the regular entry points
358 if (playmode
>PMODE_COUNT
) {
361 const char *xpos
= start
->QueryField(mode
[playmode
],"XPOS");
362 const char *ypos
= start
->QueryField(mode
[playmode
],"YPOS");
363 const char *area
= start
->QueryField(mode
[playmode
],"AREA");
364 const char *rot
= start
->QueryField(mode
[playmode
],"ROT");
366 actor
->Pos
.x
= actor
->Destination
.x
= (short) atoi( strta
->QueryField( strta
->GetRowIndex(xpos
), ip
) );
367 actor
->Pos
.y
= actor
->Destination
.y
= (short) atoi( strta
->QueryField( strta
->GetRowIndex(ypos
), ip
) );
368 actor
->SetOrientation( atoi( strta
->QueryField( strta
->GetRowIndex(rot
), ip
) ), false );
370 strta
.load("startare");
371 strnlwrcpy(actor
->Area
, strta
->QueryField( strta
->GetRowIndex(area
), 0 ), 8 );
374 int Game::JoinParty(Actor
* actor
, int join
)
376 core
->SetEventFlag(EF_PORTRAIT
);
377 actor
->CreateStats(); //create stats if they didn't exist yet
378 actor
->InitButtons(actor
->GetStat(IE_CLASS
), false); //init actor's buttons
379 actor
->SetBase(IE_EXPLORE
, 1);
380 if (join
&JP_INITPOS
) {
383 int slot
= InParty( actor
);
387 size_t size
= PCs
.size();
388 //set the lastjoined trigger
391 //update kit abilities of actor
392 actor
->ApplyKit(false);
393 //update the quickslots
394 actor
->ReinitQuickSlots();
395 //set the joining date
396 actor
->PCStats
->JoinDate
= GameTime
;
398 ieDword id
= actor
->GetID();
399 for (size_t i
=0;i
<size
; i
++) {
400 Actor
*a
= GetPC(i
, false);
401 a
->PCStats
->LastJoined
= id
;
404 Reputation
= actor
->GetStat(IE_REPUTATION
);
407 slot
= InStore( actor
);
409 std::vector
< Actor
*>::iterator m
= NPCs
.begin() + slot
;
414 PCs
.push_back( actor
);
415 if (!actor
->InParty
) {
416 actor
->InParty
= (ieByte
) (size
+1);
419 if (join
&(JP_INITPOS
|JP_SELECT
)) {
420 actor
->Selected
= 0; // don't confuse SelectActor!
421 SelectActor(actor
,true, SELECT_NORMAL
);
427 int Game::GetPartySize(bool onlyalive
) const
431 for (unsigned int i
= 0; i
< PCs
.size(); i
++) {
432 if (!IsAlive(PCs
[i
])) {
439 return (int) PCs
.size();
442 /* sends the hotkey trigger to all selected actors */
443 void Game::SetHotKey(unsigned long Key
)
445 std::vector
< Actor
*>::const_iterator m
;
447 for ( m
= selected
.begin(); m
!= selected
.end(); ++m
) {
450 if (actor
->IsSelected()) {
451 actor
->HotKey
= (ieDword
) Key
;
456 bool Game::SelectPCSingle(int index
)
458 Actor
* actor
= FindPC( index
);
459 if (!actor
|| ! actor
->ValidTarget( GA_NO_DEAD
| GA_NO_HIDDEN
))
462 SelectedSingle
= index
;
466 int Game::GetSelectedPCSingle() const
468 return SelectedSingle
;
472 * SelectActor() - handle (de)selecting actors.
473 * If selection was changed, runs "SelectionChanged" handler
475 * actor - either specific actor, or NULL for all
476 * select - whether actor(s) should be selected or deselected
478 * SELECT_REPLACE - if true, deselect all other actors when selecting one
479 * SELECT_QUIET - do not run handler if selection was changed. Used for
480 * nested calls to SelectActor()
483 bool Game::SelectActor(Actor
* actor
, bool select
, unsigned flags
)
485 std::vector
< Actor
*>::iterator m
;
487 // actor was not specified, which means all selectables should be (de)selected
489 for ( m
= selected
.begin(); m
!= selected
.end(); ++m
) {
490 (*m
)->Select( false );
491 (*m
)->SetOver( false );
496 area
->SelectActors();
498 for ( m = PCs.begin(); m != PCs.end(); ++m) {
502 SelectActor( *m, true, SELECT_QUIET );
507 if (! (flags
& SELECT_QUIET
)) {
508 core
->SetEventFlag(EF_SELECTION
);
514 // actor was specified, so we will work with him
516 if (! actor
->ValidTarget( GA_SELECT
| GA_NO_DEAD
))
519 // deselect all actors first when exclusive
520 if (flags
& SELECT_REPLACE
) {
521 if (selected
.size() == 1 && actor
->IsSelected()) {
522 assert(selected
[0] == actor
);
523 // already the only selected actor
526 SelectActor( NULL
, false, SELECT_QUIET
);
527 } else if (actor
->IsSelected()) {
532 actor
->Select( true );
533 assert(actor
->IsSelected());
534 selected
.push_back( actor
);
536 if (!actor
->IsSelected()) {
537 // already not selected
540 /*for ( m = selected.begin(); m != selected.end(); ++m) {
541 assert((*m) != actor);
545 for ( m
= selected
.begin(); m
!= selected
.end(); ++m
) {
551 actor
->Select( false );
552 assert(!actor
->IsSelected());
555 if (! (flags
& SELECT_QUIET
)) {
556 core
->SetEventFlag(EF_SELECTION
);
562 // Gets average party level, of onlyalive is true, then counts only living PCs
563 int Game::GetPartyLevel(bool onlyalive
) const
566 for (unsigned int i
= 0; i
<PCs
.size(); i
++) {
568 if (PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
572 count
+= PCs
[i
]->GetXPLevel(0);
577 // Returns map structure (ARE) if it is already loaded in memory
578 int Game::FindMap(const char *ResRef
)
580 int index
= (int) Maps
.size();
582 Map
*map
=Maps
[index
];
583 if (strnicmp(ResRef
, map
->GetScriptName(), 8) == 0) {
590 Map
* Game::GetMap(unsigned int index
) const
592 if (index
>= Maps
.size()) {
598 Map
*Game::GetMap(const char *areaname
, bool change
)
600 int index
= LoadMap(areaname
);
604 area
= GetMap(index
);
605 memcpy (CurrentArea
, areaname
, 8);
606 area
->SetupAmbients();
607 //change the tileset if needed
608 area
->ChangeMap(IsDay());
609 ChangeSong(false, true);
613 return GetMap(index
);
618 bool Game::MasterArea(const char *area
)
620 unsigned int i
=(int) mastarea
.size();
622 if (strnicmp(mastarea
[i
], area
, 8) ) {
629 void Game::SetMasterArea(const char *area
)
631 if (MasterArea(area
) ) return;
632 char *tmp
= (char *) malloc(9);
633 strnlwrcpy (tmp
,area
,8);
634 mastarea
.push_back(tmp
);
637 int Game::AddMap(Map
* map
)
639 if (MasterArea(map
->GetScriptName()) ) {
640 Maps
.insert(Maps
.begin(), 1, map
);
644 unsigned int i
= (unsigned int) Maps
.size();
645 Maps
.push_back( map
);
649 int Game::DelMap(unsigned int index
, int forced
)
651 //this function should archive the area, and remove it only if the area
652 //contains no active actors (combat, partymembers, etc)
653 if (index
>= Maps
.size()) {
656 Map
*map
= Maps
[index
];
658 if (MapIndex
==(int) index
) { //can't remove current map in any case
659 const char *name
= map
->GetScriptName();
660 memcpy(AnotherArea
, name
, sizeof(AnotherArea
) );
665 if (!map
) { //this shouldn't happen, i guess
666 printMessage("Game","Erased NULL Map\n",YELLOW
);
667 Maps
.erase( Maps
.begin()+index
);
668 if (MapIndex
>(int) index
) {
674 if (forced
|| (Maps
.size()>MAX_MAPS_LOADED
) )
676 //keep at least one master
677 const char *name
= map
->GetScriptName();
678 if (MasterArea(name
)) {
679 if(!AnotherArea
[0]) {
680 memcpy(AnotherArea
, name
, sizeof(AnotherArea
));
686 //this check must be the last, because
687 //after PurgeActors you cannot keep the
689 //Or the queues should be regenerated!
694 //remove map from memory
695 core
->SwapoutArea(Maps
[index
]);
696 delete( Maps
[index
] );
697 Maps
.erase( Maps
.begin()+index
);
698 //current map will be decreased
699 if (MapIndex
>(int) index
) {
704 //didn't remove the map
709 int Game::LoadMap(const char* ResRef
)
712 int index
= FindMap(ResRef
);
717 bool hide
= core
->HideGCWindow();
718 core
->GetGUIScriptEngine()->RunFunction("LoadScreen", "StartLoadScreen");
719 core
->GetGUIScriptEngine()->RunFunction("LoadScreen", "SetLoadScreen");
720 DataStream
* ds
= gamedata
->GetResource( ResRef
, IE_ARE_CLASS_ID
);
722 core
->LoadProgress(100);
725 PluginHolder
<MapMgr
> mM(IE_ARE_CLASS_ID
);
726 if(!mM
->Open( ds
, true )) {
727 core
->LoadProgress(100);
730 Map
* newMap
= mM
->GetMap(ResRef
, IsDay());
732 core
->LoadProgress(100);
735 core
->LoadProgress(100);
737 for (i
= 0; i
< PCs
.size(); i
++) {
738 if (stricmp( PCs
[i
]->Area
, ResRef
) == 0) {
739 newMap
->AddActor( PCs
[i
] );
742 for (i
= 0; i
< NPCs
.size(); i
++) {
743 if (stricmp( NPCs
[i
]->Area
, ResRef
) == 0) {
744 newMap
->AddActor( NPCs
[i
] );
748 core
->UnhideGCWindow();
750 return AddMap( newMap
);
753 int Game::AddNPC(Actor
* npc
)
755 int slot
= InStore( npc
); //already an npc
759 slot
= InParty( npc
);
762 } //can't add as npc already in party
763 npc
->SetPersistent(0);
764 NPCs
.push_back( npc
);
766 return (int) NPCs
.size() - 1;
769 Actor
* Game::GetNPC(unsigned int Index
)
771 if (Index
>= NPCs
.size()) {
777 void Game::SwapPCs(unsigned int Index1
, unsigned int Index2
)
779 if (Index1
>= PCs
.size()) {
783 if (Index2
>= PCs
.size()) {
786 int tmp
= PCs
[Index1
]->InParty
;
787 PCs
[Index1
]->InParty
= PCs
[Index2
]->InParty
;
788 PCs
[Index2
]->InParty
= tmp
;
789 //signal a change of the portrait window
790 core
->SetEventFlag(EF_PORTRAIT
);
793 void Game::DeleteJournalEntry(ieStrRef strref
)
795 size_t i
=Journals
.size();
797 if (Journals
[i
]->Text
==strref
) {
799 Journals
.erase(Journals
.begin()+i
);
804 void Game::DeleteJournalGroup(int Group
)
806 size_t i
=Journals
.size();
808 if (Journals
[i
]->Group
==(ieByte
) Group
) {
810 Journals
.erase(Journals
.begin()+i
);
814 /* returns true if it modified or added a journal entry */
815 bool Game::AddJournalEntry(ieStrRef strref
, int Section
, int Group
)
817 GAMJournalEntry
*je
= FindJournalEntry(strref
);
819 //don't set this entry again in the same section
820 if (je
->Section
==Section
) {
823 if ((Section
== IE_GAM_QUEST_DONE
) && Group
) {
824 //removing all of this group and adding a new entry
825 DeleteJournalGroup(Group
);
827 //modifying existing entry
828 je
->Section
= (ieByte
) Section
;
829 je
->Group
= (ieByte
) Group
;
831 locals
->Lookup("CHAPTER", chapter
);
832 je
->Chapter
= (ieByte
) chapter
;
833 je
->GameTime
= GameTime
;
837 je
= new GAMJournalEntry
;
838 je
->GameTime
= GameTime
;
840 locals
->Lookup("CHAPTER", chapter
);
841 je
->Chapter
= (ieByte
) chapter
;
843 je
->Section
= (ieByte
) Section
;
844 je
->Group
= (ieByte
) Group
;
847 Journals
.push_back( je
);
851 void Game::AddJournalEntry(GAMJournalEntry
* entry
)
853 Journals
.push_back( entry
);
856 unsigned int Game::GetJournalCount() const
858 return (unsigned int) Journals
.size();
861 GAMJournalEntry
* Game::FindJournalEntry(ieStrRef strref
)
863 unsigned int Index
= (unsigned int) Journals
.size();
865 GAMJournalEntry
*ret
= Journals
[Index
];
867 if (ret
->Text
==strref
) {
875 GAMJournalEntry
* Game::GetJournalEntry(unsigned int Index
)
877 if (Index
>= Journals
.size()) {
880 return Journals
[Index
];
883 unsigned int Game::GetSavedLocationCount() const
885 return (unsigned int) savedpositions
.size();
888 void Game::ClearSavedLocations()
890 size_t i
=savedpositions
.size();
892 delete savedpositions
[i
];
894 savedpositions
.clear();
897 GAMLocationEntry
* Game::GetSavedLocationEntry(unsigned int i
)
899 size_t current
= savedpositions
.size();
904 savedpositions
.resize(i
+1);
906 savedpositions
[current
++]=(GAMLocationEntry
*) calloc(1, sizeof(GAMLocationEntry
) );
909 return savedpositions
[i
];
912 unsigned int Game::GetPlaneLocationCount() const
914 return (unsigned int) planepositions
.size();
917 void Game::ClearPlaneLocations()
919 size_t i
=planepositions
.size();
921 delete planepositions
[i
];
923 planepositions
.clear();
926 GAMLocationEntry
* Game::GetPlaneLocationEntry(unsigned int i
)
928 size_t current
= planepositions
.size();
933 planepositions
.resize(i
+1);
935 planepositions
[current
++]=(GAMLocationEntry
*) calloc(1, sizeof(GAMLocationEntry
) );
938 return planepositions
[i
];
941 char *Game::GetFamiliar(unsigned int Index
)
943 return Familiars
[Index
];
946 //reading the challenge rating table for iwd2 (only when needed)
947 void Game::LoadCRTable()
949 AutoTable
table("moncrate");
951 int maxrow
= table
->GetRowCount()-1;
952 crtable
= new CRRow
[MAX_LEVEL
];
953 for(int i
=0;i
<MAX_LEVEL
;i
++) {
954 //row shouldn't be larger than maxrow
955 int row
= i
<maxrow
?i
:maxrow
;
956 int maxcol
= table
->GetColumnCount(row
)-1;
957 for(int j
=0;j
<MAX_CRLEVEL
;j
++) {
958 //col shouldn't be larger than maxcol
959 int col
= j
<maxcol
?j
:maxcol
;
960 crtable
[i
][j
]=atoi(table
->QueryField(row
,col
) );
966 int Game::GetXPFromCR(int cr
)
968 if (!crtable
) LoadCRTable();
970 int level
= GetPartyLevel(true);
971 if (cr
>=MAX_CRLEVEL
) {
974 printf("Challenge Rating: %d, party level: %d ", cr
, level
);
975 return crtable
[level
][cr
];
977 printMessage("Game","Cannot find moncrate.2da!\n", LIGHT_RED
);
981 void Game::ShareXP(int xp
, int flags
)
986 xp
= GetXPFromCR(xp
);
989 if (flags
&SX_DIVIDE
) {
990 int PartySize
= GetPartySize(true); //party size, only alive
994 individual
= xp
/ PartySize
;
1004 displaymsg
->DisplayConstantStringValue( STR_GOTXP
, 0xbcefbc, (ieDword
) xp
); //you have gained ... xp
1006 displaymsg
->DisplayConstantStringValue( STR_LOSTXP
, 0xbcefbc, (ieDword
) -xp
); //you have lost ... xp
1008 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1009 if (PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
1012 PCs
[i
]->AddExperience(individual
);
1016 bool Game::EveryoneStopped() const
1018 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1019 if (PCs
[i
]->GetNextStep() ) return false;
1024 //canmove=true: if some PC can't move (or hostile), then this returns false
1025 bool Game::EveryoneNearPoint(Map
*area
, const Point
&p
, int flags
) const
1027 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1028 if (flags
&ENP_ONLYSELECT
) {
1029 if(!PCs
[i
]->Selected
) {
1033 if (PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
1036 if (flags
&ENP_CANMOVE
) {
1037 //someone is uncontrollable, can't move
1038 if (PCs
[i
]->GetStat(IE_EA
)>EA_GOODCUTOFF
) {
1042 if (PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_CANTMOVE
) {
1046 if (PCs
[i
]->GetCurrentArea()!=area
) {
1049 if (Distance(p
,PCs
[i
])>MAX_TRAVELING_DISTANCE
) {
1056 //called when someone died
1057 void Game::PartyMemberDied(Actor
*actor
)
1059 //this could be null, in some extreme cases...
1060 Map
*area
= actor
->GetCurrentArea();
1062 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1063 if (PCs
[i
]==actor
) {
1066 if (PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
1069 if (PCs
[i
]->GetCurrentArea()!=area
) {
1072 PCs
[i
]->ReactToDeath(actor
->GetScriptName());
1076 //reports if someone died
1077 int Game::PartyMemberDied() const
1079 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1080 if (PCs
[i
]->GetInternalFlag()&IF_JUSTDIED
) {
1087 void Game::IncrementChapter()
1089 //chapter first set to 0 (prologue)
1090 ieDword chapter
= (ieDword
) -1;
1091 locals
->Lookup("CHAPTER",chapter
);
1092 locals
->SetAt("CHAPTER",chapter
+1);
1094 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1095 //all PCs must have this!
1096 PCs
[i
]->PCStats
->IncrementChapter();
1100 void Game::SetReputation(ieDword r
)
1103 else if (r
>200) r
=200;
1105 displaymsg
->DisplayConstantStringValue(STR_LOSTREP
,0xc0c000,(Reputation
-r
)/10);
1106 } else if (Reputation
<r
) {
1107 displaymsg
->DisplayConstantStringValue(STR_GOTREP
,0xc0c000,(r
-Reputation
)/10);
1110 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1111 PCs
[i
]->SetBase(IE_REPUTATION
, Reputation
);
1115 void Game::SetControlStatus(int value
, int mode
)
1118 case BM_OR
: ControlStatus
|=value
; break;
1119 case BM_NAND
: ControlStatus
&=~value
; break;
1120 case BM_SET
: ControlStatus
=value
; break;
1121 case BM_AND
: ControlStatus
&=value
; break;
1122 case BM_XOR
: ControlStatus
^=value
; break;
1124 core
->SetEventFlag(EF_CONTROL
);
1127 void Game::AddGold(ieDword add
)
1136 if (old
<PartyGold
) {
1137 displaymsg
->DisplayConstantStringValue( STR_GOTGOLD
, 0xc0c000, PartyGold
-old
);
1139 displaymsg
->DisplayConstantStringValue( STR_LOSTGOLD
, 0xc0c000, old
-PartyGold
);
1143 //later this could be more complicated
1144 void Game::AdvanceTime(ieDword add
)
1146 ieDword h
= GameTime
/(300*AI_UPDATE_TIME
);
1148 if (h
!=GameTime
/(300*AI_UPDATE_TIME
)) {
1149 //asking for a new weather when the hour changes
1150 WeatherBits
&=~WB_HASWEATHER
;
1152 Ticks
+=add
*interval
;
1153 //change the tileset if needed
1154 Map
*map
= GetCurrentArea();
1155 if (map
&& map
->ChangeMap(IsDay())) {
1156 //play the daylight transition movie appropriate for the area
1157 //it is needed to play only when the area truly changed its tileset
1158 //this is signalled by ChangeMap
1159 int areatype
= (area
->AreaType
&(AT_FOREST
|AT_CITY
|AT_DUNGEON
))>>3;
1162 printMessage("Game","Switching DayLight\n",GREEN
);
1164 res
=&nightmovies
[areatype
];
1166 res
=&daymovies
[areatype
];
1169 core
->PlayMovie(*res
);
1174 //returns true if there are excess players in the team
1175 bool Game::PartyOverflow() const
1177 GameControl
*gc
= core
->GetGameControl();
1181 //don't start this screen when the gui is busy
1182 if (gc
->GetDialogueFlags() & (DF_IN_DIALOG
|DF_IN_CONTAINER
|DF_FREEZE_SCRIPTS
) ) {
1188 return (PCs
.size()>partysize
);
1191 bool Game::PCInCombat(Actor
* actor
) const
1193 if (!CombatCounter
) {
1197 if (actor
->LastTarget
) {
1200 if (AttackersOf(actor
->GetID(), actor
->GetCurrentArea())) {
1206 bool Game::AnyPCInCombat() const
1208 if (!CombatCounter
) {
1212 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1213 if (PCInCombat (PCs
[i
])) {
1220 //returns true if the protagonist (or the whole party died)
1221 bool Game::EveryoneDead() const
1223 //if there are no PCs, then we assume everyone dead
1227 if (protagonist
==PM_NO
) {
1228 Actor
*nameless
= PCs
[0];
1229 if (nameless
->GetStat(IE_STATE_ID
)&STATE_NOSAVE
) {
1230 if (area
->INISpawn
) {
1231 area
->INISpawn
->RespawnNameless();
1236 // if protagonist died
1237 if (protagonist
==PM_YES
) {
1238 if (PCs
[0]->GetStat(IE_STATE_ID
)&STATE_NOSAVE
) {
1244 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1245 if (!(PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_NOSAVE
) ) {
1252 //runs all area scripts
1254 void Game::UpdateScripts()
1257 ProcessActions(false);
1260 bool PartyAttack
= false;
1262 for (idx
=0;idx
<Maps
.size();idx
++) {
1263 Maps
[idx
]->UpdateScripts();
1264 size_t acnt
=Attackers
.size();
1266 Actor
*actor
= Maps
[idx
]->GetActorByGlobalID(Attackers
[acnt
]);
1268 if ( !Maps
[idx
]->GetActorByGlobalID(actor
->LastTarget
) ) {
1269 //Actor's target left area
1270 OutAttack(Attackers
[acnt
]);
1273 //each attacker handles their own round initiation
1274 actor
->InitRound(GameTime
);
1275 if (actor
->InParty
) {
1280 //Attacker is gone from area
1281 OutAttack(Attackers
[acnt
]);
1287 //ChangeSong will set the battlesong only if CombatCounter is nonzero
1289 ChangeSong(false, true);
1291 if (CombatCounter
) {
1293 //Change song if combatcounter went down to 0
1294 if (!CombatCounter
) {
1295 ChangeSong(false, false);
1300 if (StateOverrideTime
)
1301 StateOverrideTime
--;
1302 if (BanterBlockTime
)
1305 if (Maps
.size()>MAX_MAPS_LOADED
) {
1308 //starting from 0, so we see the most recent master area first
1309 for(unsigned int i
=0;i
<idx
;i
++) {
1310 DelMap( (unsigned int) i
, false );
1314 // perhaps a StartMusic action stopped the area music?
1315 // (we should probably find a less silly way to handle this,
1316 // because nothing can ever stop area music now..)
1317 if (!core
->GetMusicMgr()->IsPlaying()) {
1318 ChangeSong(false,false);
1321 //this is used only for the death delay so far
1322 if (event_handler
) {
1324 event_handler
->call();
1325 event_handler
= NULL
;
1330 if (EveryoneDead()) {
1331 //don't check it any more
1332 protagonist
= PM_NO
;
1333 core
->GetGUIScriptEngine()->RunFunction("GUIWORLD", "DeathWindow");
1337 if (PartyOverflow()) {
1339 core
->GetGUIScriptEngine()->RunFunction("GUIWORLD", "OpenReformPartyWindow");
1344 void Game::SetTimedEvent(EventHandler func
, int count
)
1346 event_timer
= count
;
1347 event_handler
= func
;
1350 void Game::SetProtagonistMode(int mode
)
1355 void Game::SetPartySize(int size
)
1357 // 0 size means no party size control
1361 partysize
= (size_t) size
;
1364 //Get the area dependent rest movie
1365 ieResRef
*Game::GetDream(Map
*area
)
1367 //select dream based on area
1368 int daynight
= IsDay();
1369 if (area
->Dream
[daynight
][0]) {
1370 return area
->Dream
+daynight
;
1372 int dream
= (area
->AreaType
&(AT_FOREST
|AT_CITY
|AT_DUNGEON
))>>3;
1373 return restmovies
+dream
;
1376 //Start dream cutscenes for player1
1377 void Game::PlayerDream()
1379 Scriptable
*Sender
= GetPC(0,true);
1380 if (!Sender
) return;
1382 GameScript
* gs
= new GameScript( "player1d", Sender
,0,0 );
1387 //noareacheck = no random encounters
1388 //dream = 0 - based on area non-0 - select from list
1390 //hp is how much hp the rest will heal
1391 void Game::RestParty(int checks
, int dream
, int hp
)
1393 if (!(checks
&REST_NOMOVE
) ) {
1394 if (!EveryoneStopped()) {
1398 Actor
*leader
= GetPC(0, true);
1403 Map
*area
= leader
->GetCurrentArea();
1404 //we let them rest if someone is paralyzed, but the others gather around
1405 if (!(checks
&REST_NOSCATTER
) ) {
1406 if (!EveryoneNearPoint( area
, leader
->Pos
, 0 ) ) {
1407 //party too scattered
1408 displaymsg
->DisplayConstantString( STR_SCATTERED
, 0xff0000 );
1413 if (!(checks
&REST_NOCRITTER
) ) {
1414 //don't allow resting while in combat
1415 if (AnyPCInCombat()) {
1416 displaymsg
->DisplayConstantString( STR_CANTRESTMONS
, 0xff0000 );
1419 //don't allow resting if hostiles are nearby
1420 if (area
->AnyEnemyNearPoint(leader
->Pos
)) {
1421 displaymsg
->DisplayConstantString( STR_CANTRESTMONS
, 0xff0000 );
1426 //rest check, if PartyRested should be set, area should return true
1427 //area should advance gametime too (so partial rest is possible)
1429 if (!(checks
&REST_NOAREA
) ) {
1430 //you cannot rest here
1431 if (area
->AreaFlags
&1) {
1432 displaymsg
->DisplayConstantString( STR_MAYNOTREST
, 0xff0000 );
1435 //you may not rest here, find an inn
1436 if (!(area
->AreaType
&(AT_OUTDOOR
|AT_FOREST
|AT_DUNGEON
|AT_CAN_REST
) ))
1438 displaymsg
->DisplayConstantString( STR_MAYNOTREST
, 0xff0000 );
1442 if(area
->Rest( leader
->Pos
, 8, (GameTime
/AI_UPDATE_TIME
)%7200/3600) ) {
1446 AdvanceTime(2400*AI_UPDATE_TIME
);
1448 int i
= GetPartySize(true); // party size, only alive
1451 Actor
*tar
= GetPC(i
, true);
1453 tar
->ClearActions();
1454 tar
->SetModal(MS_NONE
, 0);
1455 //if hp = 0, then healing will be complete
1457 //removes fatigue, recharges spells
1462 //movie and cutscene dreams
1465 if (gamedata
->Exists("player1d",IE_BCS_CLASS_ID
, true))
1468 //select dream based on area
1470 if (dream
==0 || dream
>7) {
1471 movie
= GetDream(area
);
1473 movie
= restmovies
+dream
;
1475 if (*movie
[0]!='*') {
1476 core
->PlayMovie(*movie
);
1480 //set partyrested flags
1482 area
->PartyRested();
1483 core
->SetEventFlag(EF_ACTION
);
1485 //restindex will be -1 in the case of PST
1486 //FIXME: I don't quite see why we can't sumply use the same strings.2da entry
1487 //It seems we could reduce complexity here, and free up 2-3 string slots too
1488 int restindex
= displaymsg
->GetStringReference(STR_REST
);
1490 char* tmpstr
= NULL
;
1492 core
->GetTokenDictionary()->SetAtCopy("HOUR", hours
);
1493 if (restindex
!= -1) {
1494 strindex
= displaymsg
->GetStringReference(STR_HOURS
);
1496 strindex
= displaymsg
->GetStringReference(STR_PST_HOURS
);
1497 restindex
= displaymsg
->GetStringReference(STR_PST_REST
);
1501 if (strindex
== -1 || restindex
== -1) return;
1502 tmpstr
= core
->GetString(strindex
, 0);
1504 if (!tmpstr
) return;
1506 core
->GetTokenDictionary()->SetAtCopy("DURATION", tmpstr
);
1507 core
->FreeString(tmpstr
);
1508 displaymsg
->DisplayString(restindex
, 0xffffff, 0);
1512 void Game::TimeStop(Actor
* owner
, ieDword end
)
1514 timestop_owner
=owner
;
1515 timestop_end
=GameTime
+end
;
1518 //recalculate the party's infravision state
1519 void Game::Infravision()
1522 Map
*map
= GetCurrentArea();
1524 for(size_t i
=0;i
<PCs
.size();i
++) {
1525 Actor
*actor
= PCs
[i
];
1526 if (!IsAlive(actor
)) continue;
1527 if (actor
->GetCurrentArea()!=map
) continue;
1528 //Group infravision overrides this???
1529 if (!actor
->Selected
) continue;
1530 if (actor
->GetStat(IE_STATE_ID
) & STATE_INFRA
) {
1537 //returns the colour which should be applied onto the whole game area viewport
1538 //this is based on timestop, dream area, weather, daytime
1540 static const Color TimeStopTint
={0xe0,0xe0,0xe0,0x20}; //greyscale
1541 static const Color DreamTint
={0xf0,0xe0,0xd0,0x10}; //light brown scale
1542 static const Color NightTint
={0x80,0x80,0xe0,0x40}; //dark, bluish
1543 static const Color DuskTint
={0xe0,0x80,0x80,0x40}; //dark, reddish
1544 static const Color FogTint
={0xff,0xff,0xff,0x40}; //whitish
1545 static const Color DarkTint
={0x80,0x80,0xe0,0x10}; //slightly dark bluish
1547 const Color
*Game::GetGlobalTint() const
1549 if (timestop_end
>GameTime
) {
1550 return &TimeStopTint
;
1552 Map
*map
= GetCurrentArea();
1553 if (!map
) return NULL
;
1554 if (map
->AreaFlags
&AF_DREAM
) {
1557 if ((map
->AreaType
&(AT_OUTDOOR
|AT_DAYNIGHT
|AT_EXTENDED_NIGHT
)) == (AT_OUTDOOR
|AT_DAYNIGHT
) ) {
1558 //get daytime colour
1559 ieDword daynight
= ((GameTime
/AI_UPDATE_TIME
)%7200/300);
1560 if (daynight
<2 || daynight
>22) {
1563 if (daynight
>20 || daynight
<4) {
1567 if ((map
->AreaType
&(AT_OUTDOOR
|AT_WEATHER
)) == (AT_OUTDOOR
|AT_WEATHER
)) {
1569 if (WeatherBits
&WB_RAIN
) {
1572 if (WeatherBits
&WB_FOG
) {
1582 ieDword daynight
= ((GameTime
/AI_UPDATE_TIME
)%7200/300);
1583 if(daynight
<4 || daynight
>20) {
1589 void Game::InAttack(ieDword globalID
)
1591 std::vector
< ieDword
>::const_iterator idx
;
1593 for(idx
=Attackers
.begin(); idx
!=Attackers
.end();idx
++) {
1594 if (*idx
==globalID
) return;
1596 Attackers
.push_back(globalID
);
1599 void Game::OutAttack(ieDword globalID
)
1601 std::vector
< ieDword
>::iterator idx
;
1603 for(idx
=Attackers
.begin(); idx
!=Attackers
.end();idx
++) {
1604 if (*idx
==globalID
) {
1605 Attackers
.erase(idx
);
1611 void Game::ChangeSong(bool always
, bool force
)
1615 if (CombatCounter
) {
1619 //will select SONG_DAY or SONG_NIGHT
1620 Song
= (GameTime
/AI_UPDATE_TIME
)%7200/3600;
1622 //area may override the song played (stick in battlemusic)
1623 //always transition gracefully with ChangeSong
1624 //force just means, we schedule the song for later, if currently
1626 area
->PlayAreaSong( Song
, always
, force
);
1629 int Game::AttackersOf(ieDword globalID
, Map
*area
) const
1634 std::vector
< ieDword
>::const_iterator idx
;
1637 for(idx
=Attackers
.begin(); idx
!=Attackers
.end();idx
++) {
1638 Actor
* actor
= area
->GetActorByGlobalID(*idx
);
1640 if (actor
->LastTarget
==globalID
) {
1648 /* this method redraws weather. If update is false,
1649 then the weather particles won't change (game paused)
1651 void Game::DrawWeather(const Region
&screen
, bool update
)
1656 if (!area
->HasWeather()) {
1660 weather
->Draw( screen
);
1665 if (!(WeatherBits
& (WB_RAIN
|WB_SNOW
)) ) {
1666 if (weather
->GetPhase() == P_GROW
) {
1667 weather
->SetPhase(P_FADE
);
1671 int drawn
= weather
->Update();
1673 WeatherBits
&= ~WB_START
;
1677 if (WeatherBits
&WB_HASWEATHER
) {
1680 StartRainOrSnow(true, area
->GetWeather());
1683 /* sets the weather type */
1684 void Game::StartRainOrSnow(bool conditional
, int w
)
1686 if (conditional
&& (w
& (WB_RAIN
|WB_SNOW
)) ) {
1687 if (WeatherBits
& (WB_RAIN
| WB_SNOW
) )
1690 // whatever was responsible for calling this, we now have some set weather
1691 WeatherBits
= w
| WB_HASWEATHER
;
1692 if (w
& WB_LIGHTNING
) {
1693 if (WeatherBits
&WB_START
) {
1696 core
->PlaySound(DS_LIGHTNING1
);
1698 core
->PlaySound(DS_LIGHTNING2
);
1701 //start raining (far)
1702 core
->PlaySound(DS_LIGHTNING3
);
1706 core
->PlaySound(DS_SNOW
);
1707 weather
->SetType(SP_TYPE_POINT
, SP_PATH_FLIT
, SP_SPAWN_SOME
);
1708 weather
->SetPhase(P_GROW
);
1709 weather
->SetColor(SPARK_COLOR_WHITE
);
1713 core
->PlaySound(DS_RAIN
);
1714 weather
->SetType(SP_TYPE_LINE
, SP_PATH_RAIN
, SP_SPAWN_SOME
);
1715 weather
->SetPhase(P_GROW
);
1716 weather
->SetColor(SPARK_COLOR_STONE
);
1719 weather
->SetPhase(P_FADE
);
1722 void Game::SetExpansion(ieDword value
)
1724 if (Expansion
>=value
) {
1731 core
->SetEventFlag(EF_EXPANSION
);
1733 //TODO: move this hardcoded hack to the scripts
1735 core
->GetDictionary()->SetAt( "PlayMode", 2 );
1737 int i
= GetPartySize(false);
1739 Actor
*actor
= GetPC(i
, false);
1740 InitActorPos(actor
);
1745 void Game::DebugDump()
1749 printf("Currently loaded areas:\n");
1750 for(idx
=0;idx
<Maps
.size();idx
++) {
1751 Map
*map
= Maps
[idx
];
1753 printf("%s\n",map
->GetScriptName());
1755 printf("Current area: %s Previous area: %s\n", CurrentArea
, PreviousArea
);
1756 printf("Global script: %s\n", Scripts
[0]->GetName());
1757 printf("CombatCounter: %d\n", (int) CombatCounter
);
1758 printf("Attackers count: %d\n", (int) Attackers
.size());
1759 for(idx
=0;idx
<Attackers
.size(); idx
++) {
1760 Actor
*actor
= GetActorByGlobalID(Attackers
[idx
]);
1762 printf("Name: ???\n");
1765 Actor
*whom
= GetActorByGlobalID(actor
->LastTarget
);
1766 printf("Name: %s Attacking : %s\n", actor
->ShortName
, whom
?whom
->ShortName
:"???");
1769 printf("Party size: %d\n", (int) PCs
.size());
1770 for(idx
=0;idx
<PCs
.size();idx
++) {
1771 Actor
*actor
= PCs
[idx
];
1773 printf("Name: %s Order %d %s\n",actor
->ShortName
, actor
->InParty
, actor
->Selected
?"x":"-");
1777 Actor
*Game::GetActorByGlobalID(ieWord objectID
)
1779 size_t mc
= GetLoadedMapCount();
1781 Map
*map
= GetMap(mc
);
1782 Actor
*actor
= map
->GetActorByGlobalID(objectID
);
1783 if (actor
) return actor
;