GameScript: Move initialization out of constructor.
[gemrb.git] / gemrb / core / Game.cpp
blobd591edaa634c27435fcaa73f8960e29d8289537b
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
23 #include "win32def.h"
24 #include "Game.h"
25 #include "MapMgr.h"
26 #include "DataStream.h"
27 #include "Interface.h"
28 #include "ScriptEngine.h"
29 #include "GameControl.h"
30 #include "MusicMgr.h"
31 #include "GameScript.h"
32 #include "strrefs.h"
33 #include "defsounds.h"
34 #include "GameData.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
41 partysize = 6;
42 Ticks = 0;
43 version = 0;
44 Expansion = 0;
45 LoadMos[0] = 0;
46 SelectedSingle = 1; //the PC we are looking at (inventory, shop)
47 PartyGold = 0;
48 SetScript( core->GlobalScript, 0 );
49 MapIndex = -1;
50 Reputation = 0;
51 ControlStatus = 0;
52 CombatCounter = 0; //stored here until we know better
53 StateOverrideTime = 0;
54 StateOverrideFlag = 0;
55 BanterBlockTime = 0;
56 BanterBlockFlag = 0;
57 WeatherBits = 0;
58 crtable = NULL;
59 kaputz = NULL;
60 beasts = NULL;
61 mazedata = NULL;
62 timestop_owner = NULL;
63 timestop_end = 0;
64 event_timer = 0;
65 event_handler[0] = 0;
66 weather = new Particles(200);
67 weather->SetRegion(0, 0, core->Width, core->Height);
68 LastScriptUpdate = 0;
70 //loading master areas
71 AutoTable table;
72 if (table.load("mastarea")) {
73 int i = table->GetRowCount();
74 mastarea.reserve(i);
75 while(i--) {
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...
96 NoInterrupt();
99 Game::~Game(void)
101 size_t i;
103 delete weather;
104 for (i = 0; i < Maps.size(); i++) {
105 delete( Maps[i] );
107 for (i = 0; i < PCs.size(); i++) {
108 delete ( PCs[i] );
110 for (i = 0; i < NPCs.size(); i++) {
111 delete ( NPCs[i] );
113 for (i = 0; i < mastarea.size(); i++) {
114 free ( mastarea[i] );
117 if (crtable) {
118 delete[] crtable;
121 if (mazedata) {
122 free (mazedata);
124 if (kaputz) {
125 delete kaputz;
127 if (beasts) {
128 free (beasts);
130 i=Journals.size();
131 while(i--) {
132 delete Journals[i];
135 i=savedpositions.size();
136 while(i--) {
137 delete savedpositions[i];
140 i=planepositions.size();
141 while(i--) {
142 delete planepositions[i];
146 bool IsAlive(Actor *pc)
148 if (pc->GetStat(IE_STATE_ID)&STATE_DEAD) {
149 return false;
151 return true;
154 int Game::FindPlayer(unsigned int partyID)
156 for (unsigned int slot=0; slot<PCs.size(); slot++) {
157 if (PCs[slot]->InParty==partyID) {
158 return slot;
161 return -1;
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];
169 return NULL;
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 ) {
176 return PCs[slot];
179 return NULL;
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];
187 return NULL;
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 )
195 return NPCs[slot];
198 return NULL;
201 Actor* Game::GetPC(unsigned int slot, bool onlyalive)
203 if (slot >= PCs.size()) {
204 return NULL;
206 if (onlyalive) {
207 unsigned int i=0;
208 while(i<PCs.size() ) {
209 Actor *ac = PCs[i++];
211 if (IsAlive(ac) ) {
212 if (!slot--) {
213 return ac;
217 return NULL;
219 return PCs[slot];
222 int Game::InStore(Actor* pc) const
224 for (unsigned int i = 0; i < NPCs.size(); i++) {
225 if (NPCs[i] == pc) {
226 return i;
229 return -1;
232 int Game::InParty(Actor* pc) const
234 for (unsigned int i = 0; i < PCs.size(); i++) {
235 if (PCs[i] == pc) {
236 return i;
239 return -1;
242 int Game::DelPC(unsigned int slot, bool autoFree)
244 if (slot >= PCs.size()) {
245 return -1;
247 if (!PCs[slot]) {
248 return -1;
250 SelectActor(PCs[slot], false, SELECT_NORMAL);
251 if (autoFree) {
252 delete( PCs[slot] );
254 std::vector< Actor*>::iterator m = PCs.begin() + slot;
255 PCs.erase( m );
256 return 0;
259 int Game::DelNPC(unsigned int slot, bool autoFree)
261 if (slot >= NPCs.size()) {
262 return -1;
264 if (!NPCs[slot]) {
265 return -1;
267 if (autoFree) {
268 delete( NPCs[slot] );
270 std::vector< Actor*>::iterator m = NPCs.begin() + slot;
271 NPCs.erase( m );
272 return 0;
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) {
285 (*m)->InParty--;
288 } else 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 );
301 if (slot < 0) {
302 return slot;
304 std::vector< Actor*>::iterator m = PCs.begin() + slot;
305 PCs.erase( m );
307 for ( m = PCs.begin(); m != PCs.end(); ++m) {
308 if ( (*m)->InParty>actor->InParty) {
309 (*m)->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" ) )
328 return true;
330 return false;
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 );
355 //TODO: set viewport
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) {
368 InitActorPos(actor);
370 int slot = InParty( actor );
371 if (slot != -1) {
372 return slot;
374 if (join&JP_JOIN) {
375 actor->PCStats->JoinDate = GameTime;
376 if (!PCs.size() ) {
377 Reputation = actor->GetStat(IE_REPUTATION);
380 slot = InStore( actor );
381 if (slot >= 0) {
382 std::vector< Actor*>::iterator m = NPCs.begin() + slot;
383 NPCs.erase( m );
385 size_t size = PCs.size();
387 PCs.push_back( actor );
388 if (!actor->InParty) {
389 actor->InParty = (ieByte) (size+1);
392 return ( int ) size;
395 int Game::GetPartySize(bool onlyalive) const
397 if (onlyalive) {
398 int count = 0;
399 for (unsigned int i = 0; i < PCs.size(); i++) {
400 if (!IsAlive(PCs[i])) {
401 continue;
403 count++;
405 return count;
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) {
416 Actor *actor = *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 ))
428 return false;
430 SelectedSingle = index;
431 return true;
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
445 * flags:
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
456 if (! actor) {
457 for ( m = selected.begin(); m != selected.end(); ++m) {
458 (*m)->Select( false );
459 (*m)->SetOver( false );
462 selected.clear();
463 if (select) {
464 for ( m = PCs.begin(); m != PCs.end(); ++m) {
465 if (! *m) {
466 continue;
468 SelectActor( *m, true, SELECT_QUIET );
472 if (! (flags & SELECT_QUIET)) {
473 core->SetEventFlag(EF_SELECTION);
475 return true;
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)
482 // return true;
485 if (select) {
486 if (! actor->ValidTarget( GA_SELECT | GA_NO_DEAD ))
487 return false;
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 );
496 } else {
497 for ( m = selected.begin(); m != selected.end(); ++m) {
498 if ((*m) == actor) {
499 selected.erase( m );
500 break;
503 actor->Select( false );
506 if (! (flags & SELECT_QUIET)) {
507 core->SetEventFlag(EF_SELECTION);
509 return true;
512 // Gets average party level, of onlyalive is true, then counts only living PCs
513 int Game::GetPartyLevel(bool onlyalive) const
515 int count = 0;
516 for (unsigned int i = 0; i<PCs.size(); i++) {
517 if (onlyalive) {
518 if (PCs[i]->GetStat(IE_STATE_ID)&STATE_DEAD) {
519 continue;
522 count += PCs[i]->GetXPLevel(0);
524 return count;
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();
531 while (index--) {
532 Map *map=Maps[index];
533 if (strnicmp(ResRef, map->GetScriptName(), 8) == 0) {
534 return index;
537 return -1;
540 Map* Game::GetMap(unsigned int index) const
542 if (index >= Maps.size()) {
543 return NULL;
545 return Maps[index];
548 Map *Game::GetMap(const char *areaname, bool change)
550 int index = LoadMap(areaname);
551 if (index >= 0) {
552 if (change) {
553 MapIndex = index;
554 area = GetMap(index);
555 memcpy (CurrentArea, areaname, 8);
556 area->SetupAmbients();
557 //change the tileset if needed
558 area->ChangeMap(IsDay());
559 ChangeSong();
560 return area;
562 return GetMap(index);
564 return NULL;
567 bool Game::MasterArea(const char *area)
569 unsigned int i=(int) mastarea.size();
570 while(i--) {
571 if (strnicmp(mastarea[i], area, 8) ) {
572 return true;
575 return false;
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);
590 MapIndex++;
591 return 0;
593 unsigned int i = (unsigned int) Maps.size();
594 Maps.push_back( map );
595 return i;
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()) {
603 return -1;
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) );
610 return -1;
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) {
618 MapIndex--;
620 return 1;
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));
630 if (!forced) {
631 return -1;
635 //this check must be the last, because
636 //after PurgeActors you cannot keep the
637 //area in memory
638 //Or the queues should be regenerated!
639 if (!map->CanFree())
641 return 1;
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) {
649 MapIndex--;
651 return 1;
653 //didn't remove the map
654 return 0;
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)
661 unsigned int i;
662 int index = FindMap(ResRef);
663 if (index>=0) {
664 return index;
667 DataStream* ds = gamedata->GetResource( ResRef, IE_ARE_CLASS_ID );
668 if (!ds) {
669 return -1;
671 MapMgr* mM = ( MapMgr* ) core->GetInterface( IE_ARE_CLASS_ID );
672 if(!mM->Open( ds, true )) {
673 mM->release();
674 return -1;
676 Map* newMap = mM->GetMap(ResRef, IsDay());
677 mM->release();
678 if (!newMap) {
679 return -1;
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
698 if (slot != -1) {
699 return slot;
701 slot = InParty( npc );
702 if (slot != -1) {
703 return -1;
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()) {
714 return NULL;
716 return NPCs[Index];
719 void Game::SwapPCs(unsigned int Index1, unsigned int Index2)
721 if (Index1 >= PCs.size()) {
722 return;
725 if (Index2 >= PCs.size()) {
726 return;
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();
738 while(i--) {
739 if (Journals[i]->Text==strref) {
740 delete Journals[i];
741 Journals.erase(Journals.begin()+i);
746 void Game::DeleteJournalGroup(int Group)
748 size_t i=Journals.size();
749 while(i--) {
750 if (Journals[i]->Group==(ieByte) Group) {
751 delete Journals[i];
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);
760 if (je) {
761 //don't set this entry again in the same section
762 if (je->Section==Section) {
763 return false;
765 if ((Section == IE_GAM_QUEST_DONE) && Group) {
766 //removing all of this group and adding a new entry
767 DeleteJournalGroup(Group);
768 } else {
769 //modifying existing entry
770 je->Section = (ieByte) Section;
771 je->Group = (ieByte) Group;
772 ieDword chapter = 0;
773 locals->Lookup("CHAPTER", chapter);
774 je->Chapter = (ieByte) chapter;
775 je->GameTime = GameTime;
776 return true;
779 je = new GAMJournalEntry;
780 je->GameTime = GameTime;
781 ieDword chapter = 0;
782 locals->Lookup("CHAPTER", chapter);
783 je->Chapter = (ieByte) chapter;
784 je->Section = (ieByte) Section;
785 je->Group = (ieByte) Group;
786 je->Text = strref;
788 Journals.push_back( je );
789 return true;
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();
805 while(Index--) {
806 GAMJournalEntry *ret = Journals[Index];
808 if (ret->Text==strref) {
809 return ret;
813 return NULL;
816 GAMJournalEntry* Game::GetJournalEntry(unsigned int Index)
818 if (Index >= Journals.size()) {
819 return NULL;
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();
832 while(i--) {
833 delete savedpositions[i];
835 savedpositions.clear();
838 GAMLocationEntry* Game::GetSavedLocationEntry(unsigned int i)
840 size_t current = savedpositions.size();
841 if (i>=current) {
842 if (i>PCs.size()) {
843 return NULL;
845 savedpositions.resize(i+1);
846 while(current<=i) {
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();
861 while(i--) {
862 delete planepositions[i];
864 planepositions.clear();
867 GAMLocationEntry* Game::GetPlaneLocationEntry(unsigned int i)
869 size_t current = planepositions.size();
870 if (i>=current) {
871 if (i>PCs.size()) {
872 return NULL;
874 planepositions.resize(i+1);
875 while(current<=i) {
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");
891 if (table.ok()) {
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();
910 if (crtable) {
911 int level = GetPartyLevel(true);
912 if (cr>=MAX_CRLEVEL) {
913 cr=MAX_CRLEVEL-1;
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);
919 return 0;
922 void Game::ShareXP(int xp, int flags)
924 int individual;
926 if (flags&SX_CR) {
927 xp = GetXPFromCR(xp);
930 if (flags&SX_DIVIDE) {
931 int PartySize = GetPartySize(true); //party size, only alive
932 if (PartySize<1) {
933 return;
935 individual = xp / PartySize;
936 } else {
937 individual = xp;
940 if (!individual) {
941 return;
944 if (xp>0) {
945 core->DisplayConstantStringValue( STR_GOTXP, 0xbcefbc, (ieDword) xp); //you have gained ... xp
946 } else {
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) {
951 continue;
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;
962 return true;
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) {
971 continue;
974 if (PCs[i]->GetStat(IE_STATE_ID)&STATE_DEAD) {
975 continue;
977 if (flags&ENP_CANMOVE) {
978 //someone is uncontrollable, can't move
979 if (PCs[i]->GetStat(IE_EA)>EA_GOODCUTOFF) {
980 return false;
983 if (PCs[i]->GetStat(IE_STATE_ID)&STATE_CANTMOVE) {
984 return false;
987 if (PCs[i]->GetCurrentArea()!=area) {
988 return false;
990 if (Distance(p,PCs[i])>MAX_TRAVELING_DISTANCE) {
991 return false;
994 return true;
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) {
1005 continue;
1007 if (PCs[i]->GetStat(IE_STATE_ID)&STATE_DEAD) {
1008 continue;
1010 if (PCs[i]->GetCurrentArea()!=area) {
1011 continue;
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) {
1022 return i;
1025 return -1;
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);
1034 //clear statistics
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)
1043 if (r<10) r=10;
1044 else if (r>200) r=200;
1045 if (Reputation>r) {
1046 core->DisplayConstantStringValue(STR_LOSTREP,0xc0c000,(Reputation-r)/10);
1047 } else if (Reputation<r) {
1048 core->DisplayConstantStringValue(STR_GOTREP,0xc0c000,(r-Reputation)/10);
1050 Reputation = r;
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)
1058 switch(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)
1070 ieDword old;
1072 if (!add) {
1073 return;
1075 old = PartyGold;
1076 PartyGold += add;
1077 if (old<PartyGold) {
1078 core->DisplayConstantStringValue( STR_GOTGOLD, 0xc0c000, PartyGold-old);
1079 } else {
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);
1088 GameTime+=add;
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;
1101 ieResRef *res;
1103 printMessage("Game","Switching DayLight\n",GREEN);
1104 if (IsDay()) {
1105 res=&nightmovies[areatype];
1106 } else {
1107 res=&daymovies[areatype];
1109 if (*res[0]!='*') {
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();
1119 if (!gc) {
1120 return false;
1122 //don't start this screen when the gui is busy
1123 if (gc->GetDialogueFlags() & (DF_IN_DIALOG|DF_IN_CONTAINER|DF_FREEZE_SCRIPTS) ) {
1124 return false;
1126 if (!partysize) {
1127 return false;
1129 return (PCs.size()>partysize);
1132 bool Game::PCInCombat(Actor* actor) const
1134 if (!CombatCounter) {
1135 return false;
1138 if (actor->LastTarget) {
1139 return true;
1141 if (AttackersOf(actor->GetID(), actor->GetCurrentArea())) {
1142 return true;
1144 return false;
1147 bool Game::AnyPCInCombat() const
1149 if (!CombatCounter) {
1150 return false;
1153 for (unsigned int i=0; i<PCs.size(); i++) {
1154 if (PCInCombat (PCs[i])) {
1155 return true;
1158 return false;
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
1165 if (!PCs.size() ) {
1166 return true;
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();
1175 return false;
1177 // if protagonist died
1178 if (protagonist==PM_YES) {
1179 if (PCs[0]->GetStat(IE_STATE_ID)&STATE_NOSAVE) {
1180 return true;
1182 return false;
1184 //protagonist == 2
1185 for (unsigned int i=0; i<PCs.size(); i++) {
1186 if (!(PCs[i]->GetStat(IE_STATE_ID)&STATE_NOSAVE) ) {
1187 return false;
1190 return true;
1193 //runs all area scripts
1195 void Game::UpdateScripts()
1197 ExecuteScript( 1 );
1198 ProcessActions(false);
1199 size_t idx;
1201 for (idx=0;idx<Maps.size();idx++) {
1202 Maps[idx]->UpdateScripts();
1203 size_t acnt=Attackers.size();
1204 while(acnt--) {
1205 Actor *actor = Maps[idx]->GetActorByGlobalID(Attackers[acnt]);
1206 if (actor) {
1207 //each attacker handles their own round initiation
1208 //FIXME: individual combat counter
1209 CombatCounter++;
1210 actor->InitRound(GameTime, !(CombatCounter&1) );
1215 if (StateOverrideTime)
1216 StateOverrideTime--;
1217 if (BanterBlockTime)
1218 BanterBlockTime--;
1220 if (Maps.size()>MAX_MAPS_LOADED) {
1221 idx = Maps.size();
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()) {
1233 ChangeSong(false);
1236 //this is used only for the death delay so far
1237 if (event_handler[0]) {
1238 if (!event_timer) {
1239 core->GetGUIScriptEngine()->RunFunction(event_handler);
1240 event_handler[0]=0;
1242 event_timer--;
1245 if (EveryoneDead()) {
1246 //don't check it any more
1247 protagonist = PM_NO;
1248 core->GetGUIScriptEngine()->RunFunction("DeathWindow");
1249 return;
1252 if (PartyOverflow()) {
1253 partysize = 0;
1254 core->GetGUIScriptEngine()->RunFunction("OpenReformPartyWindow");
1255 return;
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)
1267 protagonist = mode;
1270 void Game::SetPartySize(int size)
1272 // 0 size means no party size control
1273 if (size<0) {
1274 return;
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;
1299 gs->Update();
1300 delete( gs );
1303 //noareacheck = no random encounters
1304 //dream = 0 - based on area non-0 - select from list
1305 //-1 no dream
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()) {
1311 return;
1314 Actor *leader = GetPC(0, true);
1315 if (!leader) {
1316 return;
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 );
1325 return;
1329 if (!(checks&REST_NOCRITTER) ) {
1330 //don't allow resting while in combat
1331 if (AnyPCInCombat()) {
1332 core->DisplayConstantString( STR_CANTRESTMONS, 0xff0000 );
1333 return;
1335 //don't allow resting if hostiles are nearby
1336 if (area->AnyEnemyNearPoint(leader->Pos)) {
1337 core->DisplayConstantString( STR_CANTRESTMONS, 0xff0000 );
1338 return;
1342 //rest check, if PartyRested should be set, area should return true
1343 //area should advance gametime too (so partial rest is possible)
1344 int hours = 8;
1345 if (!(checks&REST_NOAREA) ) {
1346 //you cannot rest here
1347 if (area->AreaFlags&1) {
1348 core->DisplayConstantString( STR_MAYNOTREST, 0xff0000 );
1349 return;
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 );
1355 return;
1357 //area encounters
1358 if(area->Rest( leader->Pos, 8, (GameTime/AI_UPDATE_TIME)%7200/3600) ) {
1359 return;
1362 AdvanceTime(2400*AI_UPDATE_TIME);
1365 int i = GetPartySize(true); // party size, only alive
1367 while (i--) {
1368 Actor *tar = GetPC(i, true);
1369 tar->ClearPath();
1370 tar->ClearActions();
1371 tar->SetModal(MS_NONE, 0);
1372 //if hp = 0, then healing will be complete
1373 tar->Heal(hp);
1374 //removes fatigue, recharges spells
1375 tar->Rest(0);
1376 tar->PartyRested();
1379 //movie and cutscene dreams
1380 if (dream>=0) {
1382 //cutscene dreams
1383 if (gamedata->Exists("player1d",IE_BCS_CLASS_ID, true))
1384 PlayerDream();
1386 //select dream based on area
1387 ieResRef *movie;
1388 if (dream==0 || dream>7) {
1389 movie = GetDream(area);
1390 } else {
1391 movie = restmovies+dream;
1393 if (*movie[0]!='*') {
1394 core->PlayMovie(*movie);
1398 //set partyrested flags
1399 PartyRested();
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);
1405 int strindex;
1406 char* tmpstr = NULL;
1408 core->GetTokenDictionary()->SetAtCopy("HOUR", hours);
1409 if (restindex != -1) {
1410 strindex = core->GetStringReference(STR_HOURS);
1411 } else {
1412 strindex = core->GetStringReference(STR_PST_HOURS);
1413 restindex = core->GetStringReference(STR_PST_REST);
1416 //this would be bad
1417 if (strindex == -1 || restindex == -1) return;
1418 tmpstr = core->GetString(strindex, 0);
1419 //as would this
1420 if (!tmpstr) return;
1422 core->GetTokenDictionary()->SetAtCopy("DURATION", tmpstr);
1423 core->FreeString(tmpstr);
1424 core->DisplayString(restindex, 0xffffff, 0);
1427 //timestop effect
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) {
1452 return &DreamTint;
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) {
1458 return &NightTint;
1460 if (daynight>20 || daynight<4) {
1461 return &DuskTint;
1464 if ((map->AreaType&(AT_OUTDOOR|AT_WEATHER)) == (AT_OUTDOOR|AT_WEATHER)) {
1465 //get weather tint
1466 if (WeatherBits&WB_RAIN) {
1467 return &DarkTint;
1469 if (WeatherBits&WB_FOG) {
1470 return &FogTint;
1473 return NULL;
1476 bool Game::IsDay()
1478 ieDword daynight = ((GameTime/AI_UPDATE_TIME)%7200/300);
1479 if(daynight<4 || daynight>20) {
1480 return false;
1482 return true;
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) {
1494 CombatCounter++;
1495 ChangeSong();
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()) {
1507 CombatCounter = 0;
1508 ChangeSong();
1510 break;
1515 void Game::ChangeSong(bool force)
1517 int Song;
1519 if (CombatCounter) {
1520 //battlesong
1521 Song = 3;
1522 } else {
1523 //night or day
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
1532 if (!area) {
1533 return 0;
1535 std::vector< ieDword>::const_iterator idx;
1537 int cnt = 0;
1538 for(idx=Attackers.begin(); idx!=Attackers.end();idx++) {
1539 Actor * actor = area->GetActorByGlobalID(*idx);
1540 if (actor) {
1541 if (actor->LastTarget==globalID) {
1542 cnt++;
1546 return cnt;
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)
1554 if (!weather) {
1555 return;
1557 if (!area->HasWeather()) {
1558 return;
1561 weather->Draw( screen );
1562 if (!update) {
1563 return;
1566 if (!(WeatherBits & (WB_RAIN|WB_SNOW)) ) {
1567 if (weather->GetPhase() == P_GROW) {
1568 weather->SetPhase(P_FADE);
1571 //if (GameTime&1) {
1572 int drawn = weather->Update();
1573 if (drawn) {
1574 WeatherBits &= ~WB_START;
1578 if (WeatherBits&WB_HASWEATHER) {
1579 return;
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) )
1589 return;
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) {
1595 //already raining
1596 if (GameTime&1) {
1597 core->PlaySound(DS_LIGHTNING1);
1598 } else {
1599 core->PlaySound(DS_LIGHTNING2);
1601 } else {
1602 //start raining (far)
1603 core->PlaySound(DS_LIGHTNING3);
1606 if (w&WB_SNOW) {
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);
1611 return;
1613 if (w&WB_RAIN) {
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);
1618 return;
1620 weather->SetPhase(P_FADE);
1623 void Game::SetExpansion(int exp)
1625 Expansion = exp;
1628 void Game::DebugDump()
1630 size_t idx;
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();
1650 while(mc--) {
1651 Map *map = GetMap(mc);
1652 Actor *actor = map->GetActorByGlobalID(objectID);
1653 if (actor) return actor;
1655 return NULL;