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::GetGlobalActorByGlobalID(ieDword globalID
)
209 for (slot
=0; slot
<PCs
.size(); slot
++) {
210 if (PCs
[slot
]->GetGlobalID()==globalID
) {
214 for (slot
=0; slot
<NPCs
.size(); slot
++) {
215 if (NPCs
[slot
]->GetGlobalID()==globalID
) {
222 Actor
* Game::GetPC(unsigned int slot
, bool onlyalive
)
224 if (slot
>= PCs
.size()) {
229 while(i
<PCs
.size() ) {
230 Actor
*ac
= PCs
[i
++];
243 int Game::InStore(Actor
* pc
) const
245 for (unsigned int i
= 0; i
< NPCs
.size(); i
++) {
253 int Game::InParty(Actor
* pc
) const
255 for (unsigned int i
= 0; i
< PCs
.size(); i
++) {
263 int Game::DelPC(unsigned int slot
, bool autoFree
)
265 if (slot
>= PCs
.size()) {
271 SelectActor(PCs
[slot
], false, SELECT_NORMAL
);
275 std::vector
< Actor
*>::iterator m
= PCs
.begin() + slot
;
280 int Game::DelNPC(unsigned int slot
, bool autoFree
)
282 if (slot
>= NPCs
.size()) {
289 delete( NPCs
[slot
] );
291 std::vector
< Actor
*>::iterator m
= NPCs
.begin() + slot
;
296 //i'm sure this could be faster
297 void Game::ConsolidateParty()
299 int max
= (int) PCs
.size();
300 std::vector
< Actor
*>::const_iterator m
;
301 for (int i
=1;i
<=max
;) {
302 if (FindPlayer(i
)==-1) {
304 for ( m
= PCs
.begin(); m
!= PCs
.end(); ++m
) {
305 if ( (*m
)->InParty
>i
) {
311 for ( m
= PCs
.begin(); m
!= PCs
.end(); ++m
) {
312 (*m
)->RefreshEffects(NULL
);
316 int Game::LeaveParty (Actor
* actor
)
318 core
->SetEventFlag(EF_PORTRAIT
);
319 actor
->CreateStats(); //create or update stats for leaving
320 actor
->SetBase(IE_EXPLORE
, 0);
322 SelectActor(actor
, false, SELECT_NORMAL
);
323 int slot
= InParty( actor
);
327 std::vector
< Actor
*>::iterator m
= PCs
.begin() + slot
;
330 ieDword id
= actor
->GetGlobalID();
331 for ( m
= PCs
.begin(); m
!= PCs
.end(); ++m
) {
332 (*m
)->PCStats
->LastLeft
= id
;
333 if ( (*m
)->InParty
>actor
->InParty
) {
337 //removing from party, but actor remains in 'game'
338 actor
->SetPersistent(0);
339 NPCs
.push_back( actor
);
341 if (core
->HasFeature( GF_HAS_DPLAYER
)) {
342 actor
->SetScript( "", SCR_DEFAULT
);
344 actor
->SetBase( IE_EA
, EA_NEUTRAL
);
345 return ( int ) NPCs
.size() - 1;
348 //determines if startpos.2da has rotation rows (it cannot have tutorial line)
349 bool Game::DetermineStartPosType(const TableMgr
*strta
)
351 if ((strta
->GetRowCount()>=6) && !stricmp(strta
->GetRowName(4),"START_ROT" ) )
358 #define PMODE_COUNT 3
360 void Game::InitActorPos(Actor
*actor
)
362 //start.2da row labels
363 const char *mode
[PMODE_COUNT
] = { "NORMAL", "TUTORIAL", "EXPANSION" };
365 unsigned int ip
= (unsigned int) (actor
->InParty
-1);
366 AutoTable
start("start");
367 AutoTable
strta("startpos");
368 // 0 - single player, 1 - tutorial, 2 - expansion
369 ieDword playmode
= 0;
370 core
->GetDictionary()->Lookup( "PlayMode", playmode
);
372 //Sometimes playmode is set to -1 (in pregenerate)
373 //normally execution shouldn't ever come here, but it actually does
374 //preventing problems by defaulting to the regular entry points
375 if (playmode
>PMODE_COUNT
) {
378 const char *xpos
= start
->QueryField(mode
[playmode
],"XPOS");
379 const char *ypos
= start
->QueryField(mode
[playmode
],"YPOS");
380 const char *area
= start
->QueryField(mode
[playmode
],"AREA");
381 const char *rot
= start
->QueryField(mode
[playmode
],"ROT");
383 actor
->Pos
.x
= actor
->Destination
.x
= (short) atoi( strta
->QueryField( strta
->GetRowIndex(xpos
), ip
) );
384 actor
->Pos
.y
= actor
->Destination
.y
= (short) atoi( strta
->QueryField( strta
->GetRowIndex(ypos
), ip
) );
385 actor
->SetOrientation( atoi( strta
->QueryField( strta
->GetRowIndex(rot
), ip
) ), false );
387 strta
.load("startare");
388 strnlwrcpy(actor
->Area
, strta
->QueryField( strta
->GetRowIndex(area
), 0 ), 8 );
391 int Game::JoinParty(Actor
* actor
, int join
)
393 core
->SetEventFlag(EF_PORTRAIT
);
394 actor
->CreateStats(); //create stats if they didn't exist yet
395 actor
->InitButtons(actor
->GetStat(IE_CLASS
), false); //init actor's buttons
396 actor
->SetBase(IE_EXPLORE
, 1);
397 if (join
&JP_INITPOS
) {
400 int slot
= InParty( actor
);
404 size_t size
= PCs
.size();
405 //set the lastjoined trigger
408 //update kit abilities of actor
409 actor
->ApplyKit(false);
410 //update the quickslots
411 actor
->ReinitQuickSlots();
412 //set the joining date
413 actor
->PCStats
->JoinDate
= GameTime
;
415 ieDword id
= actor
->GetGlobalID();
416 for (size_t i
=0;i
<size
; i
++) {
417 Actor
*a
= GetPC(i
, false);
418 a
->PCStats
->LastJoined
= id
;
421 Reputation
= actor
->GetStat(IE_REPUTATION
);
424 slot
= InStore( actor
);
426 std::vector
< Actor
*>::iterator m
= NPCs
.begin() + slot
;
431 PCs
.push_back( actor
);
432 if (!actor
->InParty
) {
433 actor
->InParty
= (ieByte
) (size
+1);
436 if (join
&(JP_INITPOS
|JP_SELECT
)) {
437 actor
->Selected
= 0; // don't confuse SelectActor!
438 SelectActor(actor
,true, SELECT_NORMAL
);
444 int Game::GetPartySize(bool onlyalive
) const
448 for (unsigned int i
= 0; i
< PCs
.size(); i
++) {
449 if (!IsAlive(PCs
[i
])) {
456 return (int) PCs
.size();
459 /* sends the hotkey trigger to all selected actors */
460 void Game::SetHotKey(unsigned long Key
)
462 std::vector
< Actor
*>::const_iterator m
;
464 for ( m
= selected
.begin(); m
!= selected
.end(); ++m
) {
467 if (actor
->IsSelected()) {
468 actor
->HotKey
= (ieDword
) Key
;
473 bool Game::SelectPCSingle(int index
)
475 Actor
* actor
= FindPC( index
);
476 if (!actor
|| ! actor
->ValidTarget( GA_NO_HIDDEN
))
479 SelectedSingle
= index
;
483 int Game::GetSelectedPCSingle() const
485 return SelectedSingle
;
489 * SelectActor() - handle (de)selecting actors.
490 * If selection was changed, runs "SelectionChanged" handler
492 * actor - either specific actor, or NULL for all
493 * select - whether actor(s) should be selected or deselected
495 * SELECT_REPLACE - if true, deselect all other actors when selecting one
496 * SELECT_QUIET - do not run handler if selection was changed. Used for
497 * nested calls to SelectActor()
500 bool Game::SelectActor(Actor
* actor
, bool select
, unsigned flags
)
502 std::vector
< Actor
*>::iterator m
;
504 // actor was not specified, which means all selectables should be (de)selected
506 for ( m
= selected
.begin(); m
!= selected
.end(); ++m
) {
507 (*m
)->Select( false );
508 (*m
)->SetOver( false );
513 area
->SelectActors();
515 for ( m = PCs.begin(); m != PCs.end(); ++m) {
519 SelectActor( *m, true, SELECT_QUIET );
524 if (! (flags
& SELECT_QUIET
)) {
525 core
->SetEventFlag(EF_SELECTION
);
531 // actor was specified, so we will work with him
533 if (! actor
->ValidTarget( GA_SELECT
| GA_NO_DEAD
))
536 // deselect all actors first when exclusive
537 if (flags
& SELECT_REPLACE
) {
538 if (selected
.size() == 1 && actor
->IsSelected()) {
539 assert(selected
[0] == actor
);
540 // already the only selected actor
543 SelectActor( NULL
, false, SELECT_QUIET
);
544 } else if (actor
->IsSelected()) {
549 actor
->Select( true );
550 assert(actor
->IsSelected());
551 selected
.push_back( actor
);
553 if (!actor
->IsSelected()) {
554 // already not selected
557 /*for ( m = selected.begin(); m != selected.end(); ++m) {
558 assert((*m) != actor);
562 for ( m
= selected
.begin(); m
!= selected
.end(); ++m
) {
568 actor
->Select( false );
569 assert(!actor
->IsSelected());
572 if (! (flags
& SELECT_QUIET
)) {
573 core
->SetEventFlag(EF_SELECTION
);
579 // Gets average party level, of onlyalive is true, then counts only living PCs
580 int Game::GetPartyLevel(bool onlyalive
) const
583 for (unsigned int i
= 0; i
<PCs
.size(); i
++) {
585 if (PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
589 count
+= PCs
[i
]->GetXPLevel(0);
594 // Returns map structure (ARE) if it is already loaded in memory
595 int Game::FindMap(const char *ResRef
)
597 int index
= (int) Maps
.size();
599 Map
*map
=Maps
[index
];
600 if (strnicmp(ResRef
, map
->GetScriptName(), 8) == 0) {
607 Map
* Game::GetMap(unsigned int index
) const
609 if (index
>= Maps
.size()) {
615 Map
*Game::GetMap(const char *areaname
, bool change
)
617 int index
= LoadMap(areaname
, change
);
621 area
= GetMap(index
);
622 memcpy (CurrentArea
, areaname
, 8);
623 area
->SetupAmbients();
624 //change the tileset if needed
625 area
->ChangeMap(IsDay());
626 ChangeSong(false, true);
630 return GetMap(index
);
635 bool Game::MasterArea(const char *area
)
637 unsigned int i
=(int) mastarea
.size();
639 if (strnicmp(mastarea
[i
], area
, 8) ) {
646 void Game::SetMasterArea(const char *area
)
648 if (MasterArea(area
) ) return;
649 char *tmp
= (char *) malloc(9);
650 strnlwrcpy (tmp
,area
,8);
651 mastarea
.push_back(tmp
);
654 int Game::AddMap(Map
* map
)
656 if (MasterArea(map
->GetScriptName()) ) {
657 Maps
.insert(Maps
.begin(), 1, map
);
661 unsigned int i
= (unsigned int) Maps
.size();
662 Maps
.push_back( map
);
666 int Game::DelMap(unsigned int index
, int forced
)
668 //this function should archive the area, and remove it only if the area
669 //contains no active actors (combat, partymembers, etc)
670 if (index
>= Maps
.size()) {
673 Map
*map
= Maps
[index
];
675 if (MapIndex
==(int) index
) { //can't remove current map in any case
676 const char *name
= map
->GetScriptName();
677 memcpy(AnotherArea
, name
, sizeof(AnotherArea
) );
682 if (!map
) { //this shouldn't happen, i guess
683 printMessage("Game","Erased NULL Map\n",YELLOW
);
684 Maps
.erase( Maps
.begin()+index
);
685 if (MapIndex
>(int) index
) {
691 if (forced
|| (Maps
.size()>MAX_MAPS_LOADED
) )
693 //keep at least one master
694 const char *name
= map
->GetScriptName();
695 if (MasterArea(name
)) {
696 if(!AnotherArea
[0]) {
697 memcpy(AnotherArea
, name
, sizeof(AnotherArea
));
703 //this check must be the last, because
704 //after PurgeActors you cannot keep the
706 //Or the queues should be regenerated!
711 //remove map from memory
712 core
->SwapoutArea(Maps
[index
]);
713 delete( Maps
[index
] );
714 Maps
.erase( Maps
.begin()+index
);
715 //current map will be decreased
716 if (MapIndex
>(int) index
) {
721 //didn't remove the map
726 int Game::LoadMap(const char* ResRef
, bool loadscreen
)
730 PluginHolder
<MapMgr
> mM(IE_ARE_CLASS_ID
);
732 //this shouldn't happen
737 int index
= FindMap(ResRef
);
744 hide
= core
->HideGCWindow();
745 core
->GetGUIScriptEngine()->RunFunction("LoadScreen", "StartLoadScreen");
746 core
->GetGUIScriptEngine()->RunFunction("LoadScreen", "SetLoadScreen");
748 DataStream
* ds
= gamedata
->GetResource( ResRef
, IE_ARE_CLASS_ID
);
752 if(!mM
->Open( ds
, true )) {
755 newMap
= mM
->GetMap(ResRef
, IsDay());
759 core
->LoadProgress(100);
761 for (i
= 0; i
< PCs
.size(); i
++) {
762 if (stricmp( PCs
[i
]->Area
, ResRef
) == 0) {
763 newMap
->AddActor( PCs
[i
] );
766 for (i
= 0; i
< NPCs
.size(); i
++) {
767 if (stricmp( NPCs
[i
]->Area
, ResRef
) == 0) {
768 newMap
->AddActor( NPCs
[i
] );
772 core
->UnhideGCWindow();
774 return AddMap( newMap
);
777 core
->UnhideGCWindow();
778 core
->LoadProgress(100);
782 int Game::AddNPC(Actor
* npc
)
784 int slot
= InStore( npc
); //already an npc
788 slot
= InParty( npc
);
791 } //can't add as npc already in party
792 npc
->SetPersistent(0);
793 NPCs
.push_back( npc
);
795 return (int) NPCs
.size() - 1;
798 Actor
* Game::GetNPC(unsigned int Index
)
800 if (Index
>= NPCs
.size()) {
806 void Game::SwapPCs(unsigned int Index1
, unsigned int Index2
)
808 if (Index1
>= PCs
.size()) {
812 if (Index2
>= PCs
.size()) {
815 int tmp
= PCs
[Index1
]->InParty
;
816 PCs
[Index1
]->InParty
= PCs
[Index2
]->InParty
;
817 PCs
[Index2
]->InParty
= tmp
;
818 //signal a change of the portrait window
819 core
->SetEventFlag(EF_PORTRAIT
| EF_SELECTION
);
822 void Game::DeleteJournalEntry(ieStrRef strref
)
824 size_t i
=Journals
.size();
826 if ((Journals
[i
]->Text
==strref
) || (strref
==(ieStrRef
) -1) ) {
828 Journals
.erase(Journals
.begin()+i
);
833 void Game::DeleteJournalGroup(int Group
)
835 size_t i
=Journals
.size();
837 if (Journals
[i
]->Group
==(ieByte
) Group
) {
839 Journals
.erase(Journals
.begin()+i
);
843 /* returns true if it modified or added a journal entry */
844 bool Game::AddJournalEntry(ieStrRef strref
, int Section
, int Group
)
846 GAMJournalEntry
*je
= FindJournalEntry(strref
);
848 //don't set this entry again in the same section
849 if (je
->Section
==Section
) {
852 if ((Section
== IE_GAM_QUEST_DONE
) && Group
) {
853 //removing all of this group and adding a new entry
854 DeleteJournalGroup(Group
);
856 //modifying existing entry
857 je
->Section
= (ieByte
) Section
;
858 je
->Group
= (ieByte
) Group
;
860 locals
->Lookup("CHAPTER", chapter
);
861 je
->Chapter
= (ieByte
) chapter
;
862 je
->GameTime
= GameTime
;
866 je
= new GAMJournalEntry
;
867 je
->GameTime
= GameTime
;
869 locals
->Lookup("CHAPTER", chapter
);
870 je
->Chapter
= (ieByte
) chapter
;
872 je
->Section
= (ieByte
) Section
;
873 je
->Group
= (ieByte
) Group
;
876 Journals
.push_back( je
);
880 void Game::AddJournalEntry(GAMJournalEntry
* entry
)
882 Journals
.push_back( entry
);
885 unsigned int Game::GetJournalCount() const
887 return (unsigned int) Journals
.size();
890 GAMJournalEntry
* Game::FindJournalEntry(ieStrRef strref
)
892 unsigned int Index
= (unsigned int) Journals
.size();
894 GAMJournalEntry
*ret
= Journals
[Index
];
896 if (ret
->Text
==strref
) {
904 GAMJournalEntry
* Game::GetJournalEntry(unsigned int Index
)
906 if (Index
>= Journals
.size()) {
909 return Journals
[Index
];
912 unsigned int Game::GetSavedLocationCount() const
914 return (unsigned int) savedpositions
.size();
917 void Game::ClearSavedLocations()
919 size_t i
=savedpositions
.size();
921 delete savedpositions
[i
];
923 savedpositions
.clear();
926 GAMLocationEntry
* Game::GetSavedLocationEntry(unsigned int i
)
928 size_t current
= savedpositions
.size();
933 savedpositions
.resize(i
+1);
935 savedpositions
[current
++]=(GAMLocationEntry
*) calloc(1, sizeof(GAMLocationEntry
) );
938 return savedpositions
[i
];
941 unsigned int Game::GetPlaneLocationCount() const
943 return (unsigned int) planepositions
.size();
946 void Game::ClearPlaneLocations()
948 size_t i
=planepositions
.size();
950 delete planepositions
[i
];
952 planepositions
.clear();
955 GAMLocationEntry
* Game::GetPlaneLocationEntry(unsigned int i
)
957 size_t current
= planepositions
.size();
962 planepositions
.resize(i
+1);
964 planepositions
[current
++]=(GAMLocationEntry
*) calloc(1, sizeof(GAMLocationEntry
) );
967 return planepositions
[i
];
970 char *Game::GetFamiliar(unsigned int Index
)
972 return Familiars
[Index
];
975 //reading the challenge rating table for iwd2 (only when needed)
976 void Game::LoadCRTable()
978 AutoTable
table("moncrate");
980 int maxrow
= table
->GetRowCount()-1;
981 crtable
= new CRRow
[MAX_LEVEL
];
982 for(int i
=0;i
<MAX_LEVEL
;i
++) {
983 //row shouldn't be larger than maxrow
984 int row
= i
<maxrow
?i
:maxrow
;
985 int maxcol
= table
->GetColumnCount(row
)-1;
986 for(int j
=0;j
<MAX_CRLEVEL
;j
++) {
987 //col shouldn't be larger than maxcol
988 int col
= j
<maxcol
?j
:maxcol
;
989 crtable
[i
][j
]=atoi(table
->QueryField(row
,col
) );
995 int Game::GetXPFromCR(int cr
)
997 if (!crtable
) LoadCRTable();
999 int level
= GetPartyLevel(true);
1000 if (cr
>=MAX_CRLEVEL
) {
1003 printf("Challenge Rating: %d, party level: %d ", cr
, level
);
1004 return crtable
[level
][cr
];
1006 printMessage("Game","Cannot find moncrate.2da!\n", LIGHT_RED
);
1010 void Game::ShareXP(int xp
, int flags
)
1015 xp
= GetXPFromCR(xp
);
1018 if (flags
&SX_DIVIDE
) {
1019 int PartySize
= GetPartySize(true); //party size, only alive
1023 individual
= xp
/ PartySize
;
1033 displaymsg
->DisplayConstantStringValue( STR_GOTXP
, 0xbcefbc, (ieDword
) xp
); //you have gained ... xp
1035 displaymsg
->DisplayConstantStringValue( STR_LOSTXP
, 0xbcefbc, (ieDword
) -xp
); //you have lost ... xp
1037 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1038 if (PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
1041 PCs
[i
]->AddExperience(individual
);
1045 bool Game::EveryoneStopped() const
1047 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1048 if (PCs
[i
]->GetNextStep() ) return false;
1053 //canmove=true: if some PC can't move (or hostile), then this returns false
1054 bool Game::EveryoneNearPoint(Map
*area
, const Point
&p
, int flags
) const
1056 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1057 if (flags
&ENP_ONLYSELECT
) {
1058 if(!PCs
[i
]->Selected
) {
1062 if (PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
1065 if (flags
&ENP_CANMOVE
) {
1066 //someone is uncontrollable, can't move
1067 if (PCs
[i
]->GetStat(IE_EA
)>EA_GOODCUTOFF
) {
1071 if (PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_CANTMOVE
) {
1075 if (PCs
[i
]->GetCurrentArea()!=area
) {
1078 if (Distance(p
,PCs
[i
])>MAX_TRAVELING_DISTANCE
) {
1085 //called when someone died
1086 void Game::PartyMemberDied(Actor
*actor
)
1088 //this could be null, in some extreme cases...
1089 Map
*area
= actor
->GetCurrentArea();
1091 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1092 if (PCs
[i
]==actor
) {
1095 if (PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
1098 if (PCs
[i
]->GetCurrentArea()!=area
) {
1101 PCs
[i
]->ReactToDeath(actor
->GetScriptName());
1105 //reports if someone died
1106 int Game::PartyMemberDied() const
1108 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1109 if (PCs
[i
]->GetInternalFlag()&IF_JUSTDIED
) {
1116 void Game::IncrementChapter()
1118 //chapter first set to 0 (prologue)
1119 ieDword chapter
= (ieDword
) -1;
1120 locals
->Lookup("CHAPTER",chapter
);
1121 locals
->SetAt("CHAPTER",chapter
+1);
1123 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1124 //all PCs must have this!
1125 PCs
[i
]->PCStats
->IncrementChapter();
1129 void Game::SetReputation(ieDword r
)
1132 else if (r
>200) r
=200;
1134 displaymsg
->DisplayConstantStringValue(STR_LOSTREP
,0xc0c000,(Reputation
-r
)/10);
1135 } else if (Reputation
<r
) {
1136 displaymsg
->DisplayConstantStringValue(STR_GOTREP
,0xc0c000,(r
-Reputation
)/10);
1139 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1140 PCs
[i
]->SetBase(IE_REPUTATION
, Reputation
);
1144 void Game::SetControlStatus(int value
, int mode
)
1147 case BM_OR
: ControlStatus
|=value
; break;
1148 case BM_NAND
: ControlStatus
&=~value
; break;
1149 case BM_SET
: ControlStatus
=value
; break;
1150 case BM_AND
: ControlStatus
&=value
; break;
1151 case BM_XOR
: ControlStatus
^=value
; break;
1153 core
->SetEventFlag(EF_CONTROL
);
1156 void Game::AddGold(ieDword add
)
1165 if (old
<PartyGold
) {
1166 displaymsg
->DisplayConstantStringValue( STR_GOTGOLD
, 0xc0c000, PartyGold
-old
);
1168 displaymsg
->DisplayConstantStringValue( STR_LOSTGOLD
, 0xc0c000, old
-PartyGold
);
1172 //later this could be more complicated
1173 void Game::AdvanceTime(ieDword add
)
1175 ieDword h
= GameTime
/(300*AI_UPDATE_TIME
);
1177 if (h
!=GameTime
/(300*AI_UPDATE_TIME
)) {
1178 //asking for a new weather when the hour changes
1179 WeatherBits
&=~WB_HASWEATHER
;
1181 Ticks
+=add
*interval
;
1182 //change the tileset if needed
1183 Map
*map
= GetCurrentArea();
1184 if (map
&& map
->ChangeMap(IsDay())) {
1185 //play the daylight transition movie appropriate for the area
1186 //it is needed to play only when the area truly changed its tileset
1187 //this is signalled by ChangeMap
1188 int areatype
= (area
->AreaType
&(AT_FOREST
|AT_CITY
|AT_DUNGEON
))>>3;
1191 printMessage("Game","Switching DayLight\n",GREEN
);
1193 res
=&nightmovies
[areatype
];
1195 res
=&daymovies
[areatype
];
1198 core
->PlayMovie(*res
);
1203 //returns true if there are excess players in the team
1204 bool Game::PartyOverflow() const
1206 GameControl
*gc
= core
->GetGameControl();
1210 //don't start this screen when the gui is busy
1211 if (gc
->GetDialogueFlags() & (DF_IN_DIALOG
|DF_IN_CONTAINER
|DF_FREEZE_SCRIPTS
) ) {
1217 return (PCs
.size()>partysize
);
1220 bool Game::PCInCombat(Actor
* actor
) const
1222 if (!CombatCounter
) {
1226 if (actor
->LastTarget
) {
1229 if (AttackersOf(actor
->GetGlobalID(), actor
->GetCurrentArea())) {
1235 bool Game::AnyPCInCombat() const
1237 if (!CombatCounter
) {
1241 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1242 if (PCInCombat (PCs
[i
])) {
1249 //returns true if the protagonist (or the whole party died)
1250 bool Game::EveryoneDead() const
1252 //if there are no PCs, then we assume everyone dead
1256 if (protagonist
==PM_NO
) {
1257 Actor
*nameless
= PCs
[0];
1258 if (nameless
->GetStat(IE_STATE_ID
)&STATE_NOSAVE
) {
1259 if (area
->INISpawn
) {
1260 area
->INISpawn
->RespawnNameless();
1265 // if protagonist died
1266 if (protagonist
==PM_YES
) {
1267 if (PCs
[0]->GetStat(IE_STATE_ID
)&STATE_NOSAVE
) {
1273 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1274 if (!(PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_NOSAVE
) ) {
1281 //runs all area scripts
1283 void Game::UpdateScripts()
1286 ProcessActions(false);
1289 bool PartyAttack
= false;
1291 for (idx
=0;idx
<Maps
.size();idx
++) {
1292 Maps
[idx
]->UpdateScripts();
1293 size_t acnt
=Attackers
.size();
1295 Actor
*actor
= Maps
[idx
]->GetActorByGlobalID(Attackers
[acnt
]);
1297 if ( !Maps
[idx
]->GetActorByGlobalID(actor
->LastTarget
) ) {
1298 //Actor's target left area
1299 OutAttack(Attackers
[acnt
]);
1302 //each attacker handles their own round initiation
1303 actor
->InitRound(GameTime
);
1304 if (actor
->InParty
) {
1309 //Attacker is gone from area
1310 OutAttack(Attackers
[acnt
]);
1316 //ChangeSong will set the battlesong only if CombatCounter is nonzero
1318 ChangeSong(false, true);
1320 if (CombatCounter
) {
1322 //Change song if combatcounter went down to 0
1323 if (!CombatCounter
) {
1324 ChangeSong(false, false);
1329 if (StateOverrideTime
)
1330 StateOverrideTime
--;
1331 if (BanterBlockTime
)
1334 if (Maps
.size()>MAX_MAPS_LOADED
) {
1337 //starting from 0, so we see the most recent master area first
1338 for(unsigned int i
=0;i
<idx
;i
++) {
1339 DelMap( (unsigned int) i
, false );
1343 // perhaps a StartMusic action stopped the area music?
1344 // (we should probably find a less silly way to handle this,
1345 // because nothing can ever stop area music now..)
1346 if (!core
->GetMusicMgr()->IsPlaying()) {
1347 ChangeSong(false,false);
1350 //this is used only for the death delay so far
1351 if (event_handler
) {
1353 event_handler
->call();
1354 event_handler
= NULL
;
1359 if (EveryoneDead()) {
1360 //don't check it any more
1361 protagonist
= PM_NO
;
1362 core
->GetGUIScriptEngine()->RunFunction("GUIWORLD", "DeathWindow");
1366 if (PartyOverflow()) {
1368 core
->GetGUIScriptEngine()->RunFunction("GUIWORLD", "OpenReformPartyWindow");
1373 void Game::SetTimedEvent(EventHandler func
, int count
)
1375 event_timer
= count
;
1376 event_handler
= func
;
1379 void Game::SetProtagonistMode(int mode
)
1384 void Game::SetPartySize(int size
)
1386 // 0 size means no party size control
1390 partysize
= (size_t) size
;
1393 //Get the area dependent rest movie
1394 ieResRef
*Game::GetDream(Map
*area
)
1396 //select dream based on area
1397 int daynight
= IsDay();
1398 if (area
->Dream
[daynight
][0]) {
1399 return area
->Dream
+daynight
;
1401 int dream
= (area
->AreaType
&(AT_FOREST
|AT_CITY
|AT_DUNGEON
))>>3;
1402 return restmovies
+dream
;
1405 //Start dream cutscenes for player1
1406 void Game::PlayerDream()
1408 Scriptable
*Sender
= GetPC(0,true);
1409 if (!Sender
) return;
1411 GameScript
* gs
= new GameScript( "player1d", Sender
,0,0 );
1416 //noareacheck = no random encounters
1417 //dream = 0 - based on area non-0 - select from list
1419 //hp is how much hp the rest will heal
1420 void Game::RestParty(int checks
, int dream
, int hp
)
1422 if (!(checks
&REST_NOMOVE
) ) {
1423 if (!EveryoneStopped()) {
1427 Actor
*leader
= GetPC(0, true);
1432 Map
*area
= leader
->GetCurrentArea();
1433 //we let them rest if someone is paralyzed, but the others gather around
1434 if (!(checks
&REST_NOSCATTER
) ) {
1435 if (!EveryoneNearPoint( area
, leader
->Pos
, 0 ) ) {
1436 //party too scattered
1437 displaymsg
->DisplayConstantString( STR_SCATTERED
, 0xff0000 );
1442 if (!(checks
&REST_NOCRITTER
) ) {
1443 //don't allow resting while in combat
1444 if (AnyPCInCombat()) {
1445 displaymsg
->DisplayConstantString( STR_CANTRESTMONS
, 0xff0000 );
1448 //don't allow resting if hostiles are nearby
1449 if (area
->AnyEnemyNearPoint(leader
->Pos
)) {
1450 displaymsg
->DisplayConstantString( STR_CANTRESTMONS
, 0xff0000 );
1455 //rest check, if PartyRested should be set, area should return true
1456 //area should advance gametime too (so partial rest is possible)
1458 if (!(checks
&REST_NOAREA
) ) {
1459 //you cannot rest here
1460 if (area
->AreaFlags
&1) {
1461 displaymsg
->DisplayConstantString( STR_MAYNOTREST
, 0xff0000 );
1464 //you may not rest here, find an inn
1465 if (!(area
->AreaType
&(AT_OUTDOOR
|AT_FOREST
|AT_DUNGEON
|AT_CAN_REST
) ))
1467 displaymsg
->DisplayConstantString( STR_MAYNOTREST
, 0xff0000 );
1471 if(area
->Rest( leader
->Pos
, 8, (GameTime
/AI_UPDATE_TIME
)%7200/3600) ) {
1475 AdvanceTime(2400*AI_UPDATE_TIME
);
1477 int i
= GetPartySize(true); // party size, only alive
1480 Actor
*tar
= GetPC(i
, true);
1482 tar
->ClearActions();
1483 tar
->SetModal(MS_NONE
, 0);
1484 //if hp = 0, then healing will be complete
1486 //removes fatigue, recharges spells
1491 //movie and cutscene dreams
1494 if (gamedata
->Exists("player1d",IE_BCS_CLASS_ID
, true))
1497 //select dream based on area
1499 if (dream
==0 || dream
>7) {
1500 movie
= GetDream(area
);
1502 movie
= restmovies
+dream
;
1504 if (*movie
[0]!='*') {
1505 core
->PlayMovie(*movie
);
1509 //set partyrested flags
1511 area
->PartyRested();
1512 core
->SetEventFlag(EF_ACTION
);
1514 //restindex will be -1 in the case of PST
1515 //FIXME: I don't quite see why we can't sumply use the same strings.2da entry
1516 //It seems we could reduce complexity here, and free up 2-3 string slots too
1517 int restindex
= displaymsg
->GetStringReference(STR_REST
);
1519 char* tmpstr
= NULL
;
1521 core
->GetTokenDictionary()->SetAtCopy("HOUR", hours
);
1522 if (restindex
!= -1) {
1523 strindex
= displaymsg
->GetStringReference(STR_HOURS
);
1525 strindex
= displaymsg
->GetStringReference(STR_PST_HOURS
);
1526 restindex
= displaymsg
->GetStringReference(STR_PST_REST
);
1530 if (strindex
== -1 || restindex
== -1) return;
1531 tmpstr
= core
->GetString(strindex
, 0);
1533 if (!tmpstr
) return;
1535 core
->GetTokenDictionary()->SetAtCopy("DURATION", tmpstr
);
1536 core
->FreeString(tmpstr
);
1537 displaymsg
->DisplayString(restindex
, 0xffffff, 0);
1541 void Game::TimeStop(Actor
* owner
, ieDword end
)
1543 timestop_owner
=owner
;
1544 timestop_end
=GameTime
+end
;
1547 //recalculate the party's infravision state
1548 void Game::Infravision()
1551 Map
*map
= GetCurrentArea();
1553 for(size_t i
=0;i
<PCs
.size();i
++) {
1554 Actor
*actor
= PCs
[i
];
1555 if (!IsAlive(actor
)) continue;
1556 if (actor
->GetCurrentArea()!=map
) continue;
1557 //Group infravision overrides this???
1558 if (!actor
->Selected
) continue;
1559 if (actor
->GetStat(IE_STATE_ID
) & STATE_INFRA
) {
1566 //returns the colour which should be applied onto the whole game area viewport
1567 //this is based on timestop, dream area, weather, daytime
1569 static const Color TimeStopTint
={0xe0,0xe0,0xe0,0x20}; //greyscale
1570 static const Color DreamTint
={0xf0,0xe0,0xd0,0x10}; //light brown scale
1571 static const Color NightTint
={0x80,0x80,0xe0,0x40}; //dark, bluish
1572 static const Color DuskTint
={0xe0,0x80,0x80,0x40}; //dark, reddish
1573 static const Color FogTint
={0xff,0xff,0xff,0x40}; //whitish
1574 static const Color DarkTint
={0x80,0x80,0xe0,0x10}; //slightly dark bluish
1576 const Color
*Game::GetGlobalTint() const
1578 if (timestop_end
>GameTime
) {
1579 return &TimeStopTint
;
1581 Map
*map
= GetCurrentArea();
1582 if (!map
) return NULL
;
1583 if (map
->AreaFlags
&AF_DREAM
) {
1586 if ((map
->AreaType
&(AT_OUTDOOR
|AT_DAYNIGHT
|AT_EXTENDED_NIGHT
)) == (AT_OUTDOOR
|AT_DAYNIGHT
) ) {
1587 //get daytime colour
1588 ieDword daynight
= ((GameTime
/AI_UPDATE_TIME
)%7200/300);
1589 if (daynight
<2 || daynight
>22) {
1592 if (daynight
>20 || daynight
<4) {
1596 if ((map
->AreaType
&(AT_OUTDOOR
|AT_WEATHER
)) == (AT_OUTDOOR
|AT_WEATHER
)) {
1598 if (WeatherBits
&WB_RAIN
) {
1601 if (WeatherBits
&WB_FOG
) {
1611 ieDword daynight
= ((GameTime
/AI_UPDATE_TIME
)%7200/300);
1612 if(daynight
<4 || daynight
>20) {
1618 void Game::InAttack(ieDword globalID
)
1620 std::vector
< ieDword
>::const_iterator idx
;
1622 for(idx
=Attackers
.begin(); idx
!=Attackers
.end();idx
++) {
1623 if (*idx
==globalID
) return;
1625 Attackers
.push_back(globalID
);
1628 void Game::OutAttack(ieDword globalID
)
1630 std::vector
< ieDword
>::iterator idx
;
1632 for(idx
=Attackers
.begin(); idx
!=Attackers
.end();idx
++) {
1633 if (*idx
==globalID
) {
1634 Attackers
.erase(idx
);
1640 void Game::ChangeSong(bool always
, bool force
)
1644 if (CombatCounter
) {
1648 //will select SONG_DAY or SONG_NIGHT
1649 Song
= (GameTime
/AI_UPDATE_TIME
)%7200/3600;
1651 //area may override the song played (stick in battlemusic)
1652 //always transition gracefully with ChangeSong
1653 //force just means, we schedule the song for later, if currently
1655 area
->PlayAreaSong( Song
, always
, force
);
1658 int Game::AttackersOf(ieDword globalID
, Map
*area
) const
1663 std::vector
< ieDword
>::const_iterator idx
;
1666 for(idx
=Attackers
.begin(); idx
!=Attackers
.end();idx
++) {
1667 Actor
* actor
= area
->GetActorByGlobalID(*idx
);
1669 if (actor
->LastTarget
==globalID
) {
1677 /* this method redraws weather. If update is false,
1678 then the weather particles won't change (game paused)
1680 void Game::DrawWeather(const Region
&screen
, bool update
)
1685 if (!area
->HasWeather()) {
1689 weather
->Draw( screen
);
1694 if (!(WeatherBits
& (WB_RAIN
|WB_SNOW
)) ) {
1695 if (weather
->GetPhase() == P_GROW
) {
1696 weather
->SetPhase(P_FADE
);
1700 int drawn
= weather
->Update();
1702 WeatherBits
&= ~WB_START
;
1706 if (WeatherBits
&WB_HASWEATHER
) {
1709 StartRainOrSnow(true, area
->GetWeather());
1712 /* sets the weather type */
1713 void Game::StartRainOrSnow(bool conditional
, int w
)
1715 if (conditional
&& (w
& (WB_RAIN
|WB_SNOW
)) ) {
1716 if (WeatherBits
& (WB_RAIN
| WB_SNOW
) )
1719 // whatever was responsible for calling this, we now have some set weather
1720 WeatherBits
= w
| WB_HASWEATHER
;
1721 if (w
& WB_LIGHTNING
) {
1722 if (WeatherBits
&WB_START
) {
1725 core
->PlaySound(DS_LIGHTNING1
);
1727 core
->PlaySound(DS_LIGHTNING2
);
1730 //start raining (far)
1731 core
->PlaySound(DS_LIGHTNING3
);
1735 core
->PlaySound(DS_SNOW
);
1736 weather
->SetType(SP_TYPE_POINT
, SP_PATH_FLIT
, SP_SPAWN_SOME
);
1737 weather
->SetPhase(P_GROW
);
1738 weather
->SetColor(SPARK_COLOR_WHITE
);
1742 core
->PlaySound(DS_RAIN
);
1743 weather
->SetType(SP_TYPE_LINE
, SP_PATH_RAIN
, SP_SPAWN_SOME
);
1744 weather
->SetPhase(P_GROW
);
1745 weather
->SetColor(SPARK_COLOR_STONE
);
1748 weather
->SetPhase(P_FADE
);
1751 void Game::SetExpansion(ieDword value
)
1753 if (Expansion
>=value
) {
1760 core
->SetEventFlag(EF_EXPANSION
);
1762 //TODO: move this hardcoded hack to the scripts
1764 core
->GetDictionary()->SetAt( "PlayMode", 2 );
1766 int i
= GetPartySize(false);
1768 Actor
*actor
= GetPC(i
, false);
1769 InitActorPos(actor
);
1774 void Game::DebugDump()
1778 printf("Currently loaded areas:\n");
1779 for(idx
=0;idx
<Maps
.size();idx
++) {
1780 Map
*map
= Maps
[idx
];
1782 printf("%s\n",map
->GetScriptName());
1784 printf("Current area: %s Previous area: %s\n", CurrentArea
, PreviousArea
);
1785 printf("Global script: %s\n", Scripts
[0]->GetName());
1786 printf("CombatCounter: %d\n", (int) CombatCounter
);
1787 printf("Attackers count: %d\n", (int) Attackers
.size());
1788 for(idx
=0;idx
<Attackers
.size(); idx
++) {
1789 Actor
*actor
= GetActorByGlobalID(Attackers
[idx
]);
1791 printf("Name: ???\n");
1794 Actor
*whom
= GetActorByGlobalID(actor
->LastTarget
);
1795 printf("Name: %s Attacking : %s\n", actor
->ShortName
, whom
?whom
->ShortName
:"???");
1798 printf("Party size: %d\n", (int) PCs
.size());
1799 for(idx
=0;idx
<PCs
.size();idx
++) {
1800 Actor
*actor
= PCs
[idx
];
1802 printf("Name: %s Order %d %s\n",actor
->ShortName
, actor
->InParty
, actor
->Selected
?"x":"-");
1806 Actor
*Game::GetActorByGlobalID(ieDword globalID
)
1808 size_t mc
= GetLoadedMapCount();
1810 Map
*map
= GetMap(mc
);
1811 Actor
*actor
= map
->GetActorByGlobalID(globalID
);
1812 if (actor
) return actor
;
1814 return GetGlobalActorByGlobalID(globalID
);