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 "DataStream.h"
30 #include "DisplayMessage.h"
32 #include "GameScript.h"
33 #include "Interface.h"
36 #include "ScriptEngine.h"
37 #include "GUI/GameControl.h"
39 #define MAX_MAPS_LOADED 1
41 Game::Game(void) : Scriptable( ST_GLOBAL
)
43 protagonist
= PM_YES
; //set it to 2 for iwd/iwd2 and 0 for pst
49 SelectedSingle
= 1; //the PC we are looking at (inventory, shop)
51 SetScript( core
->GlobalScript
, 0 );
55 CombatCounter
= 0; //stored here until we know better
56 StateOverrideTime
= 0;
57 StateOverrideFlag
= 0;
65 timestop_owner
= NULL
;
69 weather
= new Particles(200);
70 weather
->SetRegion(0, 0, core
->Width
, core
->Height
);
73 //loading master areas
75 if (table
.load("mastarea")) {
76 int i
= table
->GetRowCount();
79 char *tmp
= (char *) malloc(9);
80 strnuprcpy (tmp
,table
->QueryField(i
,0),8);
81 mastarea
.push_back( tmp
);
85 //loading rest/daylight switching movies (only bg2 has them)
86 memset(restmovies
,'*',sizeof(restmovies
));
87 memset(daymovies
,'*',sizeof(restmovies
));
88 memset(nightmovies
,'*',sizeof(restmovies
));
89 if (table
.load("restmov")) {
90 for(int i
=0;i
<8;i
++) {
91 strnuprcpy(restmovies
[i
],table
->QueryField(i
,0),8);
92 strnuprcpy(daymovies
[i
],table
->QueryField(i
,1),8);
93 strnuprcpy(nightmovies
[i
],table
->QueryField(i
,2),8);
97 interval
= 1000/AI_UPDATE_TIME
;
98 //FIXME:i'm not sure in this...
107 for (i
= 0; i
< Maps
.size(); i
++) {
110 for (i
= 0; i
< PCs
.size(); i
++) {
113 for (i
= 0; i
< NPCs
.size(); i
++) {
116 for (i
= 0; i
< mastarea
.size(); i
++) {
117 free ( mastarea
[i
] );
138 i
=savedpositions
.size();
140 delete savedpositions
[i
];
143 i
=planepositions
.size();
145 delete planepositions
[i
];
149 bool IsAlive(Actor
*pc
)
151 if (pc
->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
157 int Game::FindPlayer(unsigned int partyID
)
159 for (unsigned int slot
=0; slot
<PCs
.size(); slot
++) {
160 if (PCs
[slot
]->InParty
==partyID
) {
167 Actor
* Game::FindPC(unsigned int partyID
)
169 for (unsigned int slot
=0; slot
<PCs
.size(); slot
++) {
170 if (PCs
[slot
]->InParty
==partyID
) return PCs
[slot
];
175 Actor
* Game::FindPC(const char *scriptingname
)
177 for (unsigned int slot
=0; slot
<PCs
.size(); slot
++) {
178 if (strnicmp(PCs
[slot
]->GetScriptName(),scriptingname
,32)==0 ) {
185 Actor
* Game::FindNPC(unsigned int partyID
)
187 for (unsigned int slot
=0; slot
<NPCs
.size(); slot
++) {
188 if (NPCs
[slot
]->InParty
==partyID
) return NPCs
[slot
];
193 Actor
* Game::FindNPC(const char *scriptingname
)
195 for (unsigned int slot
=0; slot
<NPCs
.size(); slot
++) {
196 if (strnicmp(NPCs
[slot
]->GetScriptName(),scriptingname
,32)==0 )
204 Actor
* Game::GetPC(unsigned int slot
, bool onlyalive
)
206 if (slot
>= PCs
.size()) {
211 while(i
<PCs
.size() ) {
212 Actor
*ac
= PCs
[i
++];
225 int Game::InStore(Actor
* pc
) const
227 for (unsigned int i
= 0; i
< NPCs
.size(); i
++) {
235 int Game::InParty(Actor
* pc
) const
237 for (unsigned int i
= 0; i
< PCs
.size(); i
++) {
245 int Game::DelPC(unsigned int slot
, bool autoFree
)
247 if (slot
>= PCs
.size()) {
253 SelectActor(PCs
[slot
], false, SELECT_NORMAL
);
257 std::vector
< Actor
*>::iterator m
= PCs
.begin() + slot
;
262 int Game::DelNPC(unsigned int slot
, bool autoFree
)
264 if (slot
>= NPCs
.size()) {
271 delete( NPCs
[slot
] );
273 std::vector
< Actor
*>::iterator m
= NPCs
.begin() + slot
;
278 //i'm sure this could be faster
279 void Game::ConsolidateParty()
281 int max
= (int) PCs
.size();
282 std::vector
< Actor
*>::const_iterator m
;
283 for (int i
=1;i
<=max
;) {
284 if (FindPlayer(i
)==-1) {
286 for ( m
= PCs
.begin(); m
!= PCs
.end(); ++m
) {
287 if ( (*m
)->InParty
>i
) {
293 for ( m
= PCs
.begin(); m
!= PCs
.end(); ++m
) {
294 (*m
)->RefreshEffects(NULL
);
298 int Game::LeaveParty (Actor
* actor
)
300 actor
->CreateStats(); //create or update stats for leaving
301 actor
->SetBase(IE_EXPLORE
, 0);
302 SelectActor(actor
, false, SELECT_NORMAL
);
303 int slot
= InParty( actor
);
307 std::vector
< Actor
*>::iterator m
= PCs
.begin() + slot
;
310 for ( m
= PCs
.begin(); m
!= PCs
.end(); ++m
) {
311 if ( (*m
)->InParty
>actor
->InParty
) {
315 //removing from party, but actor remains in 'game'
316 actor
->SetPersistent(0);
317 NPCs
.push_back( actor
);
319 if (core
->HasFeature( GF_HAS_DPLAYER
)) {
320 actor
->SetScript( "", SCR_DEFAULT
);
322 actor
->SetBase( IE_EA
, EA_NEUTRAL
);
323 return ( int ) NPCs
.size() - 1;
326 //determines if startpos.2da has rotation rows (it cannot have tutorial line)
327 bool Game::DetermineStartPosType(const TableMgr
*strta
)
329 if ((strta
->GetRowCount()>=6) && !stricmp(strta
->GetRowName(4),"START_ROT" ) )
336 void Game::InitActorPos(Actor
*actor
)
338 //start.2da row labels
339 const char *mode
[3] = { "NORMAL", "TUTORIAL", "EXPANSION" };
341 unsigned int ip
= (unsigned int) (actor
->InParty
-1);
342 AutoTable
start("start");
343 AutoTable
strta("startpos");
344 // 0 - single player, 1 - tutorial, 2 - expansion
345 ieDword playmode
= 0;
346 core
->GetDictionary()->Lookup( "PlayMode", playmode
);
347 const char *xpos
= start
->QueryField(mode
[playmode
],"XPOS");
348 const char *ypos
= start
->QueryField(mode
[playmode
],"YPOS");
349 const char *area
= start
->QueryField(mode
[playmode
],"AREA");
350 const char *rot
= start
->QueryField(mode
[playmode
],"ROT");
352 actor
->Pos
.x
= actor
->Destination
.x
= (short) atoi( strta
->QueryField( strta
->GetRowIndex(xpos
), ip
) );
353 actor
->Pos
.y
= actor
->Destination
.y
= (short) atoi( strta
->QueryField( strta
->GetRowIndex(ypos
), ip
) );
354 actor
->SetOrientation( atoi( strta
->QueryField( strta
->GetRowIndex(rot
), ip
) ), false );
356 strta
.load("startare");
357 strnlwrcpy(actor
->Area
, strta
->QueryField( strta
->GetRowIndex(area
), 0 ), 8 );
359 // strta->QueryField(strta->GetRowIndex(xpos),0);
360 // strta->QueryField(strta->GetColumnIndex(ypos),0);
362 SelectActor(actor
,true, SELECT_QUIET
);
365 int Game::JoinParty(Actor
* actor
, int join
)
367 actor
->CreateStats(); //create stats if they didn't exist yet
368 actor
->InitButtons(actor
->GetStat(IE_CLASS
), false); //init actor's buttons
369 actor
->SetBase(IE_EXPLORE
, 1);
370 if (join
&JP_INITPOS
) {
373 int slot
= InParty( actor
);
378 actor
->PCStats
->JoinDate
= GameTime
;
380 Reputation
= actor
->GetStat(IE_REPUTATION
);
383 slot
= InStore( actor
);
385 std::vector
< Actor
*>::iterator m
= NPCs
.begin() + slot
;
388 size_t size
= PCs
.size();
390 PCs
.push_back( actor
);
391 if (!actor
->InParty
) {
392 actor
->InParty
= (ieByte
) (size
+1);
398 int Game::GetPartySize(bool onlyalive
) const
402 for (unsigned int i
= 0; i
< PCs
.size(); i
++) {
403 if (!IsAlive(PCs
[i
])) {
410 return (int) PCs
.size();
413 /* sends the hotkey trigger to all selected actors */
414 void Game::SetHotKey(unsigned long Key
)
416 std::vector
< Actor
*>::const_iterator m
;
418 for ( m
= selected
.begin(); m
!= selected
.end(); ++m
) {
421 if (actor
->IsSelected()) {
422 actor
->HotKey
= (ieDword
) Key
;
427 bool Game::SelectPCSingle(int index
)
429 Actor
* actor
= FindPC( index
);
430 if (!actor
|| ! actor
->ValidTarget( GA_SELECT
| GA_NO_DEAD
| GA_NO_HIDDEN
))
433 SelectedSingle
= index
;
437 int Game::GetSelectedPCSingle() const
439 return SelectedSingle
;
443 * SelectActor() - handle (de)selecting actors.
444 * If selection was changed, runs "SelectionChanged" handler
446 * actor - either specific actor, or NULL for all
447 * select - whether actor(s) should be selected or deselected
449 * SELECT_REPLACE - if true, deselect all other actors when selecting one
450 * SELECT_QUIET - do not run handler if selection was changed. Used for
451 * nested calls to SelectActor()
454 bool Game::SelectActor(Actor
* actor
, bool select
, unsigned flags
)
456 std::vector
< Actor
*>::iterator m
;
458 // actor was not specified, which means all PCs should be (de)selected
460 for ( m
= selected
.begin(); m
!= selected
.end(); ++m
) {
461 (*m
)->Select( false );
462 (*m
)->SetOver( false );
467 for ( m
= PCs
.begin(); m
!= PCs
.end(); ++m
) {
471 SelectActor( *m
, true, SELECT_QUIET
);
475 if (! (flags
& SELECT_QUIET
)) {
476 core
->SetEventFlag(EF_SELECTION
);
481 // actor was specified, so we will work with him
483 // If actor is already (de)selected, report success, but do nothing
484 //if (actor->IsSelected() == select)
489 if (! actor
->ValidTarget( GA_SELECT
| GA_NO_DEAD
))
492 // deselect all actors first when exclusive
493 if (flags
& SELECT_REPLACE
) {
494 SelectActor( NULL
, false, SELECT_QUIET
);
497 actor
->Select( true );
498 selected
.push_back( actor
);
500 for ( m
= selected
.begin(); m
!= selected
.end(); ++m
) {
506 actor
->Select( false );
509 if (! (flags
& SELECT_QUIET
)) {
510 core
->SetEventFlag(EF_SELECTION
);
515 // Gets average party level, of onlyalive is true, then counts only living PCs
516 int Game::GetPartyLevel(bool onlyalive
) const
519 for (unsigned int i
= 0; i
<PCs
.size(); i
++) {
521 if (PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
525 count
+= PCs
[i
]->GetXPLevel(0);
530 // Returns map structure (ARE) if it is already loaded in memory
531 int Game::FindMap(const char *ResRef
)
533 int index
= (int) Maps
.size();
535 Map
*map
=Maps
[index
];
536 if (strnicmp(ResRef
, map
->GetScriptName(), 8) == 0) {
543 Map
* Game::GetMap(unsigned int index
) const
545 if (index
>= Maps
.size()) {
551 Map
*Game::GetMap(const char *areaname
, bool change
)
553 int index
= LoadMap(areaname
);
557 area
= GetMap(index
);
558 memcpy (CurrentArea
, areaname
, 8);
559 area
->SetupAmbients();
560 //change the tileset if needed
561 area
->ChangeMap(IsDay());
565 return GetMap(index
);
570 bool Game::MasterArea(const char *area
)
572 unsigned int i
=(int) mastarea
.size();
574 if (strnicmp(mastarea
[i
], area
, 8) ) {
581 void Game::SetMasterArea(const char *area
)
583 if (MasterArea(area
) ) return;
584 char *tmp
= (char *) malloc(9);
585 strnlwrcpy (tmp
,area
,8);
586 mastarea
.push_back(tmp
);
589 int Game::AddMap(Map
* map
)
591 if (MasterArea(map
->GetScriptName()) ) {
592 Maps
.insert(Maps
.begin(), 1, map
);
596 unsigned int i
= (unsigned int) Maps
.size();
597 Maps
.push_back( map
);
601 int Game::DelMap(unsigned int index
, int forced
)
603 //this function should archive the area, and remove it only if the area
604 //contains no active actors (combat, partymembers, etc)
605 if (index
>= Maps
.size()) {
608 Map
*map
= Maps
[index
];
610 if (MapIndex
==(int) index
) { //can't remove current map in any case
611 const char *name
= map
->GetScriptName();
612 memcpy(AnotherArea
, name
, sizeof(AnotherArea
) );
617 if (!map
) { //this shouldn't happen, i guess
618 printMessage("Game","Erased NULL Map\n",YELLOW
);
619 Maps
.erase( Maps
.begin()+index
);
620 if (MapIndex
>(int) index
) {
626 if (forced
|| (Maps
.size()>MAX_MAPS_LOADED
) )
628 //keep at least one master
629 const char *name
= map
->GetScriptName();
630 if (MasterArea(name
)) {
631 if(!AnotherArea
[0]) {
632 memcpy(AnotherArea
, name
, sizeof(AnotherArea
));
638 //this check must be the last, because
639 //after PurgeActors you cannot keep the
641 //Or the queues should be regenerated!
646 //remove map from memory
647 core
->SwapoutArea(Maps
[index
]);
648 delete( Maps
[index
] );
649 Maps
.erase( Maps
.begin()+index
);
650 //current map will be decreased
651 if (MapIndex
>(int) index
) {
656 //didn't remove the map
660 /* Loads an area, changepf == true if you want to setup the pathfinder too */
661 //FIXME: changepf is removed now
662 int Game::LoadMap(const char* ResRef
)
665 int index
= FindMap(ResRef
);
670 DataStream
* ds
= gamedata
->GetResource( ResRef
, IE_ARE_CLASS_ID
);
674 PluginHolder
<MapMgr
> mM(IE_ARE_CLASS_ID
);
675 if(!mM
->Open( ds
, true )) {
678 Map
* newMap
= mM
->GetMap(ResRef
, IsDay());
683 for (i
= 0; i
< PCs
.size(); i
++) {
684 if (stricmp( PCs
[i
]->Area
, ResRef
) == 0) {
685 newMap
->AddActor( PCs
[i
] );
688 for (i
= 0; i
< NPCs
.size(); i
++) {
689 if (stricmp( NPCs
[i
]->Area
, ResRef
) == 0) {
690 newMap
->AddActor( NPCs
[i
] );
693 return AddMap( newMap
);
696 int Game::AddNPC(Actor
* npc
)
698 int slot
= InStore( npc
); //already an npc
702 slot
= InParty( npc
);
705 } //can't add as npc already in party
706 npc
->SetPersistent(0);
707 NPCs
.push_back( npc
);
709 return (int) NPCs
.size() - 1;
712 Actor
* Game::GetNPC(unsigned int Index
)
714 if (Index
>= NPCs
.size()) {
720 void Game::SwapPCs(unsigned int Index1
, unsigned int Index2
)
722 if (Index1
>= PCs
.size()) {
726 if (Index2
>= PCs
.size()) {
729 int tmp
= PCs
[Index1
]->InParty
;
730 PCs
[Index1
]->InParty
= PCs
[Index2
]->InParty
;
731 PCs
[Index2
]->InParty
= tmp
;
732 //signal a change of the portrait window
733 core
->SetEventFlag(EF_PORTRAIT
);
736 void Game::DeleteJournalEntry(ieStrRef strref
)
738 size_t i
=Journals
.size();
740 if (Journals
[i
]->Text
==strref
) {
742 Journals
.erase(Journals
.begin()+i
);
747 void Game::DeleteJournalGroup(int Group
)
749 size_t i
=Journals
.size();
751 if (Journals
[i
]->Group
==(ieByte
) Group
) {
753 Journals
.erase(Journals
.begin()+i
);
757 /* returns true if it modified or added a journal entry */
758 bool Game::AddJournalEntry(ieStrRef strref
, int Section
, int Group
)
760 GAMJournalEntry
*je
= FindJournalEntry(strref
);
762 //don't set this entry again in the same section
763 if (je
->Section
==Section
) {
766 if ((Section
== IE_GAM_QUEST_DONE
) && Group
) {
767 //removing all of this group and adding a new entry
768 DeleteJournalGroup(Group
);
770 //modifying existing entry
771 je
->Section
= (ieByte
) Section
;
772 je
->Group
= (ieByte
) Group
;
774 locals
->Lookup("CHAPTER", chapter
);
775 je
->Chapter
= (ieByte
) chapter
;
776 je
->GameTime
= GameTime
;
780 je
= new GAMJournalEntry
;
781 je
->GameTime
= GameTime
;
783 locals
->Lookup("CHAPTER", chapter
);
784 je
->Chapter
= (ieByte
) chapter
;
785 je
->Section
= (ieByte
) Section
;
786 je
->Group
= (ieByte
) Group
;
789 Journals
.push_back( je
);
793 void Game::AddJournalEntry(GAMJournalEntry
* entry
)
795 Journals
.push_back( entry
);
798 unsigned int Game::GetJournalCount() const
800 return (unsigned int) Journals
.size();
803 GAMJournalEntry
* Game::FindJournalEntry(ieStrRef strref
)
805 unsigned int Index
= (unsigned int) Journals
.size();
807 GAMJournalEntry
*ret
= Journals
[Index
];
809 if (ret
->Text
==strref
) {
817 GAMJournalEntry
* Game::GetJournalEntry(unsigned int Index
)
819 if (Index
>= Journals
.size()) {
822 return Journals
[Index
];
825 unsigned int Game::GetSavedLocationCount() const
827 return (unsigned int) savedpositions
.size();
830 void Game::ClearSavedLocations()
832 size_t i
=savedpositions
.size();
834 delete savedpositions
[i
];
836 savedpositions
.clear();
839 GAMLocationEntry
* Game::GetSavedLocationEntry(unsigned int i
)
841 size_t current
= savedpositions
.size();
846 savedpositions
.resize(i
+1);
848 savedpositions
[current
++]=(GAMLocationEntry
*) calloc(1, sizeof(GAMLocationEntry
) );
851 return savedpositions
[i
];
854 unsigned int Game::GetPlaneLocationCount() const
856 return (unsigned int) planepositions
.size();
859 void Game::ClearPlaneLocations()
861 size_t i
=planepositions
.size();
863 delete planepositions
[i
];
865 planepositions
.clear();
868 GAMLocationEntry
* Game::GetPlaneLocationEntry(unsigned int i
)
870 size_t current
= planepositions
.size();
875 planepositions
.resize(i
+1);
877 planepositions
[current
++]=(GAMLocationEntry
*) calloc(1, sizeof(GAMLocationEntry
) );
880 return planepositions
[i
];
883 char *Game::GetFamiliar(unsigned int Index
)
885 return Familiars
[Index
];
888 //reading the challenge rating table for iwd2 (only when needed)
889 void Game::LoadCRTable()
891 AutoTable
table("moncrate");
893 int maxrow
= table
->GetRowCount()-1;
894 crtable
= new CRRow
[MAX_LEVEL
];
895 for(int i
=0;i
<MAX_LEVEL
;i
++) {
896 //row shouldn't be larger than maxrow
897 int row
= i
<maxrow
?i
:maxrow
;
898 int maxcol
= table
->GetColumnCount(row
)-1;
899 for(int j
=0;j
<MAX_CRLEVEL
;j
++) {
900 //col shouldn't be larger than maxcol
901 int col
= j
<maxcol
?j
:maxcol
;
902 crtable
[i
][j
]=atoi(table
->QueryField(row
,col
) );
908 int Game::GetXPFromCR(int cr
)
910 if (!crtable
) LoadCRTable();
912 int level
= GetPartyLevel(true);
913 if (cr
>=MAX_CRLEVEL
) {
916 printf("Challenge Rating: %d, party level: %d ", cr
, level
);
917 return crtable
[level
][cr
];
919 printMessage("Game","Cannot find moncrate.2da!\n", LIGHT_RED
);
923 void Game::ShareXP(int xp
, int flags
)
928 xp
= GetXPFromCR(xp
);
931 if (flags
&SX_DIVIDE
) {
932 int PartySize
= GetPartySize(true); //party size, only alive
936 individual
= xp
/ PartySize
;
946 displaymsg
->DisplayConstantStringValue( STR_GOTXP
, 0xbcefbc, (ieDword
) xp
); //you have gained ... xp
948 displaymsg
->DisplayConstantStringValue( STR_LOSTXP
, 0xbcefbc, (ieDword
) -xp
); //you have lost ... xp
950 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
951 if (PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
954 PCs
[i
]->AddExperience(individual
);
958 bool Game::EveryoneStopped() const
960 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
961 if (PCs
[i
]->GetNextStep() ) return false;
966 //canmove=true: if some PC can't move (or hostile), then this returns false
967 bool Game::EveryoneNearPoint(Map
*area
, const Point
&p
, int flags
) const
969 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
970 if (flags
&ENP_ONLYSELECT
) {
971 if(!PCs
[i
]->Selected
) {
975 if (PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
978 if (flags
&ENP_CANMOVE
) {
979 //someone is uncontrollable, can't move
980 if (PCs
[i
]->GetStat(IE_EA
)>EA_GOODCUTOFF
) {
984 if (PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_CANTMOVE
) {
988 if (PCs
[i
]->GetCurrentArea()!=area
) {
991 if (Distance(p
,PCs
[i
])>MAX_TRAVELING_DISTANCE
) {
998 //called when someone died
999 void Game::PartyMemberDied(Actor
*actor
)
1001 //this could be null, in some extreme cases...
1002 Map
*area
= actor
->GetCurrentArea();
1004 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1005 if (PCs
[i
]==actor
) {
1008 if (PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
1011 if (PCs
[i
]->GetCurrentArea()!=area
) {
1014 PCs
[i
]->ReactToDeath(actor
->GetScriptName());
1018 //reports if someone died
1019 int Game::PartyMemberDied() const
1021 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1022 if (PCs
[i
]->GetInternalFlag()&IF_JUSTDIED
) {
1029 void Game::IncrementChapter()
1031 //chapter first set to 0 (prologue)
1032 ieDword chapter
= (ieDword
) -1;
1033 locals
->Lookup("CHAPTER",chapter
);
1034 locals
->SetAt("CHAPTER",chapter
+1);
1036 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1037 //all PCs must have this!
1038 PCs
[i
]->PCStats
->IncrementChapter();
1042 void Game::SetReputation(ieDword r
)
1045 else if (r
>200) r
=200;
1047 displaymsg
->DisplayConstantStringValue(STR_LOSTREP
,0xc0c000,(Reputation
-r
)/10);
1048 } else if (Reputation
<r
) {
1049 displaymsg
->DisplayConstantStringValue(STR_GOTREP
,0xc0c000,(r
-Reputation
)/10);
1052 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1053 PCs
[i
]->SetBase(IE_REPUTATION
, Reputation
);
1057 void Game::SetControlStatus(int value
, int mode
)
1060 case BM_OR
: ControlStatus
|=value
; break;
1061 case BM_NAND
: ControlStatus
&=~value
; break;
1062 case BM_SET
: ControlStatus
=value
; break;
1063 case BM_AND
: ControlStatus
&=value
; break;
1064 case BM_XOR
: ControlStatus
^=value
; break;
1066 core
->SetEventFlag(EF_CONTROL
);
1069 void Game::AddGold(ieDword add
)
1078 if (old
<PartyGold
) {
1079 displaymsg
->DisplayConstantStringValue( STR_GOTGOLD
, 0xc0c000, PartyGold
-old
);
1081 displaymsg
->DisplayConstantStringValue( STR_LOSTGOLD
, 0xc0c000, old
-PartyGold
);
1085 //later this could be more complicated
1086 void Game::AdvanceTime(ieDword add
)
1088 ieDword h
= GameTime
/(300*AI_UPDATE_TIME
);
1090 if (h
!=GameTime
/(300*AI_UPDATE_TIME
)) {
1091 //asking for a new weather when the hour changes
1092 WeatherBits
&=~WB_HASWEATHER
;
1094 Ticks
+=add
*interval
;
1095 //change the tileset if needed
1096 Map
*map
= GetCurrentArea();
1097 if (map
&& map
->ChangeMap(IsDay())) {
1098 //play the daylight transition movie appropriate for the area
1099 //it is needed to play only when the area truly changed its tileset
1100 //this is signalled by ChangeMap
1101 int areatype
= (area
->AreaType
&(AT_FOREST
|AT_CITY
|AT_DUNGEON
))>>3;
1104 printMessage("Game","Switching DayLight\n",GREEN
);
1106 res
=&nightmovies
[areatype
];
1108 res
=&daymovies
[areatype
];
1111 core
->PlayMovie(*res
);
1116 //returns true if there are excess players in the team
1117 bool Game::PartyOverflow() const
1119 GameControl
*gc
= core
->GetGameControl();
1123 //don't start this screen when the gui is busy
1124 if (gc
->GetDialogueFlags() & (DF_IN_DIALOG
|DF_IN_CONTAINER
|DF_FREEZE_SCRIPTS
) ) {
1130 return (PCs
.size()>partysize
);
1133 bool Game::PCInCombat(Actor
* actor
) const
1135 if (!CombatCounter
) {
1139 if (actor
->LastTarget
) {
1142 if (AttackersOf(actor
->GetID(), actor
->GetCurrentArea())) {
1148 bool Game::AnyPCInCombat() const
1150 if (!CombatCounter
) {
1154 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1155 if (PCInCombat (PCs
[i
])) {
1162 //returns true if the protagonist (or the whole party died)
1163 bool Game::EveryoneDead() const
1165 //if there are no PCs, then we assume everyone dead
1169 if (protagonist
==PM_NO
) {
1170 Actor
*nameless
= PCs
[0];
1171 if (nameless
->GetStat(IE_STATE_ID
)&STATE_NOSAVE
) {
1172 if (area
->INISpawn
) {
1173 area
->INISpawn
->RespawnNameless();
1178 // if protagonist died
1179 if (protagonist
==PM_YES
) {
1180 if (PCs
[0]->GetStat(IE_STATE_ID
)&STATE_NOSAVE
) {
1186 for (unsigned int i
=0; i
<PCs
.size(); i
++) {
1187 if (!(PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_NOSAVE
) ) {
1194 //runs all area scripts
1196 void Game::UpdateScripts()
1199 ProcessActions(false);
1202 for (idx
=0;idx
<Maps
.size();idx
++) {
1203 Maps
[idx
]->UpdateScripts();
1204 size_t acnt
=Attackers
.size();
1206 Actor
*actor
= Maps
[idx
]->GetActorByGlobalID(Attackers
[acnt
]);
1208 //each attacker handles their own round initiation
1209 //FIXME: individual combat counter
1211 actor
->InitRound(GameTime
, !(CombatCounter
&1) );
1216 if (StateOverrideTime
)
1217 StateOverrideTime
--;
1218 if (BanterBlockTime
)
1221 if (Maps
.size()>MAX_MAPS_LOADED
) {
1224 //starting from 0, so we see the most recent master area first
1225 for(unsigned int i
=0;i
<idx
;i
++) {
1226 DelMap( (unsigned int) i
, false );
1230 // perhaps a StartMusic action stopped the area music?
1231 // (we should probably find a less silly way to handle this,
1232 // because nothing can ever stop area music now..)
1233 if (!core
->GetMusicMgr()->IsPlaying()) {
1237 //this is used only for the death delay so far
1238 if (event_handler
) {
1240 event_handler
->call();
1241 event_handler
= NULL
;
1246 if (EveryoneDead()) {
1247 //don't check it any more
1248 protagonist
= PM_NO
;
1249 core
->GetGUIScriptEngine()->RunFunction("GUIWORLD", "DeathWindow");
1253 if (PartyOverflow()) {
1255 core
->GetGUIScriptEngine()->RunFunction("GUIWORLD", "OpenReformPartyWindow");
1260 void Game::SetTimedEvent(EventHandler func
, int count
)
1262 event_timer
= count
;
1263 event_handler
= func
;
1266 void Game::SetProtagonistMode(int mode
)
1271 void Game::SetPartySize(int size
)
1273 // 0 size means no party size control
1277 partysize
= (size_t) size
;
1280 //Get the area dependent rest movie
1281 ieResRef
*Game::GetDream(Map
*area
)
1283 //select dream based on area
1284 int daynight
= IsDay();
1285 if (area
->Dream
[daynight
][0]) {
1286 return area
->Dream
+daynight
;
1288 int dream
= (area
->AreaType
&(AT_FOREST
|AT_CITY
|AT_DUNGEON
))>>3;
1289 return restmovies
+dream
;
1292 //Start dream cutscenes for player1
1293 void Game::PlayerDream()
1295 Scriptable
*Sender
= GetPC(0,true);
1296 if (!Sender
) return;
1298 GameScript
* gs
= new GameScript( "player1d", Sender
,0,0 );
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 displaymsg
->DisplayConstantString( STR_SCATTERED
, 0xff0000 );
1329 if (!(checks
&REST_NOCRITTER
) ) {
1330 //don't allow resting while in combat
1331 if (AnyPCInCombat()) {
1332 displaymsg
->DisplayConstantString( STR_CANTRESTMONS
, 0xff0000 );
1335 //don't allow resting if hostiles are nearby
1336 if (area
->AnyEnemyNearPoint(leader
->Pos
)) {
1337 displaymsg
->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 displaymsg
->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 displaymsg
->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
= displaymsg
->GetStringReference(STR_REST
);
1406 char* tmpstr
= NULL
;
1408 core
->GetTokenDictionary()->SetAtCopy("HOUR", hours
);
1409 if (restindex
!= -1) {
1410 strindex
= displaymsg
->GetStringReference(STR_HOURS
);
1412 strindex
= displaymsg
->GetStringReference(STR_PST_HOURS
);
1413 restindex
= displaymsg
->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 displaymsg
->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(const 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
;