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