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 "GameControl.h"
32 #include "GameScript.h"
33 #include "Interface.h"
36 #include "ScriptEngine.h"
38 #define MAX_MAPS_LOADED 1
40 Game::Game(void) : Scriptable( ST_GLOBAL
)
42 protagonist
= PM_YES
; //set it to 2 for iwd/iwd2 and 0 for pst
48 SelectedSingle
= 1; //the PC we are looking at (inventory, shop)
50 SetScript( core
->GlobalScript
, 0 );
54 CombatCounter
= 0; //stored here until we know better
55 StateOverrideTime
= 0;
56 StateOverrideFlag
= 0;
64 timestop_owner
= NULL
;
68 weather
= new Particles(200);
69 weather
->SetRegion(0, 0, core
->Width
, core
->Height
);
72 //loading master areas
74 if (table
.load("mastarea")) {
75 int i
= table
->GetRowCount();
78 char *tmp
= (char *) malloc(9);
79 strnuprcpy (tmp
,table
->QueryField(i
,0),8);
80 mastarea
.push_back( tmp
);
84 //loading rest/daylight switching movies (only bg2 has them)
85 memset(restmovies
,'*',sizeof(restmovies
));
86 memset(daymovies
,'*',sizeof(restmovies
));
87 memset(nightmovies
,'*',sizeof(restmovies
));
88 if (table
.load("restmov")) {
89 for(int i
=0;i
<8;i
++) {
90 strnuprcpy(restmovies
[i
],table
->QueryField(i
,0),8);
91 strnuprcpy(daymovies
[i
],table
->QueryField(i
,1),8);
92 strnuprcpy(nightmovies
[i
],table
->QueryField(i
,2),8);
96 interval
= 1000/AI_UPDATE_TIME
;
97 //FIXME:i'm not sure in this...
106 for (i
= 0; i
< Maps
.size(); i
++) {
109 for (i
= 0; i
< PCs
.size(); i
++) {
112 for (i
= 0; i
< NPCs
.size(); i
++) {
115 for (i
= 0; i
< mastarea
.size(); i
++) {
116 free ( mastarea
[i
] );
137 i
=savedpositions
.size();
139 delete savedpositions
[i
];
142 i
=planepositions
.size();
144 delete planepositions
[i
];
148 bool IsAlive(Actor
*pc
)
150 if (pc
->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
156 int Game::FindPlayer(unsigned int partyID
)
158 for (unsigned int slot
=0; slot
<PCs
.size(); slot
++) {
159 if (PCs
[slot
]->InParty
==partyID
) {
166 Actor
* Game::FindPC(unsigned int partyID
)
168 for (unsigned int slot
=0; slot
<PCs
.size(); slot
++) {
169 if (PCs
[slot
]->InParty
==partyID
) return PCs
[slot
];
174 Actor
* Game::FindPC(const char *scriptingname
)
176 for (unsigned int slot
=0; slot
<PCs
.size(); slot
++) {
177 if (strnicmp(PCs
[slot
]->GetScriptName(),scriptingname
,32)==0 ) {
184 Actor
* Game::FindNPC(unsigned int partyID
)
186 for (unsigned int slot
=0; slot
<NPCs
.size(); slot
++) {
187 if (NPCs
[slot
]->InParty
==partyID
) return NPCs
[slot
];
192 Actor
* Game::FindNPC(const char *scriptingname
)
194 for (unsigned int slot
=0; slot
<NPCs
.size(); slot
++) {
195 if (strnicmp(NPCs
[slot
]->GetScriptName(),scriptingname
,32)==0 )
203 Actor
* Game::GetPC(unsigned int slot
, bool onlyalive
)
205 if (slot
>= PCs
.size()) {
210 while(i
<PCs
.size() ) {
211 Actor
*ac
= PCs
[i
++];
224 int Game::InStore(Actor
* pc
) const
226 for (unsigned int i
= 0; i
< NPCs
.size(); i
++) {
234 int Game::InParty(Actor
* pc
) const
236 for (unsigned int i
= 0; i
< PCs
.size(); i
++) {
244 int Game::DelPC(unsigned int slot
, bool autoFree
)
246 if (slot
>= PCs
.size()) {
252 SelectActor(PCs
[slot
], false, SELECT_NORMAL
);
256 std::vector
< Actor
*>::iterator m
= PCs
.begin() + slot
;
261 int Game::DelNPC(unsigned int slot
, bool autoFree
)
263 if (slot
>= NPCs
.size()) {
270 delete( NPCs
[slot
] );
272 std::vector
< Actor
*>::iterator m
= NPCs
.begin() + slot
;
277 //i'm sure this could be faster
278 void Game::ConsolidateParty()
280 int max
= (int) PCs
.size();
281 std::vector
< Actor
*>::const_iterator m
;
282 for (int i
=1;i
<=max
;) {
283 if (FindPlayer(i
)==-1) {
285 for ( m
= PCs
.begin(); m
!= PCs
.end(); ++m
) {
286 if ( (*m
)->InParty
>i
) {
292 for ( m
= PCs
.begin(); m
!= PCs
.end(); ++m
) {
293 (*m
)->RefreshEffects(NULL
);
297 int Game::LeaveParty (Actor
* actor
)
299 actor
->CreateStats(); //create or update stats for leaving
300 actor
->SetBase(IE_EXPLORE
, 0);
301 SelectActor(actor
, false, SELECT_NORMAL
);
302 int slot
= InParty( actor
);
306 std::vector
< Actor
*>::iterator m
= PCs
.begin() + slot
;
309 for ( m
= PCs
.begin(); m
!= PCs
.end(); ++m
) {
310 if ( (*m
)->InParty
>actor
->InParty
) {
314 //removing from party, but actor remains in 'game'
315 actor
->SetPersistent(0);
316 NPCs
.push_back( actor
);
318 if (core
->HasFeature( GF_HAS_DPLAYER
)) {
319 actor
->SetScript( "", SCR_DEFAULT
);
321 actor
->SetBase( IE_EA
, EA_NEUTRAL
);
322 return ( int ) NPCs
.size() - 1;
325 //determines if startpos.2da has rotation rows (it cannot have tutorial line)
326 bool Game::DetermineStartPosType(const TableMgr
*strta
)
328 if ((strta
->GetRowCount()>=6) && !stricmp(strta
->GetRowName(4),"START_ROT" ) )
335 void Game::InitActorPos(Actor
*actor
)
337 //start.2da row labels
338 const char *mode
[3] = { "NORMAL", "TUTORIAL", "EXPANSION" };
340 unsigned int ip
= (unsigned int) (actor
->InParty
-1);
341 AutoTable
start("start");
342 AutoTable
strta("startpos");
343 // 0 - single player, 1 - tutorial, 2 - expansion
344 ieDword playmode
= 0;
345 core
->GetDictionary()->Lookup( "PlayMode", playmode
);
346 const char *xpos
= start
->QueryField(mode
[playmode
],"XPOS");
347 const char *ypos
= start
->QueryField(mode
[playmode
],"YPOS");
348 const char *area
= start
->QueryField(mode
[playmode
],"AREA");
349 const char *rot
= start
->QueryField(mode
[playmode
],"ROT");
351 actor
->Pos
.x
= actor
->Destination
.x
= (short) atoi( strta
->QueryField( strta
->GetRowIndex(xpos
), ip
) );
352 actor
->Pos
.y
= actor
->Destination
.y
= (short) atoi( strta
->QueryField( strta
->GetRowIndex(ypos
), ip
) );
353 actor
->SetOrientation( atoi( strta
->QueryField( strta
->GetRowIndex(rot
), ip
) ), false );
355 strta
.load("startare");
356 strnlwrcpy(actor
->Area
, strta
->QueryField( strta
->GetRowIndex(area
), 0 ), 8 );
358 // strta->QueryField(strta->GetRowIndex(xpos),0);
359 // strta->QueryField(strta->GetColumnIndex(ypos),0);
361 SelectActor(actor
,true, SELECT_QUIET
);
364 int Game::JoinParty(Actor
* actor
, int join
)
366 actor
->CreateStats(); //create stats if they didn't exist yet
367 actor
->InitButtons(actor
->GetStat(IE_CLASS
), false); //init actor's buttons
368 actor
->SetBase(IE_EXPLORE
, 1);
369 if (join
&JP_INITPOS
) {
372 int slot
= InParty( actor
);
377 actor
->PCStats
->JoinDate
= GameTime
;
379 Reputation
= actor
->GetStat(IE_REPUTATION
);
382 slot
= InStore( actor
);
384 std::vector
< Actor
*>::iterator m
= NPCs
.begin() + slot
;
387 size_t size
= PCs
.size();
389 PCs
.push_back( actor
);
390 if (!actor
->InParty
) {
391 actor
->InParty
= (ieByte
) (size
+1);
397 int Game::GetPartySize(bool onlyalive
) const
401 for (unsigned int i
= 0; i
< PCs
.size(); i
++) {
402 if (!IsAlive(PCs
[i
])) {
409 return (int) PCs
.size();
412 /* sends the hotkey trigger to all selected actors */
413 void Game::SetHotKey(unsigned long Key
)
415 std::vector
< Actor
*>::const_iterator m
;
417 for ( m
= selected
.begin(); m
!= selected
.end(); ++m
) {
420 if (actor
->IsSelected()) {
421 actor
->HotKey
= (ieDword
) Key
;
426 bool Game::SelectPCSingle(int index
)
428 Actor
* actor
= FindPC( index
);
429 if (!actor
|| ! actor
->ValidTarget( GA_SELECT
| GA_NO_DEAD
| GA_NO_HIDDEN
))
432 SelectedSingle
= index
;
436 int Game::GetSelectedPCSingle() const
438 return SelectedSingle
;
442 * SelectActor() - handle (de)selecting actors.
443 * If selection was changed, runs "SelectionChanged" handler
445 * actor - either specific actor, or NULL for all
446 * select - whether actor(s) should be selected or deselected
448 * SELECT_REPLACE - if true, deselect all other actors when selecting one
449 * SELECT_QUIET - do not run handler if selection was changed. Used for
450 * nested calls to SelectActor()
453 bool Game::SelectActor(Actor
* actor
, bool select
, unsigned flags
)
455 std::vector
< Actor
*>::iterator m
;
457 // actor was not specified, which means all PCs should be (de)selected
459 for ( m
= selected
.begin(); m
!= selected
.end(); ++m
) {
460 (*m
)->Select( false );
461 (*m
)->SetOver( false );
466 for ( m
= PCs
.begin(); m
!= PCs
.end(); ++m
) {
470 SelectActor( *m
, true, SELECT_QUIET
);
474 if (! (flags
& SELECT_QUIET
)) {
475 core
->SetEventFlag(EF_SELECTION
);
480 // actor was specified, so we will work with him
482 // If actor is already (de)selected, report success, but do nothing
483 //if (actor->IsSelected() == select)
488 if (! actor
->ValidTarget( GA_SELECT
| GA_NO_DEAD
))
491 // deselect all actors first when exclusive
492 if (flags
& SELECT_REPLACE
) {
493 SelectActor( NULL
, false, SELECT_QUIET
);
496 actor
->Select( true );
497 selected
.push_back( actor
);
499 for ( m
= selected
.begin(); m
!= selected
.end(); ++m
) {
505 actor
->Select( false );
508 if (! (flags
& SELECT_QUIET
)) {
509 core
->SetEventFlag(EF_SELECTION
);
514 // Gets average party level, of onlyalive is true, then counts only living PCs
515 int Game::GetPartyLevel(bool onlyalive
) const
518 for (unsigned int i
= 0; i
<PCs
.size(); i
++) {
520 if (PCs
[i
]->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
524 count
+= PCs
[i
]->GetXPLevel(0);
529 // Returns map structure (ARE) if it is already loaded in memory
530 int Game::FindMap(const char *ResRef
)
532 int index
= (int) Maps
.size();
534 Map
*map
=Maps
[index
];
535 if (strnicmp(ResRef
, map
->GetScriptName(), 8) == 0) {
542 Map
* Game::GetMap(unsigned int index
) const
544 if (index
>= Maps
.size()) {
550 Map
*Game::GetMap(const char *areaname
, bool change
)
552 int index
= LoadMap(areaname
);
556 area
= GetMap(index
);
557 memcpy (CurrentArea
, areaname
, 8);
558 area
->SetupAmbients();
559 //change the tileset if needed
560 area
->ChangeMap(IsDay());
564 return GetMap(index
);
569 bool Game::MasterArea(const char *area
)
571 unsigned int i
=(int) mastarea
.size();
573 if (strnicmp(mastarea
[i
], area
, 8) ) {
580 void Game::SetMasterArea(const char *area
)
582 if (MasterArea(area
) ) return;
583 char *tmp
= (char *) malloc(9);
584 strnlwrcpy (tmp
,area
,8);
585 mastarea
.push_back(tmp
);
588 int Game::AddMap(Map
* map
)
590 if (MasterArea(map
->GetScriptName()) ) {
591 Maps
.insert(Maps
.begin(), 1, map
);
595 unsigned int i
= (unsigned int) Maps
.size();
596 Maps
.push_back( map
);
600 int Game::DelMap(unsigned int index
, int forced
)
602 //this function should archive the area, and remove it only if the area
603 //contains no active actors (combat, partymembers, etc)
604 if (index
>= Maps
.size()) {
607 Map
*map
= Maps
[index
];
609 if (MapIndex
==(int) index
) { //can't remove current map in any case
610 const char *name
= map
->GetScriptName();
611 memcpy(AnotherArea
, name
, sizeof(AnotherArea
) );
616 if (!map
) { //this shouldn't happen, i guess
617 printMessage("Game","Erased NULL Map\n",YELLOW
);
618 Maps
.erase( Maps
.begin()+index
);
619 if (MapIndex
>(int) index
) {
625 if (forced
|| (Maps
.size()>MAX_MAPS_LOADED
) )
627 //keep at least one master
628 const char *name
= map
->GetScriptName();
629 if (MasterArea(name
)) {
630 if(!AnotherArea
[0]) {
631 memcpy(AnotherArea
, name
, sizeof(AnotherArea
));
637 //this check must be the last, because
638 //after PurgeActors you cannot keep the
640 //Or the queues should be regenerated!
645 //remove map from memory
646 core
->SwapoutArea(Maps
[index
]);
647 delete( Maps
[index
] );
648 Maps
.erase( Maps
.begin()+index
);
649 //current map will be decreased
650 if (MapIndex
>(int) index
) {
655 //didn't remove the map
659 /* Loads an area, changepf == true if you want to setup the pathfinder too */
660 //FIXME: changepf is removed now
661 int Game::LoadMap(const char* ResRef
)
664 int index
= FindMap(ResRef
);
669 DataStream
* ds
= gamedata
->GetResource( ResRef
, IE_ARE_CLASS_ID
);
673 PluginHolder
<MapMgr
> mM(IE_ARE_CLASS_ID
);
674 if(!mM
->Open( ds
, true )) {
677 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
, const 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", Sender
,0,0 );
1302 //noareacheck = no random encounters
1303 //dream = 0 - based on area non-0 - select from list
1305 //hp is how much hp the rest will heal
1306 void Game::RestParty(int checks
, int dream
, int hp
)
1308 if (!(checks
&REST_NOMOVE
) ) {
1309 if (!EveryoneStopped()) {
1313 Actor
*leader
= GetPC(0, true);
1318 Map
*area
= leader
->GetCurrentArea();
1319 //we let them rest if someone is paralyzed, but the others gather around
1320 if (!(checks
&REST_NOSCATTER
) ) {
1321 if (!EveryoneNearPoint( area
, leader
->Pos
, 0 ) ) {
1322 //party too scattered
1323 core
->DisplayConstantString( STR_SCATTERED
, 0xff0000 );
1328 if (!(checks
&REST_NOCRITTER
) ) {
1329 //don't allow resting while in combat
1330 if (AnyPCInCombat()) {
1331 core
->DisplayConstantString( STR_CANTRESTMONS
, 0xff0000 );
1334 //don't allow resting if hostiles are nearby
1335 if (area
->AnyEnemyNearPoint(leader
->Pos
)) {
1336 core
->DisplayConstantString( STR_CANTRESTMONS
, 0xff0000 );
1341 //rest check, if PartyRested should be set, area should return true
1342 //area should advance gametime too (so partial rest is possible)
1344 if (!(checks
&REST_NOAREA
) ) {
1345 //you cannot rest here
1346 if (area
->AreaFlags
&1) {
1347 core
->DisplayConstantString( STR_MAYNOTREST
, 0xff0000 );
1350 //you may not rest here, find an inn
1351 if (!(area
->AreaType
&(AT_OUTDOOR
|AT_FOREST
|AT_DUNGEON
|AT_CAN_REST
) ))
1353 core
->DisplayConstantString( STR_MAYNOTREST
, 0xff0000 );
1357 if(area
->Rest( leader
->Pos
, 8, (GameTime
/AI_UPDATE_TIME
)%7200/3600) ) {
1361 AdvanceTime(2400*AI_UPDATE_TIME
);
1364 int i
= GetPartySize(true); // party size, only alive
1367 Actor
*tar
= GetPC(i
, true);
1369 tar
->ClearActions();
1370 tar
->SetModal(MS_NONE
, 0);
1371 //if hp = 0, then healing will be complete
1373 //removes fatigue, recharges spells
1378 //movie and cutscene dreams
1382 if (gamedata
->Exists("player1d",IE_BCS_CLASS_ID
, true))
1385 //select dream based on area
1387 if (dream
==0 || dream
>7) {
1388 movie
= GetDream(area
);
1390 movie
= restmovies
+dream
;
1392 if (*movie
[0]!='*') {
1393 core
->PlayMovie(*movie
);
1397 //set partyrested flags
1399 area
->PartyRested();
1400 core
->SetEventFlag(EF_ACTION
);
1402 //restindex will be -1 in the case of PST
1403 int restindex
= core
->GetStringReference(STR_REST
);
1405 char* tmpstr
= NULL
;
1407 core
->GetTokenDictionary()->SetAtCopy("HOUR", hours
);
1408 if (restindex
!= -1) {
1409 strindex
= core
->GetStringReference(STR_HOURS
);
1411 strindex
= core
->GetStringReference(STR_PST_HOURS
);
1412 restindex
= core
->GetStringReference(STR_PST_REST
);
1416 if (strindex
== -1 || restindex
== -1) return;
1417 tmpstr
= core
->GetString(strindex
, 0);
1419 if (!tmpstr
) return;
1421 core
->GetTokenDictionary()->SetAtCopy("DURATION", tmpstr
);
1422 core
->FreeString(tmpstr
);
1423 core
->DisplayString(restindex
, 0xffffff, 0);
1427 void Game::TimeStop(Actor
* owner
, ieDword end
)
1429 timestop_owner
=owner
;
1430 timestop_end
=GameTime
+end
;
1433 //returns the colour which should be applied onto the whole game area viewport
1434 //this is based on timestop, dream area, weather, daytime
1436 static const Color TimeStopTint
={0xe0,0xe0,0xe0,0x20}; //greyscale
1437 static const Color DreamTint
={0xf0,0xe0,0xd0,0x10}; //light brown scale
1438 static const Color NightTint
={0x80,0x80,0xe0,0x40}; //dark, bluish
1439 static const Color DuskTint
={0xe0,0x80,0x80,0x40}; //dark, reddish
1440 static const Color FogTint
={0xff,0xff,0xff,0x40}; //whitish
1441 static const Color DarkTint
={0x80,0x80,0xe0,0x10}; //slightly dark bluish
1443 const Color
*Game::GetGlobalTint() const
1445 if (timestop_end
>GameTime
) {
1446 return &TimeStopTint
;
1448 Map
*map
= GetCurrentArea();
1449 if (!map
) return NULL
;
1450 if (map
->AreaFlags
&AF_DREAM
) {
1453 if ((map
->AreaType
&(AT_OUTDOOR
|AT_DAYNIGHT
|AT_EXTENDED_NIGHT
)) == (AT_OUTDOOR
|AT_DAYNIGHT
) ) {
1454 //get daytime colour
1455 ieDword daynight
= ((GameTime
/AI_UPDATE_TIME
)%7200/300);
1456 if (daynight
<2 || daynight
>22) {
1459 if (daynight
>20 || daynight
<4) {
1463 if ((map
->AreaType
&(AT_OUTDOOR
|AT_WEATHER
)) == (AT_OUTDOOR
|AT_WEATHER
)) {
1465 if (WeatherBits
&WB_RAIN
) {
1468 if (WeatherBits
&WB_FOG
) {
1477 ieDword daynight
= ((GameTime
/AI_UPDATE_TIME
)%7200/300);
1478 if(daynight
<4 || daynight
>20) {
1484 void Game::InAttack(ieDword globalID
)
1486 std::vector
< ieDword
>::const_iterator idx
;
1488 for(idx
=Attackers
.begin(); idx
!=Attackers
.end();idx
++) {
1489 if (*idx
==globalID
) return;
1491 Attackers
.push_back(globalID
);
1492 if (!CombatCounter
) {
1498 void Game::OutAttack(ieDword globalID
)
1500 std::vector
< ieDword
>::iterator idx
;
1502 for(idx
=Attackers
.begin(); idx
!=Attackers
.end();idx
++) {
1503 if (*idx
==globalID
) {
1504 Attackers
.erase(idx
);
1505 if (!Attackers
.size()) {
1514 void Game::ChangeSong(bool force
)
1518 if (CombatCounter
) {
1523 Song
= (GameTime
/AI_UPDATE_TIME
)%7200/3600;
1525 //area may override the song played (stick in battlemusic)
1526 area
->PlayAreaSong( Song
, force
);
1529 int Game::AttackersOf(ieDword globalID
, Map
*area
) const
1534 std::vector
< ieDword
>::const_iterator idx
;
1537 for(idx
=Attackers
.begin(); idx
!=Attackers
.end();idx
++) {
1538 Actor
* actor
= area
->GetActorByGlobalID(*idx
);
1540 if (actor
->LastTarget
==globalID
) {
1548 /* this method redraws weather. If update is false,
1549 then the weather particles won't change (game paused)
1551 void Game::DrawWeather(const Region
&screen
, bool update
)
1556 if (!area
->HasWeather()) {
1560 weather
->Draw( screen
);
1565 if (!(WeatherBits
& (WB_RAIN
|WB_SNOW
)) ) {
1566 if (weather
->GetPhase() == P_GROW
) {
1567 weather
->SetPhase(P_FADE
);
1571 int drawn
= weather
->Update();
1573 WeatherBits
&= ~WB_START
;
1577 if (WeatherBits
&WB_HASWEATHER
) {
1580 StartRainOrSnow(true, area
->GetWeather());
1583 /* sets the weather type */
1584 void Game::StartRainOrSnow(bool conditional
, int w
)
1586 if (conditional
&& (w
& (WB_RAIN
|WB_SNOW
)) ) {
1587 if (WeatherBits
& (WB_RAIN
| WB_SNOW
) )
1590 // whatever was responsible for calling this, we now have some set weather
1591 WeatherBits
= w
| WB_HASWEATHER
;
1592 if (w
& WB_LIGHTNING
) {
1593 if (WeatherBits
&WB_START
) {
1596 core
->PlaySound(DS_LIGHTNING1
);
1598 core
->PlaySound(DS_LIGHTNING2
);
1601 //start raining (far)
1602 core
->PlaySound(DS_LIGHTNING3
);
1606 core
->PlaySound(DS_SNOW
);
1607 weather
->SetType(SP_TYPE_POINT
, SP_PATH_FLIT
, SP_SPAWN_SOME
);
1608 weather
->SetPhase(P_GROW
);
1609 weather
->SetColor(SPARK_COLOR_WHITE
);
1613 core
->PlaySound(DS_RAIN
);
1614 weather
->SetType(SP_TYPE_LINE
, SP_PATH_RAIN
, SP_SPAWN_SOME
);
1615 weather
->SetPhase(P_GROW
);
1616 weather
->SetColor(SPARK_COLOR_STONE
);
1619 weather
->SetPhase(P_FADE
);
1622 void Game::SetExpansion(int exp
)
1627 void Game::DebugDump()
1631 printf("Currently loaded areas:\n");
1632 for(idx
=0;idx
<Maps
.size();idx
++) {
1633 Map
*map
= Maps
[idx
];
1635 printf("%s\n",map
->GetScriptName());
1637 printf("CombatCounter: %d\n", (int) CombatCounter
);
1638 printf("Attackers count: %d\n", (int) Attackers
.size());
1639 printf("Party size: %d\n", (int) PCs
.size());
1640 for(idx
=0;idx
<PCs
.size();idx
++) {
1641 Actor
*actor
= PCs
[idx
];
1643 printf("Name: %s Order %d\n",actor
->ShortName
, actor
->InParty
);
1647 Actor
*Game::GetActorByGlobalID(ieWord objectID
) {
1648 size_t mc
= GetLoadedMapCount();
1650 Map
*map
= GetMap(mc
);
1651 Actor
*actor
= map
->GetActorByGlobalID(objectID
);
1652 if (actor
) return actor
;