1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003-2005 The GemRB Project
3 *
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.
8
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.
13
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.
17 *
18 *
19 */
20
21 #include "Interface.h"
22
23 #include "exports.h"
24 #include "globals.h"
25 #include "strrefs.h"
26 #include "ie_cursors.h"
27
28 #include "ActorMgr.h"
29 #include "AmbientMgr.h"
30 #include "AnimationMgr.h"
31 #include "ArchiveImporter.h"
32 #include "Calendar.h"
33 #include "DataFileMgr.h"
34 #include "DialogHandler.h"
35 #include "DialogMgr.h"
36 #include "DisplayMessage.h"
37 #include "EffectMgr.h"
38 #include "EffectQueue.h"
39 #include "Factory.h"
40 #include "FontManager.h"
41 #include "Game.h"
42 #include "GameScript/GameScript.h"
43 #include "ItemMgr.h"
44 #include "KeyMap.h"
45 #include "MapMgr.h"
46 #include "MoviePlayer.h"
47 #include "MusicMgr.h"
48 #include "Palette.h"
49 #ifndef STATIC_LINK
50 #include "PluginLoader.h"
51 #endif
52 #include "PluginMgr.h"
53 #include "Predicates.h"
54 #include "ProjectileServer.h"
55 #include "SaveGameIterator.h"
56 #include "SaveGameMgr.h"
57 #include "ScriptedAnimation.h"
58 #include "SoundMgr.h"
59 #include "SpellMgr.h"
60 #include "StoreMgr.h"
61 #include "StringMgr.h"
62 #include "SymbolMgr.h"
63 #include "TileMap.h"
64 #include "VEFObject.h"
65 #include "Video.h"
66 #include "WorldMapMgr.h"
67 #include "GUI/Button.h"
68 #include "GUI/Console.h"
69 #include "GUI/EventMgr.h"
70 #include "GUI/GameControl.h"
71 #include "GUI/GUIScriptInterface.h"
72 #include "GUI/Label.h"
73 #include "GUI/MapControl.h"
74 #include "GUI/TextArea.h"
75 #include "GUI/WindowManager.h"
76 #include "GUI/WorldMapControl.h"
77 #include "RNG.h"
78 #include "Scriptable/Container.h"
79 #include "System/FileStream.h"
80 #include "System/FileFilters.h"
81 #include "System/StringBuffer.h"
82
83 #include <vector>
84
85 #ifdef WIN32
86 #include "CodepageToIconv.h"
87 #elif defined(HAVE_LANGINFO_H)
88 #include <langinfo.h>
89 #endif
90
91 namespace GemRB {
92
93 GEM_EXPORT Interface* core = NULL;
94
95 static int MaximumAbility = 25;
96 static ieWordSigned *strmod = NULL;
97 static ieWordSigned *strmodex = NULL;
98 static ieWordSigned *intmod = NULL;
99 static ieWordSigned *dexmod = NULL;
100 static ieWordSigned *conmod = NULL;
101 static ieWordSigned *chrmod = NULL;
102 static ieWordSigned *lorebon = NULL;
103 static ieWordSigned *wisbon = NULL;
104 static int **reputationmod = NULL;
105 static ieVariable IWD2DeathVarFormat = "_DEAD%s";
106 static ieVariable DeathVarFormat = "SPRITE_IS_DEAD%s";
107 static int NumRareSelectSounds = 2;
108
109 static ieWord IDT_FAILURE = 0;
110 static ieWord IDT_CRITRANGE = 1;
111 static ieWord IDT_CRITMULTI = 2;
112 static ieWord IDT_SKILLPENALTY = 3;
113
114 static int MagicBit = 0;
115 static const char* DefaultSystemEncoding = "UTF-8";
116
117 // FIXME: DragOp should be initialized with the button we are dragging from
118 // for now use a dummy until we truly implement this as a drag event
119 Control ItemDragOp::dragDummy = Control(Region());
120
ItemDragOp(CREItem * item)121 ItemDragOp::ItemDragOp(CREItem* item)
122 : ControlDragOp(&dragDummy), item(item) {
123 Item* i = gamedata->GetItem(item->ItemResRef);
124 assert(i);
125 Holder<Sprite2D> pic = gamedata->GetAnySprite(i->ItemIcon, -1, 1);
126 if (pic == nullptr) {
127 // use any / the smaller icon if the dragging one is unavailable
128 pic = gamedata->GetBAMSprite(i->ItemIcon, -1, 0);
129 }
130
131 cursor = pic;
132
133 // FIXME: this VarName is not consistant
134 strnlwrcpy(dragDummy.VarName, "itembutton", MAX_VARIABLE_LENGTH - 1);
135 }
136
Interface()137 Interface::Interface()
138 {
139 unsigned int i;
140 for(i=0;i<256;i++) {
141 pl_uppercase[i]=(ieByte) toupper(i);
142 pl_lowercase[i]=(ieByte) tolower(i);
143 }
144
145 winmgr = NULL;
146 projserv = NULL;
147 VideoDriverName = "sdl";
148 AudioDriverName = "openal";
149 RtRows = NULL;
150 sgiterator = NULL;
151 game = NULL;
152 calendar = NULL;
153 keymap = NULL;
154 worldmap = NULL;
155 CurrentStore = NULL;
156 CurrentContainer = NULL;
157 UseContainer = false;
158 displaymsg = NULL;
159 slottypes = NULL;
160 slotmatrix = NULL;
161
162 UseCorruptedHack = false;
163
164 mousescrollspd = 10;
165
166 GameType[0] = '\0';
167 CheatFlag = false;
168 QuitFlag = QF_NORMAL;
169 EventFlag = EF_CONTROL;
170 #ifndef WIN32
171 CaseSensitive = true; //this is the default value, so CD1/CD2 will be resolved
172 #else
173 CaseSensitive = false;
174 #endif
175 UseSoftKeyboard = false;
176 KeepCache = false;
177 NumFingInfo = 2;
178 NumFingKboard = 3;
179 NumFingScroll = 2;
180 MouseFeedback = 0;
181 IgnoreOriginalINI = 0;
182 GUIScriptsPath[0] = 0;
183 GamePath[0] = 0;
184 SavePath[0] = 0;
185 GemRBPath[0] = 0;
186 PluginsPath[0] = 0;
187 CachePath[0] = 0;
188 GemRBOverridePath[0] = 0;
189 GemRBUnhardcodedPath[0] = 0;
190 GameName[0] = 0;
191 CustomFontPath[0] = 0;
192 GameOverridePath[0] = 0;
193 GameSoundsPath[0] = 0;
194 GameScriptsPath[0] = 0;
195 GamePortraitsPath[0] = 0;
196 GameCharactersPath[0] = 0;
197 GameDataPath[0] = 0;
198
199 strlcpy( INIConfig, "baldur.ini", sizeof(INIConfig) );
200
201 GlobalScript = "BALDUR";
202 WorldMapName[0] = "WORLDMAP";
203
204 for (int size = 0; size < MAX_CIRCLE_SIZE; size++) {
205 CopyResRef(GroundCircleBam[size], "");
206 GroundCircleScale[size] = 0;
207 }
208
209 TooltipBG = NULL;
210 DefSound = NULL;
211 DSCount = -1;
212 memset(GameFeatures, 0, sizeof( GameFeatures ));
213 //GameFeatures = 0;
214 //GameFeatures2 = 0;
215 memset(&Time, 0, sizeof(Time));
216 AreaAliasTable = NULL;
217 update_scripts = false;
218 SpecialSpellsCount = -1;
219 SpecialSpells = NULL;
220 Encoding = "default";
221
222 SystemEncoding = DefaultSystemEncoding;
223 #if defined(WIN32) && defined(HAVE_ICONV)
224 const uint32_t codepage = GetACP();
225 const char* iconvCode = GetIconvNameForCodepage(codepage);
226
227 if (nullptr == iconvCode) {
228 error("Interface", "Mapping of codepage %u unknown to iconv.", codepage);
229 }
230 SystemEncoding = iconvCode;
231 #elif defined(HAVE_LANGINFO_H)
232 SystemEncoding = nl_langinfo(CODESET);
233 #endif
234
235 TLKEncoding.encoding = "ISO-8859-1";
236 TLKEncoding.widechar = false;
237 TLKEncoding.multibyte = false;
238 TLKEncoding.zerospace = false;
239 MagicBit = HasFeature(GF_MAGICBIT);
240 VersionOverride = ItemTypes = SlotTypes = 0;
241 MultipleQuickSaves = false;
242 MaxPartySize = 6;
243 FeedbackLevel = 0;
244 CutSceneRunner = NULL;
245
246 //once GemRB own format is working well, this might be set to 0
247 SaveAsOriginal = 1;
248
249 gamedata = new GameData();
250
251 //once GemRB own format is working well, this might be set to 0
252 SaveAsOriginal = 1;
253
254 plugin_flags = new Variables();
255 plugin_flags->SetType( GEM_VARIABLES_INT );
256
257 lists = new Variables();
258 lists->SetType( GEM_VARIABLES_POINTER );
259
260 vars = new Variables();
261 vars->SetType( GEM_VARIABLES_INT );
262 vars->ParseKey(true);
263
264 tokens = new Variables();
265 tokens->SetType( GEM_VARIABLES_STRING );
266 }
267
ReleaseItemList(void * poi)268 static void ReleaseItemList(void *poi)
269 {
270 delete ((ItemList *) poi);
271 }
272
FreeAbilityTables()273 static void FreeAbilityTables()
274 {
275 #define NULL_FREE(ptr)\
276 free(ptr); ptr = nullptr
277
278 NULL_FREE(strmod);
279 NULL_FREE(strmodex);
280 NULL_FREE(intmod);
281 NULL_FREE(dexmod);
282 NULL_FREE(conmod);
283 NULL_FREE(chrmod);
284 NULL_FREE(lorebon);
285 NULL_FREE(wisbon);
286 #undef NULL_FREE
287 }
288
FreeResRefTable(ieResRef * & table,int & count)289 void Interface::FreeResRefTable(ieResRef *&table, int &count)
290 {
291 if (table) {
292 free( table );
293 count = -1;
294 }
295 }
296
~Interface(void)297 Interface::~Interface(void)
298 {
299 WindowManager::CursorMouseUp = NULL;
300 WindowManager::CursorMouseDown = NULL;
301
302 delete winmgr;
303
304 delete AreaAliasTable;
305
306 //destroy the highest objects in the hierarchy first!
307 // here gamectrl is either null (no game) or already taken out by its window (game loaded)
308 assert(game == nullptr);
309 delete calendar;
310 delete worldmap;
311 delete keymap;
312
313 FreeAbilityTables();
314
315 if (reputationmod) {
316 for (unsigned int i=0; i<20; i++) {
317 if (reputationmod[i]) {
318 free(reputationmod[i]);
319 }
320 }
321 free(reputationmod);
322 reputationmod=NULL;
323 }
324
325 if (SpecialSpells) {
326 free(SpecialSpells);
327 }
328 SurgeSpells.clear();
329
330 std::map<ResRef, Font*>::iterator fit = fonts.begin();
331 for (; fit != fonts.end(); ++fit)
332 delete (*fit).second;
333 // fonts need to be destroyed before TTF plugin
334 PluginMgr::Get()->RunCleanup();
335
336 ReleaseMemoryActor();
337 ReleaseMemorySpell();
338 EffectQueue_ReleaseMemory();
339 CharAnimations::ReleaseMemory();
340
341 FreeResRefTable(DefSound, DSCount);
342
343 free( slottypes );
344 free( slotmatrix );
345 itemtypedata.clear();
346
347 delete sgiterator;
348
349 for (size_t i = 0; i < musiclist.size(); i++) {
350 free((void *)musiclist[i]);
351 }
352
353 DamageInfoMap.clear();
354
355 delete plugin_flags;
356
357 delete projserv;
358
359 delete displaymsg;
360 delete TooltipBG;
361
362 delete vars;
363 delete tokens;
364 if (lists) {
365 lists->RemoveAll(free);
366 delete lists;
367 }
368
369 if (RtRows) {
370 RtRows->RemoveAll(ReleaseItemList);
371 delete RtRows;
372 }
373
374 Map::ReleaseMemory();
375 Actor::ReleaseMemory();
376
377 gamedata->ClearCaches();
378 delete gamedata;
379 gamedata = NULL;
380
381 // Removing all stuff from Cache, except bifs
382 if (!KeepCache) DelTree((const char *) CachePath, true);
383 }
384
StartGameControl()385 GameControl* Interface::StartGameControl()
386 {
387 assert(gamectrl == nullptr);
388
389 gamedata->DelTable(0xffffu); //dropping ALL tables
390 Region screen(0,0, Width, Height);
391 gamectrl = new GameControl(screen);
392 gamectrl->AssignScriptingRef(0, "GC");
393
394 return gamectrl;
395 }
396
397 /* handle main loop events that might destroy or create windows
398 thus cannot be called from DrawWindows directly
399 these events are pending until conditions are right
400 */
HandleEvents()401 void Interface::HandleEvents()
402 {
403 if (EventFlag&EF_SELECTION) {
404 EventFlag&=~EF_SELECTION;
405 guiscript->RunFunction( "GUICommonWindows", "SelectionChanged", false);
406 }
407
408 if (EventFlag&EF_UPDATEANIM) {
409 EventFlag&=~EF_UPDATEANIM;
410 guiscript->RunFunction( "GUICommonWindows", "UpdateAnimation", false);
411 }
412
413 if (EventFlag&EF_PORTRAIT) {
414 EventFlag&=~EF_PORTRAIT;
415
416 const Window* win = GetWindow(0, "PORTWIN");
417 if (win) {
418 guiscript->RunFunction( "GUICommonWindows", "UpdatePortraitWindow" );
419 }
420 }
421
422 if (EventFlag&EF_ACTION) {
423 EventFlag&=~EF_ACTION;
424
425 const Window* win = GetWindow(0, "ACTWIN");
426 if (win) {
427 guiscript->RunFunction( "GUICommonWindows", "UpdateActionsWindow" );
428 }
429 }
430
431 if (EventFlag&EF_CONTROL) {
432 // handle this before clearing EF_CONTROL
433 // otherwise we do this every frame
434 ToggleViewsVisible(!(game->ControlStatus & CS_HIDEGUI), "HIDE_CUT");
435
436 EventFlag&=~EF_CONTROL;
437 guiscript->RunFunction( "MessageWindow", "UpdateControlStatus" );
438
439 return;
440 }
441 if (EventFlag&EF_SHOWMAP) {
442 ieDword tmp = (ieDword) ~0;
443 vars->Lookup( "OtherWindow", tmp );
444 if (tmp == (ieDword) ~0) {
445 EventFlag &= ~EF_SHOWMAP;
446 guiscript->RunFunction( "GUIMA", "ShowMap" );
447 }
448 return;
449 }
450
451 if (EventFlag&EF_SEQUENCER) {
452 EventFlag&=~EF_SEQUENCER;
453 guiscript->RunFunction( "GUIMG", "OpenSequencerWindow" );
454 return;
455 }
456
457 if (EventFlag&EF_IDENTIFY) {
458 EventFlag&=~EF_IDENTIFY;
459 // FIXME: Implement this.
460 guiscript->RunFunction( "GUICommonWindows", "OpenIdentifyWindow" );
461 return;
462 }
463 if (EventFlag&EF_OPENSTORE) {
464 EventFlag&=~EF_OPENSTORE;
465 guiscript->RunFunction( "GUISTORE", "OpenStoreWindow" );
466 return;
467 }
468
469 if (EventFlag&EF_EXPANSION) {
470 EventFlag&=~EF_EXPANSION;
471 guiscript->RunFunction( "Game", "GameExpansion", false );
472 return;
473 }
474
475 if (EventFlag&EF_CREATEMAZE) {
476 EventFlag&=~EF_CREATEMAZE;
477 guiscript->RunFunction( "Maze", "CreateMaze", false );
478 return;
479 }
480
481 if ((EventFlag&EF_RESETTARGET) && gamectrl) {
482 EventFlag&=~EF_RESETTARGET;
483 EventFlag|=EF_TARGETMODE;
484 gamectrl->ResetTargetMode();
485 return;
486 }
487
488 if ((EventFlag&EF_TARGETMODE) && gamectrl) {
489 EventFlag&=~EF_TARGETMODE;
490 gamectrl->UpdateTargetMode();
491 return;
492 }
493
494 if (EventFlag&EF_TEXTSCREEN) {
495 EventFlag&=~EF_TEXTSCREEN;
496 winmgr->SetCursorFeedback(WindowManager::CursorFeedback(core->MouseFeedback));
497 guiscript->RunFunction( "TextScreen", "StartTextScreen" );
498 return;
499 }
500 }
501
502 /* handle main loop events that might destroy or create windows
503 thus cannot be called from DrawWindows directly
504 */
HandleFlags()505 void Interface::HandleFlags()
506 {
507 //clear events because the context changed
508 EventFlag = EF_CONTROL;
509
510 if (QuitFlag&(QF_QUITGAME|QF_EXITGAME) ) {
511 // closing windows must come before tearing anything else down
512 // some window close handlers expect game/gamecontrol to be valid
513 // let them run, then start tearing things down
514 winmgr->DestroyAllWindows();
515 // when reaching this, quitflag should be 1 or 2
516 // if Exitgame was set, we'll set Start.py too
517 QuitGame (QuitFlag&QF_EXITGAME);
518 }
519
520 if (QuitFlag & (QF_QUITGAME | QF_EXITGAME | QF_LOADGAME | QF_ENTERGAME)) {
521 delete winmgr->GetGameWindow()->RemoveSubview(gamectrl);
522 gamectrl = nullptr;
523 winmgr->GetGameWindow()->SetVisible(false);
524 //clear cutscenes; clear fade/screenshake effects
525 timer = GlobalTimer();
526 QuitFlag &= ~(QF_QUITGAME|QF_EXITGAME);
527 }
528
529 if (QuitFlag&QF_LOADGAME) {
530 QuitFlag &= ~QF_LOADGAME;
531
532 LoadGame(LoadGameIndex.get(), VersionOverride );
533 LoadGameIndex.release();
534 //after loading a game, always check if the game needs to be upgraded
535 }
536
537 if (QuitFlag&QF_ENTERGAME) {
538 winmgr->DestroyAllWindows();
539 QuitFlag &= ~QF_ENTERGAME;
540 if (game) {
541 EventFlag|=EF_EXPANSION;
542
543 Log(MESSAGE, "Core", "Setting up the Console...");
544 guiscript->RunFunction("Console", "OnLoad");
545
546 winmgr->FadeColor = Color();
547
548 GameControl* gc = StartGameControl();
549 guiscript->LoadScript( "Game" );
550 guiscript->RunFunction( "Game", "EnterGame" );
551
552 //switch map to protagonist
553 Actor* actor = GetFirstSelectedPC(true);
554 if (actor) {
555 gc->ChangeMap(actor, true);
556 }
557
558 //rearrange party slots
559 game->ConsolidateParty();
560
561 Window* gamewin = winmgr->GetGameWindow();
562 gamewin->AddSubviewInFrontOfView(gc);
563 gamewin->SetDisabled(false);
564 gamewin->SetVisible(true);
565 gamewin->Focus();
566 } else {
567 Log(ERROR, "Core", "No game to enter...");
568 QuitFlag = QF_QUITGAME;
569 }
570 }
571
572 if (QuitFlag&QF_CHANGESCRIPT) {
573 QuitFlag &= ~QF_CHANGESCRIPT;
574 guiscript->LoadScript( NextScript );
575 guiscript->RunFunction( NextScript, "OnLoad" );
576 }
577 }
578
GenerateAbilityTables()579 static bool GenerateAbilityTables()
580 {
581 FreeAbilityTables();
582
583 //range is: 0 - maximumability
584 int tablesize = MaximumAbility+1;
585 strmod = (ieWordSigned *) malloc (tablesize * 4 * sizeof(ieWordSigned) );
586 if (!strmod)
587 return false;
588 strmodex = (ieWordSigned *) malloc (101 * 4 * sizeof(ieWordSigned) );
589 if (!strmodex)
590 return false;
591 intmod = (ieWordSigned *) malloc (tablesize * 5 * sizeof(ieWordSigned) );
592 if (!intmod)
593 return false;
594 dexmod = (ieWordSigned *) malloc (tablesize * 3 * sizeof(ieWordSigned) );
595 if (!dexmod)
596 return false;
597 conmod = (ieWordSigned *) malloc (tablesize * 5 * sizeof(ieWordSigned) );
598 if (!conmod)
599 return false;
600 chrmod = (ieWordSigned *) malloc (tablesize * 1 * sizeof(ieWordSigned) );
601 if (!chrmod)
602 return false;
603 lorebon = (ieWordSigned *) malloc (tablesize * 1 * sizeof(ieWordSigned) );
604 if (!lorebon)
605 return false;
606 wisbon = (ieWordSigned *) calloc (tablesize * 1, sizeof(ieWordSigned));
607 if (!wisbon)
608 return false;
609 return true;
610 }
611
ReadAbilityTable(const ieResRef tablename,ieWordSigned * mem,int columns,int rows)612 bool Interface::ReadAbilityTable(const ieResRef tablename, ieWordSigned *mem, int columns, int rows)
613 {
614 AutoTable tab(tablename);
615 if (!tab) {
616 return false;
617 }
618 //this is a hack for rows not starting at 0 in some cases
619 int fix = 0;
620 const char * tmp = tab->GetRowName(0);
621 if (tmp && (tmp[0]!='0')) {
622 fix = atoi(tmp);
623 for (int i=0;i<fix;i++) {
624 for (int j=0;j<columns;j++) {
625 mem[rows*j+i]=(ieWordSigned) strtol(tab->QueryField(0,j),NULL,0 );
626 }
627 }
628 }
629 for (int j=0;j<columns;j++) {
630 for( int i=0;i<rows-fix;i++) {
631 mem[rows*j+i+fix] = (ieWordSigned) strtol(tab->QueryField(i,j),NULL,0 );
632 }
633 }
634 return true;
635 }
636
ReadAbilityTables()637 bool Interface::ReadAbilityTables()
638 {
639 bool ret = GenerateAbilityTables();
640 if (!ret)
641 return ret;
642 ret = ReadAbilityTable("strmod", strmod, 4, MaximumAbility + 1);
643 if (!ret)
644 return ret;
645 ret = ReadAbilityTable("strmodex", strmodex, 4, 101);
646 //3rd ed doesn't have strmodex, but has a maximum of 40
647 if (!ret && (MaximumAbility<=25) )
648 return ret;
649 ret = ReadAbilityTable("intmod", intmod, 5, MaximumAbility + 1);
650 if (!ret)
651 return ret;
652 ret = ReadAbilityTable("hpconbon", conmod, 5, MaximumAbility + 1);
653 if (!ret)
654 return ret;
655 if (!HasFeature(GF_3ED_RULES)) {
656 //no lorebon in iwd2???
657 ret = ReadAbilityTable("lorebon", lorebon, 1, MaximumAbility + 1);
658 if (!ret)
659 return ret;
660 //no dexmod in iwd2???
661 ret = ReadAbilityTable("dexmod", dexmod, 3, MaximumAbility + 1);
662 if (!ret)
663 return ret;
664 }
665 //this table is a single row (not a single column)
666 ret = ReadAbilityTable("chrmodst", chrmod, MaximumAbility + 1, 1);
667 if (!ret)
668 return ret;
669 if (gamedata->Exists("wisxpbon", IE_2DA_CLASS_ID, true)) {
670 ret = ReadAbilityTable("wisxpbon", wisbon, 1, MaximumAbility + 1);
671 if (!ret)
672 return ret;
673 }
674 return true;
675 }
676
ReadGameTimeTable()677 bool Interface::ReadGameTimeTable()
678 {
679 AutoTable table("gametime");
680 if (!table) {
681 return false;
682 }
683
684 Time.round_sec = atoi(table->QueryField("ROUND_SECONDS", "DURATION"));
685 Time.turn_sec = atoi(table->QueryField("TURN_SECONDS", "DURATION"));
686 Time.round_size = Time.round_sec * AI_UPDATE_TIME;
687 Time.rounds_per_turn = Time.turn_sec / Time.round_sec;
688 Time.attack_round_size = atoi(table->QueryField("ATTACK_ROUND", "DURATION"));
689 Time.hour_sec = 300; // move to table if pst turns out to be different
690 Time.hour_size = Time.hour_sec * AI_UPDATE_TIME;
691 Time.day_sec = Time.hour_sec * 24; // move to table if pst turns out to be different
692 Time.day_size = Time.day_sec * AI_UPDATE_TIME;
693
694 return true;
695 }
696
ReadSpecialSpells()697 bool Interface::ReadSpecialSpells()
698 {
699 int i;
700 bool result = true;
701
702 AutoTable table("splspec");
703 if (table) {
704 SpecialSpellsCount = table->GetRowCount();
705 SpecialSpells = (SpecialSpellType *) malloc( sizeof(SpecialSpellType) * SpecialSpellsCount);
706 for (i=0;i<SpecialSpellsCount;i++) {
707 strnlwrcpy(SpecialSpells[i].resref, table->GetRowName(i),8 );
708 //if there are more flags, compose this value into a bitfield
709 SpecialSpells[i].flags = atoi(table->QueryField(i, 0));
710 SpecialSpells[i].amount = atoi(table->QueryField(i, 1));
711 SpecialSpells[i].bonus_limit = atoi(table->QueryField(i, 2));
712 }
713 } else {
714 result = false;
715 }
716
717 table.load("wildmag");
718 if (table) {
719 SurgeSpell ss;
720 for (i = 0; (unsigned)i < table->GetRowCount(); i++) {
721 CopyResRef(ss.spell, table->QueryField(i, 0));
722 ss.message = strtol(table->QueryField(i, 1), NULL, 0);
723 // comment ignored
724 SurgeSpells.push_back(ss);
725 }
726 } else {
727 result = false;
728 }
729
730 return result;
731 }
732
GetSpecialSpell(const ieResRef resref)733 int Interface::GetSpecialSpell(const ieResRef resref)
734 {
735 for (int i=0;i<SpecialSpellsCount;i++) {
736 if (!strnicmp(resref, SpecialSpells[i].resref, sizeof(ieResRef))) {
737 return SpecialSpells[i].flags;
738 }
739 }
740 return 0;
741 }
742
743 //disable spells based on some circumstances
CheckSpecialSpell(const ieResRef resref,Actor * actor)744 int Interface::CheckSpecialSpell(const ieResRef resref, Actor *actor)
745 {
746 int sp = GetSpecialSpell(resref);
747
748 //the identify spell is always disabled on the menu
749 if (sp&SP_IDENTIFY) {
750 return SP_IDENTIFY;
751 }
752
753 //if actor is silenced, and spell cannot be cast in silence, disable it
754 if (actor->GetStat(IE_STATE_ID) & STATE_SILENCED ) {
755 if (!(sp&SP_SILENCE)) {
756 return SP_SILENCE;
757 }
758 }
759
760 // disable spells causing surges to be cast while in a surge (prevents nesting)
761 if (sp&SP_SURGE) {
762 return SP_SURGE;
763 }
764
765 return 0;
766 }
767
768 //Static
GetDeathVarFormat()769 const char *Interface::GetDeathVarFormat()
770 {
771 return DeathVarFormat;
772 }
773
ReadAreaAliasTable(const ieResRef tablename)774 bool Interface::ReadAreaAliasTable(const ieResRef tablename)
775 {
776 if (AreaAliasTable) {
777 AreaAliasTable->RemoveAll(NULL);
778 } else {
779 AreaAliasTable = new Variables();
780 AreaAliasTable->SetType(GEM_VARIABLES_INT);
781 }
782
783 AutoTable aa(tablename);
784 if (!aa) {
785 //don't report error when the file doesn't exist
786 return true;
787 }
788
789 int idx = aa->GetRowCount();
790 while (idx--) {
791 ieResRef key;
792
793 strnlwrcpy(key,aa->GetRowName(idx),8);
794 ieDword value = atoi(aa->QueryField(idx,0));
795 AreaAliasTable->SetAt(key, value);
796 }
797 return true;
798 }
799
800 //this isn't const
GetAreaAlias(const ieResRef areaname) const801 int Interface::GetAreaAlias(const ieResRef areaname) const
802 {
803 ieDword value;
804
805 if (AreaAliasTable && AreaAliasTable->Lookup(areaname, value)) {
806 return (int) value;
807 }
808 return -1;
809 }
810
ReadMusicTable(const ieResRef tablename,int col)811 bool Interface::ReadMusicTable(const ieResRef tablename, int col) {
812 AutoTable tm(tablename);
813 if (!tm)
814 return false;
815
816 for (unsigned int i = 0; i < tm->GetRowCount(); i++) {
817 musiclist.push_back(strdup(tm->QueryField(i, col)));
818 }
819
820 return true;
821 }
822
ReadDamageTypeTable()823 bool Interface::ReadDamageTypeTable() {
824 AutoTable tm("dmgtypes");
825 if (!tm)
826 return false;
827
828 DamageInfoStruct di;
829 for (ieDword i = 0; i < tm->GetRowCount(); i++) {
830 di.strref = displaymsg->GetStringReference(atoi(tm->QueryField(i, 0)));
831 di.resist_stat = TranslateStat(tm->QueryField(i, 1));
832 di.value = strtol(tm->QueryField(i, 2), (char **) NULL, 16);
833 di.iwd_mod_type = atoi(tm->QueryField(i, 3));
834 di.reduction = atoi(tm->QueryField(i, 4));
835 DamageInfoMap.insert(std::make_pair(di.value, di));
836 }
837
838 return true;
839 }
840
ReadReputationModTable()841 bool Interface::ReadReputationModTable() {
842 AutoTable tm("reputati");
843 if (!tm)
844 return false;
845
846 reputationmod = (int **) calloc(21, sizeof(int *));
847 int cols = tm->GetColumnCount();
848 for (unsigned int i=0; i<20; i++) {
849 reputationmod[i] = (int *) calloc(cols, sizeof(int));
850 for (int j=0; j<cols; j++) {
851 reputationmod[i][j] = atoi(tm->QueryField(i, j));
852 }
853 }
854
855 return true;
856 }
857
ReadSoundChannelsTable()858 bool Interface::ReadSoundChannelsTable() {
859 AutoTable tm("sndchann");
860 if (!tm) {
861 return false;
862 }
863
864 int ivol = tm->GetColumnIndex("VOLUME");
865 int irev = tm->GetColumnIndex("REVERB");
866 for (ieDword i = 0; i < tm->GetRowCount(); i++) {
867 const char *rowname = tm->GetRowName(i);
868 // translate some alternative names for the IWDs
869 if (!strcmp(rowname, "ACTION")) rowname = "ACTIONS";
870 else if (!strcmp(rowname, "SWING")) rowname = "SWINGS";
871 AudioDriver->SetChannelVolume(rowname, atoi(tm->QueryField(i, ivol)));
872 if (irev != -1) {
873 AudioDriver->SetChannelReverb(rowname, atof(tm->QueryField(i, irev)));
874 }
875 }
876 return true;
877 }
878
879 //Not a constant anymore, we let the caller set the entry to zero
GetMusicPlaylist(int SongType) const880 char *Interface::GetMusicPlaylist(int SongType) const {
881 if (SongType < 0 || (unsigned int)SongType >= musiclist.size())
882 return NULL;
883
884 return musiclist[SongType];
885 }
886
887 /** this is the main loop */
Main()888 void Interface::Main()
889 {
890 ieDword speed = 10;
891
892 vars->Lookup("Mouse Scroll Speed", speed);
893 SetMouseScrollSpeed((int) speed);
894
895 Font* fps = GetTextFont();
896 // TODO: if we ever want to support dynamic resolution changes this will break
897 Region fpsRgn( 0, Height - 30, 80, 30 );
898 wchar_t fpsstring[20] = {L"???.??? fps"};
899 // set for printing
900 fpsRgn.x = 5;
901 fpsRgn.y = 0;
902
903 unsigned long frame = 0, time, timebase;
904 time = GetTicks();
905 timebase = time;
906 double frames = 0.0;
907
908 do {
909 std::deque<Timer>::iterator it;
910 for (it = timers.begin(); it != timers.end();) {
911 if (it->IsRunning()) {
912 it->Update(time);
913 ++it;
914 } else {
915 it = timers.erase(it);
916 }
917 }
918
919 //don't change script when quitting is pending
920 while (QuitFlag && QuitFlag != QF_KILL) {
921 HandleFlags();
922 }
923 //eventflags are processed only when there is a game
924 if (EventFlag && gamectrl) {
925 HandleEvents();
926 }
927 HandleGUIBehaviour();
928
929 GameLoop();
930 // TODO: find other animations that need to be synchronized
931 // we can create a manager for them and everything can be updated at once
932 GlobalColorCycle.AdvanceTime(time);
933 winmgr->DrawWindows();
934 time = GetTicks();
935 if (DrawFPS) {
936 frame++;
937 if (time - timebase > 1000) {
938 frames = ( frame * 1000.0 / ( time - timebase ) );
939 timebase = time;
940 frame = 0;
941 swprintf(fpsstring, sizeof(fpsstring)/sizeof(fpsstring[0]), L"%.3f fps", frames);
942 }
943 auto lock = winmgr->DrawHUD();
944 video->DrawRect( fpsRgn, ColorBlack );
945 fps->Print(fpsRgn, String(fpsstring), IE_FONT_ALIGN_MIDDLE | IE_FONT_SINGLE_LINE, {ColorWhite, ColorBlack});
946 }
947 } while (video->SwapBuffers() == GEM_OK && !(QuitFlag&QF_KILL));
948 QuitGame(0);
949 }
950
ReadResRefTable(const ieResRef tablename,ieResRef * & data)951 int Interface::ReadResRefTable(const ieResRef tablename, ieResRef *&data)
952 {
953 int count = 0;
954
955 if (data) {
956 free(data);
957 data = NULL;
958 }
959 AutoTable tm(tablename);
960 if (!tm) {
961 Log(ERROR, "Core", "Cannot find %s.2da.", tablename);
962 return 0;
963 }
964 count = tm->GetRowCount();
965 data = (ieResRef *) calloc( count, sizeof(ieResRef) );
966 for (int i = 0; i < count; i++) {
967 strnlwrcpy( data[i], tm->QueryField( i, 0 ), 8 );
968 // * marks an empty resource
969 if (data[i][0]=='*') {
970 data[i][0]=0;
971 }
972 }
973 return count;
974 }
975
LoadSprites()976 int Interface::LoadSprites()
977 {
978 if (!IsAvailable( IE_2DA_CLASS_ID )) {
979 Log(ERROR, "Core", "No 2DA Importer Available.");
980 return GEM_ERROR;
981 }
982
983 Log(MESSAGE, "Core", "Loading Cursors...");
984 AnimationFactory* anim;
985 anim = (AnimationFactory*) gamedata->GetFactoryResource(MainCursorsImage, IE_BAM_CLASS_ID);
986 int CursorCount = 0;
987 if (anim) {
988 CursorCount = anim->GetCycleCount();
989 Cursors.reserve(CursorCount);
990 for (int i = 0; i < CursorCount; i++) {
991 Cursors.push_back(anim->GetFrame(0, (ieByte) i));
992 }
993 } else {
994 // support non-BAM cursors
995 // load MainCursorsImage + XX until all images are exhausted
996 // same layout as in the originals, with odd indices having the pressed cursor image
997 char fileName[32];
998 while (CursorCount < 99) {
999 snprintf(fileName, sizeof(fileName), "%.29s%02d", MainCursorsImage.CString(), CursorCount);
1000 ResourceHolder<ImageMgr> im = GetResourceHolder<ImageMgr>(fileName, true);
1001 if (!im) break;
1002 Cursors.push_back(im->GetSprite2D());
1003 CursorCount++;
1004 }
1005 }
1006
1007 // this is the last existing cursor type
1008 if (CursorCount<IE_CURSOR_WAY) {
1009 Log(ERROR, "Core", "Failed to load enough cursors (%d < %d).",
1010 CursorCount, IE_CURSOR_WAY);
1011 return GEM_ERROR;
1012 }
1013 WindowManager::CursorMouseUp = Cursors[0];
1014 WindowManager::CursorMouseDown = Cursors[1];
1015
1016 // Load fog-of-war bitmaps
1017 anim = (AnimationFactory*) gamedata->GetFactoryResource("fogowar", IE_BAM_CLASS_ID);
1018 Log(MESSAGE, "Core", "Loading Fog-Of-War bitmaps...");
1019 if (!anim || anim->GetCycleSize( 0 ) != 8) {
1020 // unknown type of fog anim
1021 Log(ERROR, "Core", "Failed to load Fog-of-War bitmaps.");
1022 return GEM_ERROR;
1023 }
1024
1025 FogSprites[1] = anim->GetFrame( 0, 0 );
1026 FogSprites[2] = anim->GetFrame( 1, 0 );
1027 FogSprites[3] = anim->GetFrame( 2, 0 );
1028
1029 FogSprites[4] = video->MirrorSprite( FogSprites[1], BlitFlags::MIRRORY, false );
1030
1031 assert(FogSprites[4]->renderFlags&BlitFlags::MIRRORY);
1032
1033 FogSprites[6] = video->MirrorSprite( FogSprites[3], BlitFlags::MIRRORY, false );
1034
1035 FogSprites[8] = video->MirrorSprite( FogSprites[2], BlitFlags::MIRRORX, false );
1036 assert(FogSprites[8]->renderFlags&BlitFlags::MIRRORX);
1037 FogSprites[9] = video->MirrorSprite( FogSprites[3], BlitFlags::MIRRORX, false );
1038
1039 FogSprites[12] = video->MirrorSprite( FogSprites[6], BlitFlags::MIRRORX, false );
1040 assert(FogSprites[12]->renderFlags&BlitFlags::MIRRORX);
1041
1042 // Load ground circle bitmaps (PST only)
1043 Log(MESSAGE, "Core", "Loading Ground circle bitmaps...");
1044 for (int size = 0; size < MAX_CIRCLE_SIZE; size++) {
1045 if (GroundCircleBam[size][0]) {
1046 anim = (AnimationFactory*) gamedata->GetFactoryResource(GroundCircleBam[size], IE_BAM_CLASS_ID);
1047 if (!anim || anim->GetCycleCount() != 6) {
1048 // unknown type of circle anim
1049 Log(ERROR, "Core", "Failed Loading Ground circle bitmaps...");
1050 return GEM_ERROR;
1051 }
1052
1053 for (int i = 0; i < 6; i++) {
1054 Holder<Sprite2D> sprite = anim->GetFrame(0, (ieByte) i);
1055 if (GroundCircleScale[size]) {
1056 sprite = video->SpriteScaleDown( sprite, GroundCircleScale[size] );
1057 }
1058 GroundCircles[size][i] = sprite;
1059 }
1060 }
1061 }
1062
1063 return GEM_OK;
1064 }
1065
LoadFonts()1066 int Interface::LoadFonts()
1067 {
1068 Log(MESSAGE, "Core", "Loading Fonts...");
1069 AutoTable tab("fonts");
1070 if (!tab) {
1071 Log(ERROR, "Core", "Cannot find fonts.2da.");
1072 return GEM_ERROR;
1073 }
1074
1075 // FIXME: we used to try and share like fonts
1076 // this only ever had a benefit when using non-bam fonts
1077 // it could potentially save a a few megs of space (nice for handhelds)
1078 // but we should re-enable this by simply letting Font instances share their atlas data
1079
1080 int count = tab->GetRowCount();
1081 const char* rowName = NULL;
1082 for (int row = 0; row < count; row++) {
1083 rowName = tab->GetRowName(row);
1084
1085 ResRef resref = tab->QueryField(rowName, "RESREF");
1086 const char* font_name = tab->QueryField( rowName, "FONT_NAME" );
1087 ieWord font_size = atoi( tab->QueryField( rowName, "PX_SIZE" ) ); // not available in BAM fonts.
1088 FontStyle font_style = (FontStyle)atoi( tab->QueryField( rowName, "STYLE" ) ); // not available in BAM fonts.
1089 bool background = atoi(tab->QueryField(rowName, "BACKGRND"));
1090
1091 Font* fnt = NULL;
1092 ResourceHolder<FontManager> fntMgr = GetResourceHolder<FontManager>(font_name);
1093 if (fntMgr) fnt = fntMgr->GetFont(font_size, font_style, background);
1094
1095 if (!fnt) {
1096 error("Core", "Unable to load font resource: %s for ResRef %s (check fonts.2da)", font_name, resref.CString());
1097 } else {
1098 fonts[resref] = fnt;
1099 Log(MESSAGE, "Core", "Loaded Font: %s for ResRef %s", font_name, resref.CString());
1100 }
1101 }
1102
1103 Log(MESSAGE, "Core", "Fonts Loaded...");
1104 return GEM_OK;
1105 }
1106
Init(InterfaceConfig * config)1107 int Interface::Init(InterfaceConfig* config)
1108 {
1109 Log(MESSAGE, "Core", "GemRB core version v" VERSION_GEMRB " loading ...");
1110 if (!config) {
1111 Log(FATAL, "Core", "No Configuration context.");
1112 return GEM_ERROR;
1113 }
1114
1115 const char* value = NULL;
1116 #define CONFIG_INT(key, var) \
1117 value = config->GetValueForKey(key); \
1118 if (value) \
1119 var ( atoi( value ) ); \
1120 value = nullptr
1121
1122 CONFIG_INT("Bpp", Bpp =);
1123 CONFIG_INT("CaseSensitive", CaseSensitive =);
1124 CONFIG_INT("DoubleClickDelay", EventMgr::DCDelay = );
1125 CONFIG_INT("DrawFPS", DrawFPS = );
1126 CONFIG_INT("EnableCheatKeys", EnableCheatKeys);
1127 CONFIG_INT("EndianSwitch", DataStream::SetBigEndian);
1128 CONFIG_INT("GCDebug", GameControl::DebugFlags = );
1129 CONFIG_INT("Height", Height = );
1130 CONFIG_INT("KeepCache", KeepCache = );
1131 CONFIG_INT("MaxPartySize", MaxPartySize = );
1132 MaxPartySize = std::min(std::max(1, MaxPartySize), 10);
1133 vars->SetAt("MaxPartySize", MaxPartySize); // for simple GUIScript access
1134 CONFIG_INT("MouseFeedback", MouseFeedback = );
1135 CONFIG_INT("MultipleQuickSaves", MultipleQuickSaves = );
1136 CONFIG_INT("RepeatKeyDelay", Control::ActionRepeatDelay = );
1137 CONFIG_INT("SaveAsOriginal", SaveAsOriginal = );
1138 CONFIG_INT("DebugMode", debugMode = );
1139 int touchInput = -1;
1140 CONFIG_INT("TouchInput", touchInput =);
1141 CONFIG_INT("Width", Width = );
1142 CONFIG_INT("IgnoreOriginalINI", IgnoreOriginalINI = );
1143 CONFIG_INT("UseSoftKeyboard", UseSoftKeyboard = );
1144 CONFIG_INT("NumFingScroll", NumFingScroll = );
1145 CONFIG_INT("NumFingKboard", NumFingKboard = );
1146 CONFIG_INT("NumFingInfo", NumFingInfo = );
1147 CONFIG_INT("GamepadPointerSpeed", GamepadPointerSpeed = );
1148
1149 #undef CONFIG_INT
1150
1151 // first param is the preference name, second is the key from gemrb.cfg.
1152 #define CONFIG_VARS_MAP(var, key) \
1153 value = config->GetValueForKey(key); \
1154 if (value) \
1155 vars->SetAt(var, atoi(value)); \
1156 value = nullptr
1157
1158 #define CONFIG_VARS(key) \
1159 CONFIG_VARS_MAP(key, key)
1160
1161 // using vars because my hope is to expose (some of) these in a customized GUIOPT
1162 // map gemrb.cfg value to the value used by the options (usually an option from game.ini)
1163 CONFIG_VARS("GUIEnhancements");
1164 CONFIG_VARS("SkipIntroVideos");
1165
1166 //put into vars so that reading from game.ini wont overwrite
1167 CONFIG_VARS_MAP("BitsPerPixel", "Bpp");
1168 CONFIG_VARS_MAP("Full Screen", "Fullscreen");
1169
1170 #undef CONFIG_VARS
1171 #undef CONFIG_VARS_MAP
1172
1173 #define CONFIG_STRING(key, var, default) \
1174 value = config->GetValueForKey(key); \
1175 if (value && value[0]) { \
1176 strlcpy(var, value, sizeof(var)); \
1177 } else if (default && default[0]) { \
1178 strlcpy(var, default, sizeof(var)); \
1179 } else var[0] = '\0'; \
1180 value = nullptr
1181
1182 CONFIG_STRING("GameName", GameName, GEMRB_STRING); // NOTE: potentially overriden below, once auto GameType is resolved
1183 CONFIG_STRING("GameType", GameType, "auto");
1184 // tob type is obsolete
1185 if (stricmp( GameType, "tob" ) == 0) {
1186 strlcpy( GameType, "bg2", sizeof(GameType) );
1187 }
1188
1189 #undef CONFIG_STRING
1190
1191 // assumes that default value does not need to be resolved or fixed in any way
1192 #define CONFIG_PATH(key, var, default) \
1193 value = config->GetValueForKey(key); \
1194 if (value && value[0]) { \
1195 strlcpy(var, value, sizeof(var)); \
1196 ResolveFilePath(var); \
1197 FixPath(var, true); \
1198 } else if (default && default[0]) { \
1199 strlcpy(var, default, sizeof(var)); \
1200 } else var[0] = '\0'; \
1201 value = nullptr
1202
1203 // TODO: make CustomFontPath cross platform and possibly dynamic
1204 CONFIG_PATH("CustomFontPath", CustomFontPath, "/usr/share/fonts/TTF");
1205 CONFIG_PATH("GameCharactersPath", GameCharactersPath, "characters");
1206 CONFIG_PATH("GameDataPath", GameDataPath, "data");
1207 CONFIG_PATH("GameOverridePath", GameOverridePath, "override");
1208 CONFIG_PATH("GamePortraitsPath", GamePortraitsPath, "portraits");
1209 CONFIG_PATH("GameScriptsPath", GameScriptsPath, "scripts");
1210 CONFIG_PATH("GameSoundsPath", GameSoundsPath, "sounds");
1211
1212 // Path configureation
1213 CONFIG_PATH("GemRBPath", GemRBPath,
1214 CopyGemDataPath(GemRBPath, _MAX_PATH));
1215
1216 CONFIG_PATH("CachePath", CachePath, "./Cache2");
1217 FixPath( CachePath, false );
1218
1219 // AppImage doesn't support relative urls at all
1220 // we set the path to the data dir to cover unhardcoded and co,
1221 // while plugins are statically linked, so it doesn't matter for them
1222 #ifdef DATA_DIR
1223 const char* appDir = getenv("APPDIR");
1224 if (appDir) {
1225 assert(strnlen(appDir, _MAX_PATH/2) < _MAX_PATH/2);
1226 PathJoin(GemRBPath, appDir, DATA_DIR, nullptr);
1227 }
1228 #endif
1229
1230 CONFIG_PATH("GUIScriptsPath", GUIScriptsPath, GemRBPath);
1231 CONFIG_PATH("GamePath", GamePath, ".");
1232 // guess a few paths in case this one is bad; two levels deep for the fhs layout
1233 char testPath[_MAX_PATH];
1234 bool gameFound = true;
1235 if (!PathJoin(testPath, GamePath, "chitin.key", nullptr)) {
1236 Log(WARNING, "Interface", "Invalid GamePath detected, guessing from the current dir!");
1237 if (PathJoin(testPath, "..", "chitin.key", nullptr)) {
1238 strlcpy(GamePath, "..", sizeof(GamePath));
1239 } else {
1240 if (PathJoin(testPath, "..", "..", "chitin.key", nullptr)) {
1241 strlcpy(GamePath, "../..", sizeof(GamePath));
1242 } else {
1243 gameFound = false;
1244 }
1245 }
1246 }
1247 // if nothing was found still, check for the internal demo
1248 // it's generic, but not likely to work outside of AppImages and maybe apple bundles
1249 if (!gameFound) {
1250 Log(WARNING, "Interface", "Invalid GamePath detected, looking for the GemRB demo!");
1251 if (PathJoin(testPath, "..", "demo", "chitin.key", nullptr)) {
1252 PathJoin(GamePath, "..", "demo", nullptr);
1253 strlcpy(GameType, "demo", sizeof(GameType));
1254 } else {
1255 if (PathJoin(testPath, GemRBPath, "demo", "chitin.key", nullptr)) {
1256 PathJoin(GamePath, GemRBPath, "demo", nullptr);
1257 strlcpy(GameType, "demo", sizeof(GameType));
1258 }
1259 }
1260 }
1261
1262 CONFIG_PATH("GemRBOverridePath", GemRBOverridePath, GemRBPath);
1263 CONFIG_PATH("GemRBUnhardcodedPath", GemRBUnhardcodedPath, GemRBPath);
1264 #ifdef PLUGIN_DIR
1265 CONFIG_PATH("PluginsPath", PluginsPath, PLUGIN_DIR);
1266 #else
1267 CONFIG_PATH("PluginsPath", PluginsPath, "");
1268 if (!PluginsPath[0]) {
1269 PathJoin(PluginsPath, GemRBPath, "plugins", nullptr);
1270 }
1271 #endif
1272
1273 CONFIG_PATH("SavePath", SavePath, GamePath);
1274 #undef CONFIG_PATH
1275
1276 #define CONFIG_STRING(key, var) \
1277 value = config->GetValueForKey(key); \
1278 if (value) \
1279 var = value; \
1280 value = nullptr
1281
1282 CONFIG_STRING("AudioDriver", AudioDriverName);
1283 CONFIG_STRING("VideoDriver", VideoDriverName);
1284 CONFIG_STRING("Encoding", Encoding);
1285 #undef CONFIG_STRING
1286
1287 value = config->GetValueForKey("ModPath");
1288 if (value) {
1289 for (char *path = strtok((char*)value,SPathListSeparator);
1290 path;
1291 path = strtok(NULL,SPathListSeparator)) {
1292 ModPath.push_back(path);
1293 ResolveFilePath(ModPath.back());
1294 }
1295 }
1296 value = config->GetValueForKey("SkipPlugin");
1297 if (value) {
1298 plugin_flags->SetAt( value, PLF_SKIP );
1299 }
1300 value = config->GetValueForKey("DelayPlugin");
1301 if (value) {
1302 plugin_flags->SetAt( value, PLF_DELAY );
1303 }
1304
1305 int i = 0;
1306 for(i = 0; i < MAX_CD; i++) {
1307 char keyname[] = { 'C', 'D', char('1'+i), '\0' };
1308 value = config->GetValueForKey(keyname);
1309 if (value) {
1310 for(char *path = strtok((char*)value, SPathListSeparator);
1311 path;
1312 path = strtok(NULL,SPathListSeparator)) {
1313 CD[i].push_back(path);
1314 ResolveFilePath(CD[i].back());
1315 }
1316 } else {
1317 // nothing in config so create our own
1318 char name[_MAX_PATH];
1319
1320 PathJoin(name, GamePath, keyname, nullptr);
1321 CD[i].push_back(name);
1322 PathJoin(name, GamePath, GameDataPath, keyname, nullptr);
1323 CD[i].push_back(name);
1324 }
1325 }
1326
1327 if (!MakeDirectories(CachePath)) {
1328 error("Core", "Unable to create cache directory '%s'", CachePath);
1329 }
1330
1331 if ( StupidityDetector( CachePath )) {
1332 Log(ERROR, "Core", "Cache path %s doesn't exist, not a folder or contains alien files!", CachePath );
1333 return GEM_ERROR;
1334 }
1335 if (!KeepCache) DelTree((const char *) CachePath, false);
1336
1337 // potentially disable logging before plugins are loaded (the log file is a plugin)
1338 value = config->GetValueForKey("Logging");
1339 if (value) ToggleLogging(atoi(value));
1340
1341 Log(MESSAGE, "Core", "Starting Plugin Manager...");
1342 PluginMgr *plugin = PluginMgr::Get();
1343 #if TARGET_OS_MAC
1344 // search the bundle plugins first
1345 // since bundle plugins are loaded first dyld will give them precedence
1346 // if duplicates are found in the PluginsPath
1347 char bundlePluginsPath[_MAX_PATH];
1348 CopyBundlePath(bundlePluginsPath, sizeof(bundlePluginsPath), PLUGINS);
1349 ResolveFilePath(bundlePluginsPath);
1350 #ifndef STATIC_LINK
1351 LoadPlugins(bundlePluginsPath);
1352 #endif
1353 #endif
1354 #ifndef STATIC_LINK
1355 LoadPlugins(PluginsPath);
1356 #endif
1357 if (plugin && plugin->GetPluginCount()) {
1358 Log(MESSAGE, "Core", "Plugin Loading Complete...");
1359 } else {
1360 Log(FATAL, "Core", "Plugin Loading Failed, check path...");
1361 return GEM_ERROR;
1362 }
1363 plugin->RunInitializers();
1364
1365 Log(MESSAGE, "Core", "GemRB Core Initialization...");
1366 Log(MESSAGE, "Core", "Initializing Video Driver...");
1367 video = ( Video * ) PluginMgr::Get()->GetDriver(&Video::ID, VideoDriverName.c_str());
1368 if (!video) {
1369 Log(FATAL, "Core", "No Video Driver Available.");
1370 return GEM_ERROR;
1371 }
1372 if (video->Init() == GEM_ERROR) {
1373 Log(FATAL, "Core", "Cannot Initialize Video Driver.");
1374 return GEM_ERROR;
1375 }
1376
1377 // ask the driver if a touch device is in use
1378 EventMgr::TouchInputEnabled = touchInput < 0 ? video->TouchInputEnabled() : touchInput;
1379
1380 ieDword brightness = 10;
1381 ieDword contrast = 5;
1382 vars->Lookup("Brightness Correction", brightness);
1383 vars->Lookup("Gamma Correction", contrast);
1384
1385 SetInfoTextColor(ColorWhite);
1386
1387 {
1388 Log(MESSAGE, "Core", "Initializing Search Path...");
1389 if (!IsAvailable( PLUGIN_RESOURCE_DIRECTORY )) {
1390 Log(FATAL, "Core", "no DirectoryImporter!");
1391 return GEM_ERROR;
1392 }
1393
1394 char path[_MAX_PATH];
1395
1396 PathJoin(path, CachePath, nullptr);
1397 if (!gamedata->AddSource(path, "Cache", PLUGIN_RESOURCE_DIRECTORY)) {
1398 Log(FATAL, "Core", "The cache path couldn't be registered, please check!");
1399 return GEM_ERROR;
1400 }
1401
1402 size_t i;
1403 for (i = 0; i < ModPath.size(); ++i)
1404 gamedata->AddSource(ModPath[i].c_str(), "Mod paths", PLUGIN_RESOURCE_CACHEDDIRECTORY);
1405
1406 PathJoin(path, GemRBOverridePath, "override", GameType, nullptr);
1407 if (!strcmp( GameType, "auto" ))
1408 gamedata->AddSource(path, "GemRB Override", PLUGIN_RESOURCE_NULL);
1409 else
1410 gamedata->AddSource(path, "GemRB Override", PLUGIN_RESOURCE_CACHEDDIRECTORY);
1411
1412 PathJoin(path, GemRBOverridePath, "override", "shared", nullptr);
1413 gamedata->AddSource(path, "shared GemRB Override", PLUGIN_RESOURCE_CACHEDDIRECTORY);
1414
1415 PathJoin(path, GamePath, GameOverridePath, nullptr);
1416 gamedata->AddSource(path, "Override", PLUGIN_RESOURCE_CACHEDDIRECTORY);
1417
1418 //GAME sounds are intentionally not cached, in IWD there are directory structures,
1419 //that are not cacheable, also it is totally pointless (this fixed charsounds in IWD)
1420 PathJoin(path, GamePath, GameSoundsPath, nullptr);
1421 gamedata->AddSource(path, "Sounds", PLUGIN_RESOURCE_DIRECTORY);
1422
1423 PathJoin(path, GamePath, GameScriptsPath, nullptr);
1424 gamedata->AddSource(path, "Scripts", PLUGIN_RESOURCE_CACHEDDIRECTORY);
1425
1426 PathJoin(path, GamePath, GamePortraitsPath, nullptr);
1427 gamedata->AddSource(path, "Portraits", PLUGIN_RESOURCE_CACHEDDIRECTORY);
1428
1429 PathJoin(path, GamePath, GameDataPath, nullptr);
1430 gamedata->AddSource(path, "Data", PLUGIN_RESOURCE_CACHEDDIRECTORY);
1431
1432 // accomodating silly installers that create a data/Data/.* structure
1433 PathJoin(path, GamePath, GameDataPath, "Data", nullptr);
1434 gamedata->AddSource(path, "Data", PLUGIN_RESOURCE_CACHEDDIRECTORY);
1435
1436 //IWD2 movies are on the CD but not in the BIF
1437 char *description = strdup("CD1/data");
1438 for (i = 0; i < MAX_CD; i++) {
1439 for (size_t j=0;j<CD[i].size();j++) {
1440 description[2]='1'+i;
1441 PathJoin(path, CD[i][j].c_str(), GameDataPath, nullptr);
1442 gamedata->AddSource(path, description, PLUGIN_RESOURCE_CACHEDDIRECTORY);
1443 }
1444 }
1445 free(description);
1446
1447 // most of the old gemrb override files can be found here,
1448 // so they have a lower priority than the game files and can more easily be modded
1449 PathJoin(path, GemRBUnhardcodedPath, "unhardcoded", GameType, nullptr);
1450 if (!strcmp(GameType, "auto")) {
1451 gamedata->AddSource(path, "GemRB Unhardcoded data", PLUGIN_RESOURCE_NULL);
1452 } else {
1453 gamedata->AddSource(path, "GemRB Unhardcoded data", PLUGIN_RESOURCE_CACHEDDIRECTORY);
1454 }
1455 PathJoin(path, GemRBUnhardcodedPath, "unhardcoded", "shared", nullptr);
1456 gamedata->AddSource(path, "shared GemRB Unhardcoded data", PLUGIN_RESOURCE_CACHEDDIRECTORY);
1457 }
1458
1459 {
1460 Log(MESSAGE, "Core", "Initializing KEY Importer...");
1461 char ChitinPath[_MAX_PATH];
1462 PathJoin(ChitinPath, GamePath, "chitin.key", nullptr);
1463 if (!gamedata->AddSource(ChitinPath, "chitin.key", PLUGIN_RESOURCE_KEY)) {
1464 Log(FATAL, "Core", "Failed to load \"chitin.key\"");
1465 Log(ERROR, "Core", "This means:\n- you set the GamePath config variable incorrectly,\n\
1466 - you passed a bad game path to GemRB on the command line,\n\
1467 - you are not running GemRB from within a game dir,\n\
1468 - or the game is running (Windows only).");
1469 Log(ERROR, "Core", "The path must point to a game directory with a readable chitin.key file.");
1470 return GEM_ERROR;
1471 }
1472 }
1473
1474 // fix the sample config default resolution for iwd2 and configless case also for the demo
1475 // FIXME: move defaults to gemrb.ini? Needs video init to be moved after GUIScript init
1476 if ((PathJoin(testPath, GamePath, "icewind2.ini", nullptr) || PathJoin(testPath, GamePath, "mapwinbg.png", nullptr)) && Width == 640 && Height == 480) {
1477 Width = 800;
1478 Height = 600;
1479 }
1480
1481 // SDL2 driver requires the display to be created prior to sprite creation (opengl context)
1482 // we also need the display to exist to create sprites using the display format
1483 ieDword fullscreen = 0;
1484 vars->Lookup("Full Screen", fullscreen);
1485 if (video->CreateDisplay(Size(Width, Height), Bpp, fullscreen, GameName) == GEM_ERROR) {
1486 Log(FATAL, "Core", "Cannot initialize shaders.");
1487 return GEM_ERROR;
1488 }
1489 video->SetGamma(brightness, contrast);
1490
1491 Log(MESSAGE, "Core", "Initializing GUI Script Engine...");
1492 SetNextScript("Start"); // Start is the first script executed
1493 guiscript = PluginHolder<ScriptEngine>(IE_GUI_SCRIPT_CLASS_ID);
1494 if (guiscript == nullptr) {
1495 Log(FATAL, "Core", "Missing GUI Script Engine.");
1496 return GEM_ERROR;
1497 }
1498 if (!guiscript->Init()) {
1499 Log(FATAL, "Core", "Failed to initialize GUI Script.");
1500 return GEM_ERROR;
1501 }
1502
1503 // re-set the gemrb override path, since we now have the correct GameType if 'auto' was used
1504 char path[_MAX_PATH];
1505 PathJoin(path, GemRBOverridePath, "override", GameType, nullptr);
1506 gamedata->AddSource(path, "GemRB Override", PLUGIN_RESOURCE_CACHEDDIRECTORY, RM_REPLACE_SAME_SOURCE);
1507 char unhardcodedTypePath[_MAX_PATH * 2];
1508 PathJoin(unhardcodedTypePath, GemRBUnhardcodedPath, "unhardcoded", GameType, nullptr);
1509 gamedata->AddSource(unhardcodedTypePath, "GemRB Unhardcoded data", PLUGIN_RESOURCE_CACHEDDIRECTORY, RM_REPLACE_SAME_SOURCE);
1510
1511 // Purposely add the font directory last since we will only ever need it at engine load time.
1512 if (CustomFontPath[0]) gamedata->AddSource(CustomFontPath, "CustomFonts", PLUGIN_RESOURCE_DIRECTORY);
1513
1514 Log(MESSAGE, "Core", "Reading Game Options...");
1515 if (!LoadGemRBINI()) {
1516 Log(FATAL, "Core", "Cannot Load INI.");
1517 return GEM_ERROR;
1518 }
1519
1520 // if unset, manually populate GameName (window title)
1521 if (!stricmp(GameName, GEMRB_STRING)) {
1522 if (stricmp(DefaultWindowTitle.c_str(), GEMRB_STRING)) {
1523 std::string title = GEMRB_STRING" running " + DefaultWindowTitle;
1524 strlcpy(GameName, title.c_str(), sizeof(GameName));
1525 } else {
1526 strlcpy(GameName, GEMRB_STRING" running unknown game", sizeof(GameName));
1527 }
1528 video->SetWindowTitle(GameName);
1529 }
1530
1531 // load the game ini (baldur.ini, torment.ini, icewind.ini ...)
1532 // read from our version of the config if it is present
1533 char ini_path[_MAX_PATH+4] = { '\0' };
1534 char gemrbINI[_MAX_PATH+4] = { '\0' };
1535 char tmp[_MAX_PATH+4] = { '\0' };
1536 snprintf(gemrbINI, sizeof(gemrbINI), "gem-%s", INIConfig);
1537 PathJoin(ini_path, SavePath, gemrbINI, nullptr);
1538 if (!file_exists(ini_path)) {
1539 PathJoin(ini_path, GamePath, gemrbINI, nullptr);
1540 }
1541 if (file_exists(ini_path)) {
1542 strlcpy(tmp, INIConfig, sizeof(tmp));
1543 strlcpy(INIConfig, gemrbINI, sizeof(INIConfig));
1544 } else if (!IgnoreOriginalINI) {
1545 PathJoin(ini_path, GamePath, INIConfig, nullptr);
1546 Log(MESSAGE,"Core", "Loading original game options from %s", ini_path);
1547 }
1548 if (!InitializeVarsWithINI(ini_path)) {
1549 Log(WARNING, "Core", "Unable to set dictionary default values!");
1550 }
1551
1552 // set up the tooltip delay which we store in miliseconds
1553 ieDword tooltipDelay = 0;
1554 GetDictionary()->Lookup("Tooltips", tooltipDelay);
1555 WindowManager::SetTooltipDelay(tooltipDelay * Tooltip::DELAY_FACTOR / 10);
1556
1557 // restore the game config name if we read it from our version
1558 if (tmp[0]) {
1559 strlcpy(INIConfig, tmp, sizeof(INIConfig));
1560 }
1561
1562 for (i = 0; i < 8; i++) {
1563 if (INIConfig[i] == '.')
1564 break;
1565 GameNameResRef[i] = INIConfig[i];
1566 }
1567 GameNameResRef[i] = 0;
1568
1569 Log(MESSAGE, "Core", "Reading Encoding Table...");
1570 if (!LoadEncoding()) {
1571 Log(ERROR, "Core", "Cannot Load Encoding.");
1572 }
1573
1574 Log(MESSAGE, "Core", "Creating Projectile Server...");
1575 projserv = new ProjectileServer();
1576 if (!projserv->GetHighestProjectileNumber()) {
1577 Log(ERROR, "Core", "No projectiles are available...");
1578 }
1579
1580 Log(MESSAGE, "Core", "Checking for Dialogue Manager...");
1581 if (!IsAvailable( IE_TLK_CLASS_ID )) {
1582 Log(FATAL, "Core", "No TLK Importer Available.");
1583 return GEM_ERROR;
1584 }
1585 strings = PluginHolder<StringMgr>(IE_TLK_CLASS_ID);
1586 Log(MESSAGE, "Core", "Loading Dialog.tlk file...");
1587 char strpath[_MAX_PATH];
1588 PathJoin(strpath, GamePath, "dialog.tlk", nullptr);
1589 FileStream* fs = FileStream::OpenFile(strpath);
1590 if (!fs) {
1591 Log(FATAL, "Core", "Cannot find Dialog.tlk.");
1592 return GEM_ERROR;
1593 }
1594 strings->Open(fs);
1595
1596 // does the language use an extra tlk?
1597 if (strings->HasAltTLK()) {
1598 strings2 = PluginHolder<StringMgr>(IE_TLK_CLASS_ID);
1599 Log(MESSAGE, "Core", "Loading DialogF.tlk file...");
1600 char strpath[_MAX_PATH];
1601 PathJoin(strpath, GamePath, "dialogf.tlk", nullptr);
1602 FileStream* fs = FileStream::OpenFile(strpath);
1603 if (!fs) {
1604 Log(ERROR, "Core", "Cannot find DialogF.tlk. Let us know which translation you are using.");
1605 Log(ERROR, "Core", "Falling back to main TLK file, so female text may be wrong!");
1606 strings2 = strings;
1607 } else {
1608 strings2->Open(fs);
1609 }
1610 }
1611
1612 {
1613 Log(MESSAGE, "Core", "Loading Palettes...");
1614 LoadPalette<16>(Palette16, palettes16);
1615 LoadPalette<32>(Palette32, palettes32);
1616 LoadPalette<256>(Palette256, palettes256);
1617 Log(MESSAGE, "Core", "Palettes Loaded");
1618 }
1619
1620 if (!IsAvailable( IE_BAM_CLASS_ID )) {
1621 Log(FATAL, "Core", "No BAM Importer Available.");
1622 return GEM_ERROR;
1623 }
1624
1625 Log(MESSAGE, "Core", "Initializing stock sounds...");
1626 DSCount = ReadResRefTable ("defsound", DefSound);
1627 if (DSCount == 0) {
1628 Log(FATAL, "Core", "Cannot find defsound.2da.");
1629 return GEM_ERROR;
1630 }
1631
1632 int ret = LoadSprites();
1633 if (ret) return ret;
1634
1635 ret = LoadFonts();
1636 if (ret) return ret;
1637
1638 Log(MESSAGE, "Core", "Initializing Window Manager...");
1639 winmgr = new WindowManager(video.get());
1640 RegisterScriptableWindow(winmgr->GetGameWindow(), "GAMEWIN", 0);
1641 winmgr->SetCursorFeedback(WindowManager::CursorFeedback(MouseFeedback));
1642
1643 guifact = PluginHolder<GUIFactory>(IE_CHU_CLASS_ID);
1644 if (!guifact) {
1645 Log(FATAL, "Core", "Failed to load Window Manager.");
1646 return GEM_ERROR;
1647 }
1648 guifact->SetWindowManager(*winmgr);
1649
1650 QuitFlag = QF_CHANGESCRIPT;
1651
1652 Log(MESSAGE, "Core", "Starting up the Sound Driver...");
1653 AudioDriver = ( Audio * ) PluginMgr::Get()->GetDriver(&Audio::ID, AudioDriverName.c_str());
1654 if (AudioDriver == nullptr) {
1655 Log(FATAL, "Core", "Failed to load sound driver.");
1656 return GEM_ERROR;
1657 }
1658 if (!AudioDriver->Init()) {
1659 Log(FATAL, "Core", "Failed to initialize sound driver.");
1660 return GEM_ERROR;
1661 }
1662
1663 Log(MESSAGE, "Core", "Allocating SaveGameIterator...");
1664 sgiterator = new SaveGameIterator();
1665 if (sgiterator == NULL) {
1666 Log(FATAL, "Core", "Failed to allocate SaveGameIterator.");
1667 return GEM_ERROR;
1668 }
1669
1670 Log(MESSAGE, "Core", "Initializing Music Manager...");
1671 music = PluginHolder<MusicMgr>(IE_MUS_CLASS_ID);
1672 if (!music) {
1673 Log(FATAL, "Core", "Failed to load Music Manager.");
1674 return GEM_ERROR;
1675 }
1676
1677 Log(MESSAGE, "Core", "Loading music list...");
1678 if (HasFeature( GF_HAS_SONGLIST )) {
1679 ret = ReadMusicTable("songlist", 1);
1680 } else {
1681 /*since bg1 and pst has no .2da for songlist,
1682 we must supply one in the gemrb/override folder.
1683 It should be: music.2da, first column is a .mus filename*/
1684 ret = ReadMusicTable("music", 0);
1685 }
1686 if (!ret) {
1687 Log(WARNING, "Core", "Didn't find music list.");
1688 }
1689
1690 int resdata = HasFeature( GF_RESDATA_INI );
1691 if (resdata || HasFeature(GF_SOUNDS_INI) ) {
1692 Log(MESSAGE, "Core", "Loading resource data File...");
1693 INIresdata = PluginHolder<DataFileMgr>(IE_INI_CLASS_ID);
1694 DataStream* ds = gamedata->GetResource(resdata? "resdata":"sounds", IE_INI_CLASS_ID);
1695 if (!INIresdata->Open(ds)) {
1696 Log(WARNING, "Core", "Failed to load resource data.");
1697 }
1698 }
1699
1700 Log(MESSAGE, "Core", "Setting up SFX channels...");
1701 ret = ReadSoundChannelsTable();
1702 if (!ret) {
1703 Log(WARNING, "Core", "Failed to read channel table.");
1704 }
1705
1706 if (HasFeature( GF_HAS_PARTY_INI )) {
1707 Log(MESSAGE, "Core", "Loading precreated teams setup...");
1708 INIparty = PluginHolder<DataFileMgr>(IE_INI_CLASS_ID);
1709 char tINIparty[_MAX_PATH];
1710 PathJoin(tINIparty, GamePath, "Party.ini", nullptr);
1711 FileStream* fs = FileStream::OpenFile( tINIparty );
1712 if (!INIparty->Open(fs)) {
1713 Log(WARNING, "Core", "Failed to load precreated teams.");
1714 }
1715 }
1716
1717 if (HasFeature(GF_IWD2_DEATHVARFORMAT)) {
1718 memcpy(DeathVarFormat, IWD2DeathVarFormat, sizeof(ieVariable));
1719 }
1720
1721 if (HasFeature( GF_HAS_BEASTS_INI )) {
1722 Log(MESSAGE, "Core", "Loading beasts definition File...");
1723 INIbeasts = PluginHolder<DataFileMgr>(IE_INI_CLASS_ID);
1724 char tINIbeasts[_MAX_PATH];
1725 PathJoin(tINIbeasts, GamePath, "beast.ini", nullptr);
1726 FileStream* fs = FileStream::OpenFile( tINIbeasts );
1727 if (!INIbeasts->Open(fs)) {
1728 Log(WARNING, "Core", "Failed to load beast definitions.");
1729 }
1730
1731 Log(MESSAGE, "Core", "Loading quests definition File...");
1732 INIquests = PluginHolder<DataFileMgr>(IE_INI_CLASS_ID);
1733 char tINIquests[_MAX_PATH];
1734 PathJoin(tINIquests, GamePath, "quests.ini", nullptr);
1735 FileStream* fs2 = FileStream::OpenFile( tINIquests );
1736 if (!INIquests->Open(fs2)) {
1737 Log(WARNING, "Core", "Failed to load quest definitions.");
1738 }
1739 }
1740 game = NULL;
1741 calendar = NULL;
1742 keymap = NULL;
1743
1744 Log(MESSAGE, "Core", "Initializing effects...");
1745 ret = Init_EffectQueue();
1746 if (!ret) {
1747 Log(FATAL, "Core", "Failed to initialize effects.");
1748 return GEM_ERROR;
1749 }
1750
1751 Log(MESSAGE, "Core", "Initializing Inventory Management...");
1752 ret = InitItemTypes();
1753 if (!ret) {
1754 Log(FATAL, "Core", "Failed to initialize inventory.");
1755 return GEM_ERROR;
1756 }
1757
1758 Log(MESSAGE, "Core", "Initializing string constants...");
1759 displaymsg = new DisplayMessage();
1760 if (!displaymsg) {
1761 Log(FATAL, "Core", "Failed to initialize string constants.");
1762 return GEM_ERROR;
1763 }
1764
1765 Log(MESSAGE, "Core", "Initializing random treasure...");
1766 ret = ReadRandomItems();
1767 if (!ret) {
1768 Log(WARNING, "Core", "Failed to initialize random treasure.");
1769 }
1770
1771 Log(MESSAGE, "Core", "Initializing ability tables...");
1772 ret = ReadAbilityTables();
1773 if (!ret) {
1774 Log(FATAL, "Core", "Failed to initialize ability tables...");
1775 return GEM_ERROR;
1776 }
1777
1778 Log(MESSAGE, "Core", "Reading reputation mod table...");
1779 ret = ReadReputationModTable();
1780 if (!ret) {
1781 Log(WARNING, "Core", "Failed to read reputation mod table.");
1782 }
1783
1784 if ( gamedata->Exists("WMAPLAY", IE_2DA_CLASS_ID) ) {
1785 Log(MESSAGE, "Core", "Initializing area aliases...");
1786 ret = ReadAreaAliasTable( "WMAPLAY" );
1787 if (!ret) {
1788 Log(WARNING, "Core", "Failed to load area aliases...");
1789 }
1790 }
1791
1792 Log(MESSAGE, "Core", "Reading game time table...");
1793 ret = ReadGameTimeTable();
1794 if (!ret) {
1795 Log(FATAL, "Core", "Failed to read game time table...");
1796 return GEM_ERROR;
1797 }
1798
1799 Log(MESSAGE, "Core", "Reading special spells table...");
1800 ret = ReadSpecialSpells();
1801 if (!ret) {
1802 Log(WARNING, "Core", "Failed to load special spells.");
1803 }
1804
1805 ret = ReadDamageTypeTable();
1806 Log(MESSAGE, "Core", "Reading damage type table...");
1807 if (!ret) {
1808 Log(WARNING, "Core", "Reading damage type table...");
1809 }
1810
1811 Log(MESSAGE, "Core", "Reading game script tables...");
1812 InitializeIEScript();
1813
1814 Log(MESSAGE, "Core", "Initializing keymap tables...");
1815 keymap = new KeyMap();
1816 ret = keymap->InitializeKeyMap("keymap.ini", "keymap");
1817 if (!ret) {
1818 Log(WARNING, "Core", "Failed to initialize keymaps.");
1819 }
1820
1821 Log(MESSAGE, "Core", "Core Initialization Complete!");
1822
1823 // dump the potentially changed unhardcoded path to a file that weidu looks at automatically to get our search paths
1824 char pathString[_MAX_PATH * 3];
1825 #ifdef HAVE_REALPATH
1826 if (unhardcodedTypePath[0] == '.') {
1827 // canonicalize the relative path; usually from running from the build dir
1828 char *absolutePath = realpath(unhardcodedTypePath, NULL);
1829 if (absolutePath) {
1830 strlcpy(unhardcodedTypePath, absolutePath, sizeof(unhardcodedTypePath));
1831 free(absolutePath);
1832 }
1833 }
1834 #endif
1835 snprintf(pathString, sizeof(pathString), "GemRB_Data_Path = %s", unhardcodedTypePath);
1836 PathJoin(strpath, GamePath, "gemrb_path.txt", nullptr);
1837 FileStream *pathFile = new FileStream();
1838 // don't abort if something goes wrong, since it should never happen and it's not critical
1839 if (pathFile->Create(strpath)) {
1840 pathFile->Write(pathString, strlen(pathString));
1841 pathFile->Close();
1842 }
1843 delete pathFile;
1844
1845 EventMgr::EventCallback ToggleConsole = [this](const Event& e) {
1846 if (e.type != Event::KeyDown) return false;
1847
1848 guiscript->RunFunction("Console", "ToggleConsole");
1849 return true;
1850 };
1851 EventMgr::RegisterHotKeyCallback(ToggleConsole, ' ', GEM_MOD_CTRL);
1852
1853 return GEM_OK;
1854 }
1855
IsAvailable(SClass_ID filetype) const1856 bool Interface::IsAvailable(SClass_ID filetype) const
1857 {
1858 return PluginMgr::Get()->IsAvailable( filetype );
1859 }
1860
GetWorldMap(const char * map)1861 WorldMap *Interface::GetWorldMap(const char *map)
1862 {
1863 int index = worldmap->FindAndSetCurrentMap(map?map:game->CurrentArea);
1864 return worldmap->GetWorldMap(index);
1865 }
1866
GetProjectileServer() const1867 ProjectileServer* Interface::GetProjectileServer() const
1868 {
1869 return projserv;
1870 }
1871
GetVideoDriver() const1872 Video* Interface::GetVideoDriver() const
1873 {
1874 return video.get();
1875 }
1876
GetAudioDrv(void) const1877 Audio* Interface::GetAudioDrv(void) const {
1878 return AudioDriver.get();
1879 }
1880
TypeExt(SClass_ID type) const1881 const char* Interface::TypeExt(SClass_ID type) const
1882 {
1883 switch (type) {
1884 case IE_2DA_CLASS_ID:
1885 return "2da";
1886
1887 case IE_ACM_CLASS_ID:
1888 return "acm";
1889
1890 case IE_ARE_CLASS_ID:
1891 return "are";
1892
1893 case IE_BAM_CLASS_ID:
1894 return "bam";
1895
1896 case IE_BCS_CLASS_ID:
1897 return "bcs";
1898
1899 case IE_BS_CLASS_ID:
1900 return "bs";
1901
1902 case IE_BIF_CLASS_ID:
1903 return "bif";
1904
1905 case IE_BIO_CLASS_ID:
1906 if (HasFeature(GF_BIOGRAPHY_RES)) {
1907 return "res";
1908 }
1909 return "bio";
1910
1911 case IE_BMP_CLASS_ID:
1912 return "bmp";
1913
1914 case IE_PNG_CLASS_ID:
1915 return "png";
1916
1917 case IE_CHR_CLASS_ID:
1918 return "chr";
1919
1920 case IE_CHU_CLASS_ID:
1921 return "chu";
1922
1923 case IE_CRE_CLASS_ID:
1924 return "cre";
1925
1926 case IE_DLG_CLASS_ID:
1927 return "dlg";
1928
1929 case IE_EFF_CLASS_ID:
1930 return "eff";
1931
1932 case IE_GAM_CLASS_ID:
1933 return "gam";
1934
1935 case IE_IDS_CLASS_ID:
1936 return "ids";
1937
1938 case IE_INI_CLASS_ID:
1939 return "ini";
1940
1941 case IE_ITM_CLASS_ID:
1942 return "itm";
1943
1944 case IE_MOS_CLASS_ID:
1945 return "mos";
1946
1947 case IE_MUS_CLASS_ID:
1948 return "mus";
1949
1950 case IE_MVE_CLASS_ID:
1951 return "mve";
1952
1953 case IE_OGG_CLASS_ID:
1954 return "ogg";
1955
1956 case IE_PLT_CLASS_ID:
1957 return "plt";
1958
1959 case IE_PRO_CLASS_ID:
1960 return "pro";
1961
1962 case IE_SAV_CLASS_ID:
1963 return "sav";
1964
1965 case IE_SPL_CLASS_ID:
1966 return "spl";
1967
1968 case IE_SRC_CLASS_ID:
1969 return "src";
1970
1971 case IE_STO_CLASS_ID:
1972 return "sto";
1973
1974 case IE_TIS_CLASS_ID:
1975 return "tis";
1976
1977 case IE_TLK_CLASS_ID:
1978 return "tlk";
1979
1980 case IE_TOH_CLASS_ID:
1981 return "toh";
1982
1983 case IE_TOT_CLASS_ID:
1984 return "tot";
1985
1986 case IE_VAR_CLASS_ID:
1987 return "var";
1988
1989 case IE_VEF_CLASS_ID:
1990 return "vef";
1991
1992 case IE_VVC_CLASS_ID:
1993 return "vvc";
1994
1995 case IE_WAV_CLASS_ID:
1996 return "wav";
1997
1998 case IE_WED_CLASS_ID:
1999 return "wed";
2000
2001 case IE_WFX_CLASS_ID:
2002 return "wfx";
2003
2004 case IE_WMP_CLASS_ID:
2005 return "wmp";
2006
2007 default:
2008 Log(ERROR, "Interface", "No extension associated to class ID: %lu", type);
2009 }
2010 return NULL;
2011 }
2012
UpdateString(ieStrRef strref,const char * text) const2013 ieStrRef Interface::UpdateString(ieStrRef strref, const char *text) const
2014 {
2015 char *current = GetCString(strref, 0);
2016 bool changed = strcmp(text, current) != 0;
2017 free(current);
2018 if (changed) {
2019 return strings->UpdateString( strref, text );
2020 } else {
2021 return strref;
2022 }
2023 }
2024
GetCString(ieStrRef strref,ieDword options) const2025 char* Interface::GetCString(ieStrRef strref, ieDword options) const
2026 {
2027 ieDword flags = 0;
2028
2029 if (!(options & IE_STR_STRREFOFF)) {
2030 vars->Lookup( "Strref On", flags );
2031 }
2032 if (strings2 && (signed)strref != -1 && strref & IE_STR_ALTREF) {
2033 return strings2->GetCString(strref, flags | options);
2034 } else {
2035 return strings->GetCString(strref, flags | options);
2036 }
2037 }
2038
GetString(ieStrRef strref,ieDword options) const2039 String* Interface::GetString(ieStrRef strref, ieDword options) const
2040 {
2041 ieDword flags = 0;
2042
2043 if (!(options & IE_STR_STRREFOFF)) {
2044 vars->Lookup( "Strref On", flags );
2045 }
2046
2047 if (strings2 && (signed)strref != -1 && strref & IE_STR_ALTREF) {
2048 return strings2->GetString(strref, flags | options);
2049 } else {
2050 return strings->GetString(strref, flags | options);
2051 }
2052 }
2053
SetFeature(int flag,int position)2054 void Interface::SetFeature(int flag, int position)
2055 {
2056 if (flag) {
2057 GameFeatures[position>>5] |= 1<<(position&31);
2058 } else {
2059 GameFeatures[position>>5] &= ~(1<<(position&31) );
2060 }
2061 }
2062
HasFeature(int position) const2063 ieDword Interface::HasFeature(int position) const
2064 {
2065 return GameFeatures[position>>5] & (1<<(position&31));
2066 }
2067
2068 static const char *game_flags[GF_COUNT+1]={
2069 "HasKaputz", //0 GF_HAS_KAPUTZ
2070 "AllStringsTagged", //1 GF_ALL_STRINGS_TAGGED
2071 "HasSongList", //2 GF_HAS_SONGLIST
2072 "TeamMovement", //3 GF_TEAM_MOVEMENT
2073 "UpperButtonText", //4 GF_UPPER_BUTTON_TEXT
2074 "LowerLabelText", //5 GF_LOWER_LABEL_TEXT
2075 "HasPartyIni", //6 GF_HAS_PARTY_INI
2076 "SoundFolders", //7 GF_SOUNDFOLDERS
2077 "IgnoreButtonFrames", //8 GF_IGNORE_BUTTON_FRAMES
2078 "OneByteAnimationID", //9 GF_ONE_BYTE_ANIMID
2079 "HasDPLAYER", //10GF_HAS_DPLAYER
2080 "HasEXPTABLE", //11GF_HAS_EXPTABLE
2081 "HasBeastsIni", //12GF_HAS_BEASTS_INI
2082 "HasDescIcon", //13GF_HAS_DESC_ICON
2083 "HasPickSound", //14GF_HAS_PICK_SOUND
2084 "IWDMapDimensions", //15GF_IWD_MAP_DIMENSIONS
2085 "AutomapINI", //16GF_AUTOMAP_INI
2086 "SmallFog", //17GF_SMALL_FOG
2087 "ReverseDoor", //18GF_REVERSE_DOOR
2088 "ProtagonistTalks", //19GF_PROTAGONIST_TALKS
2089 "HasSpellList", //20GF_HAS_SPELLLIST
2090 "IWD2ScriptName", //21GF_IWD2_SCRIPTNAME
2091 "DialogueScrolls", //22GF_DIALOGUE_SCROLLS
2092 "KnowWorld", //23GF_KNOW_WORLD
2093 "ReverseToHit", //24GF_REVERSE_TOHIT
2094 "SaveForHalfDamage", //25GF_SAVE_FOR_HALF
2095 "CharNameIsGabber", //26GF_CHARNAMEISGABBER
2096 "MagicBit", //27GF_MAGICBIT
2097 "CheckAbilities", //28GF_CHECK_ABILITIES
2098 "ChallengeRating", //29GF_CHALLENGERATING
2099 "SpellBookIconHack", //30GF_SPELLBOOKICONHACK
2100 "EnhancedEffects", //31GF_ENHANCED_EFFECTS
2101 "DeathOnZeroStat", //32GF_DEATH_ON_ZERO_STAT
2102 "SpawnIni", //33GF_SPAWN_INI
2103 "IWD2DeathVarFormat", //34GF_IWD2_DEATHVARFORMAT
2104 "HasResDataIni", //35GF_RESDATA_INI
2105 "OverrideCursorPos", //36GF_OVERRIDE_CURSORPOS
2106 "BreakableWeapons", //37GF_BREAKABLE_WEAPONS
2107 "3EdRules", //38GF_3ED_RULES
2108 "LevelslotPerClass", //39GF_LEVELSLOT_PER_CLASS
2109 "SelectiveMagicRes", //40GF_SELECTIVE_MAGIC_RES
2110 "HasHideInShadows", //41GF_HAS_HIDE_IN_SHADOWS
2111 "AreaVisitedVar", //42GF_AREA_VISITED_VAR
2112 "ProperBackstab", //43GF_PROPER_BACKSTAB
2113 "OnScreenText", //44GF_ONSCREEN_TEXT
2114 "HasSpecificDamageBonus", //45GF_SPECIFIC_DMG_BONUS
2115 "StrrefSaveGame", //46GF_STRREF_SAVEGAME
2116 "SimplifiedDisruption",//47GF_SIMPLE_DISRUPTION
2117 "BiographyIsRes", //48GF_BIOGRAPHY_RES
2118 "NoBiography", //49GF_NO_BIOGRAPHY
2119 "StealIsAttack", //50GF_STEAL_IS_ATTACK
2120 "CutsceneAreascripts",//51GF_CUTSCENE_AREASCRIPTS
2121 "FlexibleWorldmap", //52GF_FLEXIBLE_WMAP
2122 "AutoSearchHidden", //53GF_AUTOSEARCH_HIDDEN
2123 "PSTStateFlags", //54GF_PST_STATE_FLAGS
2124 "NoDropCanMove", //55GF_NO_DROP_CAN_MOVE
2125 "JournalHasSections", //56GF_JOURNAL_HAS_SECTIONS
2126 "CastingSounds", //57GF_CASTING_SOUNDS
2127 "EnhancedCastingSounds", //58GF_CASTING_SOUNDS2
2128 "ForceAreaScript", //59GF_FORCE_AREA_SCRIPT
2129 "AreaOverride", //60GF_AREA_OVERRIDE
2130 "NoNewVariables", //61GF_NO_NEW_VARIABLES
2131 "HasSoundsIni", //62GF_SOUNDS_INI
2132 "HasNoNPCFlag", //63GF_USEPOINT_400
2133 "HasUsePointFlag", //64GF_USEPOINT_200
2134 "HasFloatMenu", //65GF_HAS_FLOAT_MENU
2135 "RareActionSounds", //66GF_RARE_ACTION_VB
2136 "NoUndroppable", //67GF_NO_UNDROPPABLE
2137 "StartActive", //68GF_START_ACTIVE
2138 "HasInfopointDialogs", //69GF_INFOPOINT_DIALOGS
2139 "ImplicitAreaAnimBackground", //70GF_IMPLICIT_AREAANIM_BACKGROUND
2140 "HealOn100Plus", //71GF_HEAL_ON_100PLUS
2141 "InPartyAllowsDead", //72GF_IN_PARTY_ALLOWS_DEAD
2142 "ZeroTimerIsValid", //73GF_ZERO_TIMER_IS_VALID
2143 "ShopsRechargeItems", //74GF_SHOP_RECHARGE
2144 "MeleeHeaderUsesProjectile", //75GF_MELEEHEADER_USESPROJECTILE
2145 "ForceDialogPause", //76GF_FORCE_DIALOGPAUSE
2146 "RandomBanterDialogs",//77GF_RANDOM_BANTER_DIALOGS
2147 "FixedMoraleOpcode", //79GF_FIXED_MORALE_OPCODE
2148 "Happiness", //80GF_HAPPINESS
2149 "EfficientORTrigger", //81GF_EFFICIENT_OR
2150 "LayeredWaterTiles", //82GF_LAYERED_WATER_TILES
2151 NULL //for our own safety, this marks the end of the pole
2152 };
2153
2154 /** Loads gemrb.ini */
LoadGemRBINI()2155 bool Interface::LoadGemRBINI()
2156 {
2157 DataStream* inifile = gamedata->GetResource( "gemrb", IE_INI_CLASS_ID );
2158 if (! inifile) {
2159 return false;
2160 }
2161
2162 Log(MESSAGE, "Core", "Loading game type-specific GemRB setup '%s'",
2163 inifile->originalfile);
2164
2165 if (!IsAvailable( IE_INI_CLASS_ID )) {
2166 Log(ERROR, "Core", "No INI Importer Available.");
2167 return false;
2168 }
2169 PluginHolder<DataFileMgr> ini(IE_INI_CLASS_ID);
2170 ini->Open(inifile);
2171
2172 ResRef tooltipBG;
2173 const char *s;
2174 // Resrefs are already initialized in Interface::Interface()
2175 #define ASSIGN_RESREF(resref, name) \
2176 s = ini->GetKeyAsString( "resources", name, NULL ); \
2177 resref = s; s = nullptr
2178
2179 ASSIGN_RESREF(MainCursorsImage, "MainCursorsImage");
2180 ASSIGN_RESREF(TextCursorBam, "TextCursorBAM"); //console cursor
2181 ASSIGN_RESREF(ScrollCursorBam, "ScrollCursorBAM");
2182 ASSIGN_RESREF(ButtonFontResRef, "ButtonFont");
2183 ASSIGN_RESREF(TooltipFontResRef, "TooltipFont");
2184 ASSIGN_RESREF(MovieFontResRef, "MovieFont");
2185 ASSIGN_RESREF(tooltipBG, "TooltipBack");
2186 ASSIGN_RESREF(TextFontResRef, "TextFont");
2187 ASSIGN_RESREF(Palette16, "Palette16");
2188 ASSIGN_RESREF(Palette32, "Palette32");
2189 ASSIGN_RESREF(Palette256, "Palette256");
2190
2191 #undef ASSIGN_RESREF
2192
2193 //which stat determines the fist weapon (defaults to class)
2194 Actor::SetFistStat(ini->GetKeyAsInt( "resources", "FistStat", IE_CLASS));
2195
2196 int ttMargin = ini->GetKeyAsInt( "resources", "TooltipMargin", 10 );
2197
2198 if (!tooltipBG.IsEmpty()) {
2199 AnimationFactory* anim = (AnimationFactory*) gamedata->GetFactoryResource(tooltipBG, IE_BAM_CLASS_ID);
2200 Log(MESSAGE, "Core", "Initializing Tooltips...");
2201 if (anim) {
2202 TooltipBG = new TooltipBackground(anim->GetFrame(0, 0), anim->GetFrame(0, 1), anim->GetFrame(0, 2) );
2203 // FIXME: this is an arbitrary heuristic and speed
2204 TooltipBG->SetAnimationSpeed((ttMargin == 5) ? 10 : 0);
2205 TooltipBG->SetMargin(ttMargin);
2206 }
2207 }
2208
2209 DefaultWindowTitle = ini->GetKeyAsString("resources", "WindowTitle", GEMRB_STRING);
2210
2211 // These are values for how long a single step is, see Movable::DoStep.
2212 // They were found via trial-and-error, trying to match
2213 // the speeds from the original games.
2214 gamedata->SetStepTime(ini->GetKeyAsInt("resources", "StepTime", 566)); // Defaults to BG2's value
2215
2216 // The format of GroundCircle can be:
2217 // GroundCircleBAM1 = wmpickl/3
2218 // to denote that the bitmap should be scaled down 3x
2219 for (int size = 0; size < MAX_CIRCLE_SIZE; size++) {
2220 char name[30];
2221 sprintf( name, "GroundCircleBAM%d", size+1 );
2222 s = ini->GetKeyAsString( "resources", name, NULL );
2223 if (s) {
2224 const char *pos = strchr( s, '/' );
2225 if (pos) {
2226 GroundCircleScale[size] = atoi( pos+1 );
2227 strlcpy( GroundCircleBam[size], s, pos - s + 1 );
2228 } else {
2229 CopyResRef(GroundCircleBam[size], s);
2230 }
2231 }
2232 }
2233
2234 s = ini->GetKeyAsString( "resources", "INIConfig", NULL );
2235 if (s)
2236 strlcpy(INIConfig, s, sizeof(INIConfig));
2237
2238 MaximumAbility = ini->GetKeyAsInt ("resources", "MaximumAbility", 25 );
2239 NumRareSelectSounds = ini->GetKeyAsInt("resources", "NumRareSelectSounds", 2);
2240 gamedata->SetTextSpeed(ini->GetKeyAsInt("resources", "TextScreenSpeed", 100));
2241
2242 for (uint32_t i = 0; i < GF_COUNT; i++) {
2243 if (!game_flags[i]) {
2244 error("Core", "Fix the game flags!\n");
2245 }
2246 SetFeature( ini->GetKeyAsInt( "resources", game_flags[i], 0 ), i );
2247 //printMessage("Option", "", GREEN);
2248 //print("%s = %s", game_flags[i], HasFeature(i)?"yes":"no");
2249 }
2250
2251 return true;
2252 }
2253
2254 /** Load the encoding table selected in gemrb.cfg */
LoadEncoding()2255 bool Interface::LoadEncoding()
2256 {
2257 DataStream* inifile = gamedata->GetResource( Encoding.c_str(), IE_INI_CLASS_ID );
2258 if (! inifile) {
2259 return false;
2260 }
2261
2262 Log(MESSAGE, "Core", "Loading encoding definition for %s: '%s'", Encoding.c_str(),
2263 inifile->originalfile);
2264
2265 PluginHolder<DataFileMgr> ini(IE_INI_CLASS_ID);
2266 ini->Open(inifile);
2267
2268 TLKEncoding.encoding = ini->GetKeyAsString("encoding", "TLKEncoding", TLKEncoding.encoding.c_str());
2269 TLKEncoding.zerospace = ini->GetKeyAsBool("encoding", "NoSpaces", 0);
2270
2271 //TextArea::SetNoteString( ini->GetKeyAsString( "strings", "NoteString", NULL ) );
2272
2273 // TODO: lists are incomplete
2274 // maybe want to externalize this
2275 // list compiled form wiki: https://gemrb.org/Text-encodings.html
2276 const char* wideEncodings[] = {
2277 // Chinese
2278 "GBK", "BIG5",
2279 // Korean
2280 "EUCKR",
2281 // Japanese
2282 "SJIS",
2283 };
2284 size_t listSize = sizeof(wideEncodings) / sizeof(wideEncodings[0]);
2285
2286 for (size_t i = 0; i < listSize; i++) {
2287 if (TLKEncoding.encoding == wideEncodings[i]) {
2288 TLKEncoding.widechar = true;
2289 break;
2290 }
2291 }
2292
2293 const char* multibyteEncodings[] = {
2294 "UTF-8",
2295 };
2296 listSize = sizeof(multibyteEncodings) / sizeof(multibyteEncodings[0]);
2297
2298 for (size_t i = 0; i < listSize; i++) {
2299 if (TLKEncoding.encoding == multibyteEncodings[i]) {
2300 TLKEncoding.multibyte = true;
2301 break;
2302 }
2303 }
2304
2305 const char *s;
2306 unsigned int cc = (unsigned int) ini->GetKeyAsInt ("charset", "CharCount", 0);
2307 if (cc>99) cc=99;
2308 while(cc--) {
2309 char key[10];
2310 snprintf(key,9,"Letter%d", cc+1);
2311 s = ini->GetKeyAsString( "charset", key, NULL );
2312 if (s) {
2313 const char *s2 = strchr(s,',');
2314 if (s2) {
2315 unsigned char upper = atoi(s);
2316 unsigned char lower = atoi(s2+1);
2317 pl_uppercase[lower] = upper;
2318 pl_lowercase[upper] = lower;
2319 }
2320 }
2321 }
2322
2323 return true;
2324 }
2325
2326 /** Returns a preloaded Font */
GetFont(const ResRef & ResRef) const2327 Font* Interface::GetFont(const ResRef& ResRef) const
2328 {
2329 std::map<GemRB::ResRef,Font *>::const_iterator i = fonts.find(ResRef);
2330 if (i != fonts.end()) {
2331 return i->second;
2332 }
2333 return NULL;
2334 }
2335
GetTextFont() const2336 Font* Interface::GetTextFont() const
2337 {
2338 return GetFont( TextFontResRef );
2339 }
2340
GetButtonFont() const2341 Font* Interface::GetButtonFont() const
2342 {
2343 return GetFont( ButtonFontResRef );
2344 }
2345
2346 /** Get GUI Script Manager */
GetGUIScriptEngine() const2347 ScriptEngine* Interface::GetGUIScriptEngine() const
2348 {
2349 return guiscript.get();
2350 }
2351
2352 static EffectRef fx_summon_disable_ref = { "AvatarRemovalModifier", -1 };
2353
2354 //NOTE: if there were more summoned creatures, it will return only the last
SummonCreature(const ieResRef resource,const ieResRef vvcres,Scriptable * Owner,Actor * target,const Point & position,int eamod,int level,Effect * fx,bool sexmod)2355 Actor *Interface::SummonCreature(const ieResRef resource, const ieResRef vvcres, Scriptable *Owner, Actor *target, const Point &position, int eamod, int level, Effect *fx, bool sexmod)
2356 {
2357 //maximum number of monsters summoned
2358 int cnt=10;
2359 Actor * ab = NULL;
2360 const Actor *summoner = nullptr;
2361
2362 Map *map;
2363 if (target) {
2364 map = target->GetCurrentArea();
2365 } else if (Owner) {
2366 map = Owner->GetCurrentArea();
2367 } else {
2368 map = game->GetCurrentArea();
2369 }
2370 if (!map) return ab;
2371
2372 if (Owner && Owner->Type == ST_ACTOR) {
2373 summoner = (Actor *) Owner;
2374 }
2375
2376 while (cnt--) {
2377 Actor *tmp = gamedata->GetCreature(resource);
2378 if (!tmp) {
2379 return NULL;
2380 }
2381
2382 //if summoner is an actor, filter out opponent summons
2383 //this is done by clearing the filter when appropriate
2384 //non actors and neutrals can summon as much as they want
2385 ieDword flag = GA_NO_DEAD|GA_NO_ALLY|GA_NO_ENEMY;
2386
2387 if (summoner) {
2388 tmp->LastSummoner = Owner->GetGlobalID();
2389 ieDword ea = summoner->GetStat(IE_EA);
2390 if (ea<=EA_GOODCUTOFF) {
2391 flag &= ~GA_NO_ALLY;
2392 } else if (ea>=EA_EVILCUTOFF) {
2393 flag &= ~GA_NO_ENEMY;
2394 }
2395 }
2396
2397 // mark the summon, but only if they don't have a special sex already
2398 if (sexmod && tmp->BaseStats[IE_SEX] < SEX_EXTRA && tmp->BaseStats[IE_SEX] != SEX_ILLUSION) {
2399 tmp->SetBase(IE_SEX, SEX_SUMMON);
2400 }
2401
2402 // only allow up to the summoning limit of new summoned creatures
2403 // the summoned creatures have a special IE_SEX
2404 // but also only limit the party, so spore colonies can reproduce more
2405 ieDword sex = tmp->GetStat(IE_SEX);
2406 int limit = gamedata->GetSummoningLimit(sex);
2407 if (limit && sexmod && map->CountSummons(flag, sex) >= limit && summoner && summoner->InParty) {
2408 //summoning limit reached
2409 displaymsg->DisplayConstantString(STR_SUMMONINGLIMIT, DMC_WHITE);
2410 delete tmp;
2411 break;
2412 }
2413
2414 ab = tmp;
2415 //Always use Base stats for the recently summoned creature
2416
2417 int enemyally;
2418
2419 if (eamod==EAM_SOURCEALLY || eamod==EAM_SOURCEENEMY) {
2420 if (summoner) {
2421 enemyally = summoner->GetStat(IE_EA) > EA_GOODCUTOFF;
2422 } else {
2423 enemyally = true;
2424 }
2425 } else {
2426 if (target) {
2427 enemyally = target->GetBase(IE_EA)>EA_GOODCUTOFF;
2428 } else {
2429 enemyally = true;
2430 }
2431 }
2432
2433 switch (eamod) {
2434 case EAM_SOURCEALLY:
2435 case EAM_ALLY:
2436 if (enemyally) {
2437 ab->SetBase(IE_EA, EA_ENEMY); //is this the summoned EA?
2438 } else {
2439 ab->SetBase(IE_EA, EA_CONTROLLED); //is this the summoned EA?
2440 }
2441 break;
2442 case EAM_SOURCEENEMY:
2443 case EAM_ENEMY:
2444 if (enemyally) {
2445 ab->SetBase(IE_EA, EA_CONTROLLED); //is this the summoned EA?
2446 } else {
2447 ab->SetBase(IE_EA, EA_ENEMY); //is this the summoned EA?
2448 }
2449 break;
2450 case EAM_NEUTRAL:
2451 ab->SetBase(IE_EA, EA_NEUTRAL);
2452 break;
2453 default:
2454 break;
2455 }
2456
2457 map->AddActor(ab, true);
2458 ab->SetPosition(position, true, 0, 0, ab->size);
2459 ab->RefreshEffects(NULL);
2460
2461 // guessing, since this trigger was unused in the originals — likely duplicating LastSummoner
2462 if (Owner) {
2463 Owner->AddTrigger(TriggerEntry(trigger_summoned, ab->GetGlobalID()));
2464 }
2465
2466 if (vvcres[0]) {
2467 ScriptedAnimation* vvc = gamedata->GetScriptedAnimation(vvcres, false);
2468 if (vvc) {
2469 //This is the final position of the summoned creature
2470 //not the original target point
2471 vvc->Pos = ab->Pos;
2472 //force vvc to play only once
2473 vvc->PlayOnce();
2474 map->AddVVCell( new VEFObject(vvc) );
2475
2476 //set up the summon disable effect
2477 Effect *newfx = EffectQueue::CreateEffect(fx_summon_disable_ref, 0, 1, FX_DURATION_ABSOLUTE);
2478 if (newfx) {
2479 newfx->Duration = vvc->GetSequenceDuration(AI_UPDATE_TIME)*9/10 + core->GetGame()->GameTime;
2480 ApplyEffect(newfx, ab, ab);
2481 delete newfx;
2482 }
2483 }
2484 }
2485
2486 //remove the xp value of friendly summons
2487 if (ab->BaseStats[IE_EA]<EA_GOODCUTOFF) {
2488 ab->SetBase(IE_XPVALUE, 0);
2489 }
2490 if (fx) {
2491 ApplyEffect(fx, ab, Owner);
2492 }
2493
2494 //this check should happen after the fact
2495 level -= ab->GetBase(IE_XP);
2496 if(level<0 || ab->GetBase(IE_XP) == 0) {
2497 break;
2498 }
2499
2500 }
2501 return ab;
2502 }
2503
2504 /** Loads a Window in the Window Manager */
LoadWindow(ScriptingId WindowID,const ResRef & ref,Window::WindowPosition pos)2505 Window* Interface::LoadWindow(ScriptingId WindowID, const ResRef& ref, Window::WindowPosition pos)
2506 {
2507 if (ref) // is the winpack changing?
2508 guifact->LoadWindowPack(ref);
2509
2510 Window* win = GetWindow(WindowID, ref);
2511 if (!win) {
2512 win = guifact->GetWindow( WindowID );
2513 }
2514 if (win) {
2515 assert(win->GetScriptingRef());
2516 win->SetPosition(pos);
2517 winmgr->FocusWindow( win );
2518 }
2519 return win;
2520 }
2521
2522 /** Creates a Window in the Window Manager */
CreateWindow(unsigned short WindowID,const Region & frame)2523 Window* Interface::CreateWindow(unsigned short WindowID, const Region& frame)
2524 {
2525 // FIXME: this will create a window under the current "window pack"
2526 // obviously its possible the id can conflict with an existing window
2527 return guifact->CreateWindow(WindowID, frame);
2528 }
2529
SetGroupViewFlags(const std::vector<View * > & views,unsigned int flags,int op)2530 inline void SetGroupViewFlags(const std::vector<View*>& views, unsigned int flags, int op)
2531 {
2532 std::vector<View*>::const_iterator it = views.begin();
2533 for (; it != views.end(); ++it) {
2534 View* view = *it;
2535 view->SetFlags(flags, op);
2536 }
2537 }
2538
ToggleViewsVisible(bool visible,const ResRef & group)2539 void Interface::ToggleViewsVisible(bool visible, const ResRef& group)
2540 {
2541 if (game && group == ResRef("HIDE_CUT")) {
2542 game->SetControlStatus(CS_HIDEGUI, (visible) ? OP_NAND : OP_OR );
2543 }
2544
2545 std::vector<View*> views = GetViews(group);
2546 SetGroupViewFlags(views, View::Invisible, (visible) ? OP_NAND : OP_OR);
2547 }
2548
ToggleViewsEnabled(bool enabled,const ResRef & group)2549 void Interface::ToggleViewsEnabled(bool enabled, const ResRef& group)
2550 {
2551 std::vector<View*> views = GetViews(group);
2552 SetGroupViewFlags(views, View::Disabled, (enabled) ? OP_NAND : OP_OR);
2553 }
2554
IsFreezed()2555 bool Interface::IsFreezed()
2556 {
2557 return !update_scripts;
2558 }
2559
GameLoop(void)2560 void Interface::GameLoop(void)
2561 {
2562 update_scripts = false;
2563 GameControl *gc = GetGameControl();
2564 if (gc) {
2565 update_scripts = !(gc->GetDialogueFlags() & DF_FREEZE_SCRIPTS);
2566 }
2567
2568 bool do_update = GSUpdate(update_scripts);
2569
2570 if (game) {
2571 if (gc && !game->selected.empty()) {
2572 gc->ChangeMap(GetFirstSelectedPC(true), false);
2573 }
2574 //in multi player (if we ever get to it), only the server must call this
2575 if (do_update) {
2576 // the game object will run the area scripts as well
2577 game->UpdateScripts();
2578 }
2579 }
2580 }
2581
2582 /** handles hardcoded gui behaviour */
HandleGUIBehaviour(void)2583 void Interface::HandleGUIBehaviour(void)
2584 {
2585 GameControl *gc = GetGameControl();
2586 if (gc) {
2587 //this variable is used all over in the following hacks
2588 int flg = gc->GetDialogueFlags();
2589
2590 //the following part is a series of hardcoded gui behaviour
2591
2592 //initiating dialog
2593 if (flg & DF_IN_DIALOG) {
2594 // -3 noaction
2595 // -2 close
2596 // -1 open
2597 // choose option
2598 ieDword var = (ieDword) -3;
2599 vars->Lookup("DialogChoose", var);
2600 if ((int) var == -2) {
2601 // TODO: this seems to never be called? (EndDialog is called from elsewhere instead)
2602 gc->dialoghandler->EndDialog();
2603 } else if ( (int)var !=-3) {
2604 if ( (int) var == -1) {
2605 guiscript->RunFunction( "GUIWORLD", "DialogStarted" );
2606 }
2607 gc->dialoghandler->DialogChoose(var);
2608 if (!(gc->GetDialogueFlags() & (DF_OPENCONTINUEWINDOW | DF_OPENENDWINDOW)))
2609 guiscript->RunFunction( "GUIWORLD", "NextDialogState" );
2610
2611 // the last node of a dialog can have a new-dialog action! don't interfere in that case
2612 ieDword newvar = 0; vars->Lookup("DialogChoose", newvar);
2613 if (var == (ieDword) -1 || newvar != (ieDword) -1) {
2614 vars->SetAt("DialogChoose", (ieDword) -3);
2615 }
2616 }
2617 if (flg & DF_OPENCONTINUEWINDOW) {
2618 guiscript->RunFunction( "GUIWORLD", "OpenContinueMessageWindow" );
2619 gc->SetDialogueFlags(DF_OPENCONTINUEWINDOW|DF_OPENENDWINDOW, OP_NAND);
2620 } else if (flg & DF_OPENENDWINDOW) {
2621 guiscript->RunFunction( "GUIWORLD", "OpenEndMessageWindow" );
2622 gc->SetDialogueFlags(DF_OPENCONTINUEWINDOW|DF_OPENENDWINDOW, OP_NAND);
2623 }
2624 }
2625
2626 //handling container
2627 if (CurrentContainer && UseContainer) {
2628 if (!(flg & DF_IN_CONTAINER) ) {
2629 gc->SetDialogueFlags(DF_IN_CONTAINER, OP_OR);
2630 guiscript->RunFunction( "CommonWindow", "OpenContainerWindow" );
2631 }
2632 } else {
2633 if (flg & DF_IN_CONTAINER) {
2634 gc->SetDialogueFlags(DF_IN_CONTAINER, OP_NAND);
2635 guiscript->RunFunction( "CommonWindow", "CloseContainerWindow" );
2636 }
2637 }
2638 //end of gui hacks
2639 }
2640 }
2641
CreateTooltip()2642 Tooltip Interface::CreateTooltip()
2643 {
2644 Font::PrintColors colors;
2645 colors.fg = gamedata->GetColor("TOOLTIP");
2646 colors.bg = gamedata->GetColor("TOOLTIPBG");
2647
2648 TooltipBackground* bg = NULL;
2649 if (TooltipBG) {
2650 bg = new TooltipBackground(*TooltipBG);
2651 }
2652 return Tooltip(L"", GetFont(TooltipFontResRef), colors, bg);
2653 }
2654
2655 /** Get the Sound Manager */
GetSaveGameIterator() const2656 SaveGameIterator* Interface::GetSaveGameIterator() const
2657 {
2658 return sgiterator;
2659 }
2660
AskAndExit()2661 void Interface::AskAndExit()
2662 {
2663 // if askExit is 1 then we are trying to quit a second time and should instantly do so
2664 ieDword askExit = 0;
2665 vars->Lookup("AskAndExit", askExit);
2666 if (game && !askExit) {
2667 SetPause(PAUSE_ON);
2668 vars->SetAt("AskAndExit", 1);
2669
2670 guifact->LoadWindowPack("GUIOPT");
2671 guiscript->RunFunction("GUIOPT", "OpenQuitMsgWindow");
2672 Log(MESSAGE, "Info", "Press ctrl-c (or close the window) again to quit GemRB.\n");
2673 } else {
2674 ExitGemRB();
2675 }
2676 }
2677
ExitGemRB()2678 void Interface::ExitGemRB()
2679 {
2680 QuitFlag |= QF_KILL;
2681 }
2682 /** Returns the variables dictionary */
GetDictionary() const2683 Variables* Interface::GetDictionary() const
2684 {
2685 return vars;
2686 }
2687 /** Returns the token dictionary */
GetTokenDictionary() const2688 Variables* Interface::GetTokenDictionary() const
2689 {
2690 return tokens;
2691 }
2692 /** Get the Music Manager */
GetMusicMgr() const2693 MusicMgr* Interface::GetMusicMgr() const
2694 {
2695 return music.get();
2696 }
2697 /** Loads an IDS Table, returns -1 on error or the Symbol Table Index on success */
LoadSymbol(const char * ResRef)2698 int Interface::LoadSymbol(const char* ResRef)
2699 {
2700 int ind = GetSymbolIndex( ResRef );
2701 if (ind != -1) {
2702 return ind;
2703 }
2704 DataStream* str = gamedata->GetResource( ResRef, IE_IDS_CLASS_ID );
2705 if (!str) {
2706 return -1;
2707 }
2708 PluginHolder<SymbolMgr> sm(IE_IDS_CLASS_ID);
2709 if (!sm) {
2710 delete str;
2711 return -1;
2712 }
2713 if (!sm->Open(str)) {
2714 return -1;
2715 }
2716 Symbol s;
2717 strlcpy(s.ResRef, ResRef, sizeof(s.ResRef));
2718 s.sm = sm;
2719 ind = -1;
2720 for (size_t i = 0; i < symbols.size(); i++) {
2721 if (!symbols[i].sm) {
2722 ind = ( int ) i;
2723 break;
2724 }
2725 }
2726 if (ind != -1) {
2727 symbols[ind] = s;
2728 return ind;
2729 }
2730 symbols.push_back( s );
2731 return ( int ) symbols.size() - 1;
2732 }
2733 /** Gets the index of a loaded Symbol Table, returns -1 on error */
GetSymbolIndex(const char * ResRef) const2734 int Interface::GetSymbolIndex(const char* ResRef) const
2735 {
2736 for (size_t i = 0; i < symbols.size(); i++) {
2737 if (!symbols[i].sm)
2738 continue;
2739 if (strnicmp( symbols[i].ResRef, ResRef, 8 ) == 0)
2740 return ( int ) i;
2741 }
2742 return -1;
2743 }
2744 /** Gets a Loaded Symbol Table by its index, returns NULL on error */
GetSymbol(unsigned int index) const2745 Holder<SymbolMgr> Interface::GetSymbol(unsigned int index) const
2746 {
2747 if (index >= symbols.size()) {
2748 return Holder<SymbolMgr>();
2749 }
2750 if (!symbols[index].sm) {
2751 return Holder<SymbolMgr>();
2752 }
2753 return symbols[index].sm;
2754 }
2755 /** Frees a Loaded Symbol Table, returns false on error, true on success */
DelSymbol(unsigned int index)2756 bool Interface::DelSymbol(unsigned int index)
2757 {
2758 if (index >= symbols.size()) {
2759 return false;
2760 }
2761 if (!symbols[index].sm) {
2762 return false;
2763 }
2764 symbols[index].sm.release();
2765 return true;
2766 }
2767 /** Plays a Movie */
PlayMovie(const char * resref)2768 int Interface::PlayMovie(const char* resref)
2769 {
2770 const char *realResRef = resref;
2771
2772 //check whether there is an override for this movie
2773 const char *sound_resref = NULL;
2774 AutoTable mvesnd;
2775 if (mvesnd.load("mvesnd", true)) {
2776 int row = mvesnd->GetRowIndex(resref);
2777 if (row != -1) {
2778 int mvecol = mvesnd->GetColumnIndex("override");
2779 if (mvecol != -1) {
2780 realResRef = mvesnd->QueryField(row, mvecol);
2781 }
2782 int sndcol = mvesnd->GetColumnIndex("sound_override");
2783 if (sndcol != -1) {
2784 sound_resref = mvesnd->QueryField(row, sndcol);
2785 }
2786 }
2787 }
2788
2789 ResourceHolder<MoviePlayer> mp = GetResourceHolder<MoviePlayer>(realResRef);
2790 if (!mp) {
2791 return -1;
2792 }
2793
2794 //one of these two should exist (they both mean the same thing)
2795 ieDword subtitles = true;
2796 vars->Lookup("Display Movie Subtitles", subtitles);
2797 if (!subtitles) {
2798 vars->Lookup("Display Subtitles", subtitles);
2799 }
2800
2801 mp->EnableSubtitles(subtitles);
2802
2803 class IESubtitles : public MoviePlayer::SubtitleSet {
2804 using FrameMap = std::map<size_t, ieStrRef>;
2805 FrameMap subs;
2806 mutable size_t nextSubFrame;
2807 mutable String* cachedSub;
2808
2809 public:
2810 // default color taken from BGEE.lua
2811 IESubtitles(class Font* fnt, ResRef resref, const Color& col = Color(0xe9, 0xe2, 0xca, 0xff))
2812 : MoviePlayer::SubtitleSet(fnt, col)
2813 {
2814 AutoTable sttable(resref);
2815 cachedSub = NULL;
2816 nextSubFrame = 0;
2817
2818 for (size_t i = 0; i < sttable->GetRowCount(); ++i) {
2819 const char* frameField = sttable->QueryField(i, 0);
2820 const char* strField = sttable->QueryField(i, 1);
2821 if (frameField && strField) { // this skips the initial palette rows
2822 subs[atoi(frameField)] = atoi(strField);
2823 }
2824 }
2825 }
2826
2827 ~IESubtitles() override {
2828 delete cachedSub;
2829 }
2830
2831 size_t NextSubtitleFrame() const override {
2832 return nextSubFrame;
2833 }
2834
2835 const String& SubtitleAtFrame(size_t frameNum) const override {
2836 // FIXME: we cant go backwards now... we dont need to, but this is ugly
2837 if (frameNum >= NextSubtitleFrame()) {
2838 FrameMap::const_iterator it;
2839 it = subs.upper_bound(frameNum);
2840 nextSubFrame = it->first;
2841 if (it != subs.begin()) {
2842 --it;
2843 }
2844 delete cachedSub;
2845 cachedSub = core->GetString(it->second);
2846 }
2847
2848 assert(cachedSub);
2849 return *cachedSub;
2850 }
2851 };
2852
2853 AutoTable sttable(resref);
2854 Font* font = GetFont(MovieFontResRef);
2855 if (sttable && font) {
2856 int r = atoi(sttable->QueryField("red", "frame"));
2857 int g = atoi(sttable->QueryField("green", "frame"));
2858 int b = atoi(sttable->QueryField("blue", "frame"));
2859
2860 if (r || g || b) {
2861 mp->SetSubtitles(new IESubtitles(font, resref, Color(r, g, b, 0xff)));
2862 } else {
2863 mp->SetSubtitles(new IESubtitles(font, resref));
2864 }
2865 }
2866
2867 //shutting down music and ambients before movie
2868 if (music)
2869 music->HardEnd();
2870 AmbientMgr *ambim = AudioDriver->GetAmbientMgr();
2871 if (ambim) ambim->deactivate();
2872
2873 Holder<SoundHandle> sound_override;
2874 if (sound_resref) {
2875 sound_override = AudioDriver->Play(sound_resref, SFX_CHAN_NARRATOR);
2876 }
2877
2878 // clear whatever is currently on screen
2879 SetCutSceneMode(true);
2880 Region screen(0,0, Width, Height);
2881 Window* win = winmgr->MakeWindow(screen);
2882 win->SetFlags(Window::Borderless|Window::NoSounds, OP_OR);
2883 winmgr->PresentModalWindow(win);
2884 WindowManager::CursorFeedback cur = winmgr->SetCursorFeedback(WindowManager::MOUSE_NONE);
2885 winmgr->DrawWindows();
2886
2887 mp->Play(win);
2888 win->Close();
2889 winmgr->SetCursorFeedback(cur);
2890 SetCutSceneMode(false);
2891 if (sound_override) {
2892 sound_override->Stop();
2893 sound_override.release();
2894 }
2895
2896 //restarting music
2897 if (music)
2898 music->Start();
2899 if (ambim) ambim->activate();
2900
2901 //Setting the movie name to 1
2902 vars->SetAt( resref, 1 );
2903 return 0;
2904 }
2905
Roll(int dice,int size,int add) const2906 int Interface::Roll(int dice, int size, int add) const
2907 {
2908 if (dice < 1) {
2909 return add;
2910 }
2911 if (size < 1) {
2912 return add;
2913 }
2914 if (dice > 100) {
2915 return add + dice * size / 2;
2916 }
2917 for (int i = 0; i < dice; i++) {
2918 add += RAND(1, size);
2919 }
2920 return add;
2921 }
2922
GetResourceDirectory(RESOURCE_DIRECTORY dir)2923 DirectoryIterator Interface::GetResourceDirectory(RESOURCE_DIRECTORY dir)
2924 {
2925 char Path[_MAX_PATH];
2926 const char* resourcePath = NULL;
2927 DirectoryIterator::FileFilterPredicate* filter = NULL;
2928 switch (dir) {
2929 case DIRECTORY_CHR_PORTRAITS:
2930 resourcePath = GamePortraitsPath;
2931 filter = new ExtFilter("BMP");
2932 if (IsAvailable(IE_PNG_CLASS_ID)) {
2933 // chain an ORed filter for png
2934 filter = new OrPredicate<const char*>(filter, new ExtFilter("PNG"));
2935 }
2936 break;
2937 case DIRECTORY_CHR_SOUNDS:
2938 resourcePath = GameSoundsPath;
2939 if (!HasFeature( GF_SOUNDFOLDERS ))
2940 filter = new ExtFilter("WAV");
2941 break;
2942 case DIRECTORY_CHR_EXPORTS:
2943 resourcePath = GameCharactersPath;
2944 filter = new ExtFilter("CHR");
2945 break;
2946 case DIRECTORY_CHR_SCRIPTS:
2947 resourcePath = GameScriptsPath;
2948 filter = new ExtFilter("BS");
2949 filter = new OrPredicate<const char*>(filter, new ExtFilter("BCS"));
2950 break;
2951 default:
2952 error("Interface", "Uknown resource directory type: %d!", dir);
2953 }
2954
2955 PathJoin(Path, GamePath, resourcePath, nullptr);
2956 DirectoryIterator dirIt(Path);
2957 dirIt.SetFilterPredicate(filter);
2958 return dirIt;
2959 }
2960
InitializeVarsWithINI(const char * iniFileName)2961 bool Interface::InitializeVarsWithINI(const char* iniFileName)
2962 {
2963 if (!core->IsAvailable( IE_INI_CLASS_ID ))
2964 return false;
2965
2966 DataFileMgr* defaults = NULL;
2967 DataFileMgr* overrides = NULL;
2968
2969 PluginHolder<DataFileMgr> ini(IE_INI_CLASS_ID);
2970 FileStream* iniStream = FileStream::OpenFile(iniFileName);
2971 // if filename is not set we assume we are creating defaults without an INI
2972 bool opened = ini->Open(iniStream);
2973 if (iniFileName[0] && !opened) {
2974 Log(WARNING, "Core", "Unable to read defaults from '%s'. Using GemRB default values.", iniFileName);
2975 } else {
2976 overrides = ini.get();
2977 }
2978 if (!opened || iniFileName[0] == 0) {
2979 delete iniStream; // Open deletes it itself on success
2980 }
2981
2982 PluginHolder<DataFileMgr> gemINI(IE_INI_CLASS_ID);
2983 DataStream* gemINIStream = gamedata->GetResource( "defaults", IE_INI_CLASS_ID );
2984
2985 if (!gemINIStream || !gemINI->Open(gemINIStream)) {
2986 Log(WARNING, "Core", "Unable to load GemRB default values.");
2987 defaults = ini.get();
2988 } else {
2989 defaults = gemINI.get();
2990 }
2991 if (!overrides) {
2992 overrides = defaults;
2993 }
2994
2995 for (int i = 0; i < defaults->GetTagsCount(); i++) {
2996 const char* tag = defaults->GetTagNameByIndex(i);
2997 for (int j = 0; j < defaults->GetKeysCount(tag); j++) {
2998 ieDword nothing;
2999 const char* key = defaults->GetKeyNameByIndex(tag, j);
3000 //skip any existing entries. GemRB.cfg has priority
3001 if (!vars->Lookup(key, nothing)) {
3002 ieDword defaultVal = defaults->GetKeyAsInt(tag, key, 0);
3003 vars->SetAt(key, overrides->GetKeyAsInt(tag, key, defaultVal));
3004 }
3005 }
3006 }
3007
3008 // handle a few special cases
3009 if (!overrides->GetKeyAsInt("Config", "Sound", 1))
3010 AudioDriverName = "null";
3011
3012 if (overrides->GetKeyAsInt("Game Options", "Cheats", 1)) {
3013 EnableCheatKeys(1);
3014 }
3015
3016 // copies
3017 if (!overrides->GetKeyAsInt("Game Options", "Darkvision", 1)) {
3018 vars->SetAt("Infravision", (ieDword)0);
3019 }
3020
3021 if (!Width || !Height) {
3022 Height = overrides->GetKeyAsInt("Config", "ConfigHeight", Height);
3023 int tmpWidth = overrides->GetKeyAsInt("Config", "ConfigWidth", 0);
3024 if (!tmpWidth) {
3025 // Resolution is stored as width only. assume 4|3 ratio.
3026 Width = overrides->GetKeyAsInt("Program Options", "Resolution", Width);
3027 Height = 0.75 * Width;
3028 }
3029 }
3030 return true;
3031 }
3032
3033 /** Saves the gemrb config variables from the whitelist to gem-INIConfig
3034 * If GamePath is not writable, it tries SavePath
3035 */
SaveConfig()3036 bool Interface::SaveConfig()
3037 {
3038 char ini_path[_MAX_PATH] = { '\0' };
3039 char gemrbINI[_MAX_PATH+4] = { '\0' };
3040 if (strncmp(INIConfig, "gem-", 4)) {
3041 snprintf(gemrbINI, sizeof(gemrbINI), "gem-%s", INIConfig);
3042 }
3043 PathJoin(ini_path, GamePath, gemrbINI, nullptr);
3044 FileStream *fs = new FileStream();
3045 if (!fs->Create(ini_path)) {
3046 PathJoin(ini_path, SavePath, gemrbINI, nullptr);
3047 if (!fs->Create(ini_path)) {
3048 delete fs;
3049 return false;
3050 }
3051 }
3052
3053 PluginHolder<DataFileMgr> defaultsINI(IE_INI_CLASS_ID);
3054 DataStream* INIStream = gamedata->GetResource( "defaults", IE_INI_CLASS_ID );
3055
3056 if (INIStream && defaultsINI->Open(INIStream)) {
3057 // dump the formatted default config options to the file
3058 StringBuffer contents;
3059 for (int i = 0; i < defaultsINI->GetTagsCount(); i++) {
3060 const char* tag = defaultsINI->GetTagNameByIndex(i);
3061 // write section header
3062 contents.appendFormatted("[%s]\n", tag);
3063 for (int j = 0; j < defaultsINI->GetKeysCount(tag); j++) {
3064 const char* key = defaultsINI->GetKeyNameByIndex(tag, j);
3065 ieDword value = 0;
3066 bool found = vars->Lookup(key, value);
3067 assert(found);
3068 contents.appendFormatted("%s = %d\n", key, value);
3069 }
3070 }
3071
3072 fs->Write(contents.get().c_str(), contents.get().size());
3073 } else {
3074 Log(ERROR, "Core", "Unable to open GemRB defaults. Cannot determine what values to write to %s.", ini_path);
3075 }
3076
3077 delete fs;
3078 return true;
3079 }
3080
3081 // this is more of a workaround than anything else
3082 // needed for cases where the script runner is gone before finishing his queue
SetCutSceneRunner(Scriptable * runner)3083 void Interface::SetCutSceneRunner(Scriptable *runner) {
3084 CutSceneRunner = runner;
3085 }
3086
3087 /** Enables/Disables the Cut Scene Mode */
SetCutSceneMode(bool active)3088 void Interface::SetCutSceneMode(bool active)
3089 {
3090 GameControl *gc = GetGameControl();
3091 if (gc) {
3092 gc->SetCutSceneMode( active );
3093 }
3094
3095 ToggleViewsVisible(!active, "HIDE_CUT");
3096
3097 if (active) {
3098 GetGUIScriptEngine()->RunFunction("GUICommonWindows", "CloseTopWindow");
3099 } else {
3100 SetCutSceneRunner(NULL);
3101 }
3102 }
3103
3104 /** returns true if in dialogue or cutscene */
InCutSceneMode() const3105 bool Interface::InCutSceneMode() const
3106 {
3107 const GameControl *gc = GetGameControl();
3108 if (!gc || (gc->GetDialogueFlags()&DF_IN_DIALOG) || (gc->GetScreenFlags()&SF_CUTSCENE)) {
3109 return true;
3110 }
3111 return false;
3112 }
3113
3114 /** Updates the Game Script Engine State */
GSUpdate(bool update_scripts)3115 bool Interface::GSUpdate(bool update_scripts)
3116 {
3117 if(update_scripts) {
3118 return timer.Update();
3119 }
3120 else {
3121 timer.Freeze();
3122 return false;
3123 }
3124 }
3125
QuitGame(int BackToMain)3126 void Interface::QuitGame(int BackToMain)
3127 {
3128 SetCutSceneMode(false);
3129
3130 //shutting down ingame music
3131 //(do it before deleting the game)
3132 if (music) {
3133 music->HardEnd();
3134 }
3135 // stop any ambients which are still enqueued
3136 if (AudioDriver) {
3137 AmbientMgr *ambim = AudioDriver->GetAmbientMgr();
3138 if (ambim) ambim->deactivate();
3139 AudioDriver->Stop(); // also kill sounds
3140 }
3141 //delete game, worldmap
3142 if (game) {
3143 delete game;
3144 game=NULL;
3145 }
3146 if (worldmap) {
3147 delete worldmap;
3148 worldmap=NULL;
3149 }
3150 if (BackToMain) {
3151 SetNextScript("Start");
3152 }
3153 GSUpdate(true);
3154 }
3155
SetupLoadGame(Holder<SaveGame> sg,int ver_override)3156 void Interface::SetupLoadGame(Holder<SaveGame> sg, int ver_override)
3157 {
3158 LoadGameIndex = sg;
3159 VersionOverride = ver_override;
3160 QuitFlag |= QF_LOADGAME;
3161 }
3162
LoadGame(SaveGame * sg,int ver_override)3163 void Interface::LoadGame(SaveGame *sg, int ver_override)
3164 {
3165 // This function has rather painful error handling,
3166 // as it should swap all the objects or none at all
3167 // and the loading can fail for various reasons
3168
3169 // Yes, it uses goto. Other ways seemed too awkward for me.
3170
3171 gamedata->SaveAllStores();
3172 strings->CloseAux();
3173 tokens->RemoveAll(NULL); //clearing the token dictionary
3174
3175 if(calendar) delete calendar;
3176 calendar = new Calendar;
3177
3178 DataStream* gam_str = NULL;
3179 DataStream* sav_str = NULL;
3180 DataStream* wmp_str1 = NULL;
3181 DataStream* wmp_str2 = NULL;
3182
3183 Game* new_game = NULL;
3184 WorldMapArray* new_worldmap = NULL;
3185
3186 LoadProgress(10);
3187 if (!KeepCache) DelTree((const char *) CachePath, true);
3188 LoadProgress(15);
3189
3190 if (sg == NULL) {
3191 //Load the Default Game
3192 gam_str = gamedata->GetResource( GameNameResRef, IE_GAM_CLASS_ID );
3193 sav_str = NULL;
3194 wmp_str1 = gamedata->GetResource( WorldMapName[0], IE_WMP_CLASS_ID );
3195 if (!WorldMapName[1].IsEmpty()) {
3196 wmp_str2 = gamedata->GetResource( WorldMapName[1], IE_WMP_CLASS_ID );
3197 }
3198 } else {
3199 gam_str = sg->GetGame();
3200 sav_str = sg->GetSave();
3201 wmp_str1 = sg->GetWmap(0);
3202 if (!WorldMapName[1].IsEmpty()) {
3203 wmp_str2 = sg->GetWmap(1);
3204 if (!wmp_str2) {
3205 //upgrade an IWD game to HOW
3206 wmp_str2 = gamedata->GetResource( WorldMapName[1], IE_WMP_CLASS_ID );
3207 }
3208 }
3209 }
3210
3211 // These are here because of the goto
3212 PluginHolder<SaveGameMgr> gam_mgr(IE_GAM_CLASS_ID);
3213 PluginHolder<WorldMapMgr> wmp_mgr(IE_WMP_CLASS_ID);
3214 AmbientMgr *ambim = core->GetAudioDrv()->GetAmbientMgr();
3215
3216 if (!gam_str || !(wmp_str1 || wmp_str2) )
3217 goto cleanup;
3218
3219 // Load GAM file
3220 if (!gam_mgr)
3221 goto cleanup;
3222
3223 if (!gam_mgr->Open(gam_str))
3224 goto cleanup;
3225
3226 new_game = gam_mgr->LoadGame(new Game(), ver_override);
3227 if (!new_game)
3228 goto cleanup;
3229
3230 gam_str = NULL;
3231
3232 // Load WMP (WorldMap) file
3233 if (!wmp_mgr)
3234 goto cleanup;
3235
3236 if (!wmp_mgr->Open(wmp_str1, wmp_str2))
3237 goto cleanup;
3238
3239 new_worldmap = wmp_mgr->GetWorldMapArray( );
3240
3241 wmp_str1 = NULL;
3242 wmp_str2 = NULL;
3243
3244 LoadProgress(20);
3245 // Unpack SAV (archive) file to Cache dir
3246 if (sav_str) {
3247 PluginHolder<ArchiveImporter> ai(IE_SAV_CLASS_ID);
3248 if (ai) {
3249 if (ai->DecompressSaveGame(sav_str) != GEM_OK) {
3250 goto cleanup;
3251 }
3252 }
3253 delete sav_str;
3254 sav_str = NULL;
3255 }
3256
3257 // rarely caused crashes while loading, so stop the ambients
3258 if (ambim) {
3259 ambim->reset();
3260 }
3261
3262 // Let's assume that now is everything loaded OK and swap the objects
3263 delete game;
3264 delete worldmap;
3265
3266 game = new_game;
3267 worldmap = new_worldmap;
3268
3269 strings->OpenAux();
3270 LoadProgress(70);
3271 return;
3272 cleanup:
3273 // Something went wrong, so try to clean after itself
3274 delete new_game;
3275 delete new_worldmap;
3276 delete gam_str;
3277 delete wmp_str1;
3278 delete wmp_str2;
3279 delete sav_str;
3280
3281 error("Core", "Unable to load game.");
3282 }
3283
3284 /* replace the current world map but sync areas available in old and new */
UpdateWorldMap(ResRef wmResRef)3285 void Interface::UpdateWorldMap(ResRef wmResRef)
3286 {
3287 DataStream* wmp_str = gamedata->GetResource(wmResRef, IE_WMP_CLASS_ID);
3288 PluginHolder<WorldMapMgr> wmp_mgr(IE_WMP_CLASS_ID);
3289
3290 if (!wmp_str || !wmp_mgr || !wmp_mgr->Open(wmp_str, NULL)) {
3291 Log(ERROR, "Core", "Could not update world map %s", wmResRef.CString());
3292 return;
3293 }
3294
3295 WorldMapArray *new_worldmap = wmp_mgr->GetWorldMapArray();
3296 WorldMap *wm = worldmap->GetWorldMap(0);
3297 WorldMap *nwm = new_worldmap->GetWorldMap(0);
3298
3299 unsigned int i, ni;
3300 unsigned int ec = wm->GetEntryCount();
3301 //update status of the previously existing areas
3302 for(i=0;i<ec;i++) {
3303 WMPAreaEntry *ae = wm->GetEntry(i);
3304 WMPAreaEntry *nae = nwm->GetArea(ae->AreaResRef, ni);
3305 if (nae != NULL) {
3306 nae->SetAreaStatus(ae->GetAreaStatus(), OP_SET);
3307 }
3308 }
3309
3310 delete worldmap;
3311 worldmap = new_worldmap;
3312 WorldMapName[0] = wmResRef;
3313 }
3314
3315 /* swapping out old resources */
UpdateMasterScript()3316 void Interface::UpdateMasterScript()
3317 {
3318 if (game) {
3319 game->SetScript( GlobalScript, 0 );
3320 }
3321
3322 PluginHolder<WorldMapMgr> wmp_mgr(IE_WMP_CLASS_ID);
3323 if (! wmp_mgr)
3324 return;
3325
3326 if (worldmap) {
3327 DataStream *wmp_str1 = gamedata->GetResource( WorldMapName[0], IE_WMP_CLASS_ID );
3328 DataStream *wmp_str2 = gamedata->GetResource( WorldMapName[1], IE_WMP_CLASS_ID );
3329
3330 if (!wmp_mgr->Open(wmp_str1, wmp_str2)) {
3331 delete wmp_str1;
3332 delete wmp_str2;
3333 }
3334
3335 delete worldmap;
3336 worldmap = wmp_mgr->GetWorldMapArray();
3337 }
3338 }
3339
InitItemTypes()3340 bool Interface::InitItemTypes()
3341 {
3342 int i;
3343
3344 if (slotmatrix) {
3345 free(slotmatrix);
3346 }
3347
3348 AutoTable it("itemtype");
3349 ItemTypes = 0;
3350 if (it) {
3351 ItemTypes = it->GetRowCount(); //number of itemtypes
3352 if (ItemTypes<0) {
3353 ItemTypes = 0;
3354 }
3355 int InvSlotTypes = it->GetColumnCount();
3356 if (InvSlotTypes > 32) { //bit count limit
3357 InvSlotTypes = 32;
3358 }
3359 //make sure unsigned int is 32 bits
3360 slotmatrix = (ieDword *) malloc(ItemTypes * sizeof(ieDword) );
3361 for (i=0;i<ItemTypes;i++) {
3362 unsigned int value = 0;
3363 unsigned int k = 1;
3364 for (int j=0;j<InvSlotTypes;j++) {
3365 if (strtol(it->QueryField(i,j),NULL,0) ) {
3366 value |= k;
3367 }
3368 k <<= 1;
3369 }
3370 //we let any items in the inventory
3371 slotmatrix[i] = value | SLOT_INVENTORY;
3372 }
3373 }
3374
3375 //itemtype data stores (armor failure and critical damage multipliers), critical range
3376 itemtypedata.reserve(ItemTypes);
3377 for (i=0;i<ItemTypes;i++) {
3378 itemtypedata.push_back(std::vector<int>(4));
3379 //default values in case itemdata is missing (it is needed only for iwd2)
3380 if (slotmatrix[i] & SLOT_WEAPON) {
3381 itemtypedata[i][IDT_FAILURE] = 0; // armor malus
3382 itemtypedata[i][IDT_CRITRANGE] = 20; // crit range
3383 itemtypedata[i][IDT_CRITMULTI] = 2; // crit multiplier
3384 itemtypedata[i][IDT_SKILLPENALTY] = 0; // skill check malus
3385 }
3386 }
3387 AutoTable af("itemdata");
3388 if (af) {
3389 int armcount = af->GetRowCount();
3390 int colcount = af->GetColumnCount();
3391 int j;
3392 for (i = 0; i < armcount; i++) {
3393 int itemtype = (ieWord) atoi( af->QueryField(i,0) );
3394 if (itemtype<ItemTypes) {
3395 // we don't need the itemtype column, since it is equal to the position
3396 for (j=0; j < colcount-1; j++) {
3397 itemtypedata[itemtype][j] = atoi(af->QueryField(i, j+1));
3398 }
3399 }
3400 }
3401 }
3402
3403 //slottype describes the inventory structure
3404 Inventory::Init();
3405 AutoTable st("slottype");
3406 if (slottypes) {
3407 free(slottypes);
3408 slottypes = NULL;
3409 }
3410 SlotTypes = 0;
3411 if (st) {
3412 SlotTypes = st->GetRowCount();
3413 //make sure unsigned int is 32 bits
3414 slottypes = (SlotType *) malloc(SlotTypes * sizeof(SlotType) );
3415 memset(slottypes, -1, SlotTypes * sizeof(SlotType) );
3416 for (unsigned int row = 0; row < SlotTypes; row++) {
3417 bool alias;
3418 unsigned int i = (ieDword) strtol(st->GetRowName(row),NULL,0 );
3419 if (i>=SlotTypes) continue;
3420 if (slottypes[i].sloteffects!=0xffffffffu) {
3421 slottypes[row].slot = i;
3422 i=row;
3423 alias = true;
3424 } else {
3425 slottypes[row].slot = i;
3426 alias = false;
3427 }
3428 slottypes[i].slottype = (ieDword) strtol(st->QueryField(row,0),NULL,0 );
3429 slottypes[i].slotid = (ieDword) strtol(st->QueryField(row,1),NULL,0 );
3430 strnlwrcpy( slottypes[i].slotresref, st->QueryField(row,2), 8 );
3431 slottypes[i].slottip = (ieDword) strtol(st->QueryField(row,3),NULL,0 );
3432 slottypes[i].slotflags = (ieDword) strtol(st->QueryField(row,5),NULL,0 );
3433 //don't fill sloteffects for aliased slots (pst)
3434 if (alias) {
3435 continue;
3436 }
3437 slottypes[i].sloteffects = (ieDword) strtol(st->QueryField(row,4),NULL,0 );
3438 //setting special slots
3439 if (slottypes[i].slottype&SLOT_ITEM) {
3440 if (slottypes[i].slottype&SLOT_INVENTORY) {
3441 Inventory::SetInventorySlot(i);
3442 } else {
3443 Inventory::SetQuickSlot(i);
3444 }
3445 }
3446 switch (slottypes[i].sloteffects) {
3447 //fist slot, not saved, default weapon
3448 case SLOT_EFFECT_FIST: Inventory::SetFistSlot(i); break;
3449 //magic weapon slot, overrides all weapons
3450 case SLOT_EFFECT_MAGIC: Inventory::SetMagicSlot(i); break;
3451 //weapon slot, Equipping marker is relative to it
3452 case SLOT_EFFECT_MELEE: Inventory::SetWeaponSlot(i); break;
3453 //ranged slot
3454 case SLOT_EFFECT_MISSILE: Inventory::SetRangedSlot(i); break;
3455 //right hand
3456 case SLOT_EFFECT_LEFT: Inventory::SetShieldSlot(i); break;
3457 //head (for averting critical hit)
3458 case SLOT_EFFECT_HEAD: Inventory::SetHeadSlot(i); break;
3459 //armor slot
3460 case SLOT_EFFECT_ITEM: Inventory::SetArmorSlot(i); break;
3461 default:;
3462 }
3463 }
3464 }
3465 return (it && st);
3466 }
3467
FindSlot(unsigned int idx) const3468 ieDword Interface::FindSlot(unsigned int idx) const
3469 {
3470 ieDword i;
3471
3472 for (i=0;i<SlotTypes;i++) {
3473 if (idx==slottypes[i].slot) {
3474 break;
3475 }
3476 }
3477 return i;
3478 }
3479
QuerySlot(unsigned int idx) const3480 ieDword Interface::QuerySlot(unsigned int idx) const
3481 {
3482 if (idx>=SlotTypes) {
3483 return 0;
3484 }
3485 return slottypes[idx].slot;
3486 }
3487
QuerySlotType(unsigned int idx) const3488 ieDword Interface::QuerySlotType(unsigned int idx) const
3489 {
3490 if (idx>=SlotTypes) {
3491 return 0;
3492 }
3493 return slottypes[idx].slottype;
3494 }
3495
QuerySlotID(unsigned int idx) const3496 ieDword Interface::QuerySlotID(unsigned int idx) const
3497 {
3498 if (idx>=SlotTypes) {
3499 return 0;
3500 }
3501 return slottypes[idx].slotid;
3502 }
3503
QuerySlottip(unsigned int idx) const3504 ieDword Interface::QuerySlottip(unsigned int idx) const
3505 {
3506 if (idx>=SlotTypes) {
3507 return 0;
3508 }
3509 return slottypes[idx].slottip;
3510 }
3511
QuerySlotEffects(unsigned int idx) const3512 ieDword Interface::QuerySlotEffects(unsigned int idx) const
3513 {
3514 if (idx>=SlotTypes) {
3515 return 0;
3516 }
3517 return slottypes[idx].sloteffects;
3518 }
3519
QuerySlotFlags(unsigned int idx) const3520 ieDword Interface::QuerySlotFlags(unsigned int idx) const
3521 {
3522 if (idx>=SlotTypes) {
3523 return 0;
3524 }
3525 return slottypes[idx].slotflags;
3526 }
3527
QuerySlotResRef(unsigned int idx) const3528 const char *Interface::QuerySlotResRef(unsigned int idx) const
3529 {
3530 if (idx>=SlotTypes) {
3531 return "";
3532 }
3533 return slottypes[idx].slotresref;
3534 }
3535
GetArmorFailure(unsigned int itemtype) const3536 int Interface::GetArmorFailure(unsigned int itemtype) const
3537 {
3538 if (itemtype>=(unsigned int) ItemTypes) {
3539 return 0;
3540 }
3541 if (slotmatrix[itemtype]&SLOT_ARMOUR) return itemtypedata[itemtype][IDT_FAILURE];
3542 return 0;
3543 }
3544
GetShieldFailure(unsigned int itemtype) const3545 int Interface::GetShieldFailure(unsigned int itemtype) const
3546 {
3547 if (itemtype>=(unsigned int) ItemTypes) {
3548 return 0;
3549 }
3550 if (slotmatrix[itemtype]&SLOT_SHIELD) return itemtypedata[itemtype][IDT_FAILURE];
3551 return 0;
3552 }
3553
GetArmorPenalty(unsigned int itemtype) const3554 int Interface::GetArmorPenalty(unsigned int itemtype) const
3555 {
3556 if (itemtype>=(unsigned int) ItemTypes) {
3557 return 0;
3558 }
3559 if (slotmatrix[itemtype]&SLOT_ARMOUR) return itemtypedata[itemtype][IDT_SKILLPENALTY];
3560 return 0;
3561 }
3562
GetShieldPenalty(unsigned int itemtype) const3563 int Interface::GetShieldPenalty(unsigned int itemtype) const
3564 {
3565 if (itemtype>=(unsigned int) ItemTypes) {
3566 return 0;
3567 }
3568 if (slotmatrix[itemtype]&SLOT_SHIELD) return itemtypedata[itemtype][IDT_SKILLPENALTY];
3569 return 0;
3570 }
3571
GetCriticalMultiplier(unsigned int itemtype) const3572 int Interface::GetCriticalMultiplier(unsigned int itemtype) const
3573 {
3574 if (itemtype>=(unsigned int) ItemTypes) {
3575 return 2;
3576 }
3577 if (slotmatrix[itemtype]&SLOT_WEAPON) return itemtypedata[itemtype][IDT_CRITMULTI];
3578 return 2;
3579 }
3580
GetCriticalRange(unsigned int itemtype) const3581 int Interface::GetCriticalRange(unsigned int itemtype) const
3582 {
3583 if (itemtype>=(unsigned int) ItemTypes) {
3584 return 20;
3585 }
3586 if (slotmatrix[itemtype]&SLOT_WEAPON) return itemtypedata[itemtype][IDT_CRITRANGE];
3587 return 20;
3588 }
3589
3590 // checks the itemtype vs. slottype, and also checks the usability flags
3591 // vs. Actor's stats (alignment, class, race, kit etc.)
CanUseItemType(int slottype,const Item * item,const Actor * actor,bool feedback,bool equipped) const3592 int Interface::CanUseItemType(int slottype, const Item *item, const Actor *actor, bool feedback, bool equipped) const
3593 {
3594 //inventory is a special case, we allow any items to enter it
3595 if ( slottype==-1 ) {
3596 return SLOT_INVENTORY;
3597 }
3598 //if we look for ALL slot types, then SLOT_SHIELD shouldn't interfere
3599 //with twohandedness
3600 //As long as this is an Item, use the ITEM constant
3601 //switch for IE_INV_ITEM_* if it is a CREItem
3602 if (item->Flags&IE_ITEM_TWO_HANDED) {
3603 //if the item is twohanded and there are more slots, drop the shield slot
3604 if (slottype&~SLOT_SHIELD) {
3605 slottype&=~SLOT_SHIELD;
3606 }
3607 if (slottype&SLOT_SHIELD) {
3608 //cannot equip twohanded in offhand
3609 if (feedback) displaymsg->DisplayConstantString(STR_NOT_IN_OFFHAND, DMC_WHITE);
3610 return 0;
3611 }
3612 }
3613
3614 if ( (unsigned int) item->ItemType>=(unsigned int) ItemTypes) {
3615 //invalid itemtype
3616 if (feedback) displaymsg->DisplayConstantString(STR_WRONGITEMTYPE, DMC_WHITE);
3617 return 0;
3618 }
3619
3620 //if actor is supplied, check its usability fields
3621 if (actor) {
3622 //constant strings
3623 int idx = actor->Unusable(item);
3624 if (idx) {
3625 if (feedback) displaymsg->DisplayConstantString(idx, DMC_WHITE);
3626 return 0;
3627 }
3628 //custom strings
3629 ieStrRef str = actor->Disabled(item->Name, item->ItemType);
3630 if (str && !equipped) {
3631 if (feedback) displaymsg->DisplayString(str, DMC_WHITE, 0);
3632 return 0;
3633 }
3634 }
3635
3636 //if any bit is true, the answer counts as true
3637 int ret = (slotmatrix[item->ItemType]&slottype);
3638
3639 if (!ret) {
3640 if (feedback) displaymsg->DisplayConstantString(STR_WRONGITEMTYPE, DMC_WHITE);
3641 return 0;
3642 }
3643
3644 //this warning comes only when feedback is enabled
3645 if (feedback) {
3646 //this was, but that disabled equipping of amber earrings in PST
3647 //if (slotmatrix[item->ItemType]&(SLOT_QUIVER|SLOT_WEAPON|SLOT_ITEM)) {
3648 if (ret&(SLOT_QUIVER|SLOT_WEAPON|SLOT_ITEM)) {
3649 //don't ruin the return variable, it contains the usable slot bits
3650 int flg = 0;
3651 if (ret&SLOT_QUIVER) {
3652 if (item->GetWeaponHeader(true)) flg = 1;
3653 }
3654
3655 if (ret&SLOT_WEAPON) {
3656 //melee
3657 if (item->GetWeaponHeader(false)) flg = 1;
3658 //ranged
3659 if (item->GetWeaponHeader(true)) flg = 1;
3660 }
3661
3662 if (ret&SLOT_ITEM) {
3663 if (item->GetEquipmentHeaderNumber(0)!=0xffff) flg = 1;
3664 }
3665
3666 if (!flg) {
3667 displaymsg->DisplayConstantString(STR_UNUSABLEITEM, DMC_WHITE);
3668 return 0;
3669 }
3670 }
3671 }
3672
3673 return ret;
3674 }
3675
GetMessageLabel() const3676 Label* Interface::GetMessageLabel() const
3677 {
3678 return GetControl<Label>("MsgSys", 1);
3679 }
3680
GetMessageTextArea() const3681 TextArea* Interface::GetMessageTextArea() const
3682 {
3683 return GetControl<TextArea>("MsgSys", 0);
3684 }
3685
SetFeedbackLevel(int level)3686 void Interface::SetFeedbackLevel(int level)
3687 {
3688 FeedbackLevel = level;
3689 }
3690
3691
HasFeedback(int type) const3692 bool Interface::HasFeedback(int type) const
3693 {
3694 return FeedbackLevel & type;
3695 }
3696
3697 static const char *saved_extensions[]={".are",".sto",0};
3698 static const char *saved_extensions_last[]={".tot",".toh",0};
3699
3700 //returns the priority of the file to be saved
3701 //2 - save
3702 //1 - save last
3703 //0 - don't save
SavedExtension(const char * filename)3704 int Interface::SavedExtension(const char *filename)
3705 {
3706 const char *str=strchr(filename,'.');
3707 if (!str) return 0;
3708 int i=0;
3709 while(saved_extensions[i]) {
3710 if (!stricmp(saved_extensions[i], str) ) return 2;
3711 i++;
3712 }
3713 i=0;
3714 while(saved_extensions_last[i]) {
3715 if (!stricmp(saved_extensions_last[i], str) ) return 1;
3716 i++;
3717 }
3718 return 0;
3719 }
3720
3721 static const char *protected_extensions[]={".exe",".dll",".so",0};
3722
3723 //returns true if file should be saved
ProtectedExtension(const char * filename)3724 bool Interface::ProtectedExtension(const char *filename)
3725 {
3726 const char *str=strchr(filename,'.');
3727 if (!str) return false;
3728 int i=0;
3729 while(protected_extensions[i]) {
3730 if (!stricmp(protected_extensions[i], str) ) return true;
3731 i++;
3732 }
3733 return false;
3734 }
3735
RemoveFromCache(const ieResRef resref,SClass_ID ClassID)3736 void Interface::RemoveFromCache(const ieResRef resref, SClass_ID ClassID)
3737 {
3738 char filename[_MAX_PATH];
3739
3740 PathJoinExt(filename, CachePath, resref, TypeExt(ClassID));
3741 unlink ( filename);
3742 }
3743
3744 //this function checks if the path is eligible as a cache
3745 //if it contains a directory, or suspicious file extensions
3746 //we bail out, because the cache will be purged regularly.
StupidityDetector(const char * Pt)3747 bool Interface::StupidityDetector(const char* Pt)
3748 {
3749 char Path[_MAX_PATH];
3750 if (strlcpy(Path, Pt, _MAX_PATH) >= _MAX_PATH) {
3751 Log(ERROR, "Interface", "Trying to check too long path: %s!", Pt);
3752 return true;
3753 }
3754 DirectoryIterator dir(Path);
3755 // scan everything
3756 dir.SetFlags(DirectoryIterator::All, true);
3757
3758 if (!dir) {
3759 print("\n**cannot open**");
3760 return true;
3761 }
3762 do {
3763 const char *name = dir.GetName();
3764 if (dir.IsDirectory()) {
3765 if (name[0] == '.') {
3766 if (name[1] == '\0')
3767 continue;
3768 if (name[1] == '.' && name[2] == '\0')
3769 continue;
3770 }
3771 print("\n**contains another dir**");
3772 return true; //a directory in there???
3773 }
3774 if (ProtectedExtension(name) ) {
3775 print("\n**contains alien files**");
3776 return true; //an executable file in there???
3777 }
3778 } while (++dir);
3779 //ok, we got a good conscience
3780 return false;
3781 }
3782
DelTree(const char * Pt,bool onlysave)3783 void Interface::DelTree(const char* Pt, bool onlysave)
3784 {
3785 char Path[_MAX_PATH];
3786
3787 if (!Pt[0]) return; //Don't delete the root filesystem :)
3788 if (strlcpy(Path, Pt, _MAX_PATH) >= _MAX_PATH) {
3789 Log(ERROR, "Interface", "Trying to delete too long path: %s!", Pt);
3790 return;
3791 }
3792 DirectoryIterator dir(Path);
3793 dir.SetFlags(DirectoryIterator::Files);
3794 if (!dir) {
3795 return;
3796 }
3797 do {
3798 const char *name = dir.GetName();
3799 if (!onlysave || SavedExtension(name) ) {
3800 char dtmp[_MAX_PATH];
3801 dir.GetFullPath(dtmp);
3802 unlink( dtmp );
3803 }
3804 } while (++dir);
3805 }
3806
LoadProgress(int percent)3807 void Interface::LoadProgress(int percent)
3808 {
3809 vars->SetAt("Progress", percent);
3810
3811 WindowManager::CursorFeedback cur = winmgr->SetCursorFeedback(WindowManager::MOUSE_NONE);
3812 winmgr->DrawWindows();
3813 winmgr->SetCursorFeedback(cur);
3814
3815 Window* loadwin = GetWindow(0, "LOADWIN");
3816 if (loadwin) {
3817 // loadwin is NULL when LoadMap is called and passes false for the loadscreen param
3818 loadwin->RedrawControls("Progress", percent);
3819 }
3820
3821 video->SwapBuffers();
3822 }
3823
ReleaseDraggedItem()3824 void Interface::ReleaseDraggedItem()
3825 {
3826 DraggedItem = nullptr;
3827 winmgr->GetGameWindow()->SetCursor(nullptr);
3828 }
3829
DragItem(CREItem * item,const ieResRef)3830 void Interface::DragItem(CREItem *item, const ieResRef /*Picture*/)
3831 {
3832 //We should drop the dragged item and pick this up,
3833 //we shouldn't have a valid DraggedItem at this point.
3834 //Anyway, if there is still a dragged item, it will be destroyed.
3835 if (DraggedItem) {
3836 Log(WARNING, "Core", "Forgot to call ReleaseDraggedItem when leaving inventory (item destroyed)!");
3837 delete DraggedItem->item;
3838 DraggedItem = nullptr;
3839 }
3840
3841 if (!item) return;
3842
3843 DraggedItem.reset(new ItemDragOp(item));
3844 winmgr->GetGameWindow()->SetCursor(DraggedItem->cursor);
3845 }
3846
ReadItemTable(const ieResRef TableName,const char * Prefix)3847 bool Interface::ReadItemTable(const ieResRef TableName, const char * Prefix)
3848 {
3849 ieResRef ItemName;
3850 int i,j;
3851
3852 AutoTable tab(TableName);
3853 if (!tab) {
3854 return false;
3855 }
3856 i=tab->GetRowCount();
3857 for(j=0;j<i;j++) {
3858 if (Prefix) {
3859 snprintf(ItemName,sizeof(ItemName),"%s%02d",Prefix, (j+1)%100);
3860 } else {
3861 strnlwrcpy(ItemName,tab->GetRowName(j), 8);
3862 }
3863 //Variable elements are free'd, so we have to use malloc
3864 //well, not anymore, we can use ReleaseFunction
3865 int l=tab->GetColumnCount(j);
3866 if (l<1) continue;
3867 int cl = atoi(tab->GetColumnName(0));
3868 ItemList *itemlist = new ItemList(l, cl);
3869 for(int k=0;k<l;k++) {
3870 strnlwrcpy(itemlist->ResRefs[k],tab->QueryField(j,k), 8);
3871 }
3872 RtRows->SetAt(ItemName, (void*)itemlist);
3873 }
3874 return true;
3875 }
3876
ReadRandomItems()3877 bool Interface::ReadRandomItems()
3878 {
3879 ieResRef RtResRef;
3880 int i;
3881
3882 ieDword difflev=0; //rt norm or rt fury
3883 vars->Lookup("Nightmare Mode", difflev);
3884 if (RtRows) {
3885 RtRows->RemoveAll(ReleaseItemList);
3886 }
3887 else {
3888 RtRows=new Variables(10, 17); //block size, hash table size
3889 if (!RtRows) {
3890 return false;
3891 }
3892 RtRows->SetType( GEM_VARIABLES_POINTER );
3893 }
3894 AutoTable tab("randitem");
3895 if (!tab) {
3896 return false;
3897 }
3898 if (difflev>=tab->GetColumnCount()) {
3899 difflev = tab->GetColumnCount()-1;
3900 }
3901
3902 //the gold item
3903 strnlwrcpy( GoldResRef, tab->QueryField(size_t(0), size_t(0)), 8);
3904 if ( GoldResRef[0]=='*' ) {
3905 return false;
3906 }
3907 strnlwrcpy( RtResRef, tab->QueryField( 1, difflev ), 8);
3908 i=atoi( RtResRef );
3909 if (i<1) {
3910 ReadItemTable( RtResRef, 0 ); //reading the table itself
3911 return true;
3912 }
3913 if (i>5) {
3914 i=5;
3915 }
3916 while(i--) {
3917 strnlwrcpy( RtResRef, tab->QueryField(2+i,difflev), 8);
3918 ReadItemTable( RtResRef,tab->GetRowName(2+i) );
3919 }
3920 return true;
3921 }
3922
ReadItem(DataStream * str)3923 CREItem *Interface::ReadItem(DataStream *str)
3924 {
3925 CREItem *itm = new CREItem();
3926 if (ReadItem(str, itm)) return itm;
3927 delete itm;
3928 return NULL;
3929 }
3930
ReadItem(DataStream * str,CREItem * itm)3931 CREItem *Interface::ReadItem(DataStream *str, CREItem *itm)
3932 {
3933 str->ReadResRef( itm->ItemResRef );
3934 str->ReadWord( &itm->Expired );
3935 str->ReadWord( &itm->Usages[0] );
3936 str->ReadWord( &itm->Usages[1] );
3937 str->ReadWord( &itm->Usages[2] );
3938 str->ReadDword( &itm->Flags );
3939 if (ResolveRandomItem(itm)) {
3940 SanitizeItem(itm);
3941 return itm;
3942 }
3943 return NULL;
3944 }
3945
3946 //Make sure the item attributes are valid
3947 //we don't update all flags here because some need to be set later (like
3948 //unmovable items in containers (e.g. the bg2 portal key) so that they
3949 //can actually be picked up)
SanitizeItem(CREItem * item) const3950 void Interface::SanitizeItem(CREItem *item) const
3951 {
3952 //the stacked flag will be set by the engine if the item is indeed stacked
3953 //this is to fix buggy saves so TakeItemNum works
3954 //the equipped bit is also reset
3955 item->Flags &= ~(IE_INV_ITEM_STACKED|IE_INV_ITEM_EQUIPPED);
3956
3957 //this is for converting IWD items magic flag
3958 if (MagicBit && (item->Flags & IE_INV_ITEM_UNDROPPABLE)) {
3959 item->Flags |= IE_INV_ITEM_MAGICAL;
3960 item->Flags &= ~IE_INV_ITEM_UNDROPPABLE;
3961 }
3962 if (core->HasFeature(GF_NO_UNDROPPABLE)) {
3963 item->Flags &= ~IE_INV_ITEM_UNDROPPABLE;
3964 }
3965
3966 Item *itm = gamedata->GetItem(item->ItemResRef, true);
3967 if (itm) {
3968
3969 item->MaxStackAmount = itm->MaxStackAmount;
3970 //if item is stacked mark it as so
3971 if (itm->MaxStackAmount) {
3972 item->Flags |= IE_INV_ITEM_STACKED;
3973 if (item->Usages[0] == 0) {
3974 item->Usages[0] = 1;
3975 }
3976 } else {
3977 //set charge counters for non-rechargeable items if their charge is zero
3978 //set charge counters for items not using charges to one
3979 for (int i = 0; i < CHARGE_COUNTERS; i++) {
3980 ITMExtHeader *h = itm->GetExtHeader(i);
3981 if (h) {
3982 if (item->Usages[i] == 0) {
3983 if (!(h->RechargeFlags&IE_ITEM_RECHARGE)) {
3984 //HACK: the original (bg2) allows for 0 charged gems
3985 if (h->Charges) {
3986 item->Usages[i] = h->Charges;
3987 } else {
3988 item->Usages[i] = 1;
3989 }
3990 }
3991 } else if (h->Charges == 0) {
3992 item->Usages[i] = 1;
3993 }
3994 } else {
3995 item->Usages[i] = 0;
3996 }
3997 }
3998 }
3999
4000 //simply adding the item flags to the slot
4001 item->Flags |= (itm->Flags<<8);
4002 //some slot flags might be affected by the item flags
4003 if (!(item->Flags & IE_INV_ITEM_CRITICAL)) {
4004 item->Flags |= IE_INV_ITEM_DESTRUCTIBLE;
4005 }
4006
4007 // pst has no stolen flag, but "steel" in its place
4008 if ((item->Flags & IE_INV_ITEM_STOLEN2) && !HasFeature(GF_PST_STATE_FLAGS)) {
4009 item->Flags |= IE_INV_ITEM_STOLEN;
4010 }
4011
4012 //auto identify basic items
4013 if (!itm->LoreToID) {
4014 item->Flags |= IE_INV_ITEM_IDENTIFIED;
4015 }
4016
4017 gamedata->FreeItem(itm, item->ItemResRef, false);
4018 }
4019 }
4020
4021 #define MAX_LOOP 10
4022
4023 //This function generates random items based on the randitem.2da file
4024 //there could be a loop, but we don't want to freeze, so there is a limit
ResolveRandomItem(CREItem * itm)4025 bool Interface::ResolveRandomItem(CREItem *itm)
4026 {
4027 if (!RtRows) return true;
4028 for(int loop=0;loop<MAX_LOOP;loop++) {
4029 int i,j,k;
4030 char *endptr;
4031 ieResRef NewItem;
4032
4033 void* lookup;
4034 if ( !RtRows->Lookup( itm->ItemResRef, lookup ) ) {
4035 if (!gamedata->Exists(itm->ItemResRef, IE_ITM_CLASS_ID)) {
4036 Log(ERROR, "Interface", "Nonexistent random item (bad table entry) detected: %s", itm->ItemResRef);
4037 return false;
4038 }
4039 return true;
4040 }
4041 ItemList *itemlist = (ItemList*)lookup;
4042 if (itemlist->WeightOdds) {
4043 //instead of 1d19 we calculate with 2d10 (which also has 19 possible values)
4044 i=Roll(2,(itemlist->Count+1)/2,-2);
4045 } else {
4046 i=Roll(1,itemlist->Count,-1);
4047 }
4048 strnlwrcpy( NewItem, itemlist->ResRefs[i], 8);
4049 char *p = strchr(NewItem, '*');
4050 if (p) {
4051 *p=0; //doing this so endptr is ok
4052 k=strtol(p+1,NULL,10);
4053 } else {
4054 k=1;
4055 }
4056 j=strtol(NewItem,&endptr,10);
4057 if (j<1) {
4058 j=1;
4059 }
4060 if (*endptr) {
4061 strnlwrcpy(itm->ItemResRef, NewItem, 8);
4062 } else {
4063 strnlwrcpy(itm->ItemResRef, GoldResRef, 8);
4064 }
4065 if ( !memcmp( itm->ItemResRef,"no_drop",8 ) ) {
4066 itm->ItemResRef[0]=0;
4067 }
4068 if (!itm->ItemResRef[0]) {
4069 return false;
4070 }
4071 itm->Usages[0]=(ieWord) Roll(j,k,0);
4072 }
4073 Log(ERROR, "Interface", "Loop detected while generating random item:%s",
4074 itm->ItemResRef);
4075 return false;
4076 }
4077
GetEffect(ieDword opcode)4078 Effect *Interface::GetEffect(ieDword opcode)
4079 {
4080 if (opcode==0xffffffff) {
4081 return NULL;
4082 }
4083 Effect *fx = new Effect();
4084 if (!fx) {
4085 return NULL;
4086 }
4087 memset(fx,0,sizeof(Effect));
4088 fx->Opcode=opcode;
4089 return fx;
4090 }
4091
GetCurrentContainer()4092 Container *Interface::GetCurrentContainer()
4093 {
4094 return CurrentContainer;
4095 }
4096
CloseCurrentContainer()4097 int Interface::CloseCurrentContainer()
4098 {
4099 UseContainer = false;
4100 if ( !CurrentContainer) {
4101 return -1;
4102 }
4103 //remove empty ground piles on closeup
4104 CurrentContainer->GetCurrentArea()->TMap->CleanupContainer(CurrentContainer);
4105 CurrentContainer = NULL;
4106 return 0;
4107 }
4108
SetCurrentContainer(Actor * actor,Container * arg,bool flag)4109 void Interface::SetCurrentContainer(Actor *actor, Container *arg, bool flag)
4110 {
4111 //abort action if the first selected PC isn't the original actor
4112 if (actor!=GetFirstSelectedPC(false)) {
4113 CurrentContainer = NULL;
4114 return;
4115 }
4116 CurrentContainer = arg;
4117 UseContainer = flag;
4118 }
4119
GetCurrentStore()4120 Store *Interface::GetCurrentStore()
4121 {
4122 return CurrentStore;
4123 }
4124
CloseCurrentStore()4125 void Interface::CloseCurrentStore()
4126 {
4127 gamedata->SaveStore(CurrentStore);
4128 CurrentStore = NULL;
4129 }
4130
SetCurrentStore(const ieResRef resname,ieDword owner)4131 Store *Interface::SetCurrentStore(const ieResRef resname, ieDword owner)
4132 {
4133 if (CurrentStore) {
4134 if (!strnicmp(CurrentStore->Name, resname, 8)) {
4135 return CurrentStore;
4136 }
4137
4138 //not simply delete the old store, but save it
4139 CloseCurrentStore();
4140 }
4141
4142 CurrentStore = gamedata->GetStore(resname);
4143 if (CurrentStore == NULL) {
4144 return NULL;
4145 }
4146 if (owner) {
4147 CurrentStore->SetOwnerID(owner);
4148 }
4149 return CurrentStore;
4150 }
4151
SetMouseScrollSpeed(int speed)4152 void Interface::SetMouseScrollSpeed(int speed) {
4153 mousescrollspd = (speed+1)*2;
4154 }
4155
GetMouseScrollSpeed()4156 int Interface::GetMouseScrollSpeed() {
4157 return mousescrollspd;
4158 }
4159
GetRumour(const ieResRef dlgref)4160 ieStrRef Interface::GetRumour(const ieResRef dlgref)
4161 {
4162 PluginHolder<DialogMgr> dm(IE_DLG_CLASS_ID);
4163 dm->Open(gamedata->GetResource(dlgref, IE_DLG_CLASS_ID));
4164 Dialog *dlg = dm->GetDialog();
4165
4166 if (!dlg) {
4167 Log(ERROR, "Interface", "Cannot load dialog: %s", dlgref);
4168 return ieStrRef(-1);
4169 }
4170 Scriptable *pc = game->GetSelectedPCSingle(false);
4171
4172 // forcefully rerandomize
4173 RandomNumValue = RAND_ALL();
4174 ieStrRef ret = ieStrRef(-1);
4175
4176 int i = dlg->FindRandomState( pc );
4177 if (i>=0 ) {
4178 ret = dlg->GetState( i )->StrRef;
4179 }
4180 delete dlg;
4181 return ret;
4182 }
4183
4184 //plays stock sound listed in defsound.2da
PlaySound(int index,unsigned int channel)4185 Holder<SoundHandle> Interface::PlaySound(int index, unsigned int channel)
4186 {
4187 if (index<=DSCount) {
4188 return AudioDriver->Play(DefSound[index], channel);
4189 }
4190 return NULL;
4191 }
4192
GetFirstSelectedPC(bool forced)4193 Actor *Interface::GetFirstSelectedPC(bool forced)
4194 {
4195 Actor *ret = NULL;
4196 int slot = 0;
4197 int partySize = game->GetPartySize( false );
4198 if (!partySize) return NULL;
4199 for (int i = 0; i < partySize; i++) {
4200 Actor* actor = game->GetPC( i,false );
4201 if (actor->IsSelected()) {
4202 if (actor->InParty<slot || !ret) {
4203 ret = actor;
4204 slot = actor->InParty;
4205 }
4206 }
4207 }
4208
4209 if (forced && !ret) {
4210 return game->FindPC((unsigned int) 1);
4211 }
4212 return ret;
4213 }
4214
GetFirstSelectedActor()4215 Actor *Interface::GetFirstSelectedActor()
4216 {
4217 if (game->selected.size()) {
4218 return game->selected[0];
4219 }
4220 return NULL;
4221 }
4222
HasCurrentArea() const4223 bool Interface::HasCurrentArea() const
4224 {
4225 if (!game) return false;
4226 return game->GetCurrentArea() != nullptr;
4227 }
4228
4229 //this is used only for the console
GetCursorSprite()4230 Holder<Sprite2D> Interface::GetCursorSprite()
4231 {
4232 Holder<Sprite2D> spr = gamedata->GetBAMSprite(TextCursorBam, 0, 0);
4233 if (spr)
4234 {
4235 if(HasFeature(GF_OVERRIDE_CURSORPOS))
4236 {
4237 spr->Frame.x=1;
4238 spr->Frame.y=spr->Frame.h-1;
4239 }
4240 }
4241 return spr;
4242 }
4243
GetScrollCursorSprite(int frameNum,int spriteNum)4244 Holder<Sprite2D> Interface::GetScrollCursorSprite(int frameNum, int spriteNum)
4245 {
4246 return gamedata->GetBAMSprite(ScrollCursorBam, frameNum, spriteNum, true);
4247 }
4248
4249 /* we should return -1 if it isn't gold, otherwise return the gold value */
CanMoveItem(const CREItem * item) const4250 int Interface::CanMoveItem(const CREItem *item) const
4251 {
4252 //This is an inventory slot, switch to IE_ITEM_* if you use Item
4253 if (item->Flags & IE_INV_ITEM_UNDROPPABLE && !HasFeature(GF_NO_DROP_CAN_MOVE)) {
4254 return 0;
4255 }
4256 //not gold, we allow only one single coin ResRef, this is good
4257 //for all of the original games
4258 if (strnicmp(item->ItemResRef, GoldResRef, 8 ) )
4259 return -1;
4260 //gold, returns the gold value (stack size)
4261 return item->Usages[0];
4262 }
4263
4264 // dealing with applying effects
ApplySpell(const ieResRef resname,Actor * actor,Scriptable * caster,int level)4265 void Interface::ApplySpell(const ieResRef resname, Actor *actor, Scriptable *caster, int level)
4266 {
4267 Spell *spell = gamedata->GetSpell(resname);
4268 if (!spell) {
4269 return;
4270 }
4271
4272 int header = spell->GetHeaderIndexFromLevel(level);
4273 EffectQueue *fxqueue = spell->GetEffectBlock(caster, actor->Pos, header, level);
4274
4275 ApplyEffectQueue(fxqueue, actor, caster, actor->Pos);
4276 delete fxqueue;
4277 }
4278
ApplySpellPoint(const ieResRef resname,Map * area,const Point & pos,Scriptable * caster,int level)4279 void Interface::ApplySpellPoint(const ieResRef resname, Map* area, const Point &pos, Scriptable *caster, int level)
4280 {
4281 Spell *spell = gamedata->GetSpell(resname);
4282 if (!spell) {
4283 return;
4284 }
4285 int header = spell->GetHeaderIndexFromLevel(level);
4286 Projectile *pro = spell->GetProjectile(caster, header, level, pos);
4287 pro->SetCaster(caster->GetGlobalID(), level);
4288 area->AddProjectile(pro, caster->Pos, pos);
4289 }
4290
4291 //-1 means the effect was reflected back to the caster
4292 //0 means the effect was resisted and should be removed
4293 //1 means the effect was applied
ApplyEffect(Effect * effect,Actor * actor,Scriptable * caster)4294 int Interface::ApplyEffect(Effect *effect, Actor *actor, Scriptable *caster)
4295 {
4296 if (!effect) {
4297 return 0;
4298 }
4299
4300 EffectQueue *fxqueue = new EffectQueue();
4301 //AddEffect now copies the fx data, please delete your effect reference
4302 //if you created it. (Don't delete cached references)
4303 fxqueue->AddEffect( effect );
4304 int res = ApplyEffectQueue(fxqueue, actor, caster);
4305 delete fxqueue;
4306 return res;
4307 }
4308
ApplyEffectQueue(EffectQueue * fxqueue,Actor * actor,Scriptable * caster)4309 int Interface::ApplyEffectQueue(EffectQueue *fxqueue, Actor *actor, Scriptable *caster)
4310 {
4311 Point p;
4312 p.empty(); //the effect should have all its coordinates already set
4313 return ApplyEffectQueue(fxqueue, actor, caster, p);
4314 }
4315
4316 //FIXME: AddAllEffects will directly apply the effects outside of the mechanisms of Actor::RefreshEffects
4317 //This means, pcf functions may not be executed when the effect is first applied
4318 //Adding this new effect block via RefreshEffects is possible, but that might apply existing effects twice
4319
ApplyEffectQueue(EffectQueue * fxqueue,Actor * actor,Scriptable * caster,Point p)4320 int Interface::ApplyEffectQueue(EffectQueue *fxqueue, Actor *actor, Scriptable *caster, Point p)
4321 {
4322 int res = fxqueue->CheckImmunity ( actor );
4323 if (res) {
4324 if (res == -1 && caster) {
4325 //bounced back at a nonliving caster
4326 if (caster->Type!=ST_ACTOR) {
4327 return 0;
4328 }
4329 actor = (Actor *) caster;
4330 }
4331 fxqueue->SetOwner( caster );
4332
4333 if (fxqueue->AddAllEffects( actor, p)==FX_NOT_APPLIED) {
4334 res=0;
4335 }
4336 }
4337 return res;
4338 }
4339
GetEffect(const ieResRef resname,int level,const Point & p)4340 Effect *Interface::GetEffect(const ieResRef resname, int level, const Point &p)
4341 {
4342 //Don't free this reference, it is cached!
4343 Effect *effect = gamedata->GetEffect(resname);
4344 if (!effect) {
4345 return NULL;
4346 }
4347 if (!level) {
4348 level = 1;
4349 }
4350 effect->Power = level;
4351 effect->PosX=p.x;
4352 effect->PosY=p.y;
4353 return effect;
4354 }
4355
4356 // dealing with saved games
SwapoutArea(Map * map)4357 int Interface::SwapoutArea(Map *map)
4358 {
4359 //refuse to save ambush areas, for example
4360 if (map->AreaFlags & AF_NOSAVE) {
4361 Log(DEBUG, "Core", "Not saving area %s",
4362 map->GetScriptName());
4363 RemoveFromCache(map->GetScriptName(), IE_ARE_CLASS_ID);
4364 return 0;
4365 }
4366
4367 PluginHolder<MapMgr> mm(IE_ARE_CLASS_ID);
4368 if (mm == nullptr) {
4369 return -1;
4370 }
4371 int size = mm->GetStoredFileSize (map);
4372 if (size > 0) {
4373 //created streams are always autofree (close file on destruct)
4374 //this one will be destructed when we return from here
4375 FileStream str;
4376
4377 str.Create( map->GetScriptName(), IE_ARE_CLASS_ID );
4378 int ret = mm->PutArea (&str, map);
4379 if (ret <0) {
4380 Log(WARNING, "Core", "Area removed: %s",
4381 map->GetScriptName());
4382 RemoveFromCache(map->GetScriptName(), IE_ARE_CLASS_ID);
4383 }
4384 } else {
4385 Log(WARNING, "Core", "Area removed: %s",
4386 map->GetScriptName());
4387 RemoveFromCache(map->GetScriptName(), IE_ARE_CLASS_ID);
4388 }
4389 //make sure the stream isn't connected to sm, or it will be double freed
4390 return 0;
4391 }
4392
WriteCharacter(const char * name,Actor * actor)4393 int Interface::WriteCharacter(const char *name, Actor *actor)
4394 {
4395 char Path[_MAX_PATH];
4396
4397 PathJoin(Path, GamePath, GameCharactersPath, nullptr);
4398 if (!actor) {
4399 return -1;
4400 }
4401 PluginHolder<ActorMgr> gm(IE_CRE_CLASS_ID);
4402 if (gm == nullptr) {
4403 return -1;
4404 }
4405
4406 //str is freed
4407 {
4408 FileStream str;
4409
4410 if (!str.Create( Path, name, IE_CHR_CLASS_ID )
4411 || (gm->PutActor(&str, actor, true) < 0)) {
4412 Log(WARNING, "Core", "Character cannot be saved: %s", name);
4413 return -1;
4414 }
4415 }
4416
4417 //write the BIO string
4418 if (!HasFeature(GF_NO_BIOGRAPHY)) {
4419 FileStream str;
4420
4421 str.Create( Path, name, IE_BIO_CLASS_ID );
4422 //never write the string reference into this string
4423 char *tmp = GetCString(actor->GetVerbalConstant(VB_BIO),IE_STR_STRREFOFF);
4424 str.Write (tmp, strlen(tmp));
4425 free(tmp);
4426 }
4427 return 0;
4428 }
4429
WriteGame(const char * folder)4430 int Interface::WriteGame(const char *folder)
4431 {
4432 PluginHolder<SaveGameMgr> gm(IE_GAM_CLASS_ID);
4433 if (gm == nullptr) {
4434 return -1;
4435 }
4436
4437 int size = gm->GetStoredFileSize (game);
4438 if (size > 0) {
4439 //created streams are always autofree (close file on destruct)
4440 //this one will be destructed when we return from here
4441 FileStream str;
4442
4443 str.Create( folder, GameNameResRef, IE_GAM_CLASS_ID );
4444 int ret = gm->PutGame (&str, game);
4445 if (ret <0) {
4446 Log(WARNING, "Core", "Game cannot be saved: %s", folder);
4447 return -1;
4448 }
4449 } else {
4450 Log(WARNING, "Core", "Internal error, game cannot be saved: %s", folder);
4451 return -1;
4452 }
4453 return 0;
4454 }
4455
WriteWorldMap(const char * folder)4456 int Interface::WriteWorldMap(const char *folder)
4457 {
4458 PluginHolder<WorldMapMgr> wmm(IE_WMP_CLASS_ID);
4459 if (wmm == nullptr) {
4460 return -1;
4461 }
4462
4463 if (!WorldMapName[1].IsEmpty()) {
4464 worldmap->SetSingle(false);
4465 }
4466
4467 int size1 = wmm->GetStoredFileSize (worldmap, 0);
4468 int size2 = 1; //just a dummy value
4469
4470 //if size is 0 for the first worldmap, then there is a problem
4471 if (!worldmap->IsSingle() && (size1>0) ) {
4472 size2=wmm->GetStoredFileSize (worldmap, 1);
4473 }
4474
4475 int ret = 0;
4476 if ((size1 < 0) || (size2<0) ) {
4477 ret=-1;
4478 } else {
4479 //created streams are always autofree (close file on destruct)
4480 //this one will be destructed when we return from here
4481 FileStream str1;
4482 FileStream str2;
4483
4484 str1.Create( folder, WorldMapName[0], IE_WMP_CLASS_ID );
4485 if (!worldmap->IsSingle()) {
4486 str2.Create( folder, WorldMapName[1], IE_WMP_CLASS_ID );
4487 }
4488 ret = wmm->PutWorldMap (&str1, &str2, worldmap);
4489 }
4490 if (ret <0) {
4491 Log(WARNING, "Core", "Internal error, worldmap cannot be saved: %s", folder);
4492 return -1;
4493 }
4494 return 0;
4495 }
4496
CompressSave(const char * folder)4497 int Interface::CompressSave(const char *folder)
4498 {
4499 FileStream str;
4500
4501 str.Create( folder, GameNameResRef, IE_SAV_CLASS_ID );
4502 DirectoryIterator dir(CachePath);
4503 if (!dir) {
4504 return -1;
4505 }
4506 PluginHolder<ArchiveImporter> ai(IE_SAV_CLASS_ID);
4507 ai->CreateArchive( &str);
4508
4509 dir.SetFlags(DirectoryIterator::Files);
4510 //.tot and .toh should be saved last, because they are updated when an .are is saved
4511 int priority=2;
4512 while(priority) {
4513 do {
4514 const char *name = dir.GetName();
4515 if (SavedExtension(name)==priority) {
4516 char dtmp[_MAX_PATH];
4517 dir.GetFullPath(dtmp);
4518 FileStream fs;
4519 if (!fs.Open(dtmp)) {
4520 Log(ERROR, "Interface", "Failed to open \"%s\".", dtmp);
4521 }
4522 ai->AddToSaveGame(&str, &fs);
4523 }
4524 } while (++dir);
4525 //reopen list for the second round
4526 priority--;
4527 if (priority>0) {
4528 dir.Rewind();
4529 }
4530 }
4531 return 0;
4532 }
4533
GetRareSelectSoundCount() const4534 int Interface::GetRareSelectSoundCount() const { return NumRareSelectSounds; }
4535
GetMaximumAbility() const4536 int Interface::GetMaximumAbility() const { return MaximumAbility; }
4537
GetStrengthBonus(int column,int value,int ex) const4538 int Interface::GetStrengthBonus(int column, int value, int ex) const
4539 {
4540 //to hit, damage, open doors, weight allowance
4541 if (column<0 || column>3)
4542 return -9999;
4543
4544 if (value<0)
4545 value = 0;
4546 else if (value>MaximumAbility)
4547 value = MaximumAbility;
4548
4549 int bonus = 0;
4550 // only 18 (human max) has the differentiating extension
4551 if (value == 18 && !HasFeature(GF_3ED_RULES)) {
4552 if (ex<0)
4553 ex=0;
4554 else if (ex>100)
4555 ex=100;
4556 bonus += strmodex[column*101+ex];
4557 }
4558
4559 return strmod[column*(MaximumAbility+1)+value] + bonus;
4560 }
4561
4562 //The maze columns are used only in the maze spell, no need to restrict them further
GetIntelligenceBonus(int column,int value) const4563 int Interface::GetIntelligenceBonus(int column, int value) const
4564 {
4565 //learn spell, max spell level, max spell number on level, maze duration dice, maze duration dice size
4566 if (column<0 || column>4) return -9999;
4567
4568 return intmod[column*(MaximumAbility+1)+value];
4569 }
4570
GetDexterityBonus(int column,int value) const4571 int Interface::GetDexterityBonus(int column, int value) const
4572 {
4573 //no dexmod in iwd2 and only one type of modifier
4574 if (HasFeature(GF_3ED_RULES)) {
4575 return value/2-5;
4576 }
4577
4578 //reaction, missile, ac
4579 if (column<0 || column>2)
4580 return -9999;
4581
4582 return dexmod[column*(MaximumAbility+1)+value];
4583 }
4584
GetConstitutionBonus(int column,int value) const4585 int Interface::GetConstitutionBonus(int column, int value) const
4586 {
4587 //no conmod in iwd2 and also no regenation bonus
4588 if (HasFeature(GF_3ED_RULES)) {
4589 if (column == STAT_CON_HP_REGEN) {
4590 return 0;
4591 }
4592 return value/2-5;
4593 }
4594
4595 //normal, warrior, minimum, regen hp, regen fatigue
4596 if (column<0 || column>4)
4597 return -9999;
4598
4599 return conmod[column*(MaximumAbility+1)+value];
4600 }
4601
GetCharismaBonus(int column,int) const4602 int Interface::GetCharismaBonus(int column, int /*value*/) const
4603 {
4604 // store price reduction
4605 if (column<0 || column>(MaximumAbility-1))
4606 return -9999;
4607
4608 return chrmod[column];
4609 }
4610
GetLoreBonus(int column,int value) const4611 int Interface::GetLoreBonus(int column, int value) const
4612 {
4613 //no lorebon in iwd2 - lore is a skill
4614 if (HasFeature(GF_3ED_RULES)) return 0;
4615
4616 if (column<0 || column>0)
4617 return -9999;
4618
4619 return lorebon[value];
4620 }
4621
GetWisdomBonus(int column,int value) const4622 int Interface::GetWisdomBonus(int column, int value) const
4623 {
4624 if (!wisbon) return 0;
4625
4626 // xp bonus
4627 if (column<0 || column>0)
4628 return -9999;
4629
4630 return wisbon[value];
4631 }
4632
GetReputationMod(int column) const4633 int Interface::GetReputationMod(int column) const
4634 {
4635 int reputation = game->Reputation / 10 - 1;
4636
4637 if (column<0 || column>8) {
4638 return -9999;
4639 }
4640 if (reputation > 19) {
4641 reputation = 19;
4642 }
4643 if (reputation < 0) {
4644 reputation = 0;
4645 }
4646
4647 return reputationmod[reputation][column];
4648 }
4649
TogglePause()4650 PauseSetting Interface::TogglePause()
4651 {
4652 const GameControl *gc = GetGameControl();
4653 if (!gc) return PAUSE_OFF;
4654 PauseSetting pause = (PauseSetting)(~gc->GetDialogueFlags()&DF_FREEZE_SCRIPTS);
4655 if (SetPause(pause)) return pause;
4656 return (PauseSetting)(gc->GetDialogueFlags()&DF_FREEZE_SCRIPTS);
4657 }
4658
SetPause(PauseSetting pause,int flags)4659 bool Interface::SetPause(PauseSetting pause, int flags)
4660 {
4661 GameControl *gc = GetGameControl();
4662
4663 //don't allow soft pause in cutscenes and dialog
4664 if (!(flags&PF_FORCED) && InCutSceneMode()) gc = NULL;
4665
4666 if (gc && ((bool)(gc->GetDialogueFlags()&DF_FREEZE_SCRIPTS) != (bool)pause)) { // already paused
4667 int strref;
4668 if (pause) {
4669 strref = STR_PAUSED;
4670 gc->SetDialogueFlags(DF_FREEZE_SCRIPTS, OP_OR);
4671 } else {
4672 strref = STR_UNPAUSED;
4673 gc->SetDialogueFlags(DF_FREEZE_SCRIPTS, OP_NAND);
4674 }
4675 if (!(flags&PF_QUIET) ) {
4676 if (pause) gc->SetDisplayText(strref, 0); // time 0 = removed instantly on unpause (for pst)
4677 displaymsg->DisplayConstantString(strref, DMC_RED);
4678 }
4679 return true;
4680 }
4681 return false;
4682 }
4683
Autopause(ieDword flag,Scriptable * target)4684 bool Interface::Autopause(ieDword flag, Scriptable* target)
4685 {
4686 ieDword autopause_flags = 0;
4687 vars->Lookup("Auto Pause State", autopause_flags);
4688
4689 if (autopause_flags & (1<<flag)) {
4690 if (SetPause(PAUSE_ON, PF_QUIET)) {
4691 displaymsg->DisplayConstantString(STR_AP_UNUSABLE+flag, DMC_RED);
4692
4693 ieDword autopause_center = 0;
4694 vars->Lookup("Auto Pause Center", autopause_center);
4695 if (autopause_center && target) {
4696 GameControl* gc = GetGameControl();
4697 gc->MoveViewportTo(target->Pos, true);
4698
4699 if (target->Type == ST_ACTOR && ((Actor *)target)->GetStat(IE_EA) < EA_GOODCUTOFF) {
4700 core->GetGame()->SelectActor((Actor *)target, true, SELECT_REPLACE);
4701 }
4702 }
4703 return true;
4704 }
4705 }
4706 return false;
4707 }
4708
RegisterOpcodes(int count,const EffectDesc * opcodes)4709 void Interface::RegisterOpcodes(int count, const EffectDesc *opcodes)
4710 {
4711 EffectQueue_RegisterOpcodes(count, opcodes);
4712 }
4713
SetInfoTextColor(const Color & color)4714 void Interface::SetInfoTextColor(const Color &color)
4715 {
4716 InfoTextColor = color;
4717 }
4718
4719 //todo row?
GetResRefFrom2DA(const ieResRef resref,ieResRef resource1,ieResRef resource2,ieResRef resource3)4720 void Interface::GetResRefFrom2DA(const ieResRef resref, ieResRef resource1, ieResRef resource2, ieResRef resource3)
4721 {
4722 if (!resource1) {
4723 return;
4724 }
4725 resource1[0]=0;
4726 if (resource2) {
4727 resource2[0]=0;
4728 }
4729 if (resource3) {
4730 resource3[0]=0;
4731 }
4732 AutoTable tab(resref);
4733 if (tab) {
4734 unsigned int cols = tab->GetColumnCount();
4735 unsigned int row = (unsigned int) Roll(1,tab->GetRowCount(),-1);
4736 strnuprcpy(resource1, tab->QueryField(row,0), 8);
4737 if (resource2 && cols>1)
4738 strnuprcpy(resource2, tab->QueryField(row,1), 8);
4739 if (resource3 && cols>2)
4740 strnuprcpy(resource3, tab->QueryField(row,2), 8);
4741 }
4742 }
4743
GetListFrom2DAInternal(const ieResRef resref)4744 ieDword *Interface::GetListFrom2DAInternal(const ieResRef resref)
4745 {
4746 ieDword *ret;
4747
4748 AutoTable tab(resref);
4749 if (tab) {
4750 ieDword cnt = tab->GetRowCount();
4751 ret = (ieDword *) malloc((1+cnt)*sizeof(ieDword));
4752 ret[0]=cnt;
4753 while(cnt) {
4754 ret[cnt]=strtol(tab->QueryField(cnt-1, 0),NULL, 0);
4755 cnt--;
4756 }
4757 return ret;
4758 }
4759 ret = (ieDword *) malloc(sizeof(ieDword));
4760 ret[0]=0;
4761 return ret;
4762 }
4763
GetListFrom2DA(const ieResRef tablename)4764 ieDword* Interface::GetListFrom2DA(const ieResRef tablename)
4765 {
4766 ieDword *list = NULL;
4767
4768 if (!lists->Lookup(tablename, (void *&) list)) {
4769 list = GetListFrom2DAInternal(tablename);
4770 lists->SetAt(tablename, list);
4771 }
4772
4773 return list;
4774 }
4775
4776 //returns a numeric value associated with a stat name (symbol) from stats.ids
TranslateStat(const char * stat_name)4777 ieDword Interface::TranslateStat(const char *stat_name)
4778 {
4779 long tmp;
4780
4781 if (valid_number(stat_name, tmp)) {
4782 return (ieDword) tmp;
4783 }
4784
4785 int symbol = LoadSymbol( "stats" );
4786 Holder<SymbolMgr> sym = GetSymbol( symbol );
4787 if (!sym) {
4788 error("Core", "Cannot load statistic name mappings.\n");
4789 }
4790 ieDword stat = (ieDword) sym->GetValue( stat_name );
4791 if (stat==(ieDword) ~0) {
4792 Log(WARNING, "Core", "Cannot translate symbol: %s", stat_name);
4793 }
4794 return stat;
4795 }
4796
4797 // Calculates an arbitrary stat bonus, based on tables.
4798 // the master table contains the table names (as row names) and the used stat
4799 // the subtables contain stat value/bonus pairs.
4800 // Optionally an override stat value can be specified (needed for use in pcfs).
ResolveStatBonus(Actor * actor,const char * tablename,ieDword flags,int value)4801 int Interface::ResolveStatBonus(Actor *actor, const char *tablename, ieDword flags, int value)
4802 {
4803 int mastertable = gamedata->LoadTable( tablename );
4804 if (mastertable == -1) return -1;
4805 Holder<TableMgr> mtm = gamedata->GetTable( mastertable );
4806 if (!mtm) {
4807 Log(ERROR, "Core", "Cannot resolve stat bonus.");
4808 return -1;
4809 }
4810 int count = mtm->GetRowCount();
4811 if (count< 1) {
4812 return 0;
4813 }
4814 int ret = 0;
4815 // tables for additive modifiers of bonus type
4816 for (int i = 0; i < count; i++) {
4817 tablename = mtm->GetRowName(i);
4818 int checkcol = strtol(mtm->QueryField(i,1), NULL, 0);
4819 unsigned int readcol = strtol(mtm->QueryField(i,2), NULL, 0);
4820 int stat = TranslateStat(mtm->QueryField(i,0) );
4821 if (!(flags&1)) {
4822 value = actor->GetSafeStat(stat);
4823 }
4824 int table = gamedata->LoadTable( tablename );
4825 if (table == -1) continue;
4826 Holder<TableMgr> tm = gamedata->GetTable( table );
4827 if (!tm) continue;
4828
4829 int row;
4830 if (checkcol == -1) {
4831 // use the row names
4832 char tmp[30];
4833 snprintf(tmp, sizeof(tmp), "%d", value);
4834 row = tm->GetRowIndex(tmp);
4835 } else {
4836 // use the checkcol column (default of 0)
4837 row = tm->FindTableValue(checkcol, value, 0);
4838 }
4839 if (row>=0) {
4840 ret += strtol(tm->QueryField(row, readcol), NULL, 0);
4841 }
4842 }
4843 return ret;
4844 }
4845
WaitForDisc(int disc_number,const char * path)4846 void Interface::WaitForDisc(int disc_number, const char* path)
4847 {
4848 GetDictionary()->SetAt( "WaitForDisc", (ieDword) disc_number );
4849
4850 GetGUIScriptEngine()->RunFunction( "GUICommonWindows", "OpenWaitForDiscWindow" );
4851 do {
4852 winmgr->DrawWindows();
4853 for (size_t i=0;i<CD[disc_number-1].size();i++) {
4854 char name[_MAX_PATH];
4855
4856 PathJoin(name, CD[disc_number-1][i].c_str(), path, nullptr);
4857 if (file_exists (name)) {
4858 GetGUIScriptEngine()->RunFunction( "GUICommonWindows", "OpenWaitForDiscWindow" );
4859 return;
4860 }
4861 }
4862
4863 } while (video->SwapBuffers() == GEM_OK);
4864 }
4865
SetTimer(const EventHandler & handler,unsigned long interval,int repeats)4866 Timer& Interface::SetTimer(const EventHandler& handler, unsigned long interval, int repeats)
4867 {
4868 timers.push_back(Timer(interval, handler, repeats));
4869 return timers.back();
4870 }
4871
SetNextScript(const char * script)4872 void Interface::SetNextScript(const char *script)
4873 {
4874 strlcpy( NextScript, script, sizeof(NextScript) );
4875 QuitFlag |= QF_CHANGESCRIPT;
4876 }
4877
SanityCheck(const char * ver)4878 void Interface::SanityCheck(const char *ver) {
4879 if (strcmp(ver, VERSION_GEMRB)) {
4880 error("Core", "version check failed: core version %s doesn't match caller's version %s\n", VERSION_GEMRB, ver);
4881 }
4882 }
4883
4884 }
4885