TickHook: Fix crash when TickHook isn't set.
[gemrb.git] / gemrb / core / Game.cpp
blob59a218593a4fc7d803aa55a365880aad089fb4f0
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 "DisplayMessage.h"
30 #include "GameData.h"
31 #include "Interface.h"
32 #include "MapMgr.h"
33 #include "MusicMgr.h"
34 #include "Particles.h"
35 #include "ScriptEngine.h"
36 #include "GameScript/GameScript.h"
37 #include "GUI/GameControl.h"
38 #include "System/DataStream.h"
40 #define MAX_MAPS_LOADED 1
42 Game::Game(void) : Scriptable( ST_GLOBAL )
44 protagonist = PM_YES; //set it to 2 for iwd/iwd2 and 0 for pst
45 partysize = 6;
46 Ticks = 0;
47 version = 0;
48 Expansion = 0;
49 LoadMos[0] = 0;
50 SelectedSingle = 1; //the PC we are looking at (inventory, shop)
51 PartyGold = 0;
52 SetScript( core->GlobalScript, 0 );
53 MapIndex = -1;
54 Reputation = 0;
55 ControlStatus = 0;
56 CombatCounter = 0; //stored here until we know better
57 StateOverrideTime = 0;
58 StateOverrideFlag = 0;
59 BanterBlockTime = 0;
60 BanterBlockFlag = 0;
61 WeatherBits = 0;
62 crtable = NULL;
63 kaputz = NULL;
64 beasts = NULL;
65 mazedata = NULL;
66 timestop_owner = NULL;
67 timestop_end = 0;
68 event_timer = 0;
69 event_handler = NULL;
70 weather = new Particles(200);
71 weather->SetRegion(0, 0, core->Width, core->Height);
72 LastScriptUpdate = 0;
74 //loading master areas
75 AutoTable table;
76 if (table.load("mastarea")) {
77 int i = table->GetRowCount();
78 mastarea.reserve(i);
79 while(i--) {
80 char *tmp = (char *) malloc(9);
81 strnuprcpy (tmp,table->QueryField(i,0),8);
82 mastarea.push_back( tmp );
86 //loading rest/daylight switching movies (only bg2 has them)
87 memset(restmovies,'*',sizeof(restmovies));
88 memset(daymovies,'*',sizeof(restmovies));
89 memset(nightmovies,'*',sizeof(restmovies));
90 if (table.load("restmov")) {
91 for(int i=0;i<8;i++) {
92 strnuprcpy(restmovies[i],table->QueryField(i,0),8);
93 strnuprcpy(daymovies[i],table->QueryField(i,1),8);
94 strnuprcpy(nightmovies[i],table->QueryField(i,2),8);
98 interval = 1000/AI_UPDATE_TIME;
99 //FIXME:i'm not sure in this...
100 NoInterrupt();
103 Game::~Game(void)
105 size_t i;
107 delete weather;
108 for (i = 0; i < Maps.size(); i++) {
109 delete( Maps[i] );
111 for (i = 0; i < PCs.size(); i++) {
112 delete ( PCs[i] );
114 for (i = 0; i < NPCs.size(); i++) {
115 delete ( NPCs[i] );
117 for (i = 0; i < mastarea.size(); i++) {
118 free ( mastarea[i] );
121 if (crtable) {
122 delete[] crtable;
125 if (mazedata) {
126 free (mazedata);
128 if (kaputz) {
129 delete kaputz;
131 if (beasts) {
132 free (beasts);
134 i=Journals.size();
135 while(i--) {
136 delete Journals[i];
139 i=savedpositions.size();
140 while(i--) {
141 free (savedpositions[i]);
144 i=planepositions.size();
145 while(i--) {
146 free (planepositions[i]);
150 bool IsAlive(Actor *pc)
152 if (pc->GetStat(IE_STATE_ID)&STATE_DEAD) {
153 return false;
155 return true;
158 int Game::FindPlayer(unsigned int partyID)
160 for (unsigned int slot=0; slot<PCs.size(); slot++) {
161 if (PCs[slot]->InParty==partyID) {
162 return slot;
165 return -1;
168 Actor* Game::FindPC(unsigned int partyID)
170 for (unsigned int slot=0; slot<PCs.size(); slot++) {
171 if (PCs[slot]->InParty==partyID) return PCs[slot];
173 return NULL;
176 Actor* Game::FindPC(const char *scriptingname)
178 for (unsigned int slot=0; slot<PCs.size(); slot++) {
179 if (strnicmp(PCs[slot]->GetScriptName(),scriptingname,32)==0 ) {
180 return PCs[slot];
183 return NULL;
186 Actor* Game::FindNPC(unsigned int partyID)
188 for (unsigned int slot=0; slot<NPCs.size(); slot++) {
189 if (NPCs[slot]->InParty==partyID) return NPCs[slot];
191 return NULL;
194 Actor* Game::FindNPC(const char *scriptingname)
196 for (unsigned int slot=0; slot<NPCs.size(); slot++) {
197 if (strnicmp(NPCs[slot]->GetScriptName(),scriptingname,32)==0 )
199 return NPCs[slot];
202 return NULL;
205 Actor *Game::GetGlobalActorByGlobalID(ieDword globalID)
207 unsigned int slot;
209 for (slot=0; slot<PCs.size(); slot++) {
210 if (PCs[slot]->GetGlobalID()==globalID ) {
211 return PCs[slot];
214 for (slot=0; slot<NPCs.size(); slot++) {
215 if (NPCs[slot]->GetGlobalID()==globalID ) {
216 return NPCs[slot];
219 return NULL;
222 Actor* Game::GetPC(unsigned int slot, bool onlyalive)
224 if (slot >= PCs.size()) {
225 return NULL;
227 if (onlyalive) {
228 unsigned int i=0;
229 while(i<PCs.size() ) {
230 Actor *ac = PCs[i++];
232 if (IsAlive(ac) ) {
233 if (!slot--) {
234 return ac;
238 return NULL;
240 return PCs[slot];
243 int Game::InStore(Actor* pc) const
245 for (unsigned int i = 0; i < NPCs.size(); i++) {
246 if (NPCs[i] == pc) {
247 return i;
250 return -1;
253 int Game::InParty(Actor* pc) const
255 for (unsigned int i = 0; i < PCs.size(); i++) {
256 if (PCs[i] == pc) {
257 return i;
260 return -1;
263 int Game::DelPC(unsigned int slot, bool autoFree)
265 if (slot >= PCs.size()) {
266 return -1;
268 if (!PCs[slot]) {
269 return -1;
271 SelectActor(PCs[slot], false, SELECT_NORMAL);
272 if (autoFree) {
273 delete( PCs[slot] );
275 std::vector< Actor*>::iterator m = PCs.begin() + slot;
276 PCs.erase( m );
277 return 0;
280 int Game::DelNPC(unsigned int slot, bool autoFree)
282 if (slot >= NPCs.size()) {
283 return -1;
285 if (!NPCs[slot]) {
286 return -1;
288 if (autoFree) {
289 delete( NPCs[slot] );
291 std::vector< Actor*>::iterator m = NPCs.begin() + slot;
292 NPCs.erase( m );
293 return 0;
296 //i'm sure this could be faster
297 void Game::ConsolidateParty()
299 int max = (int) PCs.size();
300 std::vector< Actor*>::const_iterator m;
301 for (int i=1;i<=max;) {
302 if (FindPlayer(i)==-1) {
304 for ( m = PCs.begin(); m != PCs.end(); ++m) {
305 if ( (*m)->InParty>i) {
306 (*m)->InParty--;
309 } else i++;
311 for ( m = PCs.begin(); m != PCs.end(); ++m) {
312 (*m)->RefreshEffects(NULL);
316 int Game::LeaveParty (Actor* actor)
318 core->SetEventFlag(EF_PORTRAIT);
319 actor->CreateStats(); //create or update stats for leaving
320 actor->SetBase(IE_EXPLORE, 0);
322 SelectActor(actor, false, SELECT_NORMAL);
323 int slot = InParty( actor );
324 if (slot < 0) {
325 return slot;
327 std::vector< Actor*>::iterator m = PCs.begin() + slot;
328 PCs.erase( m );
330 ieDword id = actor->GetGlobalID();
331 for ( m = PCs.begin(); m != PCs.end(); ++m) {
332 (*m)->PCStats->LastLeft = id;
333 if ( (*m)->InParty>actor->InParty) {
334 (*m)->InParty--;
337 //removing from party, but actor remains in 'game'
338 actor->SetPersistent(0);
339 NPCs.push_back( actor );
341 if (core->HasFeature( GF_HAS_DPLAYER )) {
342 actor->SetScript( "", SCR_DEFAULT );
344 actor->SetBase( IE_EA, EA_NEUTRAL );
345 return ( int ) NPCs.size() - 1;
348 //determines if startpos.2da has rotation rows (it cannot have tutorial line)
349 bool Game::DetermineStartPosType(const TableMgr *strta)
351 if ((strta->GetRowCount()>=6) && !stricmp(strta->GetRowName(4),"START_ROT" ) )
353 return true;
355 return false;
358 #define PMODE_COUNT 3
360 void Game::InitActorPos(Actor *actor)
362 //start.2da row labels
363 const char *mode[PMODE_COUNT] = { "NORMAL", "TUTORIAL", "EXPANSION" };
365 unsigned int ip = (unsigned int) (actor->InParty-1);
366 AutoTable start("start");
367 AutoTable strta("startpos");
368 // 0 - single player, 1 - tutorial, 2 - expansion
369 ieDword playmode = 0;
370 core->GetDictionary()->Lookup( "PlayMode", playmode );
372 //Sometimes playmode is set to -1 (in pregenerate)
373 //normally execution shouldn't ever come here, but it actually does
374 //preventing problems by defaulting to the regular entry points
375 if (playmode>PMODE_COUNT) {
376 playmode = 0;
378 const char *xpos = start->QueryField(mode[playmode],"XPOS");
379 const char *ypos = start->QueryField(mode[playmode],"YPOS");
380 const char *area = start->QueryField(mode[playmode],"AREA");
381 const char *rot = start->QueryField(mode[playmode],"ROT");
383 actor->Pos.x = actor->Destination.x = (short) atoi( strta->QueryField( strta->GetRowIndex(xpos), ip ) );
384 actor->Pos.y = actor->Destination.y = (short) atoi( strta->QueryField( strta->GetRowIndex(ypos), ip ) );
385 actor->SetOrientation( atoi( strta->QueryField( strta->GetRowIndex(rot), ip) ), false );
387 strta.load("startare");
388 strnlwrcpy(actor->Area, strta->QueryField( strta->GetRowIndex(area), 0 ), 8 );
391 int Game::JoinParty(Actor* actor, int join)
393 core->SetEventFlag(EF_PORTRAIT);
394 actor->CreateStats(); //create stats if they didn't exist yet
395 actor->InitButtons(actor->GetStat(IE_CLASS), false); //init actor's buttons
396 actor->SetBase(IE_EXPLORE, 1);
397 if (join&JP_INITPOS) {
398 InitActorPos(actor);
400 int slot = InParty( actor );
401 if (slot != -1) {
402 return slot;
404 size_t size = PCs.size();
405 //set the lastjoined trigger
407 if (join&JP_JOIN) {
408 //update kit abilities of actor
409 actor->ApplyKit(false);
410 //update the quickslots
411 actor->ReinitQuickSlots();
412 //set the joining date
413 actor->PCStats->JoinDate = GameTime;
414 if (size) {
415 ieDword id = actor->GetGlobalID();
416 for (size_t i=0;i<size; i++) {
417 Actor *a = GetPC(i, false);
418 a->PCStats->LastJoined = id;
420 } else {
421 Reputation = actor->GetStat(IE_REPUTATION);
424 slot = InStore( actor );
425 if (slot >= 0) {
426 std::vector< Actor*>::iterator m = NPCs.begin() + slot;
427 NPCs.erase( m );
431 PCs.push_back( actor );
432 if (!actor->InParty) {
433 actor->InParty = (ieByte) (size+1);
436 if (join&(JP_INITPOS|JP_SELECT)) {
437 actor->Selected = 0; // don't confuse SelectActor!
438 SelectActor(actor,true, SELECT_NORMAL);
441 return ( int ) size;
444 int Game::GetPartySize(bool onlyalive) const
446 if (onlyalive) {
447 int count = 0;
448 for (unsigned int i = 0; i < PCs.size(); i++) {
449 if (!IsAlive(PCs[i])) {
450 continue;
452 count++;
454 return count;
456 return (int) PCs.size();
459 /* sends the hotkey trigger to all selected actors */
460 void Game::SetHotKey(unsigned long Key)
462 std::vector< Actor*>::const_iterator m;
464 for ( m = selected.begin(); m != selected.end(); ++m) {
465 Actor *actor = *m;
467 if (actor->IsSelected()) {
468 actor->HotKey = (ieDword) Key;
473 bool Game::SelectPCSingle(int index)
475 Actor* actor = FindPC( index );
476 if (!actor || ! actor->ValidTarget( GA_NO_HIDDEN ))
477 return false;
479 SelectedSingle = index;
480 return true;
483 int Game::GetSelectedPCSingle() const
485 return SelectedSingle;
489 * SelectActor() - handle (de)selecting actors.
490 * If selection was changed, runs "SelectionChanged" handler
492 * actor - either specific actor, or NULL for all
493 * select - whether actor(s) should be selected or deselected
494 * flags:
495 * SELECT_REPLACE - if true, deselect all other actors when selecting one
496 * SELECT_QUIET - do not run handler if selection was changed. Used for
497 * nested calls to SelectActor()
500 bool Game::SelectActor(Actor* actor, bool select, unsigned flags)
502 std::vector< Actor*>::iterator m;
504 // actor was not specified, which means all selectables should be (de)selected
505 if (! actor) {
506 for ( m = selected.begin(); m != selected.end(); ++m) {
507 (*m)->Select( false );
508 (*m)->SetOver( false );
510 selected.clear();
512 if (select) {
513 area->SelectActors();
515 for ( m = PCs.begin(); m != PCs.end(); ++m) {
516 if (! *m) {
517 continue;
519 SelectActor( *m, true, SELECT_QUIET );
524 if (! (flags & SELECT_QUIET)) {
525 core->SetEventFlag(EF_SELECTION);
527 Infravision();
528 return true;
531 // actor was specified, so we will work with him
532 if (select) {
533 if (! actor->ValidTarget( GA_SELECT | GA_NO_DEAD ))
534 return false;
536 // deselect all actors first when exclusive
537 if (flags & SELECT_REPLACE) {
538 if (selected.size() == 1 && actor->IsSelected()) {
539 assert(selected[0] == actor);
540 // already the only selected actor
541 return true;
543 SelectActor( NULL, false, SELECT_QUIET );
544 } else if (actor->IsSelected()) {
545 // already selected
546 return true;
549 actor->Select( true );
550 assert(actor->IsSelected());
551 selected.push_back( actor );
552 } else {
553 if (!actor->IsSelected()) {
554 // already not selected
555 return true;
557 /*for ( m = selected.begin(); m != selected.end(); ++m) {
558 assert((*m) != actor);
560 return true;*/
562 for ( m = selected.begin(); m != selected.end(); ++m) {
563 if ((*m) == actor) {
564 selected.erase( m );
565 break;
568 actor->Select( false );
569 assert(!actor->IsSelected());
572 if (! (flags & SELECT_QUIET)) {
573 core->SetEventFlag(EF_SELECTION);
575 Infravision();
576 return true;
579 // Gets average party level, of onlyalive is true, then counts only living PCs
580 int Game::GetPartyLevel(bool onlyalive) const
582 int count = 0;
583 for (unsigned int i = 0; i<PCs.size(); i++) {
584 if (onlyalive) {
585 if (PCs[i]->GetStat(IE_STATE_ID)&STATE_DEAD) {
586 continue;
589 count += PCs[i]->GetXPLevel(0);
591 return count;
594 // Returns map structure (ARE) if it is already loaded in memory
595 int Game::FindMap(const char *ResRef)
597 int index = (int) Maps.size();
598 while (index--) {
599 Map *map=Maps[index];
600 if (strnicmp(ResRef, map->GetScriptName(), 8) == 0) {
601 return index;
604 return -1;
607 Map* Game::GetMap(unsigned int index) const
609 if (index >= Maps.size()) {
610 return NULL;
612 return Maps[index];
615 Map *Game::GetMap(const char *areaname, bool change)
617 int index = LoadMap(areaname, change);
618 if (index >= 0) {
619 if (change) {
620 MapIndex = index;
621 area = GetMap(index);
622 memcpy (CurrentArea, areaname, 8);
623 area->SetupAmbients();
624 //change the tileset if needed
625 area->ChangeMap(IsDay());
626 ChangeSong(false, true);
627 Infravision();
628 return area;
630 return GetMap(index);
632 return NULL;
635 bool Game::MasterArea(const char *area)
637 unsigned int i=(int) mastarea.size();
638 while(i--) {
639 if (strnicmp(mastarea[i], area, 8) ) {
640 return true;
643 return false;
646 void Game::SetMasterArea(const char *area)
648 if (MasterArea(area) ) return;
649 char *tmp = (char *) malloc(9);
650 strnlwrcpy (tmp,area,8);
651 mastarea.push_back(tmp);
654 int Game::AddMap(Map* map)
656 if (MasterArea(map->GetScriptName()) ) {
657 Maps.insert(Maps.begin(), 1, map);
658 MapIndex++;
659 return 0;
661 unsigned int i = (unsigned int) Maps.size();
662 Maps.push_back( map );
663 return i;
666 int Game::DelMap(unsigned int index, int forced)
668 //this function should archive the area, and remove it only if the area
669 //contains no active actors (combat, partymembers, etc)
670 if (index >= Maps.size()) {
671 return -1;
673 Map *map = Maps[index];
675 if (MapIndex==(int) index) { //can't remove current map in any case
676 const char *name = map->GetScriptName();
677 memcpy(AnotherArea, name, sizeof(AnotherArea) );
678 return -1;
682 if (!map) { //this shouldn't happen, i guess
683 printMessage("Game","Erased NULL Map\n",YELLOW);
684 Maps.erase( Maps.begin()+index);
685 if (MapIndex>(int) index) {
686 MapIndex--;
688 return 1;
691 if (forced || (Maps.size()>MAX_MAPS_LOADED) )
693 //keep at least one master
694 const char *name = map->GetScriptName();
695 if (MasterArea(name)) {
696 if(!AnotherArea[0]) {
697 memcpy(AnotherArea, name, sizeof(AnotherArea));
698 if (!forced) {
699 return -1;
703 //this check must be the last, because
704 //after PurgeActors you cannot keep the
705 //area in memory
706 //Or the queues should be regenerated!
707 if (!map->CanFree())
709 return 1;
711 //remove map from memory
712 core->SwapoutArea(Maps[index]);
713 delete( Maps[index] );
714 Maps.erase( Maps.begin()+index);
715 //current map will be decreased
716 if (MapIndex>(int) index) {
717 MapIndex--;
719 return 1;
721 //didn't remove the map
722 return 0;
725 /* Loads an area */
726 int Game::LoadMap(const char* ResRef, bool loadscreen)
728 unsigned int i;
729 Map *newMap;
730 PluginHolder<MapMgr> mM(IE_ARE_CLASS_ID);
732 //this shouldn't happen
733 if (!mM) {
734 return -1;
737 int index = FindMap(ResRef);
738 if (index>=0) {
739 return index;
742 bool hide = false;
743 if (loadscreen) {
744 hide = core->HideGCWindow();
745 core->GetGUIScriptEngine()->RunFunction("LoadScreen", "StartLoadScreen");
746 core->GetGUIScriptEngine()->RunFunction("LoadScreen", "SetLoadScreen");
748 DataStream* ds = gamedata->GetResource( ResRef, IE_ARE_CLASS_ID );
749 if (!ds) {
750 goto failedload;
752 if(!mM->Open( ds, true )) {
753 goto failedload;
755 newMap = mM->GetMap(ResRef, IsDay());
756 if (!newMap) {
757 goto failedload;
759 core->LoadProgress(100);
761 for (i = 0; i < PCs.size(); i++) {
762 if (stricmp( PCs[i]->Area, ResRef ) == 0) {
763 newMap->AddActor( PCs[i] );
766 for (i = 0; i < NPCs.size(); i++) {
767 if (stricmp( NPCs[i]->Area, ResRef ) == 0) {
768 newMap->AddActor( NPCs[i] );
771 if (hide) {
772 core->UnhideGCWindow();
774 return AddMap( newMap );
775 failedload:
776 if (hide)
777 core->UnhideGCWindow();
778 core->LoadProgress(100);
779 return -1;
782 int Game::AddNPC(Actor* npc)
784 int slot = InStore( npc ); //already an npc
785 if (slot != -1) {
786 return slot;
788 slot = InParty( npc );
789 if (slot != -1) {
790 return -1;
791 } //can't add as npc already in party
792 npc->SetPersistent(0);
793 NPCs.push_back( npc );
795 return (int) NPCs.size() - 1;
798 Actor* Game::GetNPC(unsigned int Index)
800 if (Index >= NPCs.size()) {
801 return NULL;
803 return NPCs[Index];
806 void Game::SwapPCs(unsigned int Index1, unsigned int Index2)
808 if (Index1 >= PCs.size()) {
809 return;
812 if (Index2 >= PCs.size()) {
813 return;
815 int tmp = PCs[Index1]->InParty;
816 PCs[Index1]->InParty = PCs[Index2]->InParty;
817 PCs[Index2]->InParty = tmp;
818 //signal a change of the portrait window
819 core->SetEventFlag(EF_PORTRAIT | EF_SELECTION);
822 void Game::DeleteJournalEntry(ieStrRef strref)
824 size_t i=Journals.size();
825 while(i--) {
826 if ((Journals[i]->Text==strref) || (strref==(ieStrRef) -1) ) {
827 delete Journals[i];
828 Journals.erase(Journals.begin()+i);
833 void Game::DeleteJournalGroup(int Group)
835 size_t i=Journals.size();
836 while(i--) {
837 if (Journals[i]->Group==(ieByte) Group) {
838 delete Journals[i];
839 Journals.erase(Journals.begin()+i);
843 /* returns true if it modified or added a journal entry */
844 bool Game::AddJournalEntry(ieStrRef strref, int Section, int Group)
846 GAMJournalEntry *je = FindJournalEntry(strref);
847 if (je) {
848 //don't set this entry again in the same section
849 if (je->Section==Section) {
850 return false;
852 if ((Section == IE_GAM_QUEST_DONE) && Group) {
853 //removing all of this group and adding a new entry
854 DeleteJournalGroup(Group);
855 } else {
856 //modifying existing entry
857 je->Section = (ieByte) Section;
858 je->Group = (ieByte) Group;
859 ieDword chapter = 0;
860 locals->Lookup("CHAPTER", chapter);
861 je->Chapter = (ieByte) chapter;
862 je->GameTime = GameTime;
863 return true;
866 je = new GAMJournalEntry;
867 je->GameTime = GameTime;
868 ieDword chapter = 0;
869 locals->Lookup("CHAPTER", chapter);
870 je->Chapter = (ieByte) chapter;
871 je->unknown09 = 0;
872 je->Section = (ieByte) Section;
873 je->Group = (ieByte) Group;
874 je->Text = strref;
876 Journals.push_back( je );
877 return true;
880 void Game::AddJournalEntry(GAMJournalEntry* entry)
882 Journals.push_back( entry );
885 unsigned int Game::GetJournalCount() const
887 return (unsigned int) Journals.size();
890 GAMJournalEntry* Game::FindJournalEntry(ieStrRef strref)
892 unsigned int Index = (unsigned int) Journals.size();
893 while(Index--) {
894 GAMJournalEntry *ret = Journals[Index];
896 if (ret->Text==strref) {
897 return ret;
901 return NULL;
904 GAMJournalEntry* Game::GetJournalEntry(unsigned int Index)
906 if (Index >= Journals.size()) {
907 return NULL;
909 return Journals[Index];
912 unsigned int Game::GetSavedLocationCount() const
914 return (unsigned int) savedpositions.size();
917 void Game::ClearSavedLocations()
919 size_t i=savedpositions.size();
920 while(i--) {
921 delete savedpositions[i];
923 savedpositions.clear();
926 GAMLocationEntry* Game::GetSavedLocationEntry(unsigned int i)
928 size_t current = savedpositions.size();
929 if (i>=current) {
930 if (i>PCs.size()) {
931 return NULL;
933 savedpositions.resize(i+1);
934 while(current<=i) {
935 savedpositions[current++]=(GAMLocationEntry *) calloc(1, sizeof(GAMLocationEntry) );
938 return savedpositions[i];
941 unsigned int Game::GetPlaneLocationCount() const
943 return (unsigned int) planepositions.size();
946 void Game::ClearPlaneLocations()
948 size_t i=planepositions.size();
949 while(i--) {
950 delete planepositions[i];
952 planepositions.clear();
955 GAMLocationEntry* Game::GetPlaneLocationEntry(unsigned int i)
957 size_t current = planepositions.size();
958 if (i>=current) {
959 if (i>PCs.size()) {
960 return NULL;
962 planepositions.resize(i+1);
963 while(current<=i) {
964 planepositions[current++]=(GAMLocationEntry *) calloc(1, sizeof(GAMLocationEntry) );
967 return planepositions[i];
970 char *Game::GetFamiliar(unsigned int Index)
972 return Familiars[Index];
975 //reading the challenge rating table for iwd2 (only when needed)
976 void Game::LoadCRTable()
978 AutoTable table("moncrate");
979 if (table.ok()) {
980 int maxrow = table->GetRowCount()-1;
981 crtable = new CRRow[MAX_LEVEL];
982 for(int i=0;i<MAX_LEVEL;i++) {
983 //row shouldn't be larger than maxrow
984 int row = i<maxrow?i:maxrow;
985 int maxcol = table->GetColumnCount(row)-1;
986 for(int j=0;j<MAX_CRLEVEL;j++) {
987 //col shouldn't be larger than maxcol
988 int col = j<maxcol?j:maxcol;
989 crtable[i][j]=atoi(table->QueryField(row,col) );
995 int Game::GetXPFromCR(int cr)
997 if (!crtable) LoadCRTable();
998 if (crtable) {
999 int level = GetPartyLevel(true);
1000 if (cr>=MAX_CRLEVEL) {
1001 cr=MAX_CRLEVEL-1;
1003 printf("Challenge Rating: %d, party level: %d ", cr, level);
1004 return crtable[level][cr];
1006 printMessage("Game","Cannot find moncrate.2da!\n", LIGHT_RED);
1007 return 0;
1010 void Game::ShareXP(int xp, int flags)
1012 int individual;
1014 if (flags&SX_CR) {
1015 xp = GetXPFromCR(xp);
1018 if (flags&SX_DIVIDE) {
1019 int PartySize = GetPartySize(true); //party size, only alive
1020 if (PartySize<1) {
1021 return;
1023 individual = xp / PartySize;
1024 } else {
1025 individual = xp;
1028 if (!individual) {
1029 return;
1032 if (xp>0) {
1033 displaymsg->DisplayConstantStringValue( STR_GOTXP, 0xbcefbc, (ieDword) xp); //you have gained ... xp
1034 } else {
1035 displaymsg->DisplayConstantStringValue( STR_LOSTXP, 0xbcefbc, (ieDword) -xp); //you have lost ... xp
1037 for (unsigned int i=0; i<PCs.size(); i++) {
1038 if (PCs[i]->GetStat(IE_STATE_ID)&STATE_DEAD) {
1039 continue;
1041 PCs[i]->AddExperience(individual);
1045 bool Game::EveryoneStopped() const
1047 for (unsigned int i=0; i<PCs.size(); i++) {
1048 if (PCs[i]->GetNextStep() ) return false;
1050 return true;
1053 //canmove=true: if some PC can't move (or hostile), then this returns false
1054 bool Game::EveryoneNearPoint(Map *area, const Point &p, int flags) const
1056 for (unsigned int i=0; i<PCs.size(); i++) {
1057 if (flags&ENP_ONLYSELECT) {
1058 if(!PCs[i]->Selected) {
1059 continue;
1062 if (PCs[i]->GetStat(IE_STATE_ID)&STATE_DEAD) {
1063 continue;
1065 if (flags&ENP_CANMOVE) {
1066 //someone is uncontrollable, can't move
1067 if (PCs[i]->GetStat(IE_EA)>EA_GOODCUTOFF) {
1068 return false;
1071 if (PCs[i]->GetStat(IE_STATE_ID)&STATE_CANTMOVE) {
1072 return false;
1075 if (PCs[i]->GetCurrentArea()!=area) {
1076 return false;
1078 if (Distance(p,PCs[i])>MAX_TRAVELING_DISTANCE) {
1079 return false;
1082 return true;
1085 //called when someone died
1086 void Game::PartyMemberDied(Actor *actor)
1088 //this could be null, in some extreme cases...
1089 Map *area = actor->GetCurrentArea();
1091 for (unsigned int i=0; i<PCs.size(); i++) {
1092 if (PCs[i]==actor) {
1093 continue;
1095 if (PCs[i]->GetStat(IE_STATE_ID)&STATE_DEAD) {
1096 continue;
1098 if (PCs[i]->GetCurrentArea()!=area) {
1099 continue;
1101 PCs[i]->ReactToDeath(actor->GetScriptName());
1105 //reports if someone died
1106 int Game::PartyMemberDied() const
1108 for (unsigned int i=0; i<PCs.size(); i++) {
1109 if (PCs[i]->GetInternalFlag()&IF_JUSTDIED) {
1110 return i;
1113 return -1;
1116 void Game::IncrementChapter()
1118 //chapter first set to 0 (prologue)
1119 ieDword chapter = (ieDword) -1;
1120 locals->Lookup("CHAPTER",chapter);
1121 locals->SetAt("CHAPTER",chapter+1);
1122 //clear statistics
1123 for (unsigned int i=0; i<PCs.size(); i++) {
1124 //all PCs must have this!
1125 PCs[i]->PCStats->IncrementChapter();
1129 void Game::SetReputation(ieDword r)
1131 if (r<10) r=10;
1132 else if (r>200) r=200;
1133 if (Reputation>r) {
1134 displaymsg->DisplayConstantStringValue(STR_LOSTREP,0xc0c000,(Reputation-r)/10);
1135 } else if (Reputation<r) {
1136 displaymsg->DisplayConstantStringValue(STR_GOTREP,0xc0c000,(r-Reputation)/10);
1138 Reputation = r;
1139 for (unsigned int i=0; i<PCs.size(); i++) {
1140 PCs[i]->SetBase(IE_REPUTATION, Reputation);
1144 void Game::SetControlStatus(int value, int mode)
1146 switch(mode) {
1147 case BM_OR: ControlStatus|=value; break;
1148 case BM_NAND: ControlStatus&=~value; break;
1149 case BM_SET: ControlStatus=value; break;
1150 case BM_AND: ControlStatus&=value; break;
1151 case BM_XOR: ControlStatus^=value; break;
1153 core->SetEventFlag(EF_CONTROL);
1156 void Game::AddGold(ieDword add)
1158 ieDword old;
1160 if (!add) {
1161 return;
1163 old = PartyGold;
1164 PartyGold += add;
1165 if (old<PartyGold) {
1166 displaymsg->DisplayConstantStringValue( STR_GOTGOLD, 0xc0c000, PartyGold-old);
1167 } else {
1168 displaymsg->DisplayConstantStringValue( STR_LOSTGOLD, 0xc0c000, old-PartyGold);
1172 //later this could be more complicated
1173 void Game::AdvanceTime(ieDword add)
1175 ieDword h = GameTime/(300*AI_UPDATE_TIME);
1176 GameTime+=add;
1177 if (h!=GameTime/(300*AI_UPDATE_TIME)) {
1178 //asking for a new weather when the hour changes
1179 WeatherBits&=~WB_HASWEATHER;
1181 Ticks+=add*interval;
1182 //change the tileset if needed
1183 Map *map = GetCurrentArea();
1184 if (map && map->ChangeMap(IsDay())) {
1185 //play the daylight transition movie appropriate for the area
1186 //it is needed to play only when the area truly changed its tileset
1187 //this is signalled by ChangeMap
1188 int areatype = (area->AreaType&(AT_FOREST|AT_CITY|AT_DUNGEON))>>3;
1189 ieResRef *res;
1191 printMessage("Game","Switching DayLight\n",GREEN);
1192 if (IsDay()) {
1193 res=&nightmovies[areatype];
1194 } else {
1195 res=&daymovies[areatype];
1197 if (*res[0]!='*') {
1198 core->PlayMovie(*res);
1203 //returns true if there are excess players in the team
1204 bool Game::PartyOverflow() const
1206 GameControl *gc = core->GetGameControl();
1207 if (!gc) {
1208 return false;
1210 //don't start this screen when the gui is busy
1211 if (gc->GetDialogueFlags() & (DF_IN_DIALOG|DF_IN_CONTAINER|DF_FREEZE_SCRIPTS) ) {
1212 return false;
1214 if (!partysize) {
1215 return false;
1217 return (PCs.size()>partysize);
1220 bool Game::PCInCombat(Actor* actor) const
1222 if (!CombatCounter) {
1223 return false;
1226 if (actor->LastTarget) {
1227 return true;
1229 if (AttackersOf(actor->GetGlobalID(), actor->GetCurrentArea())) {
1230 return true;
1232 return false;
1235 bool Game::AnyPCInCombat() const
1237 if (!CombatCounter) {
1238 return false;
1241 for (unsigned int i=0; i<PCs.size(); i++) {
1242 if (PCInCombat (PCs[i])) {
1243 return true;
1246 return false;
1249 //returns true if the protagonist (or the whole party died)
1250 bool Game::EveryoneDead() const
1252 //if there are no PCs, then we assume everyone dead
1253 if (!PCs.size() ) {
1254 return true;
1256 if (protagonist==PM_NO) {
1257 Actor *nameless = PCs[0];
1258 if (nameless->GetStat(IE_STATE_ID)&STATE_NOSAVE) {
1259 if (area->INISpawn) {
1260 area->INISpawn->RespawnNameless();
1263 return false;
1265 // if protagonist died
1266 if (protagonist==PM_YES) {
1267 if (PCs[0]->GetStat(IE_STATE_ID)&STATE_NOSAVE) {
1268 return true;
1270 return false;
1272 //protagonist == 2
1273 for (unsigned int i=0; i<PCs.size(); i++) {
1274 if (!(PCs[i]->GetStat(IE_STATE_ID)&STATE_NOSAVE) ) {
1275 return false;
1278 return true;
1281 //runs all area scripts
1283 void Game::UpdateScripts()
1285 ExecuteScript( 1 );
1286 ProcessActions(false);
1287 size_t idx;
1289 bool PartyAttack = false;
1291 for (idx=0;idx<Maps.size();idx++) {
1292 Maps[idx]->UpdateScripts();
1293 size_t acnt=Attackers.size();
1294 while(acnt--) {
1295 Actor *actor = Maps[idx]->GetActorByGlobalID(Attackers[acnt]);
1296 if (actor) {
1297 if ( !Maps[idx]->GetActorByGlobalID(actor->LastTarget) ) {
1298 //Actor's target left area
1299 OutAttack(Attackers[acnt]);
1300 continue;
1301 } else {
1302 //each attacker handles their own round initiation
1303 actor->InitRound(GameTime);
1304 if (actor->InParty) {
1305 PartyAttack = true;
1308 } else {
1309 //Attacker is gone from area
1310 OutAttack(Attackers[acnt]);
1315 if (PartyAttack) {
1316 //ChangeSong will set the battlesong only if CombatCounter is nonzero
1317 CombatCounter=150;
1318 ChangeSong(false, true);
1319 } else {
1320 if (CombatCounter) {
1321 CombatCounter--;
1322 //Change song if combatcounter went down to 0
1323 if (!CombatCounter) {
1324 ChangeSong(false, false);
1329 if (StateOverrideTime)
1330 StateOverrideTime--;
1331 if (BanterBlockTime)
1332 BanterBlockTime--;
1334 if (Maps.size()>MAX_MAPS_LOADED) {
1335 idx = Maps.size();
1337 //starting from 0, so we see the most recent master area first
1338 for(unsigned int i=0;i<idx;i++) {
1339 DelMap( (unsigned int) i, false );
1343 // perhaps a StartMusic action stopped the area music?
1344 // (we should probably find a less silly way to handle this,
1345 // because nothing can ever stop area music now..)
1346 if (!core->GetMusicMgr()->IsPlaying()) {
1347 ChangeSong(false,false);
1350 //this is used only for the death delay so far
1351 if (event_handler) {
1352 if (!event_timer) {
1353 event_handler->call();
1354 event_handler = NULL;
1356 event_timer--;
1359 if (EveryoneDead()) {
1360 //don't check it any more
1361 protagonist = PM_NO;
1362 core->GetGUIScriptEngine()->RunFunction("GUIWORLD", "DeathWindow");
1363 return;
1366 if (PartyOverflow()) {
1367 partysize = 0;
1368 core->GetGUIScriptEngine()->RunFunction("GUIWORLD", "OpenReformPartyWindow");
1369 return;
1373 void Game::SetTimedEvent(EventHandler func, int count)
1375 event_timer = count;
1376 event_handler = func;
1379 void Game::SetProtagonistMode(int mode)
1381 protagonist = mode;
1384 void Game::SetPartySize(int size)
1386 // 0 size means no party size control
1387 if (size<0) {
1388 return;
1390 partysize = (size_t) size;
1393 //Get the area dependent rest movie
1394 ieResRef *Game::GetDream(Map *area)
1396 //select dream based on area
1397 int daynight = IsDay();
1398 if (area->Dream[daynight][0]) {
1399 return area->Dream+daynight;
1401 int dream = (area->AreaType&(AT_FOREST|AT_CITY|AT_DUNGEON))>>3;
1402 return restmovies+dream;
1405 //Start dream cutscenes for player1
1406 void Game::PlayerDream()
1408 Scriptable *Sender = GetPC(0,true);
1409 if (!Sender) return;
1411 GameScript* gs = new GameScript( "player1d", Sender,0,0 );
1412 gs->Update();
1413 delete( gs );
1416 //noareacheck = no random encounters
1417 //dream = 0 - based on area non-0 - select from list
1418 //-1 no dream
1419 //hp is how much hp the rest will heal
1420 void Game::RestParty(int checks, int dream, int hp)
1422 if (!(checks&REST_NOMOVE) ) {
1423 if (!EveryoneStopped()) {
1424 return;
1427 Actor *leader = GetPC(0, true);
1428 if (!leader) {
1429 return;
1432 Map *area = leader->GetCurrentArea();
1433 //we let them rest if someone is paralyzed, but the others gather around
1434 if (!(checks&REST_NOSCATTER) ) {
1435 if (!EveryoneNearPoint( area, leader->Pos, 0 ) ) {
1436 //party too scattered
1437 displaymsg->DisplayConstantString( STR_SCATTERED, 0xff0000 );
1438 return;
1442 if (!(checks&REST_NOCRITTER) ) {
1443 //don't allow resting while in combat
1444 if (AnyPCInCombat()) {
1445 displaymsg->DisplayConstantString( STR_CANTRESTMONS, 0xff0000 );
1446 return;
1448 //don't allow resting if hostiles are nearby
1449 if (area->AnyEnemyNearPoint(leader->Pos)) {
1450 displaymsg->DisplayConstantString( STR_CANTRESTMONS, 0xff0000 );
1451 return;
1455 //rest check, if PartyRested should be set, area should return true
1456 //area should advance gametime too (so partial rest is possible)
1457 int hours = 8;
1458 if (!(checks&REST_NOAREA) ) {
1459 //you cannot rest here
1460 if (area->AreaFlags&1) {
1461 displaymsg->DisplayConstantString( STR_MAYNOTREST, 0xff0000 );
1462 return;
1464 //you may not rest here, find an inn
1465 if (!(area->AreaType&(AT_OUTDOOR|AT_FOREST|AT_DUNGEON|AT_CAN_REST) ))
1467 displaymsg->DisplayConstantString( STR_MAYNOTREST, 0xff0000 );
1468 return;
1470 //area encounters
1471 if(area->Rest( leader->Pos, 8, (GameTime/AI_UPDATE_TIME)%7200/3600) ) {
1472 return;
1475 AdvanceTime(2400*AI_UPDATE_TIME);
1477 int i = GetPartySize(true); // party size, only alive
1479 while (i--) {
1480 Actor *tar = GetPC(i, true);
1481 tar->ClearPath();
1482 tar->ClearActions();
1483 tar->SetModal(MS_NONE, 0);
1484 //if hp = 0, then healing will be complete
1485 tar->Heal(hp);
1486 //removes fatigue, recharges spells
1487 tar->Rest(0);
1488 tar->PartyRested();
1491 //movie and cutscene dreams
1492 if (dream>=0) {
1493 //cutscene dreams
1494 if (gamedata->Exists("player1d",IE_BCS_CLASS_ID, true))
1495 PlayerDream();
1497 //select dream based on area
1498 ieResRef *movie;
1499 if (dream==0 || dream>7) {
1500 movie = GetDream(area);
1501 } else {
1502 movie = restmovies+dream;
1504 if (*movie[0]!='*') {
1505 core->PlayMovie(*movie);
1509 //set partyrested flags
1510 PartyRested();
1511 area->PartyRested();
1512 core->SetEventFlag(EF_ACTION);
1514 //restindex will be -1 in the case of PST
1515 //FIXME: I don't quite see why we can't sumply use the same strings.2da entry
1516 //It seems we could reduce complexity here, and free up 2-3 string slots too
1517 int restindex = displaymsg->GetStringReference(STR_REST);
1518 int strindex;
1519 char* tmpstr = NULL;
1521 core->GetTokenDictionary()->SetAtCopy("HOUR", hours);
1522 if (restindex != -1) {
1523 strindex = displaymsg->GetStringReference(STR_HOURS);
1524 } else {
1525 strindex = displaymsg->GetStringReference(STR_PST_HOURS);
1526 restindex = displaymsg->GetStringReference(STR_PST_REST);
1529 //this would be bad
1530 if (strindex == -1 || restindex == -1) return;
1531 tmpstr = core->GetString(strindex, 0);
1532 //as would this
1533 if (!tmpstr) return;
1535 core->GetTokenDictionary()->SetAtCopy("DURATION", tmpstr);
1536 core->FreeString(tmpstr);
1537 displaymsg->DisplayString(restindex, 0xffffff, 0);
1540 //timestop effect
1541 void Game::TimeStop(Actor* owner, ieDword end)
1543 timestop_owner=owner;
1544 timestop_end=GameTime+end;
1547 //recalculate the party's infravision state
1548 void Game::Infravision()
1550 hasInfra = false;
1551 Map *map = GetCurrentArea();
1552 if (!map) return;
1553 for(size_t i=0;i<PCs.size();i++) {
1554 Actor *actor = PCs[i];
1555 if (!IsAlive(actor)) continue;
1556 if (actor->GetCurrentArea()!=map) continue;
1557 //Group infravision overrides this???
1558 if (!actor->Selected) continue;
1559 if (actor->GetStat(IE_STATE_ID) & STATE_INFRA) {
1560 hasInfra = true;
1561 return;
1566 //returns the colour which should be applied onto the whole game area viewport
1567 //this is based on timestop, dream area, weather, daytime
1569 static const Color TimeStopTint={0xe0,0xe0,0xe0,0x20}; //greyscale
1570 static const Color DreamTint={0xf0,0xe0,0xd0,0x10}; //light brown scale
1571 static const Color NightTint={0x80,0x80,0xe0,0x40}; //dark, bluish
1572 static const Color DuskTint={0xe0,0x80,0x80,0x40}; //dark, reddish
1573 static const Color FogTint={0xff,0xff,0xff,0x40}; //whitish
1574 static const Color DarkTint={0x80,0x80,0xe0,0x10}; //slightly dark bluish
1576 const Color *Game::GetGlobalTint() const
1578 if (timestop_end>GameTime) {
1579 return &TimeStopTint;
1581 Map *map = GetCurrentArea();
1582 if (!map) return NULL;
1583 if (map->AreaFlags&AF_DREAM) {
1584 return &DreamTint;
1586 if ((map->AreaType&(AT_OUTDOOR|AT_DAYNIGHT|AT_EXTENDED_NIGHT)) == (AT_OUTDOOR|AT_DAYNIGHT) ) {
1587 //get daytime colour
1588 ieDword daynight = ((GameTime/AI_UPDATE_TIME)%7200/300);
1589 if (daynight<2 || daynight>22) {
1590 return &NightTint;
1592 if (daynight>20 || daynight<4) {
1593 return &DuskTint;
1596 if ((map->AreaType&(AT_OUTDOOR|AT_WEATHER)) == (AT_OUTDOOR|AT_WEATHER)) {
1597 //get weather tint
1598 if (WeatherBits&WB_RAIN) {
1599 return &DarkTint;
1601 if (WeatherBits&WB_FOG) {
1602 return &FogTint;
1606 return NULL;
1609 bool Game::IsDay()
1611 ieDword daynight = ((GameTime/AI_UPDATE_TIME)%7200/300);
1612 if(daynight<4 || daynight>20) {
1613 return false;
1615 return true;
1618 void Game::InAttack(ieDword globalID)
1620 std::vector< ieDword>::const_iterator idx;
1622 for(idx=Attackers.begin(); idx!=Attackers.end();idx++) {
1623 if (*idx==globalID) return;
1625 Attackers.push_back(globalID);
1628 void Game::OutAttack(ieDword globalID)
1630 std::vector< ieDword>::iterator idx;
1632 for(idx=Attackers.begin(); idx!=Attackers.end();idx++) {
1633 if (*idx==globalID) {
1634 Attackers.erase(idx);
1635 break;
1640 void Game::ChangeSong(bool always, bool force)
1642 int Song;
1644 if (CombatCounter) {
1645 //battlesong
1646 Song = SONG_BATTLE;
1647 } else {
1648 //will select SONG_DAY or SONG_NIGHT
1649 Song = (GameTime/AI_UPDATE_TIME)%7200/3600;
1651 //area may override the song played (stick in battlemusic)
1652 //always transition gracefully with ChangeSong
1653 //force just means, we schedule the song for later, if currently
1654 //is playing
1655 area->PlayAreaSong( Song, always, force );
1658 int Game::AttackersOf(ieDword globalID, Map *area) const
1660 if (!area) {
1661 return 0;
1663 std::vector< ieDword>::const_iterator idx;
1665 int cnt = 0;
1666 for(idx=Attackers.begin(); idx!=Attackers.end();idx++) {
1667 Actor * actor = area->GetActorByGlobalID(*idx);
1668 if (actor) {
1669 if (actor->LastTarget==globalID) {
1670 cnt++;
1674 return cnt;
1677 /* this method redraws weather. If update is false,
1678 then the weather particles won't change (game paused)
1680 void Game::DrawWeather(const Region &screen, bool update)
1682 if (!weather) {
1683 return;
1685 if (!area->HasWeather()) {
1686 return;
1689 weather->Draw( screen );
1690 if (!update) {
1691 return;
1694 if (!(WeatherBits & (WB_RAIN|WB_SNOW)) ) {
1695 if (weather->GetPhase() == P_GROW) {
1696 weather->SetPhase(P_FADE);
1699 //if (GameTime&1) {
1700 int drawn = weather->Update();
1701 if (drawn) {
1702 WeatherBits &= ~WB_START;
1706 if (WeatherBits&WB_HASWEATHER) {
1707 return;
1709 StartRainOrSnow(true, area->GetWeather());
1712 /* sets the weather type */
1713 void Game::StartRainOrSnow(bool conditional, int w)
1715 if (conditional && (w & (WB_RAIN|WB_SNOW)) ) {
1716 if (WeatherBits & (WB_RAIN | WB_SNOW) )
1717 return;
1719 // whatever was responsible for calling this, we now have some set weather
1720 WeatherBits = w | WB_HASWEATHER;
1721 if (w & WB_LIGHTNING) {
1722 if (WeatherBits&WB_START) {
1723 //already raining
1724 if (GameTime&1) {
1725 core->PlaySound(DS_LIGHTNING1);
1726 } else {
1727 core->PlaySound(DS_LIGHTNING2);
1729 } else {
1730 //start raining (far)
1731 core->PlaySound(DS_LIGHTNING3);
1734 if (w&WB_SNOW) {
1735 core->PlaySound(DS_SNOW);
1736 weather->SetType(SP_TYPE_POINT, SP_PATH_FLIT, SP_SPAWN_SOME);
1737 weather->SetPhase(P_GROW);
1738 weather->SetColor(SPARK_COLOR_WHITE);
1739 return;
1741 if (w&WB_RAIN) {
1742 core->PlaySound(DS_RAIN);
1743 weather->SetType(SP_TYPE_LINE, SP_PATH_RAIN, SP_SPAWN_SOME);
1744 weather->SetPhase(P_GROW);
1745 weather->SetColor(SPARK_COLOR_STONE);
1746 return;
1748 weather->SetPhase(P_FADE);
1751 void Game::SetExpansion(ieDword value)
1753 if (Expansion>=value) {
1754 return;
1756 Expansion = value;
1758 switch(Expansion) {
1759 default:
1760 core->SetEventFlag(EF_EXPANSION);
1761 break;
1762 //TODO: move this hardcoded hack to the scripts
1763 case 5:
1764 core->GetDictionary()->SetAt( "PlayMode", 2 );
1766 int i = GetPartySize(false);
1767 while(i--) {
1768 Actor *actor = GetPC(i, false);
1769 InitActorPos(actor);
1774 void Game::DebugDump()
1776 size_t idx;
1778 printf("Currently loaded areas:\n");
1779 for(idx=0;idx<Maps.size();idx++) {
1780 Map *map = Maps[idx];
1782 printf("%s\n",map->GetScriptName());
1784 printf("Current area: %s Previous area: %s\n", CurrentArea, PreviousArea);
1785 printf("Global script: %s\n", Scripts[0]->GetName());
1786 printf("CombatCounter: %d\n", (int) CombatCounter);
1787 printf("Attackers count: %d\n", (int) Attackers.size());
1788 for(idx=0;idx<Attackers.size(); idx++) {
1789 Actor *actor = GetActorByGlobalID(Attackers[idx]);
1790 if (!actor) {
1791 printf("Name: ???\n");
1792 continue;
1794 Actor *whom = GetActorByGlobalID(actor->LastTarget);
1795 printf("Name: %s Attacking : %s\n", actor->ShortName, whom?whom->ShortName:"???");
1798 printf("Party size: %d\n", (int) PCs.size());
1799 for(idx=0;idx<PCs.size();idx++) {
1800 Actor *actor = PCs[idx];
1802 printf("Name: %s Order %d %s\n",actor->ShortName, actor->InParty, actor->Selected?"x":"-");
1806 Actor *Game::GetActorByGlobalID(ieDword globalID)
1808 size_t mc = GetLoadedMapCount();
1809 while(mc--) {
1810 Map *map = GetMap(mc);
1811 Actor *actor = map->GetActorByGlobalID(globalID);
1812 if (actor) return actor;
1814 return GetGlobalActorByGlobalID(globalID);