fixed the last commit to set and check the target more often, thanks fuzzie
[gemrb.git] / gemrb / core / Game.cpp
blob9065a59741739f84b885be1b922456a0ddf5c05f
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::GetPC(unsigned int slot, bool onlyalive)
207 if (slot >= PCs.size()) {
208 return NULL;
210 if (onlyalive) {
211 unsigned int i=0;
212 while(i<PCs.size() ) {
213 Actor *ac = PCs[i++];
215 if (IsAlive(ac) ) {
216 if (!slot--) {
217 return ac;
221 return NULL;
223 return PCs[slot];
226 int Game::InStore(Actor* pc) const
228 for (unsigned int i = 0; i < NPCs.size(); i++) {
229 if (NPCs[i] == pc) {
230 return i;
233 return -1;
236 int Game::InParty(Actor* pc) const
238 for (unsigned int i = 0; i < PCs.size(); i++) {
239 if (PCs[i] == pc) {
240 return i;
243 return -1;
246 int Game::DelPC(unsigned int slot, bool autoFree)
248 if (slot >= PCs.size()) {
249 return -1;
251 if (!PCs[slot]) {
252 return -1;
254 SelectActor(PCs[slot], false, SELECT_NORMAL);
255 if (autoFree) {
256 delete( PCs[slot] );
258 std::vector< Actor*>::iterator m = PCs.begin() + slot;
259 PCs.erase( m );
260 return 0;
263 int Game::DelNPC(unsigned int slot, bool autoFree)
265 if (slot >= NPCs.size()) {
266 return -1;
268 if (!NPCs[slot]) {
269 return -1;
271 if (autoFree) {
272 delete( NPCs[slot] );
274 std::vector< Actor*>::iterator m = NPCs.begin() + slot;
275 NPCs.erase( m );
276 return 0;
279 //i'm sure this could be faster
280 void Game::ConsolidateParty()
282 int max = (int) PCs.size();
283 std::vector< Actor*>::const_iterator m;
284 for (int i=1;i<=max;) {
285 if (FindPlayer(i)==-1) {
287 for ( m = PCs.begin(); m != PCs.end(); ++m) {
288 if ( (*m)->InParty>i) {
289 (*m)->InParty--;
292 } else i++;
294 for ( m = PCs.begin(); m != PCs.end(); ++m) {
295 (*m)->RefreshEffects(NULL);
299 int Game::LeaveParty (Actor* actor)
301 core->SetEventFlag(EF_PORTRAIT);
302 actor->CreateStats(); //create or update stats for leaving
303 actor->SetBase(IE_EXPLORE, 0);
305 SelectActor(actor, false, SELECT_NORMAL);
306 int slot = InParty( actor );
307 if (slot < 0) {
308 return slot;
310 std::vector< Actor*>::iterator m = PCs.begin() + slot;
311 PCs.erase( m );
313 ieDword id = actor->GetID();
314 for ( m = PCs.begin(); m != PCs.end(); ++m) {
315 (*m)->PCStats->LastLeft = id;
316 if ( (*m)->InParty>actor->InParty) {
317 (*m)->InParty--;
320 //removing from party, but actor remains in 'game'
321 actor->SetPersistent(0);
322 NPCs.push_back( actor );
324 if (core->HasFeature( GF_HAS_DPLAYER )) {
325 actor->SetScript( "", SCR_DEFAULT );
327 actor->SetBase( IE_EA, EA_NEUTRAL );
328 return ( int ) NPCs.size() - 1;
331 //determines if startpos.2da has rotation rows (it cannot have tutorial line)
332 bool Game::DetermineStartPosType(const TableMgr *strta)
334 if ((strta->GetRowCount()>=6) && !stricmp(strta->GetRowName(4),"START_ROT" ) )
336 return true;
338 return false;
341 #define PMODE_COUNT 3
343 void Game::InitActorPos(Actor *actor)
345 //start.2da row labels
346 const char *mode[PMODE_COUNT] = { "NORMAL", "TUTORIAL", "EXPANSION" };
348 unsigned int ip = (unsigned int) (actor->InParty-1);
349 AutoTable start("start");
350 AutoTable strta("startpos");
351 // 0 - single player, 1 - tutorial, 2 - expansion
352 ieDword playmode = 0;
353 core->GetDictionary()->Lookup( "PlayMode", playmode );
355 //Sometimes playmode is set to -1 (in pregenerate)
356 //normally execution shouldn't ever come here, but it actually does
357 //preventing problems by defaulting to the regular entry points
358 if (playmode>PMODE_COUNT) {
359 playmode = 0;
361 const char *xpos = start->QueryField(mode[playmode],"XPOS");
362 const char *ypos = start->QueryField(mode[playmode],"YPOS");
363 const char *area = start->QueryField(mode[playmode],"AREA");
364 const char *rot = start->QueryField(mode[playmode],"ROT");
366 actor->Pos.x = actor->Destination.x = (short) atoi( strta->QueryField( strta->GetRowIndex(xpos), ip ) );
367 actor->Pos.y = actor->Destination.y = (short) atoi( strta->QueryField( strta->GetRowIndex(ypos), ip ) );
368 actor->SetOrientation( atoi( strta->QueryField( strta->GetRowIndex(rot), ip) ), false );
370 strta.load("startare");
371 strnlwrcpy(actor->Area, strta->QueryField( strta->GetRowIndex(area), 0 ), 8 );
374 int Game::JoinParty(Actor* actor, int join)
376 core->SetEventFlag(EF_PORTRAIT);
377 actor->CreateStats(); //create stats if they didn't exist yet
378 actor->InitButtons(actor->GetStat(IE_CLASS), false); //init actor's buttons
379 actor->SetBase(IE_EXPLORE, 1);
380 if (join&JP_INITPOS) {
381 InitActorPos(actor);
383 int slot = InParty( actor );
384 if (slot != -1) {
385 return slot;
387 size_t size = PCs.size();
388 //set the lastjoined trigger
390 if (join&JP_JOIN) {
391 //update kit abilities of actor
392 actor->ApplyKit(false);
393 //update the quickslots
394 actor->ReinitQuickSlots();
395 //set the joining date
396 actor->PCStats->JoinDate = GameTime;
397 if (size) {
398 ieDword id = actor->GetID();
399 for (size_t i=0;i<size; i++) {
400 Actor *a = GetPC(i, false);
401 a->PCStats->LastJoined = id;
403 } else {
404 Reputation = actor->GetStat(IE_REPUTATION);
407 slot = InStore( actor );
408 if (slot >= 0) {
409 std::vector< Actor*>::iterator m = NPCs.begin() + slot;
410 NPCs.erase( m );
414 PCs.push_back( actor );
415 if (!actor->InParty) {
416 actor->InParty = (ieByte) (size+1);
419 if (join&(JP_INITPOS|JP_SELECT)) {
420 actor->Selected = 0; // don't confuse SelectActor!
421 SelectActor(actor,true, SELECT_NORMAL);
424 return ( int ) size;
427 int Game::GetPartySize(bool onlyalive) const
429 if (onlyalive) {
430 int count = 0;
431 for (unsigned int i = 0; i < PCs.size(); i++) {
432 if (!IsAlive(PCs[i])) {
433 continue;
435 count++;
437 return count;
439 return (int) PCs.size();
442 /* sends the hotkey trigger to all selected actors */
443 void Game::SetHotKey(unsigned long Key)
445 std::vector< Actor*>::const_iterator m;
447 for ( m = selected.begin(); m != selected.end(); ++m) {
448 Actor *actor = *m;
450 if (actor->IsSelected()) {
451 actor->HotKey = (ieDword) Key;
456 bool Game::SelectPCSingle(int index)
458 Actor* actor = FindPC( index );
459 if (!actor || ! actor->ValidTarget( GA_NO_DEAD | GA_NO_HIDDEN ))
460 return false;
462 SelectedSingle = index;
463 return true;
466 int Game::GetSelectedPCSingle() const
468 return SelectedSingle;
472 * SelectActor() - handle (de)selecting actors.
473 * If selection was changed, runs "SelectionChanged" handler
475 * actor - either specific actor, or NULL for all
476 * select - whether actor(s) should be selected or deselected
477 * flags:
478 * SELECT_REPLACE - if true, deselect all other actors when selecting one
479 * SELECT_QUIET - do not run handler if selection was changed. Used for
480 * nested calls to SelectActor()
483 bool Game::SelectActor(Actor* actor, bool select, unsigned flags)
485 std::vector< Actor*>::iterator m;
487 // actor was not specified, which means all selectables should be (de)selected
488 if (! actor) {
489 for ( m = selected.begin(); m != selected.end(); ++m) {
490 (*m)->Select( false );
491 (*m)->SetOver( false );
493 selected.clear();
495 if (select) {
496 area->SelectActors();
498 for ( m = PCs.begin(); m != PCs.end(); ++m) {
499 if (! *m) {
500 continue;
502 SelectActor( *m, true, SELECT_QUIET );
507 if (! (flags & SELECT_QUIET)) {
508 core->SetEventFlag(EF_SELECTION);
510 Infravision();
511 return true;
514 // actor was specified, so we will work with him
515 if (select) {
516 if (! actor->ValidTarget( GA_SELECT | GA_NO_DEAD ))
517 return false;
519 // deselect all actors first when exclusive
520 if (flags & SELECT_REPLACE) {
521 if (selected.size() == 1 && actor->IsSelected()) {
522 assert(selected[0] == actor);
523 // already the only selected actor
524 return true;
526 SelectActor( NULL, false, SELECT_QUIET );
527 } else if (actor->IsSelected()) {
528 // already selected
529 return true;
532 actor->Select( true );
533 assert(actor->IsSelected());
534 selected.push_back( actor );
535 } else {
536 if (!actor->IsSelected()) {
537 // already not selected
538 return true;
540 /*for ( m = selected.begin(); m != selected.end(); ++m) {
541 assert((*m) != actor);
543 return true;*/
545 for ( m = selected.begin(); m != selected.end(); ++m) {
546 if ((*m) == actor) {
547 selected.erase( m );
548 break;
551 actor->Select( false );
552 assert(!actor->IsSelected());
555 if (! (flags & SELECT_QUIET)) {
556 core->SetEventFlag(EF_SELECTION);
558 Infravision();
559 return true;
562 // Gets average party level, of onlyalive is true, then counts only living PCs
563 int Game::GetPartyLevel(bool onlyalive) const
565 int count = 0;
566 for (unsigned int i = 0; i<PCs.size(); i++) {
567 if (onlyalive) {
568 if (PCs[i]->GetStat(IE_STATE_ID)&STATE_DEAD) {
569 continue;
572 count += PCs[i]->GetXPLevel(0);
574 return count;
577 // Returns map structure (ARE) if it is already loaded in memory
578 int Game::FindMap(const char *ResRef)
580 int index = (int) Maps.size();
581 while (index--) {
582 Map *map=Maps[index];
583 if (strnicmp(ResRef, map->GetScriptName(), 8) == 0) {
584 return index;
587 return -1;
590 Map* Game::GetMap(unsigned int index) const
592 if (index >= Maps.size()) {
593 return NULL;
595 return Maps[index];
598 Map *Game::GetMap(const char *areaname, bool change)
600 int index = LoadMap(areaname);
601 if (index >= 0) {
602 if (change) {
603 MapIndex = index;
604 area = GetMap(index);
605 memcpy (CurrentArea, areaname, 8);
606 area->SetupAmbients();
607 //change the tileset if needed
608 area->ChangeMap(IsDay());
609 ChangeSong(false, true);
610 Infravision();
611 return area;
613 return GetMap(index);
615 return NULL;
618 bool Game::MasterArea(const char *area)
620 unsigned int i=(int) mastarea.size();
621 while(i--) {
622 if (strnicmp(mastarea[i], area, 8) ) {
623 return true;
626 return false;
629 void Game::SetMasterArea(const char *area)
631 if (MasterArea(area) ) return;
632 char *tmp = (char *) malloc(9);
633 strnlwrcpy (tmp,area,8);
634 mastarea.push_back(tmp);
637 int Game::AddMap(Map* map)
639 if (MasterArea(map->GetScriptName()) ) {
640 Maps.insert(Maps.begin(), 1, map);
641 MapIndex++;
642 return 0;
644 unsigned int i = (unsigned int) Maps.size();
645 Maps.push_back( map );
646 return i;
649 int Game::DelMap(unsigned int index, int forced)
651 //this function should archive the area, and remove it only if the area
652 //contains no active actors (combat, partymembers, etc)
653 if (index >= Maps.size()) {
654 return -1;
656 Map *map = Maps[index];
658 if (MapIndex==(int) index) { //can't remove current map in any case
659 const char *name = map->GetScriptName();
660 memcpy(AnotherArea, name, sizeof(AnotherArea) );
661 return -1;
665 if (!map) { //this shouldn't happen, i guess
666 printMessage("Game","Erased NULL Map\n",YELLOW);
667 Maps.erase( Maps.begin()+index);
668 if (MapIndex>(int) index) {
669 MapIndex--;
671 return 1;
674 if (forced || (Maps.size()>MAX_MAPS_LOADED) )
676 //keep at least one master
677 const char *name = map->GetScriptName();
678 if (MasterArea(name)) {
679 if(!AnotherArea[0]) {
680 memcpy(AnotherArea, name, sizeof(AnotherArea));
681 if (!forced) {
682 return -1;
686 //this check must be the last, because
687 //after PurgeActors you cannot keep the
688 //area in memory
689 //Or the queues should be regenerated!
690 if (!map->CanFree())
692 return 1;
694 //remove map from memory
695 core->SwapoutArea(Maps[index]);
696 delete( Maps[index] );
697 Maps.erase( Maps.begin()+index);
698 //current map will be decreased
699 if (MapIndex>(int) index) {
700 MapIndex--;
702 return 1;
704 //didn't remove the map
705 return 0;
708 /* Loads an area */
709 int Game::LoadMap(const char* ResRef)
711 unsigned int i;
712 int index = FindMap(ResRef);
713 if (index>=0) {
714 return index;
717 bool hide = core->HideGCWindow();
718 core->GetGUIScriptEngine()->RunFunction("LoadScreen", "StartLoadScreen");
719 core->GetGUIScriptEngine()->RunFunction("LoadScreen", "SetLoadScreen");
720 DataStream* ds = gamedata->GetResource( ResRef, IE_ARE_CLASS_ID );
721 if (!ds) {
722 core->LoadProgress(100);
723 return -1;
725 PluginHolder<MapMgr> mM(IE_ARE_CLASS_ID);
726 if(!mM->Open( ds, true )) {
727 core->LoadProgress(100);
728 return -1;
730 Map* newMap = mM->GetMap(ResRef, IsDay());
731 if (!newMap) {
732 core->LoadProgress(100);
733 return -1;
735 core->LoadProgress(100);
737 for (i = 0; i < PCs.size(); i++) {
738 if (stricmp( PCs[i]->Area, ResRef ) == 0) {
739 newMap->AddActor( PCs[i] );
742 for (i = 0; i < NPCs.size(); i++) {
743 if (stricmp( NPCs[i]->Area, ResRef ) == 0) {
744 newMap->AddActor( NPCs[i] );
747 if (hide) {
748 core->UnhideGCWindow();
750 return AddMap( newMap );
753 int Game::AddNPC(Actor* npc)
755 int slot = InStore( npc ); //already an npc
756 if (slot != -1) {
757 return slot;
759 slot = InParty( npc );
760 if (slot != -1) {
761 return -1;
762 } //can't add as npc already in party
763 npc->SetPersistent(0);
764 NPCs.push_back( npc );
766 return (int) NPCs.size() - 1;
769 Actor* Game::GetNPC(unsigned int Index)
771 if (Index >= NPCs.size()) {
772 return NULL;
774 return NPCs[Index];
777 void Game::SwapPCs(unsigned int Index1, unsigned int Index2)
779 if (Index1 >= PCs.size()) {
780 return;
783 if (Index2 >= PCs.size()) {
784 return;
786 int tmp = PCs[Index1]->InParty;
787 PCs[Index1]->InParty = PCs[Index2]->InParty;
788 PCs[Index2]->InParty = tmp;
789 //signal a change of the portrait window
790 core->SetEventFlag(EF_PORTRAIT);
793 void Game::DeleteJournalEntry(ieStrRef strref)
795 size_t i=Journals.size();
796 while(i--) {
797 if (Journals[i]->Text==strref) {
798 delete Journals[i];
799 Journals.erase(Journals.begin()+i);
804 void Game::DeleteJournalGroup(int Group)
806 size_t i=Journals.size();
807 while(i--) {
808 if (Journals[i]->Group==(ieByte) Group) {
809 delete Journals[i];
810 Journals.erase(Journals.begin()+i);
814 /* returns true if it modified or added a journal entry */
815 bool Game::AddJournalEntry(ieStrRef strref, int Section, int Group)
817 GAMJournalEntry *je = FindJournalEntry(strref);
818 if (je) {
819 //don't set this entry again in the same section
820 if (je->Section==Section) {
821 return false;
823 if ((Section == IE_GAM_QUEST_DONE) && Group) {
824 //removing all of this group and adding a new entry
825 DeleteJournalGroup(Group);
826 } else {
827 //modifying existing entry
828 je->Section = (ieByte) Section;
829 je->Group = (ieByte) Group;
830 ieDword chapter = 0;
831 locals->Lookup("CHAPTER", chapter);
832 je->Chapter = (ieByte) chapter;
833 je->GameTime = GameTime;
834 return true;
837 je = new GAMJournalEntry;
838 je->GameTime = GameTime;
839 ieDword chapter = 0;
840 locals->Lookup("CHAPTER", chapter);
841 je->Chapter = (ieByte) chapter;
842 je->unknown09 = 0;
843 je->Section = (ieByte) Section;
844 je->Group = (ieByte) Group;
845 je->Text = strref;
847 Journals.push_back( je );
848 return true;
851 void Game::AddJournalEntry(GAMJournalEntry* entry)
853 Journals.push_back( entry );
856 unsigned int Game::GetJournalCount() const
858 return (unsigned int) Journals.size();
861 GAMJournalEntry* Game::FindJournalEntry(ieStrRef strref)
863 unsigned int Index = (unsigned int) Journals.size();
864 while(Index--) {
865 GAMJournalEntry *ret = Journals[Index];
867 if (ret->Text==strref) {
868 return ret;
872 return NULL;
875 GAMJournalEntry* Game::GetJournalEntry(unsigned int Index)
877 if (Index >= Journals.size()) {
878 return NULL;
880 return Journals[Index];
883 unsigned int Game::GetSavedLocationCount() const
885 return (unsigned int) savedpositions.size();
888 void Game::ClearSavedLocations()
890 size_t i=savedpositions.size();
891 while(i--) {
892 delete savedpositions[i];
894 savedpositions.clear();
897 GAMLocationEntry* Game::GetSavedLocationEntry(unsigned int i)
899 size_t current = savedpositions.size();
900 if (i>=current) {
901 if (i>PCs.size()) {
902 return NULL;
904 savedpositions.resize(i+1);
905 while(current<=i) {
906 savedpositions[current++]=(GAMLocationEntry *) calloc(1, sizeof(GAMLocationEntry) );
909 return savedpositions[i];
912 unsigned int Game::GetPlaneLocationCount() const
914 return (unsigned int) planepositions.size();
917 void Game::ClearPlaneLocations()
919 size_t i=planepositions.size();
920 while(i--) {
921 delete planepositions[i];
923 planepositions.clear();
926 GAMLocationEntry* Game::GetPlaneLocationEntry(unsigned int i)
928 size_t current = planepositions.size();
929 if (i>=current) {
930 if (i>PCs.size()) {
931 return NULL;
933 planepositions.resize(i+1);
934 while(current<=i) {
935 planepositions[current++]=(GAMLocationEntry *) calloc(1, sizeof(GAMLocationEntry) );
938 return planepositions[i];
941 char *Game::GetFamiliar(unsigned int Index)
943 return Familiars[Index];
946 //reading the challenge rating table for iwd2 (only when needed)
947 void Game::LoadCRTable()
949 AutoTable table("moncrate");
950 if (table.ok()) {
951 int maxrow = table->GetRowCount()-1;
952 crtable = new CRRow[MAX_LEVEL];
953 for(int i=0;i<MAX_LEVEL;i++) {
954 //row shouldn't be larger than maxrow
955 int row = i<maxrow?i:maxrow;
956 int maxcol = table->GetColumnCount(row)-1;
957 for(int j=0;j<MAX_CRLEVEL;j++) {
958 //col shouldn't be larger than maxcol
959 int col = j<maxcol?j:maxcol;
960 crtable[i][j]=atoi(table->QueryField(row,col) );
966 int Game::GetXPFromCR(int cr)
968 if (!crtable) LoadCRTable();
969 if (crtable) {
970 int level = GetPartyLevel(true);
971 if (cr>=MAX_CRLEVEL) {
972 cr=MAX_CRLEVEL-1;
974 printf("Challenge Rating: %d, party level: %d ", cr, level);
975 return crtable[level][cr];
977 printMessage("Game","Cannot find moncrate.2da!\n", LIGHT_RED);
978 return 0;
981 void Game::ShareXP(int xp, int flags)
983 int individual;
985 if (flags&SX_CR) {
986 xp = GetXPFromCR(xp);
989 if (flags&SX_DIVIDE) {
990 int PartySize = GetPartySize(true); //party size, only alive
991 if (PartySize<1) {
992 return;
994 individual = xp / PartySize;
995 } else {
996 individual = xp;
999 if (!individual) {
1000 return;
1003 if (xp>0) {
1004 displaymsg->DisplayConstantStringValue( STR_GOTXP, 0xbcefbc, (ieDword) xp); //you have gained ... xp
1005 } else {
1006 displaymsg->DisplayConstantStringValue( STR_LOSTXP, 0xbcefbc, (ieDword) -xp); //you have lost ... xp
1008 for (unsigned int i=0; i<PCs.size(); i++) {
1009 if (PCs[i]->GetStat(IE_STATE_ID)&STATE_DEAD) {
1010 continue;
1012 PCs[i]->AddExperience(individual);
1016 bool Game::EveryoneStopped() const
1018 for (unsigned int i=0; i<PCs.size(); i++) {
1019 if (PCs[i]->GetNextStep() ) return false;
1021 return true;
1024 //canmove=true: if some PC can't move (or hostile), then this returns false
1025 bool Game::EveryoneNearPoint(Map *area, const Point &p, int flags) const
1027 for (unsigned int i=0; i<PCs.size(); i++) {
1028 if (flags&ENP_ONLYSELECT) {
1029 if(!PCs[i]->Selected) {
1030 continue;
1033 if (PCs[i]->GetStat(IE_STATE_ID)&STATE_DEAD) {
1034 continue;
1036 if (flags&ENP_CANMOVE) {
1037 //someone is uncontrollable, can't move
1038 if (PCs[i]->GetStat(IE_EA)>EA_GOODCUTOFF) {
1039 return false;
1042 if (PCs[i]->GetStat(IE_STATE_ID)&STATE_CANTMOVE) {
1043 return false;
1046 if (PCs[i]->GetCurrentArea()!=area) {
1047 return false;
1049 if (Distance(p,PCs[i])>MAX_TRAVELING_DISTANCE) {
1050 return false;
1053 return true;
1056 //called when someone died
1057 void Game::PartyMemberDied(Actor *actor)
1059 //this could be null, in some extreme cases...
1060 Map *area = actor->GetCurrentArea();
1062 for (unsigned int i=0; i<PCs.size(); i++) {
1063 if (PCs[i]==actor) {
1064 continue;
1066 if (PCs[i]->GetStat(IE_STATE_ID)&STATE_DEAD) {
1067 continue;
1069 if (PCs[i]->GetCurrentArea()!=area) {
1070 continue;
1072 PCs[i]->ReactToDeath(actor->GetScriptName());
1076 //reports if someone died
1077 int Game::PartyMemberDied() const
1079 for (unsigned int i=0; i<PCs.size(); i++) {
1080 if (PCs[i]->GetInternalFlag()&IF_JUSTDIED) {
1081 return i;
1084 return -1;
1087 void Game::IncrementChapter()
1089 //chapter first set to 0 (prologue)
1090 ieDword chapter = (ieDword) -1;
1091 locals->Lookup("CHAPTER",chapter);
1092 locals->SetAt("CHAPTER",chapter+1);
1093 //clear statistics
1094 for (unsigned int i=0; i<PCs.size(); i++) {
1095 //all PCs must have this!
1096 PCs[i]->PCStats->IncrementChapter();
1100 void Game::SetReputation(ieDword r)
1102 if (r<10) r=10;
1103 else if (r>200) r=200;
1104 if (Reputation>r) {
1105 displaymsg->DisplayConstantStringValue(STR_LOSTREP,0xc0c000,(Reputation-r)/10);
1106 } else if (Reputation<r) {
1107 displaymsg->DisplayConstantStringValue(STR_GOTREP,0xc0c000,(r-Reputation)/10);
1109 Reputation = r;
1110 for (unsigned int i=0; i<PCs.size(); i++) {
1111 PCs[i]->SetBase(IE_REPUTATION, Reputation);
1115 void Game::SetControlStatus(int value, int mode)
1117 switch(mode) {
1118 case BM_OR: ControlStatus|=value; break;
1119 case BM_NAND: ControlStatus&=~value; break;
1120 case BM_SET: ControlStatus=value; break;
1121 case BM_AND: ControlStatus&=value; break;
1122 case BM_XOR: ControlStatus^=value; break;
1124 core->SetEventFlag(EF_CONTROL);
1127 void Game::AddGold(ieDword add)
1129 ieDword old;
1131 if (!add) {
1132 return;
1134 old = PartyGold;
1135 PartyGold += add;
1136 if (old<PartyGold) {
1137 displaymsg->DisplayConstantStringValue( STR_GOTGOLD, 0xc0c000, PartyGold-old);
1138 } else {
1139 displaymsg->DisplayConstantStringValue( STR_LOSTGOLD, 0xc0c000, old-PartyGold);
1143 //later this could be more complicated
1144 void Game::AdvanceTime(ieDword add)
1146 ieDword h = GameTime/(300*AI_UPDATE_TIME);
1147 GameTime+=add;
1148 if (h!=GameTime/(300*AI_UPDATE_TIME)) {
1149 //asking for a new weather when the hour changes
1150 WeatherBits&=~WB_HASWEATHER;
1152 Ticks+=add*interval;
1153 //change the tileset if needed
1154 Map *map = GetCurrentArea();
1155 if (map && map->ChangeMap(IsDay())) {
1156 //play the daylight transition movie appropriate for the area
1157 //it is needed to play only when the area truly changed its tileset
1158 //this is signalled by ChangeMap
1159 int areatype = (area->AreaType&(AT_FOREST|AT_CITY|AT_DUNGEON))>>3;
1160 ieResRef *res;
1162 printMessage("Game","Switching DayLight\n",GREEN);
1163 if (IsDay()) {
1164 res=&nightmovies[areatype];
1165 } else {
1166 res=&daymovies[areatype];
1168 if (*res[0]!='*') {
1169 core->PlayMovie(*res);
1174 //returns true if there are excess players in the team
1175 bool Game::PartyOverflow() const
1177 GameControl *gc = core->GetGameControl();
1178 if (!gc) {
1179 return false;
1181 //don't start this screen when the gui is busy
1182 if (gc->GetDialogueFlags() & (DF_IN_DIALOG|DF_IN_CONTAINER|DF_FREEZE_SCRIPTS) ) {
1183 return false;
1185 if (!partysize) {
1186 return false;
1188 return (PCs.size()>partysize);
1191 bool Game::PCInCombat(Actor* actor) const
1193 if (!CombatCounter) {
1194 return false;
1197 if (actor->LastTarget) {
1198 return true;
1200 if (AttackersOf(actor->GetID(), actor->GetCurrentArea())) {
1201 return true;
1203 return false;
1206 bool Game::AnyPCInCombat() const
1208 if (!CombatCounter) {
1209 return false;
1212 for (unsigned int i=0; i<PCs.size(); i++) {
1213 if (PCInCombat (PCs[i])) {
1214 return true;
1217 return false;
1220 //returns true if the protagonist (or the whole party died)
1221 bool Game::EveryoneDead() const
1223 //if there are no PCs, then we assume everyone dead
1224 if (!PCs.size() ) {
1225 return true;
1227 if (protagonist==PM_NO) {
1228 Actor *nameless = PCs[0];
1229 if (nameless->GetStat(IE_STATE_ID)&STATE_NOSAVE) {
1230 if (area->INISpawn) {
1231 area->INISpawn->RespawnNameless();
1234 return false;
1236 // if protagonist died
1237 if (protagonist==PM_YES) {
1238 if (PCs[0]->GetStat(IE_STATE_ID)&STATE_NOSAVE) {
1239 return true;
1241 return false;
1243 //protagonist == 2
1244 for (unsigned int i=0; i<PCs.size(); i++) {
1245 if (!(PCs[i]->GetStat(IE_STATE_ID)&STATE_NOSAVE) ) {
1246 return false;
1249 return true;
1252 //runs all area scripts
1254 void Game::UpdateScripts()
1256 ExecuteScript( 1 );
1257 ProcessActions(false);
1258 size_t idx;
1260 bool PartyAttack = false;
1262 for (idx=0;idx<Maps.size();idx++) {
1263 Maps[idx]->UpdateScripts();
1264 size_t acnt=Attackers.size();
1265 while(acnt--) {
1266 Actor *actor = Maps[idx]->GetActorByGlobalID(Attackers[acnt]);
1267 if (actor) {
1268 if ( !Maps[idx]->GetActorByGlobalID(actor->LastTarget) ) {
1269 //Actor's target left area
1270 OutAttack(Attackers[acnt]);
1271 continue;
1272 } else {
1273 //each attacker handles their own round initiation
1274 actor->InitRound(GameTime);
1275 if (actor->InParty) {
1276 PartyAttack = true;
1279 } else {
1280 //Attacker is gone from area
1281 OutAttack(Attackers[acnt]);
1286 if (PartyAttack) {
1287 //ChangeSong will set the battlesong only if CombatCounter is nonzero
1288 CombatCounter=150;
1289 ChangeSong(false, true);
1290 } else {
1291 if (CombatCounter) {
1292 CombatCounter--;
1293 //Change song if combatcounter went down to 0
1294 if (!CombatCounter) {
1295 ChangeSong(false, false);
1300 if (StateOverrideTime)
1301 StateOverrideTime--;
1302 if (BanterBlockTime)
1303 BanterBlockTime--;
1305 if (Maps.size()>MAX_MAPS_LOADED) {
1306 idx = Maps.size();
1308 //starting from 0, so we see the most recent master area first
1309 for(unsigned int i=0;i<idx;i++) {
1310 DelMap( (unsigned int) i, false );
1314 // perhaps a StartMusic action stopped the area music?
1315 // (we should probably find a less silly way to handle this,
1316 // because nothing can ever stop area music now..)
1317 if (!core->GetMusicMgr()->IsPlaying()) {
1318 ChangeSong(false,false);
1321 //this is used only for the death delay so far
1322 if (event_handler) {
1323 if (!event_timer) {
1324 event_handler->call();
1325 event_handler = NULL;
1327 event_timer--;
1330 if (EveryoneDead()) {
1331 //don't check it any more
1332 protagonist = PM_NO;
1333 core->GetGUIScriptEngine()->RunFunction("GUIWORLD", "DeathWindow");
1334 return;
1337 if (PartyOverflow()) {
1338 partysize = 0;
1339 core->GetGUIScriptEngine()->RunFunction("GUIWORLD", "OpenReformPartyWindow");
1340 return;
1344 void Game::SetTimedEvent(EventHandler func, int count)
1346 event_timer = count;
1347 event_handler = func;
1350 void Game::SetProtagonistMode(int mode)
1352 protagonist = mode;
1355 void Game::SetPartySize(int size)
1357 // 0 size means no party size control
1358 if (size<0) {
1359 return;
1361 partysize = (size_t) size;
1364 //Get the area dependent rest movie
1365 ieResRef *Game::GetDream(Map *area)
1367 //select dream based on area
1368 int daynight = IsDay();
1369 if (area->Dream[daynight][0]) {
1370 return area->Dream+daynight;
1372 int dream = (area->AreaType&(AT_FOREST|AT_CITY|AT_DUNGEON))>>3;
1373 return restmovies+dream;
1376 //Start dream cutscenes for player1
1377 void Game::PlayerDream()
1379 Scriptable *Sender = GetPC(0,true);
1380 if (!Sender) return;
1382 GameScript* gs = new GameScript( "player1d", Sender,0,0 );
1383 gs->Update();
1384 delete( gs );
1387 //noareacheck = no random encounters
1388 //dream = 0 - based on area non-0 - select from list
1389 //-1 no dream
1390 //hp is how much hp the rest will heal
1391 void Game::RestParty(int checks, int dream, int hp)
1393 if (!(checks&REST_NOMOVE) ) {
1394 if (!EveryoneStopped()) {
1395 return;
1398 Actor *leader = GetPC(0, true);
1399 if (!leader) {
1400 return;
1403 Map *area = leader->GetCurrentArea();
1404 //we let them rest if someone is paralyzed, but the others gather around
1405 if (!(checks&REST_NOSCATTER) ) {
1406 if (!EveryoneNearPoint( area, leader->Pos, 0 ) ) {
1407 //party too scattered
1408 displaymsg->DisplayConstantString( STR_SCATTERED, 0xff0000 );
1409 return;
1413 if (!(checks&REST_NOCRITTER) ) {
1414 //don't allow resting while in combat
1415 if (AnyPCInCombat()) {
1416 displaymsg->DisplayConstantString( STR_CANTRESTMONS, 0xff0000 );
1417 return;
1419 //don't allow resting if hostiles are nearby
1420 if (area->AnyEnemyNearPoint(leader->Pos)) {
1421 displaymsg->DisplayConstantString( STR_CANTRESTMONS, 0xff0000 );
1422 return;
1426 //rest check, if PartyRested should be set, area should return true
1427 //area should advance gametime too (so partial rest is possible)
1428 int hours = 8;
1429 if (!(checks&REST_NOAREA) ) {
1430 //you cannot rest here
1431 if (area->AreaFlags&1) {
1432 displaymsg->DisplayConstantString( STR_MAYNOTREST, 0xff0000 );
1433 return;
1435 //you may not rest here, find an inn
1436 if (!(area->AreaType&(AT_OUTDOOR|AT_FOREST|AT_DUNGEON|AT_CAN_REST) ))
1438 displaymsg->DisplayConstantString( STR_MAYNOTREST, 0xff0000 );
1439 return;
1441 //area encounters
1442 if(area->Rest( leader->Pos, 8, (GameTime/AI_UPDATE_TIME)%7200/3600) ) {
1443 return;
1446 AdvanceTime(2400*AI_UPDATE_TIME);
1448 int i = GetPartySize(true); // party size, only alive
1450 while (i--) {
1451 Actor *tar = GetPC(i, true);
1452 tar->ClearPath();
1453 tar->ClearActions();
1454 tar->SetModal(MS_NONE, 0);
1455 //if hp = 0, then healing will be complete
1456 tar->Heal(hp);
1457 //removes fatigue, recharges spells
1458 tar->Rest(0);
1459 tar->PartyRested();
1462 //movie and cutscene dreams
1463 if (dream>=0) {
1464 //cutscene dreams
1465 if (gamedata->Exists("player1d",IE_BCS_CLASS_ID, true))
1466 PlayerDream();
1468 //select dream based on area
1469 ieResRef *movie;
1470 if (dream==0 || dream>7) {
1471 movie = GetDream(area);
1472 } else {
1473 movie = restmovies+dream;
1475 if (*movie[0]!='*') {
1476 core->PlayMovie(*movie);
1480 //set partyrested flags
1481 PartyRested();
1482 area->PartyRested();
1483 core->SetEventFlag(EF_ACTION);
1485 //restindex will be -1 in the case of PST
1486 //FIXME: I don't quite see why we can't sumply use the same strings.2da entry
1487 //It seems we could reduce complexity here, and free up 2-3 string slots too
1488 int restindex = displaymsg->GetStringReference(STR_REST);
1489 int strindex;
1490 char* tmpstr = NULL;
1492 core->GetTokenDictionary()->SetAtCopy("HOUR", hours);
1493 if (restindex != -1) {
1494 strindex = displaymsg->GetStringReference(STR_HOURS);
1495 } else {
1496 strindex = displaymsg->GetStringReference(STR_PST_HOURS);
1497 restindex = displaymsg->GetStringReference(STR_PST_REST);
1500 //this would be bad
1501 if (strindex == -1 || restindex == -1) return;
1502 tmpstr = core->GetString(strindex, 0);
1503 //as would this
1504 if (!tmpstr) return;
1506 core->GetTokenDictionary()->SetAtCopy("DURATION", tmpstr);
1507 core->FreeString(tmpstr);
1508 displaymsg->DisplayString(restindex, 0xffffff, 0);
1511 //timestop effect
1512 void Game::TimeStop(Actor* owner, ieDword end)
1514 timestop_owner=owner;
1515 timestop_end=GameTime+end;
1518 //recalculate the party's infravision state
1519 void Game::Infravision()
1521 hasInfra = false;
1522 Map *map = GetCurrentArea();
1523 if (!map) return;
1524 for(size_t i=0;i<PCs.size();i++) {
1525 Actor *actor = PCs[i];
1526 if (!IsAlive(actor)) continue;
1527 if (actor->GetCurrentArea()!=map) continue;
1528 //Group infravision overrides this???
1529 if (!actor->Selected) continue;
1530 if (actor->GetStat(IE_STATE_ID) & STATE_INFRA) {
1531 hasInfra = true;
1532 return;
1537 //returns the colour which should be applied onto the whole game area viewport
1538 //this is based on timestop, dream area, weather, daytime
1540 static const Color TimeStopTint={0xe0,0xe0,0xe0,0x20}; //greyscale
1541 static const Color DreamTint={0xf0,0xe0,0xd0,0x10}; //light brown scale
1542 static const Color NightTint={0x80,0x80,0xe0,0x40}; //dark, bluish
1543 static const Color DuskTint={0xe0,0x80,0x80,0x40}; //dark, reddish
1544 static const Color FogTint={0xff,0xff,0xff,0x40}; //whitish
1545 static const Color DarkTint={0x80,0x80,0xe0,0x10}; //slightly dark bluish
1547 const Color *Game::GetGlobalTint() const
1549 if (timestop_end>GameTime) {
1550 return &TimeStopTint;
1552 Map *map = GetCurrentArea();
1553 if (!map) return NULL;
1554 if (map->AreaFlags&AF_DREAM) {
1555 return &DreamTint;
1557 if ((map->AreaType&(AT_OUTDOOR|AT_DAYNIGHT|AT_EXTENDED_NIGHT)) == (AT_OUTDOOR|AT_DAYNIGHT) ) {
1558 //get daytime colour
1559 ieDword daynight = ((GameTime/AI_UPDATE_TIME)%7200/300);
1560 if (daynight<2 || daynight>22) {
1561 return &NightTint;
1563 if (daynight>20 || daynight<4) {
1564 return &DuskTint;
1567 if ((map->AreaType&(AT_OUTDOOR|AT_WEATHER)) == (AT_OUTDOOR|AT_WEATHER)) {
1568 //get weather tint
1569 if (WeatherBits&WB_RAIN) {
1570 return &DarkTint;
1572 if (WeatherBits&WB_FOG) {
1573 return &FogTint;
1577 return NULL;
1580 bool Game::IsDay()
1582 ieDword daynight = ((GameTime/AI_UPDATE_TIME)%7200/300);
1583 if(daynight<4 || daynight>20) {
1584 return false;
1586 return true;
1589 void Game::InAttack(ieDword globalID)
1591 std::vector< ieDword>::const_iterator idx;
1593 for(idx=Attackers.begin(); idx!=Attackers.end();idx++) {
1594 if (*idx==globalID) return;
1596 Attackers.push_back(globalID);
1599 void Game::OutAttack(ieDword globalID)
1601 std::vector< ieDword>::iterator idx;
1603 for(idx=Attackers.begin(); idx!=Attackers.end();idx++) {
1604 if (*idx==globalID) {
1605 Attackers.erase(idx);
1606 break;
1611 void Game::ChangeSong(bool always, bool force)
1613 int Song;
1615 if (CombatCounter) {
1616 //battlesong
1617 Song = SONG_BATTLE;
1618 } else {
1619 //will select SONG_DAY or SONG_NIGHT
1620 Song = (GameTime/AI_UPDATE_TIME)%7200/3600;
1622 //area may override the song played (stick in battlemusic)
1623 //always transition gracefully with ChangeSong
1624 //force just means, we schedule the song for later, if currently
1625 //is playing
1626 area->PlayAreaSong( Song, always, force );
1629 int Game::AttackersOf(ieDword globalID, Map *area) const
1631 if (!area) {
1632 return 0;
1634 std::vector< ieDword>::const_iterator idx;
1636 int cnt = 0;
1637 for(idx=Attackers.begin(); idx!=Attackers.end();idx++) {
1638 Actor * actor = area->GetActorByGlobalID(*idx);
1639 if (actor) {
1640 if (actor->LastTarget==globalID) {
1641 cnt++;
1645 return cnt;
1648 /* this method redraws weather. If update is false,
1649 then the weather particles won't change (game paused)
1651 void Game::DrawWeather(const Region &screen, bool update)
1653 if (!weather) {
1654 return;
1656 if (!area->HasWeather()) {
1657 return;
1660 weather->Draw( screen );
1661 if (!update) {
1662 return;
1665 if (!(WeatherBits & (WB_RAIN|WB_SNOW)) ) {
1666 if (weather->GetPhase() == P_GROW) {
1667 weather->SetPhase(P_FADE);
1670 //if (GameTime&1) {
1671 int drawn = weather->Update();
1672 if (drawn) {
1673 WeatherBits &= ~WB_START;
1677 if (WeatherBits&WB_HASWEATHER) {
1678 return;
1680 StartRainOrSnow(true, area->GetWeather());
1683 /* sets the weather type */
1684 void Game::StartRainOrSnow(bool conditional, int w)
1686 if (conditional && (w & (WB_RAIN|WB_SNOW)) ) {
1687 if (WeatherBits & (WB_RAIN | WB_SNOW) )
1688 return;
1690 // whatever was responsible for calling this, we now have some set weather
1691 WeatherBits = w | WB_HASWEATHER;
1692 if (w & WB_LIGHTNING) {
1693 if (WeatherBits&WB_START) {
1694 //already raining
1695 if (GameTime&1) {
1696 core->PlaySound(DS_LIGHTNING1);
1697 } else {
1698 core->PlaySound(DS_LIGHTNING2);
1700 } else {
1701 //start raining (far)
1702 core->PlaySound(DS_LIGHTNING3);
1705 if (w&WB_SNOW) {
1706 core->PlaySound(DS_SNOW);
1707 weather->SetType(SP_TYPE_POINT, SP_PATH_FLIT, SP_SPAWN_SOME);
1708 weather->SetPhase(P_GROW);
1709 weather->SetColor(SPARK_COLOR_WHITE);
1710 return;
1712 if (w&WB_RAIN) {
1713 core->PlaySound(DS_RAIN);
1714 weather->SetType(SP_TYPE_LINE, SP_PATH_RAIN, SP_SPAWN_SOME);
1715 weather->SetPhase(P_GROW);
1716 weather->SetColor(SPARK_COLOR_STONE);
1717 return;
1719 weather->SetPhase(P_FADE);
1722 void Game::SetExpansion(ieDword value)
1724 if (Expansion>=value) {
1725 return;
1727 Expansion = value;
1729 switch(Expansion) {
1730 default:
1731 core->SetEventFlag(EF_EXPANSION);
1732 break;
1733 //TODO: move this hardcoded hack to the scripts
1734 case 5:
1735 core->GetDictionary()->SetAt( "PlayMode", 2 );
1737 int i = GetPartySize(false);
1738 while(i--) {
1739 Actor *actor = GetPC(i, false);
1740 InitActorPos(actor);
1745 void Game::DebugDump()
1747 size_t idx;
1749 printf("Currently loaded areas:\n");
1750 for(idx=0;idx<Maps.size();idx++) {
1751 Map *map = Maps[idx];
1753 printf("%s\n",map->GetScriptName());
1755 printf("Current area: %s Previous area: %s\n", CurrentArea, PreviousArea);
1756 printf("Global script: %s\n", Scripts[0]->GetName());
1757 printf("CombatCounter: %d\n", (int) CombatCounter);
1758 printf("Attackers count: %d\n", (int) Attackers.size());
1759 for(idx=0;idx<Attackers.size(); idx++) {
1760 Actor *actor = GetActorByGlobalID(Attackers[idx]);
1761 if (!actor) {
1762 printf("Name: ???\n");
1763 continue;
1765 Actor *whom = GetActorByGlobalID(actor->LastTarget);
1766 printf("Name: %s Attacking : %s\n", actor->ShortName, whom?whom->ShortName:"???");
1769 printf("Party size: %d\n", (int) PCs.size());
1770 for(idx=0;idx<PCs.size();idx++) {
1771 Actor *actor = PCs[idx];
1773 printf("Name: %s Order %d %s\n",actor->ShortName, actor->InParty, actor->Selected?"x":"-");
1777 Actor *Game::GetActorByGlobalID(ieWord objectID)
1779 size_t mc = GetLoadedMapCount();
1780 while(mc--) {
1781 Map *map = GetMap(mc);
1782 Actor *actor = map->GetActorByGlobalID(objectID);
1783 if (actor) return actor;
1785 return NULL;