Move EventMgr to GUI.
[gemrb.git] / gemrb / core / Game.cpp
blob2d3d1f2e725690a0aa6d6112f04b81d47ae2e732
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
23 #include "Game.h"
25 #include "defsounds.h"
26 #include "strrefs.h"
27 #include "win32def.h"
29 #include "DataStream.h"
30 #include "DisplayMessage.h"
31 #include "GameData.h"
32 #include "GameScript.h"
33 #include "Interface.h"
34 #include "MapMgr.h"
35 #include "MusicMgr.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
44 partysize = 6;
45 Ticks = 0;
46 version = 0;
47 Expansion = 0;
48 LoadMos[0] = 0;
49 SelectedSingle = 1; //the PC we are looking at (inventory, shop)
50 PartyGold = 0;
51 SetScript( core->GlobalScript, 0 );
52 MapIndex = -1;
53 Reputation = 0;
54 ControlStatus = 0;
55 CombatCounter = 0; //stored here until we know better
56 StateOverrideTime = 0;
57 StateOverrideFlag = 0;
58 BanterBlockTime = 0;
59 BanterBlockFlag = 0;
60 WeatherBits = 0;
61 crtable = NULL;
62 kaputz = NULL;
63 beasts = NULL;
64 mazedata = NULL;
65 timestop_owner = NULL;
66 timestop_end = 0;
67 event_timer = 0;
68 event_handler = NULL;
69 weather = new Particles(200);
70 weather->SetRegion(0, 0, core->Width, core->Height);
71 LastScriptUpdate = 0;
73 //loading master areas
74 AutoTable table;
75 if (table.load("mastarea")) {
76 int i = table->GetRowCount();
77 mastarea.reserve(i);
78 while(i--) {
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...
99 NoInterrupt();
102 Game::~Game(void)
104 size_t i;
106 delete weather;
107 for (i = 0; i < Maps.size(); i++) {
108 delete( Maps[i] );
110 for (i = 0; i < PCs.size(); i++) {
111 delete ( PCs[i] );
113 for (i = 0; i < NPCs.size(); i++) {
114 delete ( NPCs[i] );
116 for (i = 0; i < mastarea.size(); i++) {
117 free ( mastarea[i] );
120 if (crtable) {
121 delete[] crtable;
124 if (mazedata) {
125 free (mazedata);
127 if (kaputz) {
128 delete kaputz;
130 if (beasts) {
131 free (beasts);
133 i=Journals.size();
134 while(i--) {
135 delete Journals[i];
138 i=savedpositions.size();
139 while(i--) {
140 delete savedpositions[i];
143 i=planepositions.size();
144 while(i--) {
145 delete planepositions[i];
149 bool IsAlive(Actor *pc)
151 if (pc->GetStat(IE_STATE_ID)&STATE_DEAD) {
152 return false;
154 return true;
157 int Game::FindPlayer(unsigned int partyID)
159 for (unsigned int slot=0; slot<PCs.size(); slot++) {
160 if (PCs[slot]->InParty==partyID) {
161 return slot;
164 return -1;
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];
172 return NULL;
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 ) {
179 return PCs[slot];
182 return NULL;
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];
190 return NULL;
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 )
198 return NPCs[slot];
201 return NULL;
204 Actor* Game::GetPC(unsigned int slot, bool onlyalive)
206 if (slot >= PCs.size()) {
207 return NULL;
209 if (onlyalive) {
210 unsigned int i=0;
211 while(i<PCs.size() ) {
212 Actor *ac = PCs[i++];
214 if (IsAlive(ac) ) {
215 if (!slot--) {
216 return ac;
220 return NULL;
222 return PCs[slot];
225 int Game::InStore(Actor* pc) const
227 for (unsigned int i = 0; i < NPCs.size(); i++) {
228 if (NPCs[i] == pc) {
229 return i;
232 return -1;
235 int Game::InParty(Actor* pc) const
237 for (unsigned int i = 0; i < PCs.size(); i++) {
238 if (PCs[i] == pc) {
239 return i;
242 return -1;
245 int Game::DelPC(unsigned int slot, bool autoFree)
247 if (slot >= PCs.size()) {
248 return -1;
250 if (!PCs[slot]) {
251 return -1;
253 SelectActor(PCs[slot], false, SELECT_NORMAL);
254 if (autoFree) {
255 delete( PCs[slot] );
257 std::vector< Actor*>::iterator m = PCs.begin() + slot;
258 PCs.erase( m );
259 return 0;
262 int Game::DelNPC(unsigned int slot, bool autoFree)
264 if (slot >= NPCs.size()) {
265 return -1;
267 if (!NPCs[slot]) {
268 return -1;
270 if (autoFree) {
271 delete( NPCs[slot] );
273 std::vector< Actor*>::iterator m = NPCs.begin() + slot;
274 NPCs.erase( m );
275 return 0;
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) {
288 (*m)->InParty--;
291 } else 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 );
304 if (slot < 0) {
305 return slot;
307 std::vector< Actor*>::iterator m = PCs.begin() + slot;
308 PCs.erase( m );
310 for ( m = PCs.begin(); m != PCs.end(); ++m) {
311 if ( (*m)->InParty>actor->InParty) {
312 (*m)->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" ) )
331 return true;
333 return false;
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 );
358 //TODO: set viewport
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) {
371 InitActorPos(actor);
373 int slot = InParty( actor );
374 if (slot != -1) {
375 return slot;
377 if (join&JP_JOIN) {
378 actor->PCStats->JoinDate = GameTime;
379 if (!PCs.size() ) {
380 Reputation = actor->GetStat(IE_REPUTATION);
383 slot = InStore( actor );
384 if (slot >= 0) {
385 std::vector< Actor*>::iterator m = NPCs.begin() + slot;
386 NPCs.erase( m );
388 size_t size = PCs.size();
390 PCs.push_back( actor );
391 if (!actor->InParty) {
392 actor->InParty = (ieByte) (size+1);
395 return ( int ) size;
398 int Game::GetPartySize(bool onlyalive) const
400 if (onlyalive) {
401 int count = 0;
402 for (unsigned int i = 0; i < PCs.size(); i++) {
403 if (!IsAlive(PCs[i])) {
404 continue;
406 count++;
408 return count;
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) {
419 Actor *actor = *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 ))
431 return false;
433 SelectedSingle = index;
434 return true;
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
448 * flags:
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
459 if (! actor) {
460 for ( m = selected.begin(); m != selected.end(); ++m) {
461 (*m)->Select( false );
462 (*m)->SetOver( false );
465 selected.clear();
466 if (select) {
467 for ( m = PCs.begin(); m != PCs.end(); ++m) {
468 if (! *m) {
469 continue;
471 SelectActor( *m, true, SELECT_QUIET );
475 if (! (flags & SELECT_QUIET)) {
476 core->SetEventFlag(EF_SELECTION);
478 return true;
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)
485 // return true;
488 if (select) {
489 if (! actor->ValidTarget( GA_SELECT | GA_NO_DEAD ))
490 return false;
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 );
499 } else {
500 for ( m = selected.begin(); m != selected.end(); ++m) {
501 if ((*m) == actor) {
502 selected.erase( m );
503 break;
506 actor->Select( false );
509 if (! (flags & SELECT_QUIET)) {
510 core->SetEventFlag(EF_SELECTION);
512 return true;
515 // Gets average party level, of onlyalive is true, then counts only living PCs
516 int Game::GetPartyLevel(bool onlyalive) const
518 int count = 0;
519 for (unsigned int i = 0; i<PCs.size(); i++) {
520 if (onlyalive) {
521 if (PCs[i]->GetStat(IE_STATE_ID)&STATE_DEAD) {
522 continue;
525 count += PCs[i]->GetXPLevel(0);
527 return count;
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();
534 while (index--) {
535 Map *map=Maps[index];
536 if (strnicmp(ResRef, map->GetScriptName(), 8) == 0) {
537 return index;
540 return -1;
543 Map* Game::GetMap(unsigned int index) const
545 if (index >= Maps.size()) {
546 return NULL;
548 return Maps[index];
551 Map *Game::GetMap(const char *areaname, bool change)
553 int index = LoadMap(areaname);
554 if (index >= 0) {
555 if (change) {
556 MapIndex = index;
557 area = GetMap(index);
558 memcpy (CurrentArea, areaname, 8);
559 area->SetupAmbients();
560 //change the tileset if needed
561 area->ChangeMap(IsDay());
562 ChangeSong();
563 return area;
565 return GetMap(index);
567 return NULL;
570 bool Game::MasterArea(const char *area)
572 unsigned int i=(int) mastarea.size();
573 while(i--) {
574 if (strnicmp(mastarea[i], area, 8) ) {
575 return true;
578 return false;
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);
593 MapIndex++;
594 return 0;
596 unsigned int i = (unsigned int) Maps.size();
597 Maps.push_back( map );
598 return i;
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()) {
606 return -1;
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) );
613 return -1;
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) {
621 MapIndex--;
623 return 1;
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));
633 if (!forced) {
634 return -1;
638 //this check must be the last, because
639 //after PurgeActors you cannot keep the
640 //area in memory
641 //Or the queues should be regenerated!
642 if (!map->CanFree())
644 return 1;
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) {
652 MapIndex--;
654 return 1;
656 //didn't remove the map
657 return 0;
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)
664 unsigned int i;
665 int index = FindMap(ResRef);
666 if (index>=0) {
667 return index;
670 DataStream* ds = gamedata->GetResource( ResRef, IE_ARE_CLASS_ID );
671 if (!ds) {
672 return -1;
674 PluginHolder<MapMgr> mM(IE_ARE_CLASS_ID);
675 if(!mM->Open( ds, true )) {
676 return -1;
678 Map* newMap = mM->GetMap(ResRef, IsDay());
679 if (!newMap) {
680 return -1;
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
699 if (slot != -1) {
700 return slot;
702 slot = InParty( npc );
703 if (slot != -1) {
704 return -1;
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()) {
715 return NULL;
717 return NPCs[Index];
720 void Game::SwapPCs(unsigned int Index1, unsigned int Index2)
722 if (Index1 >= PCs.size()) {
723 return;
726 if (Index2 >= PCs.size()) {
727 return;
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();
739 while(i--) {
740 if (Journals[i]->Text==strref) {
741 delete Journals[i];
742 Journals.erase(Journals.begin()+i);
747 void Game::DeleteJournalGroup(int Group)
749 size_t i=Journals.size();
750 while(i--) {
751 if (Journals[i]->Group==(ieByte) Group) {
752 delete Journals[i];
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);
761 if (je) {
762 //don't set this entry again in the same section
763 if (je->Section==Section) {
764 return false;
766 if ((Section == IE_GAM_QUEST_DONE) && Group) {
767 //removing all of this group and adding a new entry
768 DeleteJournalGroup(Group);
769 } else {
770 //modifying existing entry
771 je->Section = (ieByte) Section;
772 je->Group = (ieByte) Group;
773 ieDword chapter = 0;
774 locals->Lookup("CHAPTER", chapter);
775 je->Chapter = (ieByte) chapter;
776 je->GameTime = GameTime;
777 return true;
780 je = new GAMJournalEntry;
781 je->GameTime = GameTime;
782 ieDword chapter = 0;
783 locals->Lookup("CHAPTER", chapter);
784 je->Chapter = (ieByte) chapter;
785 je->Section = (ieByte) Section;
786 je->Group = (ieByte) Group;
787 je->Text = strref;
789 Journals.push_back( je );
790 return true;
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();
806 while(Index--) {
807 GAMJournalEntry *ret = Journals[Index];
809 if (ret->Text==strref) {
810 return ret;
814 return NULL;
817 GAMJournalEntry* Game::GetJournalEntry(unsigned int Index)
819 if (Index >= Journals.size()) {
820 return NULL;
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();
833 while(i--) {
834 delete savedpositions[i];
836 savedpositions.clear();
839 GAMLocationEntry* Game::GetSavedLocationEntry(unsigned int i)
841 size_t current = savedpositions.size();
842 if (i>=current) {
843 if (i>PCs.size()) {
844 return NULL;
846 savedpositions.resize(i+1);
847 while(current<=i) {
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();
862 while(i--) {
863 delete planepositions[i];
865 planepositions.clear();
868 GAMLocationEntry* Game::GetPlaneLocationEntry(unsigned int i)
870 size_t current = planepositions.size();
871 if (i>=current) {
872 if (i>PCs.size()) {
873 return NULL;
875 planepositions.resize(i+1);
876 while(current<=i) {
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");
892 if (table.ok()) {
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();
911 if (crtable) {
912 int level = GetPartyLevel(true);
913 if (cr>=MAX_CRLEVEL) {
914 cr=MAX_CRLEVEL-1;
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);
920 return 0;
923 void Game::ShareXP(int xp, int flags)
925 int individual;
927 if (flags&SX_CR) {
928 xp = GetXPFromCR(xp);
931 if (flags&SX_DIVIDE) {
932 int PartySize = GetPartySize(true); //party size, only alive
933 if (PartySize<1) {
934 return;
936 individual = xp / PartySize;
937 } else {
938 individual = xp;
941 if (!individual) {
942 return;
945 if (xp>0) {
946 displaymsg->DisplayConstantStringValue( STR_GOTXP, 0xbcefbc, (ieDword) xp); //you have gained ... xp
947 } else {
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) {
952 continue;
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;
963 return true;
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) {
972 continue;
975 if (PCs[i]->GetStat(IE_STATE_ID)&STATE_DEAD) {
976 continue;
978 if (flags&ENP_CANMOVE) {
979 //someone is uncontrollable, can't move
980 if (PCs[i]->GetStat(IE_EA)>EA_GOODCUTOFF) {
981 return false;
984 if (PCs[i]->GetStat(IE_STATE_ID)&STATE_CANTMOVE) {
985 return false;
988 if (PCs[i]->GetCurrentArea()!=area) {
989 return false;
991 if (Distance(p,PCs[i])>MAX_TRAVELING_DISTANCE) {
992 return false;
995 return true;
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) {
1006 continue;
1008 if (PCs[i]->GetStat(IE_STATE_ID)&STATE_DEAD) {
1009 continue;
1011 if (PCs[i]->GetCurrentArea()!=area) {
1012 continue;
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) {
1023 return i;
1026 return -1;
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);
1035 //clear statistics
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)
1044 if (r<10) r=10;
1045 else if (r>200) r=200;
1046 if (Reputation>r) {
1047 displaymsg->DisplayConstantStringValue(STR_LOSTREP,0xc0c000,(Reputation-r)/10);
1048 } else if (Reputation<r) {
1049 displaymsg->DisplayConstantStringValue(STR_GOTREP,0xc0c000,(r-Reputation)/10);
1051 Reputation = r;
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)
1059 switch(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)
1071 ieDword old;
1073 if (!add) {
1074 return;
1076 old = PartyGold;
1077 PartyGold += add;
1078 if (old<PartyGold) {
1079 displaymsg->DisplayConstantStringValue( STR_GOTGOLD, 0xc0c000, PartyGold-old);
1080 } else {
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);
1089 GameTime+=add;
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;
1102 ieResRef *res;
1104 printMessage("Game","Switching DayLight\n",GREEN);
1105 if (IsDay()) {
1106 res=&nightmovies[areatype];
1107 } else {
1108 res=&daymovies[areatype];
1110 if (*res[0]!='*') {
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();
1120 if (!gc) {
1121 return false;
1123 //don't start this screen when the gui is busy
1124 if (gc->GetDialogueFlags() & (DF_IN_DIALOG|DF_IN_CONTAINER|DF_FREEZE_SCRIPTS) ) {
1125 return false;
1127 if (!partysize) {
1128 return false;
1130 return (PCs.size()>partysize);
1133 bool Game::PCInCombat(Actor* actor) const
1135 if (!CombatCounter) {
1136 return false;
1139 if (actor->LastTarget) {
1140 return true;
1142 if (AttackersOf(actor->GetID(), actor->GetCurrentArea())) {
1143 return true;
1145 return false;
1148 bool Game::AnyPCInCombat() const
1150 if (!CombatCounter) {
1151 return false;
1154 for (unsigned int i=0; i<PCs.size(); i++) {
1155 if (PCInCombat (PCs[i])) {
1156 return true;
1159 return false;
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
1166 if (!PCs.size() ) {
1167 return true;
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();
1176 return false;
1178 // if protagonist died
1179 if (protagonist==PM_YES) {
1180 if (PCs[0]->GetStat(IE_STATE_ID)&STATE_NOSAVE) {
1181 return true;
1183 return false;
1185 //protagonist == 2
1186 for (unsigned int i=0; i<PCs.size(); i++) {
1187 if (!(PCs[i]->GetStat(IE_STATE_ID)&STATE_NOSAVE) ) {
1188 return false;
1191 return true;
1194 //runs all area scripts
1196 void Game::UpdateScripts()
1198 ExecuteScript( 1 );
1199 ProcessActions(false);
1200 size_t idx;
1202 for (idx=0;idx<Maps.size();idx++) {
1203 Maps[idx]->UpdateScripts();
1204 size_t acnt=Attackers.size();
1205 while(acnt--) {
1206 Actor *actor = Maps[idx]->GetActorByGlobalID(Attackers[acnt]);
1207 if (actor) {
1208 //each attacker handles their own round initiation
1209 //FIXME: individual combat counter
1210 CombatCounter++;
1211 actor->InitRound(GameTime, !(CombatCounter&1) );
1216 if (StateOverrideTime)
1217 StateOverrideTime--;
1218 if (BanterBlockTime)
1219 BanterBlockTime--;
1221 if (Maps.size()>MAX_MAPS_LOADED) {
1222 idx = Maps.size();
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()) {
1234 ChangeSong(false);
1237 //this is used only for the death delay so far
1238 if (event_handler) {
1239 if (!event_timer) {
1240 event_handler->call();
1241 event_handler = NULL;
1243 event_timer--;
1246 if (EveryoneDead()) {
1247 //don't check it any more
1248 protagonist = PM_NO;
1249 core->GetGUIScriptEngine()->RunFunction("GUIWORLD", "DeathWindow");
1250 return;
1253 if (PartyOverflow()) {
1254 partysize = 0;
1255 core->GetGUIScriptEngine()->RunFunction("GUIWORLD", "OpenReformPartyWindow");
1256 return;
1260 void Game::SetTimedEvent(EventHandler func, int count)
1262 event_timer = count;
1263 event_handler = func;
1266 void Game::SetProtagonistMode(int mode)
1268 protagonist = mode;
1271 void Game::SetPartySize(int size)
1273 // 0 size means no party size control
1274 if (size<0) {
1275 return;
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 );
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 displaymsg->DisplayConstantString( STR_SCATTERED, 0xff0000 );
1325 return;
1329 if (!(checks&REST_NOCRITTER) ) {
1330 //don't allow resting while in combat
1331 if (AnyPCInCombat()) {
1332 displaymsg->DisplayConstantString( STR_CANTRESTMONS, 0xff0000 );
1333 return;
1335 //don't allow resting if hostiles are nearby
1336 if (area->AnyEnemyNearPoint(leader->Pos)) {
1337 displaymsg->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 displaymsg->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 displaymsg->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 = displaymsg->GetStringReference(STR_REST);
1405 int strindex;
1406 char* tmpstr = NULL;
1408 core->GetTokenDictionary()->SetAtCopy("HOUR", hours);
1409 if (restindex != -1) {
1410 strindex = displaymsg->GetStringReference(STR_HOURS);
1411 } else {
1412 strindex = displaymsg->GetStringReference(STR_PST_HOURS);
1413 restindex = displaymsg->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 displaymsg->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(const 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;