1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003 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
26 #include "DataStream.h"
27 #include "Interface.h"
28 #include "ScriptEngine.h"
29 #include "GameControl.h"
31 #include "GameScript.h"
33 #include "defsounds.h"
36 #define MAX_MAPS_LOADED 1
38 Game::Game(void) : Scriptable( ST_GLOBAL
)
40 protagonist
= PM_YES
; //set it to 2 for iwd/iwd2 and 0 for pst
46 SelectedSingle
= 1; //the PC we are looking at (inventory, shop)
48 SetScript( core
->GlobalScript
, 0 );
52 CombatCounter
= 0; //stored here until we know better
53 StateOverrideTime
= 0;
54 StateOverrideFlag
= 0;
62 timestop_owner
= NULL
;
66 weather
= new Particles(200);
67 weather
->SetRegion(0, 0, core
->Width
, core
->Height
);
70 //loading master areas
72 if (table
.load("mastarea")) {
73 int i
= table
->GetRowCount();
76 char *tmp
= (char *) malloc(9);
77 strnuprcpy (tmp
,table
->QueryField(i
,0),8);
78 mastarea
.push_back( tmp
);
82 //loading rest/daylight switching movies (only bg2 has them)
83 memset(restmovies
,'*',sizeof(restmovies
));
84 memset(daymovies
,'*',sizeof(restmovies
));
85 memset(nightmovies
,'*',sizeof(restmovies
));
86 if (table
.load("restmov")) {
87 for(int i
=0;i
<8;i
++) {
88 strnuprcpy(restmovies
[i
],table
->QueryField(i
,0),8);
89 strnuprcpy(daymovies
[i
],table
->QueryField(i
,1),8);
90 strnuprcpy(nightmovies
[i
],table
->QueryField(i
,2),8);
94 interval
= 1000/AI_UPDATE_TIME
;
95 //FIXME:i'm not sure in this...
104 for (i
= 0; i
< Maps
.size(); i
++) {
107 for (i
= 0; i
< PCs
.size(); i
++) {
110 for (i
= 0; i
< NPCs
.size(); i
++) {
113 for (i
= 0; i
< mastarea
.size(); i
++) {
114 free ( mastarea
[i
] );
135 i
=savedpositions
.size();
137 delete savedpositions
[i
];
140 i
=planepositions
.size();
142 delete planepositions
[i
];
146 bool IsAlive(Actor
*pc
)
148 if (pc
->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
154 int Game::FindPlayer(unsigned int partyID
)
156 for (unsigned int slot
=0; slot
<PCs
.size(); slot
++) {
157 if (PCs
[slot
]->InParty
==partyID
) {
164 Actor
* Game::FindPC(unsigned int partyID
)
166 for (unsigned int slot
=0; slot
<PCs
.size(); slot
++) {
167 if (PCs
[slot
]->InParty
==partyID
) return PCs
[slot
];
172 Actor
* Game::FindPC(const char *scriptingname
)
174 for (unsigned int slot
=0; slot
<PCs
.size(); slot
++) {
175 if (strnicmp(PCs
[slot
]->GetScriptName(),scriptingname
,32)==0 ) {
182 Actor
* Game::FindNPC(unsigned int partyID
)
184 for (unsigned int slot
=0; slot
<NPCs
.size(); slot
++) {
185 if (NPCs
[slot
]->InParty
==partyID
) return NPCs
[slot
];
190 Actor
* Game::FindNPC(const char *scriptingname
)
192 for (unsigned int slot
=0; slot
<NPCs
.size(); slot
++) {
193 if (strnicmp(NPCs
[slot
]->GetScriptName(),scriptingname
,32)==0 )
201 Actor
* Game::GetPC(unsigned int slot
, bool onlyalive
)
203 if (slot
>= PCs
.size()) {
208 while(i
<PCs
.size() ) {
209 Actor
*ac
= PCs
[i
++];
222 int Game::InStore(Actor
* pc
) const
224 for (unsigned int i
= 0; i
< NPCs
.size(); i
++) {
232 int Game::InParty(Actor
* pc
) const
234 for (unsigned int i
= 0; i
< PCs
.size(); i
++) {
242 int Game::DelPC(unsigned int slot
, bool autoFree
)
244 if (slot
>= PCs
.size()) {
250 SelectActor(PCs
[slot
], false, SELECT_NORMAL
);
254 std::vector
< Actor
*>::iterator m
= PCs
.begin() + slot
;
259 int Game::DelNPC(unsigned int slot
, bool autoFree
)
261 if (slot
>= NPCs
.size()) {
268 delete( NPCs
[slot
] );
270 std::vector
< Actor
*>::iterator m
= NPCs
.begin() + slot
;
275 //i'm sure this could be faster
276 void Game::ConsolidateParty()
278 int max
= (int) PCs
.size();
279 std::vector
< Actor
*>::const_iterator m
;
280 for (int i
=1;i
<=max
;) {
281 if (FindPlayer(i
)==-1) {
283 for ( m
= PCs
.begin(); m
!= PCs
.end(); ++m
) {
284 if ( (*m
)->InParty
>i
) {
290 for ( m
= PCs
.begin(); m
!= PCs
.end(); ++m
) {
291 (*m
)->RefreshEffects(NULL
);
295 int Game::LeaveParty (Actor
* actor
)
297 actor
->CreateStats(); //create or update stats for leaving
298 actor
->SetBase(IE_EXPLORE
, 0);
299 SelectActor(actor
, false, SELECT_NORMAL
);
300 int slot
= InParty( actor
);
304 std::vector
< Actor
*>::iterator m
= PCs
.begin() + slot
;
307 for ( m
= PCs
.begin(); m
!= PCs
.end(); ++m
) {
308 if ( (*m
)->InParty
>actor
->InParty
) {
312 //removing from party, but actor remains in 'game'
313 actor
->SetPersistent(0);
314 NPCs
.push_back( actor
);
316 if (core
->HasFeature( GF_HAS_DPLAYER
)) {
317 actor
->SetScript( "", SCR_DEFAULT
);
319 actor
->SetBase( IE_EA
, EA_NEUTRAL
);
320 return ( int ) NPCs
.size() - 1;
323 //determines if startpos.2da has rotation rows (it cannot have tutorial line)
324 bool Game::DetermineStartPosType(const TableMgr
*strta
)
326 if ((strta
->GetRowCount()>=6) && !stricmp(strta
->GetRowName(4),"START_ROT" ) )
333 void Game::InitActorPos(Actor
*actor
)
335 //start.2da row labels
336 const char *mode
[3] = { "NORMAL", "TUTORIAL", "EXPANSION" };
338 unsigned int ip
= (unsigned int) (actor
->InParty
-1);
339 AutoTable
start("start");
340 AutoTable
strta("startpos");
341 // 0 - single player, 1 - tutorial, 2 - expansion
342 ieDword playmode
= 0;
343 core
->GetDictionary()->Lookup( "PlayMode", playmode
);
344 const char *xpos
= start
->QueryField(mode
[playmode
],"XPOS");
345 const char *ypos
= start
->QueryField(mode
[playmode
],"YPOS");
346 const char *area
= start
->QueryField(mode
[playmode
],"AREA");
347 const char *rot
= start
->QueryField(mode
[playmode
],"ROT");
349 actor
->Pos
.x
= actor
->Destination
.x
= (short) atoi( strta
->QueryField( strta
->GetRowIndex(xpos
), ip
) );
350 actor
->Pos
.y
= actor
->Destination
.y
= (short) atoi( strta
->QueryField( strta
->GetRowIndex(ypos
), ip
) );
351 actor
->SetOrientation( atoi( strta
->QueryField( strta
->GetRowIndex(rot
), ip
) ), false );
353 strta
.load("startare");
354 strnlwrcpy(actor
->Area
, strta
->QueryField( strta
->GetRowIndex(area
), 0 ), 8 );
356 // strta->QueryField(strta->GetRowIndex(xpos),0);
357 // strta->QueryField(strta->GetColumnIndex(ypos),0);
359 SelectActor(actor
,true, SELECT_QUIET
);
362 int Game::JoinParty(Actor
* actor
, int join
)
364 actor
->CreateStats(); //create stats if they didn't exist yet
365 actor
->InitButtons(actor
->GetStat(IE_CLASS
), false); //init actor's buttons
366 actor
->SetBase(IE_EXPLORE
, 1);
367 if (join
&JP_INITPOS
) {
370 int slot
= InParty( actor
);
375 actor
->PCStats
->JoinDate
= GameTime
;
377 Reputation
= actor
->GetStat(IE_REPUTATION
);
380 slot
= InStore( actor
);
382 std::vector
< Actor
*>::iterator m
= NPCs
.begin() + slot
;
385 size_t size
= PCs
.size();
387 PCs
.push_back( actor
);
388 if (!actor
->InParty
) {
389 actor
->InParty
= (ieByte
) (size
+1);
395 int Game::GetPartySize(bool onlyalive
) const
399 for (unsigned int i
= 0; i
< PCs
.size(); i
++) {
400 if (!IsAlive(PCs
[i
])) {
407 return (int) PCs
.size();
410 /* sends the hotkey trigger to all selected actors */
411 void Game::SetHotKey(unsigned long Key
)
413 std::vector
< Actor
*>::const_iterator m
;
415 for ( m
= selected
.begin(); m
!= selected
.end(); ++m
) {
418 if (actor
->IsSelected()) {
419 actor
->HotKey
= (ieDword
) Key
;
424 bool Game::SelectPCSingle(int index
)
426 Actor
* actor
= FindPC( index
);
427 if (!actor
|| ! actor
->ValidTarget( GA_SELECT
| GA_NO_DEAD
| GA_NO_HIDDEN
))
430 SelectedSingle
= index
;
434 int Game::GetSelectedPCSingle() const
436 return SelectedSingle
;
440 * SelectActor() - handle (de)selecting actors.
441 * If selection was changed, runs "SelectionChanged" handler
443 * actor - either specific actor, or NULL for all
444 * select - whether actor(s) should be selected or deselected
446 * SELECT_REPLACE - if true, deselect all other actors when selecting one
447 * SELECT_QUIET - do not run handler if selection was changed. Used for
448 * nested calls to SelectActor()
451 bool Game::SelectActor(Actor
* actor
, bool select
, unsigned flags
)
453 std::vector
< Actor
*>::iterator m
;
455 // actor was not specified, which means all PCs should be (de)selected
457 for ( m
= selected
.begin(); m
!= selected
.end(); ++m
) {
458 (*m
)->Select( false );
459 (*m
)->SetOver( false );
464 for ( m
= PCs
.begin(); m
!= PCs
.end(); ++m
) {
468 SelectActor( *m
, true, SELECT_QUIET
);
472 if (! (flags
& SELECT_QUIET
)) {
473 core
->SetEventFlag(EF_SELECTION
);
478 // actor was specified, so we will work with him
480 // If actor is already (de)selected, report success, but do nothing
481 //if (actor->IsSelected() == select)
486 if (! actor
->ValidTarget( GA_SELECT
| GA_NO_DEAD
))
489 // deselect all actors first when exclusive
490 if (flags
& SELECT_REPLACE
) {
491 SelectActor( NULL
, false, SELECT_QUIET
);
494 actor
->Select( true );
495 selected
.push_back( actor
);
497 for ( m
= selected
.begin(); m
!= selected
.end(); ++m
) {
503 actor
->Select( false );
506 if (! (flags
& SELECT_QUIET
)) {
507 core
->SetEventFlag(EF_SELECTION
);
512 // Gets average party level, of onlyalive is true, then counts only living PCs
513 int Game::GetPartyLevel(bool onlyalive
) const
516 for (unsigned int i
= 0; i
<PCs
.size(); i
++) {
518 if (PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
522 count
+= PCs
[i
]->GetXPLevel(0);
527 // Returns map structure (ARE) if it is already loaded in memory
528 int Game::FindMap(const char *ResRef
)
530 int index
= (int) Maps
.size();
532 Map
*map
=Maps
[index
];
533 if (strnicmp(ResRef
, map
->GetScriptName(), 8) == 0) {
540 Map
* Game::GetMap(unsigned int index
) const
542 if (index
>= Maps
.size()) {
548 Map
*Game::GetMap(const char *areaname
, bool change
)
550 int index
= LoadMap(areaname
);
554 area
= GetMap(index
);
555 memcpy (CurrentArea
, areaname
, 8);
556 area
->SetupAmbients();
557 //change the tileset if needed
558 area
->ChangeMap(IsDay());
562 return GetMap(index
);
567 bool Game::MasterArea(const char *area
)
569 unsigned int i
=(int) mastarea
.size();
571 if (strnicmp(mastarea
[i
], area
, 8) ) {
578 void Game::SetMasterArea(const char *area
)
580 if (MasterArea(area
) ) return;
581 char *tmp
= (char *) malloc(9);
582 strnlwrcpy (tmp
,area
,8);
583 mastarea
.push_back(tmp
);
586 int Game::AddMap(Map
* map
)
588 if (MasterArea(map
->GetScriptName()) ) {
589 Maps
.insert(Maps
.begin(), 1, map
);
593 unsigned int i
= (unsigned int) Maps
.size();
594 Maps
.push_back( map
);
598 int Game::DelMap(unsigned int index
, int forced
)
600 //this function should archive the area, and remove it only if the area
601 //contains no active actors (combat, partymembers, etc)
602 if (index
>= Maps
.size()) {
605 Map
*map
= Maps
[index
];
607 if (MapIndex
==(int) index
) { //can't remove current map in any case
608 const char *name
= map
->GetScriptName();
609 memcpy(AnotherArea
, name
, sizeof(AnotherArea
) );
614 if (!map
) { //this shouldn't happen, i guess
615 printMessage("Game","Erased NULL Map\n",YELLOW
);
616 Maps
.erase( Maps
.begin()+index
);
617 if (MapIndex
>(int) index
) {
623 if (forced
|| (Maps
.size()>MAX_MAPS_LOADED
) )
625 //keep at least one master
626 const char *name
= map
->GetScriptName();
627 if (MasterArea(name
)) {
628 if(!AnotherArea
[0]) {
629 memcpy(AnotherArea
, name
, sizeof(AnotherArea
));
635 //this check must be the last, because
636 //after PurgeActors you cannot keep the
638 //Or the queues should be regenerated!
643 //remove map from memory
644 core
->SwapoutArea(Maps
[index
]);
645 delete( Maps
[index
] );
646 Maps
.erase( Maps
.begin()+index
);
647 //current map will be decreased
648 if (MapIndex
>(int) index
) {
653 //didn't remove the map
657 /* Loads an area, changepf == true if you want to setup the pathfinder too */
658 //FIXME: changepf is removed now
659 int Game::LoadMap(const char* ResRef
)
662 int index
= FindMap(ResRef
);
667 DataStream
* ds
= gamedata
->GetResource( ResRef
, IE_ARE_CLASS_ID
);
671 MapMgr
* mM
= ( MapMgr
* ) core
->GetInterface( IE_ARE_CLASS_ID
);
672 if(!mM
->Open( ds
, true )) {
676 Map
* newMap
= mM
->GetMap(ResRef
, IsDay());
682 for (i
= 0; i
< PCs
.size(); i
++) {
683 if (stricmp( PCs
[i
]->Area
, ResRef
) == 0) {
684 newMap
->AddActor( PCs
[i
] );
687 for (i
= 0; i
< NPCs
.size(); i
++) {
688 if (stricmp( NPCs
[i
]->Area
, ResRef
) == 0) {
689 newMap
->AddActor( NPCs
[i
] );
692 return AddMap( newMap
);
695 int Game::AddNPC(Actor
* npc
)
697 int slot
= InStore( npc
); //already an npc
701 slot
= InParty( npc
);
704 } //can't add as npc already in party
705 npc
->SetPersistent(0);
706 NPCs
.push_back( npc
);
708 return (int) NPCs
.size() - 1;
711 Actor
* Game::GetNPC(unsigned int Index
)
713 if (Index
>= NPCs
.size()) {
719 void Game::SwapPCs(unsigned int Index1
, unsigned int Index2
)
721 if (Index1
>= PCs
.size()) {
725 if (Index2
>= PCs
.size()) {
728 int tmp
= PCs
[Index1
]->InParty
;
729 PCs
[Index1
]->InParty
= PCs
[Index2
]->InParty
;
730 PCs
[Index2
]->InParty
= tmp
;
731 //signal a change of the portrait window
732 core
->SetEventFlag(EF_PORTRAIT
);
735 void Game::DeleteJournalEntry(ieStrRef strref
)
737 size_t i
=Journals
.size();
739 if (Journals
[i
]->Text
==strref
) {
741 Journals
.erase(Journals
.begin()+i
);
746 void Game::DeleteJournalGroup(int Group
)
748 size_t i
=Journals
.size();
750 if (Journals
[i
]->Group
==(ieByte
) Group
) {
752 Journals
.erase(Journals
.begin()+i
);
756 /* returns true if it modified or added a journal entry */
757 bool Game::AddJournalEntry(ieStrRef strref
, int Section
, int Group
)
759 GAMJournalEntry
*je
= FindJournalEntry(strref
);
761 //don't set this entry again in the same section
762 if (je
->Section
==Section
) {
765 if ((Section
== IE_GAM_QUEST_DONE
) && Group
) {
766 //removing all of this group and adding a new entry
767 DeleteJournalGroup(Group
);
769 //modifying existing entry
770 je
->Section
= (ieByte
) Section
;
771 je
->Group
= (ieByte
) Group
;
773 locals
->Lookup("CHAPTER", chapter
);
774 je
->Chapter
= (ieByte
) chapter
;
775 je
->GameTime
= GameTime
;
779 je
= new GAMJournalEntry
;
780 je
->GameTime
= GameTime
;
782 locals
->Lookup("CHAPTER", chapter
);
783 je
->Chapter
= (ieByte
) chapter
;
784 je
->Section
= (ieByte
) Section
;
785 je
->Group
= (ieByte
) Group
;
788 Journals
.push_back( je
);
792 void Game::AddJournalEntry(GAMJournalEntry
* entry
)
794 Journals
.push_back( entry
);
797 unsigned int Game::GetJournalCount() const
799 return (unsigned int) Journals
.size();
802 GAMJournalEntry
* Game::FindJournalEntry(ieStrRef strref
)
804 unsigned int Index
= (unsigned int) Journals
.size();
806 GAMJournalEntry
*ret
= Journals
[Index
];
808 if (ret
->Text
==strref
) {
816 GAMJournalEntry
* Game::GetJournalEntry(unsigned int Index
)
818 if (Index
>= Journals
.size()) {
821 return Journals
[Index
];
824 unsigned int Game::GetSavedLocationCount() const
826 return (unsigned int) savedpositions
.size();
829 void Game::ClearSavedLocations()
831 size_t i
=savedpositions
.size();
833 delete savedpositions
[i
];
835 savedpositions
.clear();
838 GAMLocationEntry
* Game::GetSavedLocationEntry(unsigned int i
)
840 size_t current
= savedpositions
.size();
845 savedpositions
.resize(i
+1);
847 savedpositions
[current
++]=(GAMLocationEntry
*) calloc(1, sizeof(GAMLocationEntry
) );
850 return savedpositions
[i
];
853 unsigned int Game::GetPlaneLocationCount() const
855 return (unsigned int) planepositions
.size();
858 void Game::ClearPlaneLocations()
860 size_t i
=planepositions
.size();
862 delete planepositions
[i
];
864 planepositions
.clear();
867 GAMLocationEntry
* Game::GetPlaneLocationEntry(unsigned int i
)
869 size_t current
= planepositions
.size();
874 planepositions
.resize(i
+1);
876 planepositions
[current
++]=(GAMLocationEntry
*) calloc(1, sizeof(GAMLocationEntry
) );
879 return planepositions
[i
];
882 char *Game::GetFamiliar(unsigned int Index
)
884 return Familiars
[Index
];
887 //reading the challenge rating table for iwd2 (only when needed)
888 void Game::LoadCRTable()
890 AutoTable
table("moncrate");
892 int maxrow
= table
->GetRowCount()-1;
893 crtable
= new CRRow
[MAX_LEVEL
];
894 for(int i
=0;i
<MAX_LEVEL
;i
++) {
895 //row shouldn't be larger than maxrow
896 int row
= i
<maxrow
?i
:maxrow
;
897 int maxcol
= table
->GetColumnCount(row
)-1;
898 for(int j
=0;j
<MAX_CRLEVEL
;j
++) {
899 //col shouldn't be larger than maxcol
900 int col
= j
<maxcol
?j
:maxcol
;
901 crtable
[i
][j
]=atoi(table
->QueryField(row
,col
) );
907 int Game::GetXPFromCR(int cr
)
909 if (!crtable
) LoadCRTable();
911 int level
= GetPartyLevel(true);
912 if (cr
>=MAX_CRLEVEL
) {
915 printf("Challenge Rating: %d, party level: %d ", cr
, level
);
916 return crtable
[level
][cr
];
918 printMessage("Game","Cannot find moncrate.2da!\n", LIGHT_RED
);
922 void Game::ShareXP(int xp
, int flags
)
927 xp
= GetXPFromCR(xp
);
930 if (flags
&SX_DIVIDE
) {
931 int PartySize
= GetPartySize(true); //party size, only alive
935 individual
= xp
/ PartySize
;
945 core
->DisplayConstantStringValue( STR_GOTXP
, 0xbcefbc, (ieDword
) xp
); //you have gained ... xp
947 core
->DisplayConstantStringValue( STR_LOSTXP
, 0xbcefbc, (ieDword
) -xp
); //you have lost ... xp
949 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
950 if (PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
953 PCs
[i
]->AddExperience(individual
);
957 bool Game::EveryoneStopped() const
959 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
960 if (PCs
[i
]->GetNextStep() ) return false;
965 //canmove=true: if some PC can't move (or hostile), then this returns false
966 bool Game::EveryoneNearPoint(Map
*area
, Point
&p
, int flags
) const
968 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
969 if (flags
&ENP_ONLYSELECT
) {
970 if(!PCs
[i
]->Selected
) {
974 if (PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
977 if (flags
&ENP_CANMOVE
) {
978 //someone is uncontrollable, can't move
979 if (PCs
[i
]->GetStat(IE_EA
)>EA_GOODCUTOFF
) {
983 if (PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_CANTMOVE
) {
987 if (PCs
[i
]->GetCurrentArea()!=area
) {
990 if (Distance(p
,PCs
[i
])>MAX_TRAVELING_DISTANCE
) {
997 //called when someone died
998 void Game::PartyMemberDied(Actor
*actor
)
1000 //this could be null, in some extreme cases...
1001 Map
*area
= actor
->GetCurrentArea();
1003 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1004 if (PCs
[i
]==actor
) {
1007 if (PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
1010 if (PCs
[i
]->GetCurrentArea()!=area
) {
1013 PCs
[i
]->ReactToDeath(actor
->GetScriptName());
1017 //reports if someone died
1018 int Game::PartyMemberDied() const
1020 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1021 if (PCs
[i
]->GetInternalFlag()&IF_JUSTDIED
) {
1028 void Game::IncrementChapter()
1030 //chapter first set to 0 (prologue)
1031 ieDword chapter
= (ieDword
) -1;
1032 locals
->Lookup("CHAPTER",chapter
);
1033 locals
->SetAt("CHAPTER",chapter
+1);
1035 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1036 //all PCs must have this!
1037 PCs
[i
]->PCStats
->IncrementChapter();
1041 void Game::SetReputation(ieDword r
)
1044 else if (r
>200) r
=200;
1046 core
->DisplayConstantStringValue(STR_LOSTREP
,0xc0c000,(Reputation
-r
)/10);
1047 } else if (Reputation
<r
) {
1048 core
->DisplayConstantStringValue(STR_GOTREP
,0xc0c000,(r
-Reputation
)/10);
1051 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1052 PCs
[i
]->SetBase(IE_REPUTATION
, Reputation
);
1056 void Game::SetControlStatus(int value
, int mode
)
1059 case BM_OR
: ControlStatus
|=value
; break;
1060 case BM_NAND
: ControlStatus
&=~value
; break;
1061 case BM_SET
: ControlStatus
=value
; break;
1062 case BM_AND
: ControlStatus
&=value
; break;
1063 case BM_XOR
: ControlStatus
^=value
; break;
1065 core
->SetEventFlag(EF_CONTROL
);
1068 void Game::AddGold(ieDword add
)
1077 if (old
<PartyGold
) {
1078 core
->DisplayConstantStringValue( STR_GOTGOLD
, 0xc0c000, PartyGold
-old
);
1080 core
->DisplayConstantStringValue( STR_LOSTGOLD
, 0xc0c000, old
-PartyGold
);
1084 //later this could be more complicated
1085 void Game::AdvanceTime(ieDword add
)
1087 ieDword h
= GameTime
/(300*AI_UPDATE_TIME
);
1089 if (h
!=GameTime
/(300*AI_UPDATE_TIME
)) {
1090 //asking for a new weather when the hour changes
1091 WeatherBits
&=~WB_HASWEATHER
;
1093 Ticks
+=add
*interval
;
1094 //change the tileset if needed
1095 Map
*map
= GetCurrentArea();
1096 if (map
&& map
->ChangeMap(IsDay())) {
1097 //play the daylight transition movie appropriate for the area
1098 //it is needed to play only when the area truly changed its tileset
1099 //this is signalled by ChangeMap
1100 int areatype
= (area
->AreaType
&(AT_FOREST
|AT_CITY
|AT_DUNGEON
))>>3;
1103 printMessage("Game","Switching DayLight\n",GREEN
);
1105 res
=&nightmovies
[areatype
];
1107 res
=&daymovies
[areatype
];
1110 core
->PlayMovie(*res
);
1115 //returns true if there are excess players in the team
1116 bool Game::PartyOverflow() const
1118 GameControl
*gc
= core
->GetGameControl();
1122 //don't start this screen when the gui is busy
1123 if (gc
->GetDialogueFlags() & (DF_IN_DIALOG
|DF_IN_CONTAINER
|DF_FREEZE_SCRIPTS
) ) {
1129 return (PCs
.size()>partysize
);
1132 bool Game::PCInCombat(Actor
* actor
) const
1134 if (!CombatCounter
) {
1138 if (actor
->LastTarget
) {
1141 if (AttackersOf(actor
->GetID(), actor
->GetCurrentArea())) {
1147 bool Game::AnyPCInCombat() const
1149 if (!CombatCounter
) {
1153 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1154 if (PCInCombat (PCs
[i
])) {
1161 //returns true if the protagonist (or the whole party died)
1162 bool Game::EveryoneDead() const
1164 //if there are no PCs, then we assume everyone dead
1168 if (protagonist
==PM_NO
) {
1169 Actor
*nameless
= PCs
[0];
1170 if (nameless
->GetStat(IE_STATE_ID
)&STATE_NOSAVE
) {
1171 if (area
->INISpawn
) {
1172 area
->INISpawn
->RespawnNameless();
1177 // if protagonist died
1178 if (protagonist
==PM_YES
) {
1179 if (PCs
[0]->GetStat(IE_STATE_ID
)&STATE_NOSAVE
) {
1185 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1186 if (!(PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_NOSAVE
) ) {
1193 //runs all area scripts
1195 void Game::UpdateScripts()
1198 ProcessActions(false);
1201 for (idx
=0;idx
<Maps
.size();idx
++) {
1202 Maps
[idx
]->UpdateScripts();
1203 size_t acnt
=Attackers
.size();
1205 Actor
*actor
= Maps
[idx
]->GetActorByGlobalID(Attackers
[acnt
]);
1207 //each attacker handles their own round initiation
1208 //FIXME: individual combat counter
1210 actor
->InitRound(GameTime
, !(CombatCounter
&1) );
1215 if (StateOverrideTime
)
1216 StateOverrideTime
--;
1217 if (BanterBlockTime
)
1220 if (Maps
.size()>MAX_MAPS_LOADED
) {
1223 //starting from 0, so we see the most recent master area first
1224 for(unsigned int i
=0;i
<idx
;i
++) {
1225 DelMap( (unsigned int) i
, false );
1229 // perhaps a StartMusic action stopped the area music?
1230 // (we should probably find a less silly way to handle this,
1231 // because nothing can ever stop area music now..)
1232 if (!core
->GetMusicMgr()->IsPlaying()) {
1236 //this is used only for the death delay so far
1237 if (event_handler
[0]) {
1239 core
->GetGUIScriptEngine()->RunFunction(event_handler
);
1245 if (EveryoneDead()) {
1246 //don't check it any more
1247 protagonist
= PM_NO
;
1248 core
->GetGUIScriptEngine()->RunFunction("DeathWindow");
1252 if (PartyOverflow()) {
1254 core
->GetGUIScriptEngine()->RunFunction("OpenReformPartyWindow");
1259 void Game::SetTimedEvent(const char *fname
, int count
)
1261 event_timer
= count
;
1262 strncpy(event_handler
, fname
, sizeof(event_handler
) );
1265 void Game::SetProtagonistMode(int mode
)
1270 void Game::SetPartySize(int size
)
1272 // 0 size means no party size control
1276 partysize
= (size_t) size
;
1279 //Get the area dependent rest movie
1280 ieResRef
*Game::GetDream(Map
*area
)
1282 //select dream based on area
1283 int daynight
= IsDay();
1284 if (area
->Dream
[daynight
][0]) {
1285 return area
->Dream
+daynight
;
1287 int dream
= (area
->AreaType
&(AT_FOREST
|AT_CITY
|AT_DUNGEON
))>>3;
1288 return restmovies
+dream
;
1291 //Start dream cutscenes for player1
1292 void Game::PlayerDream()
1294 Scriptable
*Sender
= GetPC(0,true);
1295 if (!Sender
) return;
1297 GameScript
* gs
= new GameScript( "player1d", ST_ACTOR
, Sender
->locals
,0,0 );
1298 gs
->MySelf
= Sender
;
1303 //noareacheck = no random encounters
1304 //dream = 0 - based on area non-0 - select from list
1306 //hp is how much hp the rest will heal
1307 void Game::RestParty(int checks
, int dream
, int hp
)
1309 if (!(checks
&REST_NOMOVE
) ) {
1310 if (!EveryoneStopped()) {
1314 Actor
*leader
= GetPC(0, true);
1319 Map
*area
= leader
->GetCurrentArea();
1320 //we let them rest if someone is paralyzed, but the others gather around
1321 if (!(checks
&REST_NOSCATTER
) ) {
1322 if (!EveryoneNearPoint( area
, leader
->Pos
, 0 ) ) {
1323 //party too scattered
1324 core
->DisplayConstantString( STR_SCATTERED
, 0xff0000 );
1329 if (!(checks
&REST_NOCRITTER
) ) {
1330 //don't allow resting while in combat
1331 if (AnyPCInCombat()) {
1332 core
->DisplayConstantString( STR_CANTRESTMONS
, 0xff0000 );
1335 //don't allow resting if hostiles are nearby
1336 if (area
->AnyEnemyNearPoint(leader
->Pos
)) {
1337 core
->DisplayConstantString( STR_CANTRESTMONS
, 0xff0000 );
1342 //rest check, if PartyRested should be set, area should return true
1343 //area should advance gametime too (so partial rest is possible)
1345 if (!(checks
&REST_NOAREA
) ) {
1346 //you cannot rest here
1347 if (area
->AreaFlags
&1) {
1348 core
->DisplayConstantString( STR_MAYNOTREST
, 0xff0000 );
1351 //you may not rest here, find an inn
1352 if (!(area
->AreaType
&(AT_OUTDOOR
|AT_FOREST
|AT_DUNGEON
|AT_CAN_REST
) ))
1354 core
->DisplayConstantString( STR_MAYNOTREST
, 0xff0000 );
1358 if(area
->Rest( leader
->Pos
, 8, (GameTime
/AI_UPDATE_TIME
)%7200/3600) ) {
1362 AdvanceTime(2400*AI_UPDATE_TIME
);
1365 int i
= GetPartySize(true); // party size, only alive
1368 Actor
*tar
= GetPC(i
, true);
1370 tar
->ClearActions();
1371 tar
->SetModal(MS_NONE
, 0);
1372 //if hp = 0, then healing will be complete
1374 //removes fatigue, recharges spells
1379 //movie and cutscene dreams
1383 if (gamedata
->Exists("player1d",IE_BCS_CLASS_ID
, true))
1386 //select dream based on area
1388 if (dream
==0 || dream
>7) {
1389 movie
= GetDream(area
);
1391 movie
= restmovies
+dream
;
1393 if (*movie
[0]!='*') {
1394 core
->PlayMovie(*movie
);
1398 //set partyrested flags
1400 area
->PartyRested();
1401 core
->SetEventFlag(EF_ACTION
);
1403 //restindex will be -1 in the case of PST
1404 int restindex
= core
->GetStringReference(STR_REST
);
1406 char* tmpstr
= NULL
;
1408 core
->GetTokenDictionary()->SetAtCopy("HOUR", hours
);
1409 if (restindex
!= -1) {
1410 strindex
= core
->GetStringReference(STR_HOURS
);
1412 strindex
= core
->GetStringReference(STR_PST_HOURS
);
1413 restindex
= core
->GetStringReference(STR_PST_REST
);
1417 if (strindex
== -1 || restindex
== -1) return;
1418 tmpstr
= core
->GetString(strindex
, 0);
1420 if (!tmpstr
) return;
1422 core
->GetTokenDictionary()->SetAtCopy("DURATION", tmpstr
);
1423 core
->FreeString(tmpstr
);
1424 core
->DisplayString(restindex
, 0xffffff, 0);
1428 void Game::TimeStop(Actor
* owner
, ieDword end
)
1430 timestop_owner
=owner
;
1431 timestop_end
=GameTime
+end
;
1434 //returns the colour which should be applied onto the whole game area viewport
1435 //this is based on timestop, dream area, weather, daytime
1437 static const Color TimeStopTint
={0xe0,0xe0,0xe0,0x20}; //greyscale
1438 static const Color DreamTint
={0xf0,0xe0,0xd0,0x10}; //light brown scale
1439 static const Color NightTint
={0x80,0x80,0xe0,0x40}; //dark, bluish
1440 static const Color DuskTint
={0xe0,0x80,0x80,0x40}; //dark, reddish
1441 static const Color FogTint
={0xff,0xff,0xff,0x40}; //whitish
1442 static const Color DarkTint
={0x80,0x80,0xe0,0x10}; //slightly dark bluish
1444 const Color
*Game::GetGlobalTint() const
1446 if (timestop_end
>GameTime
) {
1447 return &TimeStopTint
;
1449 Map
*map
= GetCurrentArea();
1450 if (!map
) return NULL
;
1451 if (map
->AreaFlags
&AF_DREAM
) {
1454 if ((map
->AreaType
&(AT_OUTDOOR
|AT_DAYNIGHT
|AT_EXTENDED_NIGHT
)) == (AT_OUTDOOR
|AT_DAYNIGHT
) ) {
1455 //get daytime colour
1456 ieDword daynight
= ((GameTime
/AI_UPDATE_TIME
)%7200/300);
1457 if (daynight
<2 || daynight
>22) {
1460 if (daynight
>20 || daynight
<4) {
1464 if ((map
->AreaType
&(AT_OUTDOOR
|AT_WEATHER
)) == (AT_OUTDOOR
|AT_WEATHER
)) {
1466 if (WeatherBits
&WB_RAIN
) {
1469 if (WeatherBits
&WB_FOG
) {
1478 ieDword daynight
= ((GameTime
/AI_UPDATE_TIME
)%7200/300);
1479 if(daynight
<4 || daynight
>20) {
1485 void Game::InAttack(ieDword globalID
)
1487 std::vector
< ieDword
>::const_iterator idx
;
1489 for(idx
=Attackers
.begin(); idx
!=Attackers
.end();idx
++) {
1490 if (*idx
==globalID
) return;
1492 Attackers
.push_back(globalID
);
1493 if (!CombatCounter
) {
1499 void Game::OutAttack(ieDword globalID
)
1501 std::vector
< ieDword
>::iterator idx
;
1503 for(idx
=Attackers
.begin(); idx
!=Attackers
.end();idx
++) {
1504 if (*idx
==globalID
) {
1505 Attackers
.erase(idx
);
1506 if (!Attackers
.size()) {
1515 void Game::ChangeSong(bool force
)
1519 if (CombatCounter
) {
1524 Song
= (GameTime
/AI_UPDATE_TIME
)%7200/3600;
1526 //area may override the song played (stick in battlemusic)
1527 area
->PlayAreaSong( Song
, force
);
1530 int Game::AttackersOf(ieDword globalID
, Map
*area
) const
1535 std::vector
< ieDword
>::const_iterator idx
;
1538 for(idx
=Attackers
.begin(); idx
!=Attackers
.end();idx
++) {
1539 Actor
* actor
= area
->GetActorByGlobalID(*idx
);
1541 if (actor
->LastTarget
==globalID
) {
1549 /* this method redraws weather. If update is false,
1550 then the weather particles won't change (game paused)
1552 void Game::DrawWeather(Region
&screen
, bool update
)
1557 if (!area
->HasWeather()) {
1561 weather
->Draw( screen
);
1566 if (!(WeatherBits
& (WB_RAIN
|WB_SNOW
)) ) {
1567 if (weather
->GetPhase() == P_GROW
) {
1568 weather
->SetPhase(P_FADE
);
1572 int drawn
= weather
->Update();
1574 WeatherBits
&= ~WB_START
;
1578 if (WeatherBits
&WB_HASWEATHER
) {
1581 StartRainOrSnow(true, area
->GetWeather());
1584 /* sets the weather type */
1585 void Game::StartRainOrSnow(bool conditional
, int w
)
1587 if (conditional
&& (w
& (WB_RAIN
|WB_SNOW
)) ) {
1588 if (WeatherBits
& (WB_RAIN
| WB_SNOW
) )
1591 // whatever was responsible for calling this, we now have some set weather
1592 WeatherBits
= w
| WB_HASWEATHER
;
1593 if (w
& WB_LIGHTNING
) {
1594 if (WeatherBits
&WB_START
) {
1597 core
->PlaySound(DS_LIGHTNING1
);
1599 core
->PlaySound(DS_LIGHTNING2
);
1602 //start raining (far)
1603 core
->PlaySound(DS_LIGHTNING3
);
1607 core
->PlaySound(DS_SNOW
);
1608 weather
->SetType(SP_TYPE_POINT
, SP_PATH_FLIT
, SP_SPAWN_SOME
);
1609 weather
->SetPhase(P_GROW
);
1610 weather
->SetColor(SPARK_COLOR_WHITE
);
1614 core
->PlaySound(DS_RAIN
);
1615 weather
->SetType(SP_TYPE_LINE
, SP_PATH_RAIN
, SP_SPAWN_SOME
);
1616 weather
->SetPhase(P_GROW
);
1617 weather
->SetColor(SPARK_COLOR_STONE
);
1620 weather
->SetPhase(P_FADE
);
1623 void Game::SetExpansion(int exp
)
1628 void Game::DebugDump()
1632 printf("Currently loaded areas:\n");
1633 for(idx
=0;idx
<Maps
.size();idx
++) {
1634 Map
*map
= Maps
[idx
];
1636 printf("%s\n",map
->GetScriptName());
1638 printf("CombatCounter: %d\n", (int) CombatCounter
);
1639 printf("Attackers count: %d\n", (int) Attackers
.size());
1640 printf("Party size: %d\n", (int) PCs
.size());
1641 for(idx
=0;idx
<PCs
.size();idx
++) {
1642 Actor
*actor
= PCs
[idx
];
1644 printf("Name: %s Order %d\n",actor
->ShortName
, actor
->InParty
);
1648 Actor
*Game::GetActorByGlobalID(ieWord objectID
) {
1649 size_t mc
= GetLoadedMapCount();
1651 Map
*map
= GetMap(mc
);
1652 Actor
*actor
= map
->GetActorByGlobalID(objectID
);
1653 if (actor
) return actor
;