/* GemRB - Infinity Engine Emulator * Copyright (C) 2003-2005 The GemRB Project * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * */ #include "Interface.h" #include "exports.h" #include "globals.h" #include "strrefs.h" #include "ie_cursors.h" #include "ActorMgr.h" #include "AmbientMgr.h" #include "AnimationMgr.h" #include "ArchiveImporter.h" #include "Calendar.h" #include "DataFileMgr.h" #include "DialogHandler.h" #include "DialogMgr.h" #include "DisplayMessage.h" #include "EffectMgr.h" #include "EffectQueue.h" #include "Factory.h" #include "FontManager.h" #include "Game.h" #include "GameScript/GameScript.h" #include "ItemMgr.h" #include "KeyMap.h" #include "MapMgr.h" #include "MoviePlayer.h" #include "MusicMgr.h" #include "Palette.h" #ifndef STATIC_LINK #include "PluginLoader.h" #endif #include "PluginMgr.h" #include "Predicates.h" #include "ProjectileServer.h" #include "SaveGameIterator.h" #include "SaveGameMgr.h" #include "ScriptedAnimation.h" #include "SoundMgr.h" #include "SpellMgr.h" #include "StoreMgr.h" #include "StringMgr.h" #include "SymbolMgr.h" #include "TileMap.h" #include "VEFObject.h" #include "Video.h" #include "WorldMapMgr.h" #include "GUI/Button.h" #include "GUI/Console.h" #include "GUI/EventMgr.h" #include "GUI/GameControl.h" #include "GUI/GUIScriptInterface.h" #include "GUI/Label.h" #include "GUI/MapControl.h" #include "GUI/TextArea.h" #include "GUI/WindowManager.h" #include "GUI/WorldMapControl.h" #include "RNG.h" #include "Scriptable/Container.h" #include "System/FileStream.h" #include "System/FileFilters.h" #include "System/StringBuffer.h" #include #ifdef WIN32 #include "CodepageToIconv.h" #elif defined(HAVE_LANGINFO_H) #include #endif namespace GemRB { GEM_EXPORT Interface* core = NULL; static int MaximumAbility = 25; static ieWordSigned *strmod = NULL; static ieWordSigned *strmodex = NULL; static ieWordSigned *intmod = NULL; static ieWordSigned *dexmod = NULL; static ieWordSigned *conmod = NULL; static ieWordSigned *chrmod = NULL; static ieWordSigned *lorebon = NULL; static ieWordSigned *wisbon = NULL; static int **reputationmod = NULL; static ieVariable IWD2DeathVarFormat = "_DEAD%s"; static ieVariable DeathVarFormat = "SPRITE_IS_DEAD%s"; static int NumRareSelectSounds = 2; static ieWord IDT_FAILURE = 0; static ieWord IDT_CRITRANGE = 1; static ieWord IDT_CRITMULTI = 2; static ieWord IDT_SKILLPENALTY = 3; static int MagicBit = 0; static const char* DefaultSystemEncoding = "UTF-8"; // FIXME: DragOp should be initialized with the button we are dragging from // for now use a dummy until we truly implement this as a drag event Control ItemDragOp::dragDummy = Control(Region()); ItemDragOp::ItemDragOp(CREItem* item) : ControlDragOp(&dragDummy), item(item) { Item* i = gamedata->GetItem(item->ItemResRef); assert(i); Holder pic = gamedata->GetAnySprite(i->ItemIcon, -1, 1); if (pic == nullptr) { // use any / the smaller icon if the dragging one is unavailable pic = gamedata->GetBAMSprite(i->ItemIcon, -1, 0); } cursor = pic; // FIXME: this VarName is not consistant strnlwrcpy(dragDummy.VarName, "itembutton", MAX_VARIABLE_LENGTH - 1); } Interface::Interface() { unsigned int i; for(i=0;i<256;i++) { pl_uppercase[i]=(ieByte) toupper(i); pl_lowercase[i]=(ieByte) tolower(i); } winmgr = NULL; projserv = NULL; VideoDriverName = "sdl"; AudioDriverName = "openal"; RtRows = NULL; sgiterator = NULL; game = NULL; calendar = NULL; keymap = NULL; worldmap = NULL; CurrentStore = NULL; CurrentContainer = NULL; UseContainer = false; displaymsg = NULL; slottypes = NULL; slotmatrix = NULL; UseCorruptedHack = false; mousescrollspd = 10; GameType[0] = '\0'; CheatFlag = false; QuitFlag = QF_NORMAL; EventFlag = EF_CONTROL; #ifndef WIN32 CaseSensitive = true; //this is the default value, so CD1/CD2 will be resolved #else CaseSensitive = false; #endif UseSoftKeyboard = false; KeepCache = false; NumFingInfo = 2; NumFingKboard = 3; NumFingScroll = 2; MouseFeedback = 0; IgnoreOriginalINI = 0; GUIScriptsPath[0] = 0; GamePath[0] = 0; SavePath[0] = 0; GemRBPath[0] = 0; PluginsPath[0] = 0; CachePath[0] = 0; GemRBOverridePath[0] = 0; GemRBUnhardcodedPath[0] = 0; GameName[0] = 0; CustomFontPath[0] = 0; GameOverridePath[0] = 0; GameSoundsPath[0] = 0; GameScriptsPath[0] = 0; GamePortraitsPath[0] = 0; GameCharactersPath[0] = 0; GameDataPath[0] = 0; strlcpy( INIConfig, "baldur.ini", sizeof(INIConfig) ); GlobalScript = "BALDUR"; WorldMapName[0] = "WORLDMAP"; for (int size = 0; size < MAX_CIRCLE_SIZE; size++) { CopyResRef(GroundCircleBam[size], ""); GroundCircleScale[size] = 0; } TooltipBG = NULL; DefSound = NULL; DSCount = -1; memset(GameFeatures, 0, sizeof( GameFeatures )); //GameFeatures = 0; //GameFeatures2 = 0; memset(&Time, 0, sizeof(Time)); AreaAliasTable = NULL; update_scripts = false; SpecialSpellsCount = -1; SpecialSpells = NULL; Encoding = "default"; SystemEncoding = DefaultSystemEncoding; #if defined(WIN32) && defined(HAVE_ICONV) const uint32_t codepage = GetACP(); const char* iconvCode = GetIconvNameForCodepage(codepage); if (nullptr == iconvCode) { error("Interface", "Mapping of codepage %u unknown to iconv.", codepage); } SystemEncoding = iconvCode; #elif defined(HAVE_LANGINFO_H) SystemEncoding = nl_langinfo(CODESET); #endif TLKEncoding.encoding = "ISO-8859-1"; TLKEncoding.widechar = false; TLKEncoding.multibyte = false; TLKEncoding.zerospace = false; MagicBit = HasFeature(GF_MAGICBIT); VersionOverride = ItemTypes = SlotTypes = 0; MultipleQuickSaves = false; MaxPartySize = 6; FeedbackLevel = 0; CutSceneRunner = NULL; //once GemRB own format is working well, this might be set to 0 SaveAsOriginal = 1; gamedata = new GameData(); //once GemRB own format is working well, this might be set to 0 SaveAsOriginal = 1; plugin_flags = new Variables(); plugin_flags->SetType( GEM_VARIABLES_INT ); lists = new Variables(); lists->SetType( GEM_VARIABLES_POINTER ); vars = new Variables(); vars->SetType( GEM_VARIABLES_INT ); vars->ParseKey(true); tokens = new Variables(); tokens->SetType( GEM_VARIABLES_STRING ); } static void ReleaseItemList(void *poi) { delete ((ItemList *) poi); } static void FreeAbilityTables() { #define NULL_FREE(ptr)\ free(ptr); ptr = nullptr NULL_FREE(strmod); NULL_FREE(strmodex); NULL_FREE(intmod); NULL_FREE(dexmod); NULL_FREE(conmod); NULL_FREE(chrmod); NULL_FREE(lorebon); NULL_FREE(wisbon); #undef NULL_FREE } void Interface::FreeResRefTable(ieResRef *&table, int &count) { if (table) { free( table ); count = -1; } } Interface::~Interface(void) { WindowManager::CursorMouseUp = NULL; WindowManager::CursorMouseDown = NULL; delete winmgr; delete AreaAliasTable; //destroy the highest objects in the hierarchy first! // here gamectrl is either null (no game) or already taken out by its window (game loaded) assert(game == nullptr); delete calendar; delete worldmap; delete keymap; FreeAbilityTables(); if (reputationmod) { for (unsigned int i=0; i<20; i++) { if (reputationmod[i]) { free(reputationmod[i]); } } free(reputationmod); reputationmod=NULL; } if (SpecialSpells) { free(SpecialSpells); } SurgeSpells.clear(); std::map::iterator fit = fonts.begin(); for (; fit != fonts.end(); ++fit) delete (*fit).second; // fonts need to be destroyed before TTF plugin PluginMgr::Get()->RunCleanup(); ReleaseMemoryActor(); ReleaseMemorySpell(); EffectQueue_ReleaseMemory(); CharAnimations::ReleaseMemory(); FreeResRefTable(DefSound, DSCount); free( slottypes ); free( slotmatrix ); itemtypedata.clear(); delete sgiterator; for (size_t i = 0; i < musiclist.size(); i++) { free((void *)musiclist[i]); } DamageInfoMap.clear(); delete plugin_flags; delete projserv; delete displaymsg; delete TooltipBG; delete vars; delete tokens; if (lists) { lists->RemoveAll(free); delete lists; } if (RtRows) { RtRows->RemoveAll(ReleaseItemList); delete RtRows; } Map::ReleaseMemory(); Actor::ReleaseMemory(); gamedata->ClearCaches(); delete gamedata; gamedata = NULL; // Removing all stuff from Cache, except bifs if (!KeepCache) DelTree((const char *) CachePath, true); } GameControl* Interface::StartGameControl() { assert(gamectrl == nullptr); gamedata->DelTable(0xffffu); //dropping ALL tables Region screen(0,0, Width, Height); gamectrl = new GameControl(screen); gamectrl->AssignScriptingRef(0, "GC"); return gamectrl; } /* handle main loop events that might destroy or create windows thus cannot be called from DrawWindows directly these events are pending until conditions are right */ void Interface::HandleEvents() { if (EventFlag&EF_SELECTION) { EventFlag&=~EF_SELECTION; guiscript->RunFunction( "GUICommonWindows", "SelectionChanged", false); } if (EventFlag&EF_UPDATEANIM) { EventFlag&=~EF_UPDATEANIM; guiscript->RunFunction( "GUICommonWindows", "UpdateAnimation", false); } if (EventFlag&EF_PORTRAIT) { EventFlag&=~EF_PORTRAIT; const Window* win = GetWindow(0, "PORTWIN"); if (win) { guiscript->RunFunction( "GUICommonWindows", "UpdatePortraitWindow" ); } } if (EventFlag&EF_ACTION) { EventFlag&=~EF_ACTION; const Window* win = GetWindow(0, "ACTWIN"); if (win) { guiscript->RunFunction( "GUICommonWindows", "UpdateActionsWindow" ); } } if (EventFlag&EF_CONTROL) { // handle this before clearing EF_CONTROL // otherwise we do this every frame ToggleViewsVisible(!(game->ControlStatus & CS_HIDEGUI), "HIDE_CUT"); EventFlag&=~EF_CONTROL; guiscript->RunFunction( "MessageWindow", "UpdateControlStatus" ); return; } if (EventFlag&EF_SHOWMAP) { ieDword tmp = (ieDword) ~0; vars->Lookup( "OtherWindow", tmp ); if (tmp == (ieDword) ~0) { EventFlag &= ~EF_SHOWMAP; guiscript->RunFunction( "GUIMA", "ShowMap" ); } return; } if (EventFlag&EF_SEQUENCER) { EventFlag&=~EF_SEQUENCER; guiscript->RunFunction( "GUIMG", "OpenSequencerWindow" ); return; } if (EventFlag&EF_IDENTIFY) { EventFlag&=~EF_IDENTIFY; // FIXME: Implement this. guiscript->RunFunction( "GUICommonWindows", "OpenIdentifyWindow" ); return; } if (EventFlag&EF_OPENSTORE) { EventFlag&=~EF_OPENSTORE; guiscript->RunFunction( "GUISTORE", "OpenStoreWindow" ); return; } if (EventFlag&EF_EXPANSION) { EventFlag&=~EF_EXPANSION; guiscript->RunFunction( "Game", "GameExpansion", false ); return; } if (EventFlag&EF_CREATEMAZE) { EventFlag&=~EF_CREATEMAZE; guiscript->RunFunction( "Maze", "CreateMaze", false ); return; } if ((EventFlag&EF_RESETTARGET) && gamectrl) { EventFlag&=~EF_RESETTARGET; EventFlag|=EF_TARGETMODE; gamectrl->ResetTargetMode(); return; } if ((EventFlag&EF_TARGETMODE) && gamectrl) { EventFlag&=~EF_TARGETMODE; gamectrl->UpdateTargetMode(); return; } if (EventFlag&EF_TEXTSCREEN) { EventFlag&=~EF_TEXTSCREEN; winmgr->SetCursorFeedback(WindowManager::CursorFeedback(core->MouseFeedback)); guiscript->RunFunction( "TextScreen", "StartTextScreen" ); return; } } /* handle main loop events that might destroy or create windows thus cannot be called from DrawWindows directly */ void Interface::HandleFlags() { //clear events because the context changed EventFlag = EF_CONTROL; if (QuitFlag&(QF_QUITGAME|QF_EXITGAME) ) { // closing windows must come before tearing anything else down // some window close handlers expect game/gamecontrol to be valid // let them run, then start tearing things down winmgr->DestroyAllWindows(); // when reaching this, quitflag should be 1 or 2 // if Exitgame was set, we'll set Start.py too QuitGame (QuitFlag&QF_EXITGAME); } if (QuitFlag & (QF_QUITGAME | QF_EXITGAME | QF_LOADGAME | QF_ENTERGAME)) { delete winmgr->GetGameWindow()->RemoveSubview(gamectrl); gamectrl = nullptr; winmgr->GetGameWindow()->SetVisible(false); //clear cutscenes; clear fade/screenshake effects timer = GlobalTimer(); QuitFlag &= ~(QF_QUITGAME|QF_EXITGAME); } if (QuitFlag&QF_LOADGAME) { QuitFlag &= ~QF_LOADGAME; LoadGame(LoadGameIndex.get(), VersionOverride ); LoadGameIndex.release(); //after loading a game, always check if the game needs to be upgraded } if (QuitFlag&QF_ENTERGAME) { winmgr->DestroyAllWindows(); QuitFlag &= ~QF_ENTERGAME; if (game) { EventFlag|=EF_EXPANSION; Log(MESSAGE, "Core", "Setting up the Console..."); guiscript->RunFunction("Console", "OnLoad"); winmgr->FadeColor = Color(); GameControl* gc = StartGameControl(); guiscript->LoadScript( "Game" ); guiscript->RunFunction( "Game", "EnterGame" ); //switch map to protagonist Actor* actor = GetFirstSelectedPC(true); if (actor) { gc->ChangeMap(actor, true); } //rearrange party slots game->ConsolidateParty(); Window* gamewin = winmgr->GetGameWindow(); gamewin->AddSubviewInFrontOfView(gc); gamewin->SetDisabled(false); gamewin->SetVisible(true); gamewin->Focus(); } else { Log(ERROR, "Core", "No game to enter..."); QuitFlag = QF_QUITGAME; } } if (QuitFlag&QF_CHANGESCRIPT) { QuitFlag &= ~QF_CHANGESCRIPT; guiscript->LoadScript( NextScript ); guiscript->RunFunction( NextScript, "OnLoad" ); } } static bool GenerateAbilityTables() { FreeAbilityTables(); //range is: 0 - maximumability int tablesize = MaximumAbility+1; strmod = (ieWordSigned *) malloc (tablesize * 4 * sizeof(ieWordSigned) ); if (!strmod) return false; strmodex = (ieWordSigned *) malloc (101 * 4 * sizeof(ieWordSigned) ); if (!strmodex) return false; intmod = (ieWordSigned *) malloc (tablesize * 5 * sizeof(ieWordSigned) ); if (!intmod) return false; dexmod = (ieWordSigned *) malloc (tablesize * 3 * sizeof(ieWordSigned) ); if (!dexmod) return false; conmod = (ieWordSigned *) malloc (tablesize * 5 * sizeof(ieWordSigned) ); if (!conmod) return false; chrmod = (ieWordSigned *) malloc (tablesize * 1 * sizeof(ieWordSigned) ); if (!chrmod) return false; lorebon = (ieWordSigned *) malloc (tablesize * 1 * sizeof(ieWordSigned) ); if (!lorebon) return false; wisbon = (ieWordSigned *) calloc (tablesize * 1, sizeof(ieWordSigned)); if (!wisbon) return false; return true; } bool Interface::ReadAbilityTable(const ieResRef tablename, ieWordSigned *mem, int columns, int rows) { AutoTable tab(tablename); if (!tab) { return false; } //this is a hack for rows not starting at 0 in some cases int fix = 0; const char * tmp = tab->GetRowName(0); if (tmp && (tmp[0]!='0')) { fix = atoi(tmp); for (int i=0;iQueryField(0,j),NULL,0 ); } } } for (int j=0;jQueryField(i,j),NULL,0 ); } } return true; } bool Interface::ReadAbilityTables() { bool ret = GenerateAbilityTables(); if (!ret) return ret; ret = ReadAbilityTable("strmod", strmod, 4, MaximumAbility + 1); if (!ret) return ret; ret = ReadAbilityTable("strmodex", strmodex, 4, 101); //3rd ed doesn't have strmodex, but has a maximum of 40 if (!ret && (MaximumAbility<=25) ) return ret; ret = ReadAbilityTable("intmod", intmod, 5, MaximumAbility + 1); if (!ret) return ret; ret = ReadAbilityTable("hpconbon", conmod, 5, MaximumAbility + 1); if (!ret) return ret; if (!HasFeature(GF_3ED_RULES)) { //no lorebon in iwd2??? ret = ReadAbilityTable("lorebon", lorebon, 1, MaximumAbility + 1); if (!ret) return ret; //no dexmod in iwd2??? ret = ReadAbilityTable("dexmod", dexmod, 3, MaximumAbility + 1); if (!ret) return ret; } //this table is a single row (not a single column) ret = ReadAbilityTable("chrmodst", chrmod, MaximumAbility + 1, 1); if (!ret) return ret; if (gamedata->Exists("wisxpbon", IE_2DA_CLASS_ID, true)) { ret = ReadAbilityTable("wisxpbon", wisbon, 1, MaximumAbility + 1); if (!ret) return ret; } return true; } bool Interface::ReadGameTimeTable() { AutoTable table("gametime"); if (!table) { return false; } Time.round_sec = atoi(table->QueryField("ROUND_SECONDS", "DURATION")); Time.turn_sec = atoi(table->QueryField("TURN_SECONDS", "DURATION")); Time.round_size = Time.round_sec * AI_UPDATE_TIME; Time.rounds_per_turn = Time.turn_sec / Time.round_sec; Time.attack_round_size = atoi(table->QueryField("ATTACK_ROUND", "DURATION")); Time.hour_sec = 300; // move to table if pst turns out to be different Time.hour_size = Time.hour_sec * AI_UPDATE_TIME; Time.day_sec = Time.hour_sec * 24; // move to table if pst turns out to be different Time.day_size = Time.day_sec * AI_UPDATE_TIME; return true; } bool Interface::ReadSpecialSpells() { int i; bool result = true; AutoTable table("splspec"); if (table) { SpecialSpellsCount = table->GetRowCount(); SpecialSpells = (SpecialSpellType *) malloc( sizeof(SpecialSpellType) * SpecialSpellsCount); for (i=0;iGetRowName(i),8 ); //if there are more flags, compose this value into a bitfield SpecialSpells[i].flags = atoi(table->QueryField(i, 0)); SpecialSpells[i].amount = atoi(table->QueryField(i, 1)); SpecialSpells[i].bonus_limit = atoi(table->QueryField(i, 2)); } } else { result = false; } table.load("wildmag"); if (table) { SurgeSpell ss; for (i = 0; (unsigned)i < table->GetRowCount(); i++) { CopyResRef(ss.spell, table->QueryField(i, 0)); ss.message = strtol(table->QueryField(i, 1), NULL, 0); // comment ignored SurgeSpells.push_back(ss); } } else { result = false; } return result; } int Interface::GetSpecialSpell(const ieResRef resref) { for (int i=0;iGetStat(IE_STATE_ID) & STATE_SILENCED ) { if (!(sp&SP_SILENCE)) { return SP_SILENCE; } } // disable spells causing surges to be cast while in a surge (prevents nesting) if (sp&SP_SURGE) { return SP_SURGE; } return 0; } //Static const char *Interface::GetDeathVarFormat() { return DeathVarFormat; } bool Interface::ReadAreaAliasTable(const ieResRef tablename) { if (AreaAliasTable) { AreaAliasTable->RemoveAll(NULL); } else { AreaAliasTable = new Variables(); AreaAliasTable->SetType(GEM_VARIABLES_INT); } AutoTable aa(tablename); if (!aa) { //don't report error when the file doesn't exist return true; } int idx = aa->GetRowCount(); while (idx--) { ieResRef key; strnlwrcpy(key,aa->GetRowName(idx),8); ieDword value = atoi(aa->QueryField(idx,0)); AreaAliasTable->SetAt(key, value); } return true; } //this isn't const int Interface::GetAreaAlias(const ieResRef areaname) const { ieDword value; if (AreaAliasTable && AreaAliasTable->Lookup(areaname, value)) { return (int) value; } return -1; } bool Interface::ReadMusicTable(const ieResRef tablename, int col) { AutoTable tm(tablename); if (!tm) return false; for (unsigned int i = 0; i < tm->GetRowCount(); i++) { musiclist.push_back(strdup(tm->QueryField(i, col))); } return true; } bool Interface::ReadDamageTypeTable() { AutoTable tm("dmgtypes"); if (!tm) return false; DamageInfoStruct di; for (ieDword i = 0; i < tm->GetRowCount(); i++) { di.strref = displaymsg->GetStringReference(atoi(tm->QueryField(i, 0))); di.resist_stat = TranslateStat(tm->QueryField(i, 1)); di.value = strtol(tm->QueryField(i, 2), (char **) NULL, 16); di.iwd_mod_type = atoi(tm->QueryField(i, 3)); di.reduction = atoi(tm->QueryField(i, 4)); DamageInfoMap.insert(std::make_pair(di.value, di)); } return true; } bool Interface::ReadReputationModTable() { AutoTable tm("reputati"); if (!tm) return false; reputationmod = (int **) calloc(21, sizeof(int *)); int cols = tm->GetColumnCount(); for (unsigned int i=0; i<20; i++) { reputationmod[i] = (int *) calloc(cols, sizeof(int)); for (int j=0; jQueryField(i, j)); } } return true; } bool Interface::ReadSoundChannelsTable() { AutoTable tm("sndchann"); if (!tm) { return false; } int ivol = tm->GetColumnIndex("VOLUME"); int irev = tm->GetColumnIndex("REVERB"); for (ieDword i = 0; i < tm->GetRowCount(); i++) { const char *rowname = tm->GetRowName(i); // translate some alternative names for the IWDs if (!strcmp(rowname, "ACTION")) rowname = "ACTIONS"; else if (!strcmp(rowname, "SWING")) rowname = "SWINGS"; AudioDriver->SetChannelVolume(rowname, atoi(tm->QueryField(i, ivol))); if (irev != -1) { AudioDriver->SetChannelReverb(rowname, atof(tm->QueryField(i, irev))); } } return true; } //Not a constant anymore, we let the caller set the entry to zero char *Interface::GetMusicPlaylist(int SongType) const { if (SongType < 0 || (unsigned int)SongType >= musiclist.size()) return NULL; return musiclist[SongType]; } /** this is the main loop */ void Interface::Main() { ieDword speed = 10; vars->Lookup("Mouse Scroll Speed", speed); SetMouseScrollSpeed((int) speed); Font* fps = GetTextFont(); // TODO: if we ever want to support dynamic resolution changes this will break Region fpsRgn( 0, Height - 30, 80, 30 ); wchar_t fpsstring[20] = {L"???.??? fps"}; // set for printing fpsRgn.x = 5; fpsRgn.y = 0; unsigned long frame = 0, time, timebase; time = GetTicks(); timebase = time; double frames = 0.0; do { std::deque::iterator it; for (it = timers.begin(); it != timers.end();) { if (it->IsRunning()) { it->Update(time); ++it; } else { it = timers.erase(it); } } //don't change script when quitting is pending while (QuitFlag && QuitFlag != QF_KILL) { HandleFlags(); } //eventflags are processed only when there is a game if (EventFlag && gamectrl) { HandleEvents(); } HandleGUIBehaviour(); GameLoop(); // TODO: find other animations that need to be synchronized // we can create a manager for them and everything can be updated at once GlobalColorCycle.AdvanceTime(time); winmgr->DrawWindows(); time = GetTicks(); if (DrawFPS) { frame++; if (time - timebase > 1000) { frames = ( frame * 1000.0 / ( time - timebase ) ); timebase = time; frame = 0; swprintf(fpsstring, sizeof(fpsstring)/sizeof(fpsstring[0]), L"%.3f fps", frames); } auto lock = winmgr->DrawHUD(); video->DrawRect( fpsRgn, ColorBlack ); fps->Print(fpsRgn, String(fpsstring), IE_FONT_ALIGN_MIDDLE | IE_FONT_SINGLE_LINE, {ColorWhite, ColorBlack}); } } while (video->SwapBuffers() == GEM_OK && !(QuitFlag&QF_KILL)); QuitGame(0); } int Interface::ReadResRefTable(const ieResRef tablename, ieResRef *&data) { int count = 0; if (data) { free(data); data = NULL; } AutoTable tm(tablename); if (!tm) { Log(ERROR, "Core", "Cannot find %s.2da.", tablename); return 0; } count = tm->GetRowCount(); data = (ieResRef *) calloc( count, sizeof(ieResRef) ); for (int i = 0; i < count; i++) { strnlwrcpy( data[i], tm->QueryField( i, 0 ), 8 ); // * marks an empty resource if (data[i][0]=='*') { data[i][0]=0; } } return count; } int Interface::LoadSprites() { if (!IsAvailable( IE_2DA_CLASS_ID )) { Log(ERROR, "Core", "No 2DA Importer Available."); return GEM_ERROR; } Log(MESSAGE, "Core", "Loading Cursors..."); AnimationFactory* anim; anim = (AnimationFactory*) gamedata->GetFactoryResource(MainCursorsImage, IE_BAM_CLASS_ID); int CursorCount = 0; if (anim) { CursorCount = anim->GetCycleCount(); Cursors.reserve(CursorCount); for (int i = 0; i < CursorCount; i++) { Cursors.push_back(anim->GetFrame(0, (ieByte) i)); } } else { // support non-BAM cursors // load MainCursorsImage + XX until all images are exhausted // same layout as in the originals, with odd indices having the pressed cursor image char fileName[32]; while (CursorCount < 99) { snprintf(fileName, sizeof(fileName), "%.29s%02d", MainCursorsImage.CString(), CursorCount); ResourceHolder im = GetResourceHolder(fileName, true); if (!im) break; Cursors.push_back(im->GetSprite2D()); CursorCount++; } } // this is the last existing cursor type if (CursorCountGetFactoryResource("fogowar", IE_BAM_CLASS_ID); Log(MESSAGE, "Core", "Loading Fog-Of-War bitmaps..."); if (!anim || anim->GetCycleSize( 0 ) != 8) { // unknown type of fog anim Log(ERROR, "Core", "Failed to load Fog-of-War bitmaps."); return GEM_ERROR; } FogSprites[1] = anim->GetFrame( 0, 0 ); FogSprites[2] = anim->GetFrame( 1, 0 ); FogSprites[3] = anim->GetFrame( 2, 0 ); FogSprites[4] = video->MirrorSprite( FogSprites[1], BlitFlags::MIRRORY, false ); assert(FogSprites[4]->renderFlags&BlitFlags::MIRRORY); FogSprites[6] = video->MirrorSprite( FogSprites[3], BlitFlags::MIRRORY, false ); FogSprites[8] = video->MirrorSprite( FogSprites[2], BlitFlags::MIRRORX, false ); assert(FogSprites[8]->renderFlags&BlitFlags::MIRRORX); FogSprites[9] = video->MirrorSprite( FogSprites[3], BlitFlags::MIRRORX, false ); FogSprites[12] = video->MirrorSprite( FogSprites[6], BlitFlags::MIRRORX, false ); assert(FogSprites[12]->renderFlags&BlitFlags::MIRRORX); // Load ground circle bitmaps (PST only) Log(MESSAGE, "Core", "Loading Ground circle bitmaps..."); for (int size = 0; size < MAX_CIRCLE_SIZE; size++) { if (GroundCircleBam[size][0]) { anim = (AnimationFactory*) gamedata->GetFactoryResource(GroundCircleBam[size], IE_BAM_CLASS_ID); if (!anim || anim->GetCycleCount() != 6) { // unknown type of circle anim Log(ERROR, "Core", "Failed Loading Ground circle bitmaps..."); return GEM_ERROR; } for (int i = 0; i < 6; i++) { Holder sprite = anim->GetFrame(0, (ieByte) i); if (GroundCircleScale[size]) { sprite = video->SpriteScaleDown( sprite, GroundCircleScale[size] ); } GroundCircles[size][i] = sprite; } } } return GEM_OK; } int Interface::LoadFonts() { Log(MESSAGE, "Core", "Loading Fonts..."); AutoTable tab("fonts"); if (!tab) { Log(ERROR, "Core", "Cannot find fonts.2da."); return GEM_ERROR; } // FIXME: we used to try and share like fonts // this only ever had a benefit when using non-bam fonts // it could potentially save a a few megs of space (nice for handhelds) // but we should re-enable this by simply letting Font instances share their atlas data int count = tab->GetRowCount(); const char* rowName = NULL; for (int row = 0; row < count; row++) { rowName = tab->GetRowName(row); ResRef resref = tab->QueryField(rowName, "RESREF"); const char* font_name = tab->QueryField( rowName, "FONT_NAME" ); ieWord font_size = atoi( tab->QueryField( rowName, "PX_SIZE" ) ); // not available in BAM fonts. FontStyle font_style = (FontStyle)atoi( tab->QueryField( rowName, "STYLE" ) ); // not available in BAM fonts. bool background = atoi(tab->QueryField(rowName, "BACKGRND")); Font* fnt = NULL; ResourceHolder fntMgr = GetResourceHolder(font_name); if (fntMgr) fnt = fntMgr->GetFont(font_size, font_style, background); if (!fnt) { error("Core", "Unable to load font resource: %s for ResRef %s (check fonts.2da)", font_name, resref.CString()); } else { fonts[resref] = fnt; Log(MESSAGE, "Core", "Loaded Font: %s for ResRef %s", font_name, resref.CString()); } } Log(MESSAGE, "Core", "Fonts Loaded..."); return GEM_OK; } int Interface::Init(InterfaceConfig* config) { Log(MESSAGE, "Core", "GemRB core version v" VERSION_GEMRB " loading ..."); if (!config) { Log(FATAL, "Core", "No Configuration context."); return GEM_ERROR; } const char* value = NULL; #define CONFIG_INT(key, var) \ value = config->GetValueForKey(key); \ if (value) \ var ( atoi( value ) ); \ value = nullptr CONFIG_INT("Bpp", Bpp =); CONFIG_INT("CaseSensitive", CaseSensitive =); CONFIG_INT("DoubleClickDelay", EventMgr::DCDelay = ); CONFIG_INT("DrawFPS", DrawFPS = ); CONFIG_INT("EnableCheatKeys", EnableCheatKeys); CONFIG_INT("EndianSwitch", DataStream::SetBigEndian); CONFIG_INT("GCDebug", GameControl::DebugFlags = ); CONFIG_INT("Height", Height = ); CONFIG_INT("KeepCache", KeepCache = ); CONFIG_INT("MaxPartySize", MaxPartySize = ); MaxPartySize = std::min(std::max(1, MaxPartySize), 10); vars->SetAt("MaxPartySize", MaxPartySize); // for simple GUIScript access CONFIG_INT("MouseFeedback", MouseFeedback = ); CONFIG_INT("MultipleQuickSaves", MultipleQuickSaves = ); CONFIG_INT("RepeatKeyDelay", Control::ActionRepeatDelay = ); CONFIG_INT("SaveAsOriginal", SaveAsOriginal = ); CONFIG_INT("DebugMode", debugMode = ); int touchInput = -1; CONFIG_INT("TouchInput", touchInput =); CONFIG_INT("Width", Width = ); CONFIG_INT("IgnoreOriginalINI", IgnoreOriginalINI = ); CONFIG_INT("UseSoftKeyboard", UseSoftKeyboard = ); CONFIG_INT("NumFingScroll", NumFingScroll = ); CONFIG_INT("NumFingKboard", NumFingKboard = ); CONFIG_INT("NumFingInfo", NumFingInfo = ); CONFIG_INT("GamepadPointerSpeed", GamepadPointerSpeed = ); #undef CONFIG_INT // first param is the preference name, second is the key from gemrb.cfg. #define CONFIG_VARS_MAP(var, key) \ value = config->GetValueForKey(key); \ if (value) \ vars->SetAt(var, atoi(value)); \ value = nullptr #define CONFIG_VARS(key) \ CONFIG_VARS_MAP(key, key) // using vars because my hope is to expose (some of) these in a customized GUIOPT // map gemrb.cfg value to the value used by the options (usually an option from game.ini) CONFIG_VARS("GUIEnhancements"); CONFIG_VARS("SkipIntroVideos"); //put into vars so that reading from game.ini wont overwrite CONFIG_VARS_MAP("BitsPerPixel", "Bpp"); CONFIG_VARS_MAP("Full Screen", "Fullscreen"); #undef CONFIG_VARS #undef CONFIG_VARS_MAP #define CONFIG_STRING(key, var, default) \ value = config->GetValueForKey(key); \ if (value && value[0]) { \ strlcpy(var, value, sizeof(var)); \ } else if (default && default[0]) { \ strlcpy(var, default, sizeof(var)); \ } else var[0] = '\0'; \ value = nullptr CONFIG_STRING("GameName", GameName, GEMRB_STRING); // NOTE: potentially overriden below, once auto GameType is resolved CONFIG_STRING("GameType", GameType, "auto"); // tob type is obsolete if (stricmp( GameType, "tob" ) == 0) { strlcpy( GameType, "bg2", sizeof(GameType) ); } #undef CONFIG_STRING // assumes that default value does not need to be resolved or fixed in any way #define CONFIG_PATH(key, var, default) \ value = config->GetValueForKey(key); \ if (value && value[0]) { \ strlcpy(var, value, sizeof(var)); \ ResolveFilePath(var); \ FixPath(var, true); \ } else if (default && default[0]) { \ strlcpy(var, default, sizeof(var)); \ } else var[0] = '\0'; \ value = nullptr // TODO: make CustomFontPath cross platform and possibly dynamic CONFIG_PATH("CustomFontPath", CustomFontPath, "/usr/share/fonts/TTF"); CONFIG_PATH("GameCharactersPath", GameCharactersPath, "characters"); CONFIG_PATH("GameDataPath", GameDataPath, "data"); CONFIG_PATH("GameOverridePath", GameOverridePath, "override"); CONFIG_PATH("GamePortraitsPath", GamePortraitsPath, "portraits"); CONFIG_PATH("GameScriptsPath", GameScriptsPath, "scripts"); CONFIG_PATH("GameSoundsPath", GameSoundsPath, "sounds"); // Path configureation CONFIG_PATH("GemRBPath", GemRBPath, CopyGemDataPath(GemRBPath, _MAX_PATH)); CONFIG_PATH("CachePath", CachePath, "./Cache2"); FixPath( CachePath, false ); // AppImage doesn't support relative urls at all // we set the path to the data dir to cover unhardcoded and co, // while plugins are statically linked, so it doesn't matter for them #ifdef DATA_DIR const char* appDir = getenv("APPDIR"); if (appDir) { assert(strnlen(appDir, _MAX_PATH/2) < _MAX_PATH/2); PathJoin(GemRBPath, appDir, DATA_DIR, nullptr); } #endif CONFIG_PATH("GUIScriptsPath", GUIScriptsPath, GemRBPath); CONFIG_PATH("GamePath", GamePath, "."); // guess a few paths in case this one is bad; two levels deep for the fhs layout char testPath[_MAX_PATH]; bool gameFound = true; if (!PathJoin(testPath, GamePath, "chitin.key", nullptr)) { Log(WARNING, "Interface", "Invalid GamePath detected, guessing from the current dir!"); if (PathJoin(testPath, "..", "chitin.key", nullptr)) { strlcpy(GamePath, "..", sizeof(GamePath)); } else { if (PathJoin(testPath, "..", "..", "chitin.key", nullptr)) { strlcpy(GamePath, "../..", sizeof(GamePath)); } else { gameFound = false; } } } // if nothing was found still, check for the internal demo // it's generic, but not likely to work outside of AppImages and maybe apple bundles if (!gameFound) { Log(WARNING, "Interface", "Invalid GamePath detected, looking for the GemRB demo!"); if (PathJoin(testPath, "..", "demo", "chitin.key", nullptr)) { PathJoin(GamePath, "..", "demo", nullptr); strlcpy(GameType, "demo", sizeof(GameType)); } else { if (PathJoin(testPath, GemRBPath, "demo", "chitin.key", nullptr)) { PathJoin(GamePath, GemRBPath, "demo", nullptr); strlcpy(GameType, "demo", sizeof(GameType)); } } } CONFIG_PATH("GemRBOverridePath", GemRBOverridePath, GemRBPath); CONFIG_PATH("GemRBUnhardcodedPath", GemRBUnhardcodedPath, GemRBPath); #ifdef PLUGIN_DIR CONFIG_PATH("PluginsPath", PluginsPath, PLUGIN_DIR); #else CONFIG_PATH("PluginsPath", PluginsPath, ""); if (!PluginsPath[0]) { PathJoin(PluginsPath, GemRBPath, "plugins", nullptr); } #endif CONFIG_PATH("SavePath", SavePath, GamePath); #undef CONFIG_PATH #define CONFIG_STRING(key, var) \ value = config->GetValueForKey(key); \ if (value) \ var = value; \ value = nullptr CONFIG_STRING("AudioDriver", AudioDriverName); CONFIG_STRING("VideoDriver", VideoDriverName); CONFIG_STRING("Encoding", Encoding); #undef CONFIG_STRING value = config->GetValueForKey("ModPath"); if (value) { for (char *path = strtok((char*)value,SPathListSeparator); path; path = strtok(NULL,SPathListSeparator)) { ModPath.push_back(path); ResolveFilePath(ModPath.back()); } } value = config->GetValueForKey("SkipPlugin"); if (value) { plugin_flags->SetAt( value, PLF_SKIP ); } value = config->GetValueForKey("DelayPlugin"); if (value) { plugin_flags->SetAt( value, PLF_DELAY ); } int i = 0; for(i = 0; i < MAX_CD; i++) { char keyname[] = { 'C', 'D', char('1'+i), '\0' }; value = config->GetValueForKey(keyname); if (value) { for(char *path = strtok((char*)value, SPathListSeparator); path; path = strtok(NULL,SPathListSeparator)) { CD[i].push_back(path); ResolveFilePath(CD[i].back()); } } else { // nothing in config so create our own char name[_MAX_PATH]; PathJoin(name, GamePath, keyname, nullptr); CD[i].push_back(name); PathJoin(name, GamePath, GameDataPath, keyname, nullptr); CD[i].push_back(name); } } if (!MakeDirectories(CachePath)) { error("Core", "Unable to create cache directory '%s'", CachePath); } if ( StupidityDetector( CachePath )) { Log(ERROR, "Core", "Cache path %s doesn't exist, not a folder or contains alien files!", CachePath ); return GEM_ERROR; } if (!KeepCache) DelTree((const char *) CachePath, false); // potentially disable logging before plugins are loaded (the log file is a plugin) value = config->GetValueForKey("Logging"); if (value) ToggleLogging(atoi(value)); Log(MESSAGE, "Core", "Starting Plugin Manager..."); PluginMgr *plugin = PluginMgr::Get(); #if TARGET_OS_MAC // search the bundle plugins first // since bundle plugins are loaded first dyld will give them precedence // if duplicates are found in the PluginsPath char bundlePluginsPath[_MAX_PATH]; CopyBundlePath(bundlePluginsPath, sizeof(bundlePluginsPath), PLUGINS); ResolveFilePath(bundlePluginsPath); #ifndef STATIC_LINK LoadPlugins(bundlePluginsPath); #endif #endif #ifndef STATIC_LINK LoadPlugins(PluginsPath); #endif if (plugin && plugin->GetPluginCount()) { Log(MESSAGE, "Core", "Plugin Loading Complete..."); } else { Log(FATAL, "Core", "Plugin Loading Failed, check path..."); return GEM_ERROR; } plugin->RunInitializers(); Log(MESSAGE, "Core", "GemRB Core Initialization..."); Log(MESSAGE, "Core", "Initializing Video Driver..."); video = ( Video * ) PluginMgr::Get()->GetDriver(&Video::ID, VideoDriverName.c_str()); if (!video) { Log(FATAL, "Core", "No Video Driver Available."); return GEM_ERROR; } if (video->Init() == GEM_ERROR) { Log(FATAL, "Core", "Cannot Initialize Video Driver."); return GEM_ERROR; } // ask the driver if a touch device is in use EventMgr::TouchInputEnabled = touchInput < 0 ? video->TouchInputEnabled() : touchInput; ieDword brightness = 10; ieDword contrast = 5; vars->Lookup("Brightness Correction", brightness); vars->Lookup("Gamma Correction", contrast); SetInfoTextColor(ColorWhite); { Log(MESSAGE, "Core", "Initializing Search Path..."); if (!IsAvailable( PLUGIN_RESOURCE_DIRECTORY )) { Log(FATAL, "Core", "no DirectoryImporter!"); return GEM_ERROR; } char path[_MAX_PATH]; PathJoin(path, CachePath, nullptr); if (!gamedata->AddSource(path, "Cache", PLUGIN_RESOURCE_DIRECTORY)) { Log(FATAL, "Core", "The cache path couldn't be registered, please check!"); return GEM_ERROR; } size_t i; for (i = 0; i < ModPath.size(); ++i) gamedata->AddSource(ModPath[i].c_str(), "Mod paths", PLUGIN_RESOURCE_CACHEDDIRECTORY); PathJoin(path, GemRBOverridePath, "override", GameType, nullptr); if (!strcmp( GameType, "auto" )) gamedata->AddSource(path, "GemRB Override", PLUGIN_RESOURCE_NULL); else gamedata->AddSource(path, "GemRB Override", PLUGIN_RESOURCE_CACHEDDIRECTORY); PathJoin(path, GemRBOverridePath, "override", "shared", nullptr); gamedata->AddSource(path, "shared GemRB Override", PLUGIN_RESOURCE_CACHEDDIRECTORY); PathJoin(path, GamePath, GameOverridePath, nullptr); gamedata->AddSource(path, "Override", PLUGIN_RESOURCE_CACHEDDIRECTORY); //GAME sounds are intentionally not cached, in IWD there are directory structures, //that are not cacheable, also it is totally pointless (this fixed charsounds in IWD) PathJoin(path, GamePath, GameSoundsPath, nullptr); gamedata->AddSource(path, "Sounds", PLUGIN_RESOURCE_DIRECTORY); PathJoin(path, GamePath, GameScriptsPath, nullptr); gamedata->AddSource(path, "Scripts", PLUGIN_RESOURCE_CACHEDDIRECTORY); PathJoin(path, GamePath, GamePortraitsPath, nullptr); gamedata->AddSource(path, "Portraits", PLUGIN_RESOURCE_CACHEDDIRECTORY); PathJoin(path, GamePath, GameDataPath, nullptr); gamedata->AddSource(path, "Data", PLUGIN_RESOURCE_CACHEDDIRECTORY); // accomodating silly installers that create a data/Data/.* structure PathJoin(path, GamePath, GameDataPath, "Data", nullptr); gamedata->AddSource(path, "Data", PLUGIN_RESOURCE_CACHEDDIRECTORY); //IWD2 movies are on the CD but not in the BIF char *description = strdup("CD1/data"); for (i = 0; i < MAX_CD; i++) { for (size_t j=0;jAddSource(path, description, PLUGIN_RESOURCE_CACHEDDIRECTORY); } } free(description); // most of the old gemrb override files can be found here, // so they have a lower priority than the game files and can more easily be modded PathJoin(path, GemRBUnhardcodedPath, "unhardcoded", GameType, nullptr); if (!strcmp(GameType, "auto")) { gamedata->AddSource(path, "GemRB Unhardcoded data", PLUGIN_RESOURCE_NULL); } else { gamedata->AddSource(path, "GemRB Unhardcoded data", PLUGIN_RESOURCE_CACHEDDIRECTORY); } PathJoin(path, GemRBUnhardcodedPath, "unhardcoded", "shared", nullptr); gamedata->AddSource(path, "shared GemRB Unhardcoded data", PLUGIN_RESOURCE_CACHEDDIRECTORY); } { Log(MESSAGE, "Core", "Initializing KEY Importer..."); char ChitinPath[_MAX_PATH]; PathJoin(ChitinPath, GamePath, "chitin.key", nullptr); if (!gamedata->AddSource(ChitinPath, "chitin.key", PLUGIN_RESOURCE_KEY)) { Log(FATAL, "Core", "Failed to load \"chitin.key\""); Log(ERROR, "Core", "This means:\n- you set the GamePath config variable incorrectly,\n\ - you passed a bad game path to GemRB on the command line,\n\ - you are not running GemRB from within a game dir,\n\ - or the game is running (Windows only)."); Log(ERROR, "Core", "The path must point to a game directory with a readable chitin.key file."); return GEM_ERROR; } } // fix the sample config default resolution for iwd2 and configless case also for the demo // FIXME: move defaults to gemrb.ini? Needs video init to be moved after GUIScript init if ((PathJoin(testPath, GamePath, "icewind2.ini", nullptr) || PathJoin(testPath, GamePath, "mapwinbg.png", nullptr)) && Width == 640 && Height == 480) { Width = 800; Height = 600; } // SDL2 driver requires the display to be created prior to sprite creation (opengl context) // we also need the display to exist to create sprites using the display format ieDword fullscreen = 0; vars->Lookup("Full Screen", fullscreen); if (video->CreateDisplay(Size(Width, Height), Bpp, fullscreen, GameName) == GEM_ERROR) { Log(FATAL, "Core", "Cannot initialize shaders."); return GEM_ERROR; } video->SetGamma(brightness, contrast); Log(MESSAGE, "Core", "Initializing GUI Script Engine..."); SetNextScript("Start"); // Start is the first script executed guiscript = PluginHolder(IE_GUI_SCRIPT_CLASS_ID); if (guiscript == nullptr) { Log(FATAL, "Core", "Missing GUI Script Engine."); return GEM_ERROR; } if (!guiscript->Init()) { Log(FATAL, "Core", "Failed to initialize GUI Script."); return GEM_ERROR; } // re-set the gemrb override path, since we now have the correct GameType if 'auto' was used char path[_MAX_PATH]; PathJoin(path, GemRBOverridePath, "override", GameType, nullptr); gamedata->AddSource(path, "GemRB Override", PLUGIN_RESOURCE_CACHEDDIRECTORY, RM_REPLACE_SAME_SOURCE); char unhardcodedTypePath[_MAX_PATH * 2]; PathJoin(unhardcodedTypePath, GemRBUnhardcodedPath, "unhardcoded", GameType, nullptr); gamedata->AddSource(unhardcodedTypePath, "GemRB Unhardcoded data", PLUGIN_RESOURCE_CACHEDDIRECTORY, RM_REPLACE_SAME_SOURCE); // Purposely add the font directory last since we will only ever need it at engine load time. if (CustomFontPath[0]) gamedata->AddSource(CustomFontPath, "CustomFonts", PLUGIN_RESOURCE_DIRECTORY); Log(MESSAGE, "Core", "Reading Game Options..."); if (!LoadGemRBINI()) { Log(FATAL, "Core", "Cannot Load INI."); return GEM_ERROR; } // if unset, manually populate GameName (window title) if (!stricmp(GameName, GEMRB_STRING)) { if (stricmp(DefaultWindowTitle.c_str(), GEMRB_STRING)) { std::string title = GEMRB_STRING" running " + DefaultWindowTitle; strlcpy(GameName, title.c_str(), sizeof(GameName)); } else { strlcpy(GameName, GEMRB_STRING" running unknown game", sizeof(GameName)); } video->SetWindowTitle(GameName); } // load the game ini (baldur.ini, torment.ini, icewind.ini ...) // read from our version of the config if it is present char ini_path[_MAX_PATH+4] = { '\0' }; char gemrbINI[_MAX_PATH+4] = { '\0' }; char tmp[_MAX_PATH+4] = { '\0' }; snprintf(gemrbINI, sizeof(gemrbINI), "gem-%s", INIConfig); PathJoin(ini_path, SavePath, gemrbINI, nullptr); if (!file_exists(ini_path)) { PathJoin(ini_path, GamePath, gemrbINI, nullptr); } if (file_exists(ini_path)) { strlcpy(tmp, INIConfig, sizeof(tmp)); strlcpy(INIConfig, gemrbINI, sizeof(INIConfig)); } else if (!IgnoreOriginalINI) { PathJoin(ini_path, GamePath, INIConfig, nullptr); Log(MESSAGE,"Core", "Loading original game options from %s", ini_path); } if (!InitializeVarsWithINI(ini_path)) { Log(WARNING, "Core", "Unable to set dictionary default values!"); } // set up the tooltip delay which we store in miliseconds ieDword tooltipDelay = 0; GetDictionary()->Lookup("Tooltips", tooltipDelay); WindowManager::SetTooltipDelay(tooltipDelay * Tooltip::DELAY_FACTOR / 10); // restore the game config name if we read it from our version if (tmp[0]) { strlcpy(INIConfig, tmp, sizeof(INIConfig)); } for (i = 0; i < 8; i++) { if (INIConfig[i] == '.') break; GameNameResRef[i] = INIConfig[i]; } GameNameResRef[i] = 0; Log(MESSAGE, "Core", "Reading Encoding Table..."); if (!LoadEncoding()) { Log(ERROR, "Core", "Cannot Load Encoding."); } Log(MESSAGE, "Core", "Creating Projectile Server..."); projserv = new ProjectileServer(); if (!projserv->GetHighestProjectileNumber()) { Log(ERROR, "Core", "No projectiles are available..."); } Log(MESSAGE, "Core", "Checking for Dialogue Manager..."); if (!IsAvailable( IE_TLK_CLASS_ID )) { Log(FATAL, "Core", "No TLK Importer Available."); return GEM_ERROR; } strings = PluginHolder(IE_TLK_CLASS_ID); Log(MESSAGE, "Core", "Loading Dialog.tlk file..."); char strpath[_MAX_PATH]; PathJoin(strpath, GamePath, "dialog.tlk", nullptr); FileStream* fs = FileStream::OpenFile(strpath); if (!fs) { Log(FATAL, "Core", "Cannot find Dialog.tlk."); return GEM_ERROR; } strings->Open(fs); // does the language use an extra tlk? if (strings->HasAltTLK()) { strings2 = PluginHolder(IE_TLK_CLASS_ID); Log(MESSAGE, "Core", "Loading DialogF.tlk file..."); char strpath[_MAX_PATH]; PathJoin(strpath, GamePath, "dialogf.tlk", nullptr); FileStream* fs = FileStream::OpenFile(strpath); if (!fs) { Log(ERROR, "Core", "Cannot find DialogF.tlk. Let us know which translation you are using."); Log(ERROR, "Core", "Falling back to main TLK file, so female text may be wrong!"); strings2 = strings; } else { strings2->Open(fs); } } { Log(MESSAGE, "Core", "Loading Palettes..."); LoadPalette<16>(Palette16, palettes16); LoadPalette<32>(Palette32, palettes32); LoadPalette<256>(Palette256, palettes256); Log(MESSAGE, "Core", "Palettes Loaded"); } if (!IsAvailable( IE_BAM_CLASS_ID )) { Log(FATAL, "Core", "No BAM Importer Available."); return GEM_ERROR; } Log(MESSAGE, "Core", "Initializing stock sounds..."); DSCount = ReadResRefTable ("defsound", DefSound); if (DSCount == 0) { Log(FATAL, "Core", "Cannot find defsound.2da."); return GEM_ERROR; } int ret = LoadSprites(); if (ret) return ret; ret = LoadFonts(); if (ret) return ret; Log(MESSAGE, "Core", "Initializing Window Manager..."); winmgr = new WindowManager(video.get()); RegisterScriptableWindow(winmgr->GetGameWindow(), "GAMEWIN", 0); winmgr->SetCursorFeedback(WindowManager::CursorFeedback(MouseFeedback)); guifact = PluginHolder(IE_CHU_CLASS_ID); if (!guifact) { Log(FATAL, "Core", "Failed to load Window Manager."); return GEM_ERROR; } guifact->SetWindowManager(*winmgr); QuitFlag = QF_CHANGESCRIPT; Log(MESSAGE, "Core", "Starting up the Sound Driver..."); AudioDriver = ( Audio * ) PluginMgr::Get()->GetDriver(&Audio::ID, AudioDriverName.c_str()); if (AudioDriver == nullptr) { Log(FATAL, "Core", "Failed to load sound driver."); return GEM_ERROR; } if (!AudioDriver->Init()) { Log(FATAL, "Core", "Failed to initialize sound driver."); return GEM_ERROR; } Log(MESSAGE, "Core", "Allocating SaveGameIterator..."); sgiterator = new SaveGameIterator(); if (sgiterator == NULL) { Log(FATAL, "Core", "Failed to allocate SaveGameIterator."); return GEM_ERROR; } Log(MESSAGE, "Core", "Initializing Music Manager..."); music = PluginHolder(IE_MUS_CLASS_ID); if (!music) { Log(FATAL, "Core", "Failed to load Music Manager."); return GEM_ERROR; } Log(MESSAGE, "Core", "Loading music list..."); if (HasFeature( GF_HAS_SONGLIST )) { ret = ReadMusicTable("songlist", 1); } else { /*since bg1 and pst has no .2da for songlist, we must supply one in the gemrb/override folder. It should be: music.2da, first column is a .mus filename*/ ret = ReadMusicTable("music", 0); } if (!ret) { Log(WARNING, "Core", "Didn't find music list."); } int resdata = HasFeature( GF_RESDATA_INI ); if (resdata || HasFeature(GF_SOUNDS_INI) ) { Log(MESSAGE, "Core", "Loading resource data File..."); INIresdata = PluginHolder(IE_INI_CLASS_ID); DataStream* ds = gamedata->GetResource(resdata? "resdata":"sounds", IE_INI_CLASS_ID); if (!INIresdata->Open(ds)) { Log(WARNING, "Core", "Failed to load resource data."); } } Log(MESSAGE, "Core", "Setting up SFX channels..."); ret = ReadSoundChannelsTable(); if (!ret) { Log(WARNING, "Core", "Failed to read channel table."); } if (HasFeature( GF_HAS_PARTY_INI )) { Log(MESSAGE, "Core", "Loading precreated teams setup..."); INIparty = PluginHolder(IE_INI_CLASS_ID); char tINIparty[_MAX_PATH]; PathJoin(tINIparty, GamePath, "Party.ini", nullptr); FileStream* fs = FileStream::OpenFile( tINIparty ); if (!INIparty->Open(fs)) { Log(WARNING, "Core", "Failed to load precreated teams."); } } if (HasFeature(GF_IWD2_DEATHVARFORMAT)) { memcpy(DeathVarFormat, IWD2DeathVarFormat, sizeof(ieVariable)); } if (HasFeature( GF_HAS_BEASTS_INI )) { Log(MESSAGE, "Core", "Loading beasts definition File..."); INIbeasts = PluginHolder(IE_INI_CLASS_ID); char tINIbeasts[_MAX_PATH]; PathJoin(tINIbeasts, GamePath, "beast.ini", nullptr); FileStream* fs = FileStream::OpenFile( tINIbeasts ); if (!INIbeasts->Open(fs)) { Log(WARNING, "Core", "Failed to load beast definitions."); } Log(MESSAGE, "Core", "Loading quests definition File..."); INIquests = PluginHolder(IE_INI_CLASS_ID); char tINIquests[_MAX_PATH]; PathJoin(tINIquests, GamePath, "quests.ini", nullptr); FileStream* fs2 = FileStream::OpenFile( tINIquests ); if (!INIquests->Open(fs2)) { Log(WARNING, "Core", "Failed to load quest definitions."); } } game = NULL; calendar = NULL; keymap = NULL; Log(MESSAGE, "Core", "Initializing effects..."); ret = Init_EffectQueue(); if (!ret) { Log(FATAL, "Core", "Failed to initialize effects."); return GEM_ERROR; } Log(MESSAGE, "Core", "Initializing Inventory Management..."); ret = InitItemTypes(); if (!ret) { Log(FATAL, "Core", "Failed to initialize inventory."); return GEM_ERROR; } Log(MESSAGE, "Core", "Initializing string constants..."); displaymsg = new DisplayMessage(); if (!displaymsg) { Log(FATAL, "Core", "Failed to initialize string constants."); return GEM_ERROR; } Log(MESSAGE, "Core", "Initializing random treasure..."); ret = ReadRandomItems(); if (!ret) { Log(WARNING, "Core", "Failed to initialize random treasure."); } Log(MESSAGE, "Core", "Initializing ability tables..."); ret = ReadAbilityTables(); if (!ret) { Log(FATAL, "Core", "Failed to initialize ability tables..."); return GEM_ERROR; } Log(MESSAGE, "Core", "Reading reputation mod table..."); ret = ReadReputationModTable(); if (!ret) { Log(WARNING, "Core", "Failed to read reputation mod table."); } if ( gamedata->Exists("WMAPLAY", IE_2DA_CLASS_ID) ) { Log(MESSAGE, "Core", "Initializing area aliases..."); ret = ReadAreaAliasTable( "WMAPLAY" ); if (!ret) { Log(WARNING, "Core", "Failed to load area aliases..."); } } Log(MESSAGE, "Core", "Reading game time table..."); ret = ReadGameTimeTable(); if (!ret) { Log(FATAL, "Core", "Failed to read game time table..."); return GEM_ERROR; } Log(MESSAGE, "Core", "Reading special spells table..."); ret = ReadSpecialSpells(); if (!ret) { Log(WARNING, "Core", "Failed to load special spells."); } ret = ReadDamageTypeTable(); Log(MESSAGE, "Core", "Reading damage type table..."); if (!ret) { Log(WARNING, "Core", "Reading damage type table..."); } Log(MESSAGE, "Core", "Reading game script tables..."); InitializeIEScript(); Log(MESSAGE, "Core", "Initializing keymap tables..."); keymap = new KeyMap(); ret = keymap->InitializeKeyMap("keymap.ini", "keymap"); if (!ret) { Log(WARNING, "Core", "Failed to initialize keymaps."); } Log(MESSAGE, "Core", "Core Initialization Complete!"); // dump the potentially changed unhardcoded path to a file that weidu looks at automatically to get our search paths char pathString[_MAX_PATH * 3]; #ifdef HAVE_REALPATH if (unhardcodedTypePath[0] == '.') { // canonicalize the relative path; usually from running from the build dir char *absolutePath = realpath(unhardcodedTypePath, NULL); if (absolutePath) { strlcpy(unhardcodedTypePath, absolutePath, sizeof(unhardcodedTypePath)); free(absolutePath); } } #endif snprintf(pathString, sizeof(pathString), "GemRB_Data_Path = %s", unhardcodedTypePath); PathJoin(strpath, GamePath, "gemrb_path.txt", nullptr); FileStream *pathFile = new FileStream(); // don't abort if something goes wrong, since it should never happen and it's not critical if (pathFile->Create(strpath)) { pathFile->Write(pathString, strlen(pathString)); pathFile->Close(); } delete pathFile; EventMgr::EventCallback ToggleConsole = [this](const Event& e) { if (e.type != Event::KeyDown) return false; guiscript->RunFunction("Console", "ToggleConsole"); return true; }; EventMgr::RegisterHotKeyCallback(ToggleConsole, ' ', GEM_MOD_CTRL); return GEM_OK; } bool Interface::IsAvailable(SClass_ID filetype) const { return PluginMgr::Get()->IsAvailable( filetype ); } WorldMap *Interface::GetWorldMap(const char *map) { int index = worldmap->FindAndSetCurrentMap(map?map:game->CurrentArea); return worldmap->GetWorldMap(index); } ProjectileServer* Interface::GetProjectileServer() const { return projserv; } Video* Interface::GetVideoDriver() const { return video.get(); } Audio* Interface::GetAudioDrv(void) const { return AudioDriver.get(); } const char* Interface::TypeExt(SClass_ID type) const { switch (type) { case IE_2DA_CLASS_ID: return "2da"; case IE_ACM_CLASS_ID: return "acm"; case IE_ARE_CLASS_ID: return "are"; case IE_BAM_CLASS_ID: return "bam"; case IE_BCS_CLASS_ID: return "bcs"; case IE_BS_CLASS_ID: return "bs"; case IE_BIF_CLASS_ID: return "bif"; case IE_BIO_CLASS_ID: if (HasFeature(GF_BIOGRAPHY_RES)) { return "res"; } return "bio"; case IE_BMP_CLASS_ID: return "bmp"; case IE_PNG_CLASS_ID: return "png"; case IE_CHR_CLASS_ID: return "chr"; case IE_CHU_CLASS_ID: return "chu"; case IE_CRE_CLASS_ID: return "cre"; case IE_DLG_CLASS_ID: return "dlg"; case IE_EFF_CLASS_ID: return "eff"; case IE_GAM_CLASS_ID: return "gam"; case IE_IDS_CLASS_ID: return "ids"; case IE_INI_CLASS_ID: return "ini"; case IE_ITM_CLASS_ID: return "itm"; case IE_MOS_CLASS_ID: return "mos"; case IE_MUS_CLASS_ID: return "mus"; case IE_MVE_CLASS_ID: return "mve"; case IE_OGG_CLASS_ID: return "ogg"; case IE_PLT_CLASS_ID: return "plt"; case IE_PRO_CLASS_ID: return "pro"; case IE_SAV_CLASS_ID: return "sav"; case IE_SPL_CLASS_ID: return "spl"; case IE_SRC_CLASS_ID: return "src"; case IE_STO_CLASS_ID: return "sto"; case IE_TIS_CLASS_ID: return "tis"; case IE_TLK_CLASS_ID: return "tlk"; case IE_TOH_CLASS_ID: return "toh"; case IE_TOT_CLASS_ID: return "tot"; case IE_VAR_CLASS_ID: return "var"; case IE_VEF_CLASS_ID: return "vef"; case IE_VVC_CLASS_ID: return "vvc"; case IE_WAV_CLASS_ID: return "wav"; case IE_WED_CLASS_ID: return "wed"; case IE_WFX_CLASS_ID: return "wfx"; case IE_WMP_CLASS_ID: return "wmp"; default: Log(ERROR, "Interface", "No extension associated to class ID: %lu", type); } return NULL; } ieStrRef Interface::UpdateString(ieStrRef strref, const char *text) const { char *current = GetCString(strref, 0); bool changed = strcmp(text, current) != 0; free(current); if (changed) { return strings->UpdateString( strref, text ); } else { return strref; } } char* Interface::GetCString(ieStrRef strref, ieDword options) const { ieDword flags = 0; if (!(options & IE_STR_STRREFOFF)) { vars->Lookup( "Strref On", flags ); } if (strings2 && (signed)strref != -1 && strref & IE_STR_ALTREF) { return strings2->GetCString(strref, flags | options); } else { return strings->GetCString(strref, flags | options); } } String* Interface::GetString(ieStrRef strref, ieDword options) const { ieDword flags = 0; if (!(options & IE_STR_STRREFOFF)) { vars->Lookup( "Strref On", flags ); } if (strings2 && (signed)strref != -1 && strref & IE_STR_ALTREF) { return strings2->GetString(strref, flags | options); } else { return strings->GetString(strref, flags | options); } } void Interface::SetFeature(int flag, int position) { if (flag) { GameFeatures[position>>5] |= 1<<(position&31); } else { GameFeatures[position>>5] &= ~(1<<(position&31) ); } } ieDword Interface::HasFeature(int position) const { return GameFeatures[position>>5] & (1<<(position&31)); } static const char *game_flags[GF_COUNT+1]={ "HasKaputz", //0 GF_HAS_KAPUTZ "AllStringsTagged", //1 GF_ALL_STRINGS_TAGGED "HasSongList", //2 GF_HAS_SONGLIST "TeamMovement", //3 GF_TEAM_MOVEMENT "UpperButtonText", //4 GF_UPPER_BUTTON_TEXT "LowerLabelText", //5 GF_LOWER_LABEL_TEXT "HasPartyIni", //6 GF_HAS_PARTY_INI "SoundFolders", //7 GF_SOUNDFOLDERS "IgnoreButtonFrames", //8 GF_IGNORE_BUTTON_FRAMES "OneByteAnimationID", //9 GF_ONE_BYTE_ANIMID "HasDPLAYER", //10GF_HAS_DPLAYER "HasEXPTABLE", //11GF_HAS_EXPTABLE "HasBeastsIni", //12GF_HAS_BEASTS_INI "HasDescIcon", //13GF_HAS_DESC_ICON "HasPickSound", //14GF_HAS_PICK_SOUND "IWDMapDimensions", //15GF_IWD_MAP_DIMENSIONS "AutomapINI", //16GF_AUTOMAP_INI "SmallFog", //17GF_SMALL_FOG "ReverseDoor", //18GF_REVERSE_DOOR "ProtagonistTalks", //19GF_PROTAGONIST_TALKS "HasSpellList", //20GF_HAS_SPELLLIST "IWD2ScriptName", //21GF_IWD2_SCRIPTNAME "DialogueScrolls", //22GF_DIALOGUE_SCROLLS "KnowWorld", //23GF_KNOW_WORLD "ReverseToHit", //24GF_REVERSE_TOHIT "SaveForHalfDamage", //25GF_SAVE_FOR_HALF "CharNameIsGabber", //26GF_CHARNAMEISGABBER "MagicBit", //27GF_MAGICBIT "CheckAbilities", //28GF_CHECK_ABILITIES "ChallengeRating", //29GF_CHALLENGERATING "SpellBookIconHack", //30GF_SPELLBOOKICONHACK "EnhancedEffects", //31GF_ENHANCED_EFFECTS "DeathOnZeroStat", //32GF_DEATH_ON_ZERO_STAT "SpawnIni", //33GF_SPAWN_INI "IWD2DeathVarFormat", //34GF_IWD2_DEATHVARFORMAT "HasResDataIni", //35GF_RESDATA_INI "OverrideCursorPos", //36GF_OVERRIDE_CURSORPOS "BreakableWeapons", //37GF_BREAKABLE_WEAPONS "3EdRules", //38GF_3ED_RULES "LevelslotPerClass", //39GF_LEVELSLOT_PER_CLASS "SelectiveMagicRes", //40GF_SELECTIVE_MAGIC_RES "HasHideInShadows", //41GF_HAS_HIDE_IN_SHADOWS "AreaVisitedVar", //42GF_AREA_VISITED_VAR "ProperBackstab", //43GF_PROPER_BACKSTAB "OnScreenText", //44GF_ONSCREEN_TEXT "HasSpecificDamageBonus", //45GF_SPECIFIC_DMG_BONUS "StrrefSaveGame", //46GF_STRREF_SAVEGAME "SimplifiedDisruption",//47GF_SIMPLE_DISRUPTION "BiographyIsRes", //48GF_BIOGRAPHY_RES "NoBiography", //49GF_NO_BIOGRAPHY "StealIsAttack", //50GF_STEAL_IS_ATTACK "CutsceneAreascripts",//51GF_CUTSCENE_AREASCRIPTS "FlexibleWorldmap", //52GF_FLEXIBLE_WMAP "AutoSearchHidden", //53GF_AUTOSEARCH_HIDDEN "PSTStateFlags", //54GF_PST_STATE_FLAGS "NoDropCanMove", //55GF_NO_DROP_CAN_MOVE "JournalHasSections", //56GF_JOURNAL_HAS_SECTIONS "CastingSounds", //57GF_CASTING_SOUNDS "EnhancedCastingSounds", //58GF_CASTING_SOUNDS2 "ForceAreaScript", //59GF_FORCE_AREA_SCRIPT "AreaOverride", //60GF_AREA_OVERRIDE "NoNewVariables", //61GF_NO_NEW_VARIABLES "HasSoundsIni", //62GF_SOUNDS_INI "HasNoNPCFlag", //63GF_USEPOINT_400 "HasUsePointFlag", //64GF_USEPOINT_200 "HasFloatMenu", //65GF_HAS_FLOAT_MENU "RareActionSounds", //66GF_RARE_ACTION_VB "NoUndroppable", //67GF_NO_UNDROPPABLE "StartActive", //68GF_START_ACTIVE "HasInfopointDialogs", //69GF_INFOPOINT_DIALOGS "ImplicitAreaAnimBackground", //70GF_IMPLICIT_AREAANIM_BACKGROUND "HealOn100Plus", //71GF_HEAL_ON_100PLUS "InPartyAllowsDead", //72GF_IN_PARTY_ALLOWS_DEAD "ZeroTimerIsValid", //73GF_ZERO_TIMER_IS_VALID "ShopsRechargeItems", //74GF_SHOP_RECHARGE "MeleeHeaderUsesProjectile", //75GF_MELEEHEADER_USESPROJECTILE "ForceDialogPause", //76GF_FORCE_DIALOGPAUSE "RandomBanterDialogs",//77GF_RANDOM_BANTER_DIALOGS "FixedMoraleOpcode", //79GF_FIXED_MORALE_OPCODE "Happiness", //80GF_HAPPINESS "EfficientORTrigger", //81GF_EFFICIENT_OR "LayeredWaterTiles", //82GF_LAYERED_WATER_TILES NULL //for our own safety, this marks the end of the pole }; /** Loads gemrb.ini */ bool Interface::LoadGemRBINI() { DataStream* inifile = gamedata->GetResource( "gemrb", IE_INI_CLASS_ID ); if (! inifile) { return false; } Log(MESSAGE, "Core", "Loading game type-specific GemRB setup '%s'", inifile->originalfile); if (!IsAvailable( IE_INI_CLASS_ID )) { Log(ERROR, "Core", "No INI Importer Available."); return false; } PluginHolder ini(IE_INI_CLASS_ID); ini->Open(inifile); ResRef tooltipBG; const char *s; // Resrefs are already initialized in Interface::Interface() #define ASSIGN_RESREF(resref, name) \ s = ini->GetKeyAsString( "resources", name, NULL ); \ resref = s; s = nullptr ASSIGN_RESREF(MainCursorsImage, "MainCursorsImage"); ASSIGN_RESREF(TextCursorBam, "TextCursorBAM"); //console cursor ASSIGN_RESREF(ScrollCursorBam, "ScrollCursorBAM"); ASSIGN_RESREF(ButtonFontResRef, "ButtonFont"); ASSIGN_RESREF(TooltipFontResRef, "TooltipFont"); ASSIGN_RESREF(MovieFontResRef, "MovieFont"); ASSIGN_RESREF(tooltipBG, "TooltipBack"); ASSIGN_RESREF(TextFontResRef, "TextFont"); ASSIGN_RESREF(Palette16, "Palette16"); ASSIGN_RESREF(Palette32, "Palette32"); ASSIGN_RESREF(Palette256, "Palette256"); #undef ASSIGN_RESREF //which stat determines the fist weapon (defaults to class) Actor::SetFistStat(ini->GetKeyAsInt( "resources", "FistStat", IE_CLASS)); int ttMargin = ini->GetKeyAsInt( "resources", "TooltipMargin", 10 ); if (!tooltipBG.IsEmpty()) { AnimationFactory* anim = (AnimationFactory*) gamedata->GetFactoryResource(tooltipBG, IE_BAM_CLASS_ID); Log(MESSAGE, "Core", "Initializing Tooltips..."); if (anim) { TooltipBG = new TooltipBackground(anim->GetFrame(0, 0), anim->GetFrame(0, 1), anim->GetFrame(0, 2) ); // FIXME: this is an arbitrary heuristic and speed TooltipBG->SetAnimationSpeed((ttMargin == 5) ? 10 : 0); TooltipBG->SetMargin(ttMargin); } } DefaultWindowTitle = ini->GetKeyAsString("resources", "WindowTitle", GEMRB_STRING); // These are values for how long a single step is, see Movable::DoStep. // They were found via trial-and-error, trying to match // the speeds from the original games. gamedata->SetStepTime(ini->GetKeyAsInt("resources", "StepTime", 566)); // Defaults to BG2's value // The format of GroundCircle can be: // GroundCircleBAM1 = wmpickl/3 // to denote that the bitmap should be scaled down 3x for (int size = 0; size < MAX_CIRCLE_SIZE; size++) { char name[30]; sprintf( name, "GroundCircleBAM%d", size+1 ); s = ini->GetKeyAsString( "resources", name, NULL ); if (s) { const char *pos = strchr( s, '/' ); if (pos) { GroundCircleScale[size] = atoi( pos+1 ); strlcpy( GroundCircleBam[size], s, pos - s + 1 ); } else { CopyResRef(GroundCircleBam[size], s); } } } s = ini->GetKeyAsString( "resources", "INIConfig", NULL ); if (s) strlcpy(INIConfig, s, sizeof(INIConfig)); MaximumAbility = ini->GetKeyAsInt ("resources", "MaximumAbility", 25 ); NumRareSelectSounds = ini->GetKeyAsInt("resources", "NumRareSelectSounds", 2); gamedata->SetTextSpeed(ini->GetKeyAsInt("resources", "TextScreenSpeed", 100)); for (uint32_t i = 0; i < GF_COUNT; i++) { if (!game_flags[i]) { error("Core", "Fix the game flags!\n"); } SetFeature( ini->GetKeyAsInt( "resources", game_flags[i], 0 ), i ); //printMessage("Option", "", GREEN); //print("%s = %s", game_flags[i], HasFeature(i)?"yes":"no"); } return true; } /** Load the encoding table selected in gemrb.cfg */ bool Interface::LoadEncoding() { DataStream* inifile = gamedata->GetResource( Encoding.c_str(), IE_INI_CLASS_ID ); if (! inifile) { return false; } Log(MESSAGE, "Core", "Loading encoding definition for %s: '%s'", Encoding.c_str(), inifile->originalfile); PluginHolder ini(IE_INI_CLASS_ID); ini->Open(inifile); TLKEncoding.encoding = ini->GetKeyAsString("encoding", "TLKEncoding", TLKEncoding.encoding.c_str()); TLKEncoding.zerospace = ini->GetKeyAsBool("encoding", "NoSpaces", 0); //TextArea::SetNoteString( ini->GetKeyAsString( "strings", "NoteString", NULL ) ); // TODO: lists are incomplete // maybe want to externalize this // list compiled form wiki: https://gemrb.org/Text-encodings.html const char* wideEncodings[] = { // Chinese "GBK", "BIG5", // Korean "EUCKR", // Japanese "SJIS", }; size_t listSize = sizeof(wideEncodings) / sizeof(wideEncodings[0]); for (size_t i = 0; i < listSize; i++) { if (TLKEncoding.encoding == wideEncodings[i]) { TLKEncoding.widechar = true; break; } } const char* multibyteEncodings[] = { "UTF-8", }; listSize = sizeof(multibyteEncodings) / sizeof(multibyteEncodings[0]); for (size_t i = 0; i < listSize; i++) { if (TLKEncoding.encoding == multibyteEncodings[i]) { TLKEncoding.multibyte = true; break; } } const char *s; unsigned int cc = (unsigned int) ini->GetKeyAsInt ("charset", "CharCount", 0); if (cc>99) cc=99; while(cc--) { char key[10]; snprintf(key,9,"Letter%d", cc+1); s = ini->GetKeyAsString( "charset", key, NULL ); if (s) { const char *s2 = strchr(s,','); if (s2) { unsigned char upper = atoi(s); unsigned char lower = atoi(s2+1); pl_uppercase[lower] = upper; pl_lowercase[upper] = lower; } } } return true; } /** Returns a preloaded Font */ Font* Interface::GetFont(const ResRef& ResRef) const { std::map::const_iterator i = fonts.find(ResRef); if (i != fonts.end()) { return i->second; } return NULL; } Font* Interface::GetTextFont() const { return GetFont( TextFontResRef ); } Font* Interface::GetButtonFont() const { return GetFont( ButtonFontResRef ); } /** Get GUI Script Manager */ ScriptEngine* Interface::GetGUIScriptEngine() const { return guiscript.get(); } static EffectRef fx_summon_disable_ref = { "AvatarRemovalModifier", -1 }; //NOTE: if there were more summoned creatures, it will return only the last Actor *Interface::SummonCreature(const ieResRef resource, const ieResRef vvcres, Scriptable *Owner, Actor *target, const Point &position, int eamod, int level, Effect *fx, bool sexmod) { //maximum number of monsters summoned int cnt=10; Actor * ab = NULL; const Actor *summoner = nullptr; Map *map; if (target) { map = target->GetCurrentArea(); } else if (Owner) { map = Owner->GetCurrentArea(); } else { map = game->GetCurrentArea(); } if (!map) return ab; if (Owner && Owner->Type == ST_ACTOR) { summoner = (Actor *) Owner; } while (cnt--) { Actor *tmp = gamedata->GetCreature(resource); if (!tmp) { return NULL; } //if summoner is an actor, filter out opponent summons //this is done by clearing the filter when appropriate //non actors and neutrals can summon as much as they want ieDword flag = GA_NO_DEAD|GA_NO_ALLY|GA_NO_ENEMY; if (summoner) { tmp->LastSummoner = Owner->GetGlobalID(); ieDword ea = summoner->GetStat(IE_EA); if (ea<=EA_GOODCUTOFF) { flag &= ~GA_NO_ALLY; } else if (ea>=EA_EVILCUTOFF) { flag &= ~GA_NO_ENEMY; } } // mark the summon, but only if they don't have a special sex already if (sexmod && tmp->BaseStats[IE_SEX] < SEX_EXTRA && tmp->BaseStats[IE_SEX] != SEX_ILLUSION) { tmp->SetBase(IE_SEX, SEX_SUMMON); } // only allow up to the summoning limit of new summoned creatures // the summoned creatures have a special IE_SEX // but also only limit the party, so spore colonies can reproduce more ieDword sex = tmp->GetStat(IE_SEX); int limit = gamedata->GetSummoningLimit(sex); if (limit && sexmod && map->CountSummons(flag, sex) >= limit && summoner && summoner->InParty) { //summoning limit reached displaymsg->DisplayConstantString(STR_SUMMONINGLIMIT, DMC_WHITE); delete tmp; break; } ab = tmp; //Always use Base stats for the recently summoned creature int enemyally; if (eamod==EAM_SOURCEALLY || eamod==EAM_SOURCEENEMY) { if (summoner) { enemyally = summoner->GetStat(IE_EA) > EA_GOODCUTOFF; } else { enemyally = true; } } else { if (target) { enemyally = target->GetBase(IE_EA)>EA_GOODCUTOFF; } else { enemyally = true; } } switch (eamod) { case EAM_SOURCEALLY: case EAM_ALLY: if (enemyally) { ab->SetBase(IE_EA, EA_ENEMY); //is this the summoned EA? } else { ab->SetBase(IE_EA, EA_CONTROLLED); //is this the summoned EA? } break; case EAM_SOURCEENEMY: case EAM_ENEMY: if (enemyally) { ab->SetBase(IE_EA, EA_CONTROLLED); //is this the summoned EA? } else { ab->SetBase(IE_EA, EA_ENEMY); //is this the summoned EA? } break; case EAM_NEUTRAL: ab->SetBase(IE_EA, EA_NEUTRAL); break; default: break; } map->AddActor(ab, true); ab->SetPosition(position, true, 0, 0, ab->size); ab->RefreshEffects(NULL); // guessing, since this trigger was unused in the originals — likely duplicating LastSummoner if (Owner) { Owner->AddTrigger(TriggerEntry(trigger_summoned, ab->GetGlobalID())); } if (vvcres[0]) { ScriptedAnimation* vvc = gamedata->GetScriptedAnimation(vvcres, false); if (vvc) { //This is the final position of the summoned creature //not the original target point vvc->Pos = ab->Pos; //force vvc to play only once vvc->PlayOnce(); map->AddVVCell( new VEFObject(vvc) ); //set up the summon disable effect Effect *newfx = EffectQueue::CreateEffect(fx_summon_disable_ref, 0, 1, FX_DURATION_ABSOLUTE); if (newfx) { newfx->Duration = vvc->GetSequenceDuration(AI_UPDATE_TIME)*9/10 + core->GetGame()->GameTime; ApplyEffect(newfx, ab, ab); delete newfx; } } } //remove the xp value of friendly summons if (ab->BaseStats[IE_EA]SetBase(IE_XPVALUE, 0); } if (fx) { ApplyEffect(fx, ab, Owner); } //this check should happen after the fact level -= ab->GetBase(IE_XP); if(level<0 || ab->GetBase(IE_XP) == 0) { break; } } return ab; } /** Loads a Window in the Window Manager */ Window* Interface::LoadWindow(ScriptingId WindowID, const ResRef& ref, Window::WindowPosition pos) { if (ref) // is the winpack changing? guifact->LoadWindowPack(ref); Window* win = GetWindow(WindowID, ref); if (!win) { win = guifact->GetWindow( WindowID ); } if (win) { assert(win->GetScriptingRef()); win->SetPosition(pos); winmgr->FocusWindow( win ); } return win; } /** Creates a Window in the Window Manager */ Window* Interface::CreateWindow(unsigned short WindowID, const Region& frame) { // FIXME: this will create a window under the current "window pack" // obviously its possible the id can conflict with an existing window return guifact->CreateWindow(WindowID, frame); } inline void SetGroupViewFlags(const std::vector& views, unsigned int flags, int op) { std::vector::const_iterator it = views.begin(); for (; it != views.end(); ++it) { View* view = *it; view->SetFlags(flags, op); } } void Interface::ToggleViewsVisible(bool visible, const ResRef& group) { if (game && group == ResRef("HIDE_CUT")) { game->SetControlStatus(CS_HIDEGUI, (visible) ? OP_NAND : OP_OR ); } std::vector views = GetViews(group); SetGroupViewFlags(views, View::Invisible, (visible) ? OP_NAND : OP_OR); } void Interface::ToggleViewsEnabled(bool enabled, const ResRef& group) { std::vector views = GetViews(group); SetGroupViewFlags(views, View::Disabled, (enabled) ? OP_NAND : OP_OR); } bool Interface::IsFreezed() { return !update_scripts; } void Interface::GameLoop(void) { update_scripts = false; GameControl *gc = GetGameControl(); if (gc) { update_scripts = !(gc->GetDialogueFlags() & DF_FREEZE_SCRIPTS); } bool do_update = GSUpdate(update_scripts); if (game) { if (gc && !game->selected.empty()) { gc->ChangeMap(GetFirstSelectedPC(true), false); } //in multi player (if we ever get to it), only the server must call this if (do_update) { // the game object will run the area scripts as well game->UpdateScripts(); } } } /** handles hardcoded gui behaviour */ void Interface::HandleGUIBehaviour(void) { GameControl *gc = GetGameControl(); if (gc) { //this variable is used all over in the following hacks int flg = gc->GetDialogueFlags(); //the following part is a series of hardcoded gui behaviour //initiating dialog if (flg & DF_IN_DIALOG) { // -3 noaction // -2 close // -1 open // choose option ieDword var = (ieDword) -3; vars->Lookup("DialogChoose", var); if ((int) var == -2) { // TODO: this seems to never be called? (EndDialog is called from elsewhere instead) gc->dialoghandler->EndDialog(); } else if ( (int)var !=-3) { if ( (int) var == -1) { guiscript->RunFunction( "GUIWORLD", "DialogStarted" ); } gc->dialoghandler->DialogChoose(var); if (!(gc->GetDialogueFlags() & (DF_OPENCONTINUEWINDOW | DF_OPENENDWINDOW))) guiscript->RunFunction( "GUIWORLD", "NextDialogState" ); // the last node of a dialog can have a new-dialog action! don't interfere in that case ieDword newvar = 0; vars->Lookup("DialogChoose", newvar); if (var == (ieDword) -1 || newvar != (ieDword) -1) { vars->SetAt("DialogChoose", (ieDword) -3); } } if (flg & DF_OPENCONTINUEWINDOW) { guiscript->RunFunction( "GUIWORLD", "OpenContinueMessageWindow" ); gc->SetDialogueFlags(DF_OPENCONTINUEWINDOW|DF_OPENENDWINDOW, OP_NAND); } else if (flg & DF_OPENENDWINDOW) { guiscript->RunFunction( "GUIWORLD", "OpenEndMessageWindow" ); gc->SetDialogueFlags(DF_OPENCONTINUEWINDOW|DF_OPENENDWINDOW, OP_NAND); } } //handling container if (CurrentContainer && UseContainer) { if (!(flg & DF_IN_CONTAINER) ) { gc->SetDialogueFlags(DF_IN_CONTAINER, OP_OR); guiscript->RunFunction( "CommonWindow", "OpenContainerWindow" ); } } else { if (flg & DF_IN_CONTAINER) { gc->SetDialogueFlags(DF_IN_CONTAINER, OP_NAND); guiscript->RunFunction( "CommonWindow", "CloseContainerWindow" ); } } //end of gui hacks } } Tooltip Interface::CreateTooltip() { Font::PrintColors colors; colors.fg = gamedata->GetColor("TOOLTIP"); colors.bg = gamedata->GetColor("TOOLTIPBG"); TooltipBackground* bg = NULL; if (TooltipBG) { bg = new TooltipBackground(*TooltipBG); } return Tooltip(L"", GetFont(TooltipFontResRef), colors, bg); } /** Get the Sound Manager */ SaveGameIterator* Interface::GetSaveGameIterator() const { return sgiterator; } void Interface::AskAndExit() { // if askExit is 1 then we are trying to quit a second time and should instantly do so ieDword askExit = 0; vars->Lookup("AskAndExit", askExit); if (game && !askExit) { SetPause(PAUSE_ON); vars->SetAt("AskAndExit", 1); guifact->LoadWindowPack("GUIOPT"); guiscript->RunFunction("GUIOPT", "OpenQuitMsgWindow"); Log(MESSAGE, "Info", "Press ctrl-c (or close the window) again to quit GemRB.\n"); } else { ExitGemRB(); } } void Interface::ExitGemRB() { QuitFlag |= QF_KILL; } /** Returns the variables dictionary */ Variables* Interface::GetDictionary() const { return vars; } /** Returns the token dictionary */ Variables* Interface::GetTokenDictionary() const { return tokens; } /** Get the Music Manager */ MusicMgr* Interface::GetMusicMgr() const { return music.get(); } /** Loads an IDS Table, returns -1 on error or the Symbol Table Index on success */ int Interface::LoadSymbol(const char* ResRef) { int ind = GetSymbolIndex( ResRef ); if (ind != -1) { return ind; } DataStream* str = gamedata->GetResource( ResRef, IE_IDS_CLASS_ID ); if (!str) { return -1; } PluginHolder sm(IE_IDS_CLASS_ID); if (!sm) { delete str; return -1; } if (!sm->Open(str)) { return -1; } Symbol s; strlcpy(s.ResRef, ResRef, sizeof(s.ResRef)); s.sm = sm; ind = -1; for (size_t i = 0; i < symbols.size(); i++) { if (!symbols[i].sm) { ind = ( int ) i; break; } } if (ind != -1) { symbols[ind] = s; return ind; } symbols.push_back( s ); return ( int ) symbols.size() - 1; } /** Gets the index of a loaded Symbol Table, returns -1 on error */ int Interface::GetSymbolIndex(const char* ResRef) const { for (size_t i = 0; i < symbols.size(); i++) { if (!symbols[i].sm) continue; if (strnicmp( symbols[i].ResRef, ResRef, 8 ) == 0) return ( int ) i; } return -1; } /** Gets a Loaded Symbol Table by its index, returns NULL on error */ Holder Interface::GetSymbol(unsigned int index) const { if (index >= symbols.size()) { return Holder(); } if (!symbols[index].sm) { return Holder(); } return symbols[index].sm; } /** Frees a Loaded Symbol Table, returns false on error, true on success */ bool Interface::DelSymbol(unsigned int index) { if (index >= symbols.size()) { return false; } if (!symbols[index].sm) { return false; } symbols[index].sm.release(); return true; } /** Plays a Movie */ int Interface::PlayMovie(const char* resref) { const char *realResRef = resref; //check whether there is an override for this movie const char *sound_resref = NULL; AutoTable mvesnd; if (mvesnd.load("mvesnd", true)) { int row = mvesnd->GetRowIndex(resref); if (row != -1) { int mvecol = mvesnd->GetColumnIndex("override"); if (mvecol != -1) { realResRef = mvesnd->QueryField(row, mvecol); } int sndcol = mvesnd->GetColumnIndex("sound_override"); if (sndcol != -1) { sound_resref = mvesnd->QueryField(row, sndcol); } } } ResourceHolder mp = GetResourceHolder(realResRef); if (!mp) { return -1; } //one of these two should exist (they both mean the same thing) ieDword subtitles = true; vars->Lookup("Display Movie Subtitles", subtitles); if (!subtitles) { vars->Lookup("Display Subtitles", subtitles); } mp->EnableSubtitles(subtitles); class IESubtitles : public MoviePlayer::SubtitleSet { using FrameMap = std::map; FrameMap subs; mutable size_t nextSubFrame; mutable String* cachedSub; public: // default color taken from BGEE.lua IESubtitles(class Font* fnt, ResRef resref, const Color& col = Color(0xe9, 0xe2, 0xca, 0xff)) : MoviePlayer::SubtitleSet(fnt, col) { AutoTable sttable(resref); cachedSub = NULL; nextSubFrame = 0; for (size_t i = 0; i < sttable->GetRowCount(); ++i) { const char* frameField = sttable->QueryField(i, 0); const char* strField = sttable->QueryField(i, 1); if (frameField && strField) { // this skips the initial palette rows subs[atoi(frameField)] = atoi(strField); } } } ~IESubtitles() override { delete cachedSub; } size_t NextSubtitleFrame() const override { return nextSubFrame; } const String& SubtitleAtFrame(size_t frameNum) const override { // FIXME: we cant go backwards now... we dont need to, but this is ugly if (frameNum >= NextSubtitleFrame()) { FrameMap::const_iterator it; it = subs.upper_bound(frameNum); nextSubFrame = it->first; if (it != subs.begin()) { --it; } delete cachedSub; cachedSub = core->GetString(it->second); } assert(cachedSub); return *cachedSub; } }; AutoTable sttable(resref); Font* font = GetFont(MovieFontResRef); if (sttable && font) { int r = atoi(sttable->QueryField("red", "frame")); int g = atoi(sttable->QueryField("green", "frame")); int b = atoi(sttable->QueryField("blue", "frame")); if (r || g || b) { mp->SetSubtitles(new IESubtitles(font, resref, Color(r, g, b, 0xff))); } else { mp->SetSubtitles(new IESubtitles(font, resref)); } } //shutting down music and ambients before movie if (music) music->HardEnd(); AmbientMgr *ambim = AudioDriver->GetAmbientMgr(); if (ambim) ambim->deactivate(); Holder sound_override; if (sound_resref) { sound_override = AudioDriver->Play(sound_resref, SFX_CHAN_NARRATOR); } // clear whatever is currently on screen SetCutSceneMode(true); Region screen(0,0, Width, Height); Window* win = winmgr->MakeWindow(screen); win->SetFlags(Window::Borderless|Window::NoSounds, OP_OR); winmgr->PresentModalWindow(win); WindowManager::CursorFeedback cur = winmgr->SetCursorFeedback(WindowManager::MOUSE_NONE); winmgr->DrawWindows(); mp->Play(win); win->Close(); winmgr->SetCursorFeedback(cur); SetCutSceneMode(false); if (sound_override) { sound_override->Stop(); sound_override.release(); } //restarting music if (music) music->Start(); if (ambim) ambim->activate(); //Setting the movie name to 1 vars->SetAt( resref, 1 ); return 0; } int Interface::Roll(int dice, int size, int add) const { if (dice < 1) { return add; } if (size < 1) { return add; } if (dice > 100) { return add + dice * size / 2; } for (int i = 0; i < dice; i++) { add += RAND(1, size); } return add; } DirectoryIterator Interface::GetResourceDirectory(RESOURCE_DIRECTORY dir) { char Path[_MAX_PATH]; const char* resourcePath = NULL; DirectoryIterator::FileFilterPredicate* filter = NULL; switch (dir) { case DIRECTORY_CHR_PORTRAITS: resourcePath = GamePortraitsPath; filter = new ExtFilter("BMP"); if (IsAvailable(IE_PNG_CLASS_ID)) { // chain an ORed filter for png filter = new OrPredicate(filter, new ExtFilter("PNG")); } break; case DIRECTORY_CHR_SOUNDS: resourcePath = GameSoundsPath; if (!HasFeature( GF_SOUNDFOLDERS )) filter = new ExtFilter("WAV"); break; case DIRECTORY_CHR_EXPORTS: resourcePath = GameCharactersPath; filter = new ExtFilter("CHR"); break; case DIRECTORY_CHR_SCRIPTS: resourcePath = GameScriptsPath; filter = new ExtFilter("BS"); filter = new OrPredicate(filter, new ExtFilter("BCS")); break; default: error("Interface", "Uknown resource directory type: %d!", dir); } PathJoin(Path, GamePath, resourcePath, nullptr); DirectoryIterator dirIt(Path); dirIt.SetFilterPredicate(filter); return dirIt; } bool Interface::InitializeVarsWithINI(const char* iniFileName) { if (!core->IsAvailable( IE_INI_CLASS_ID )) return false; DataFileMgr* defaults = NULL; DataFileMgr* overrides = NULL; PluginHolder ini(IE_INI_CLASS_ID); FileStream* iniStream = FileStream::OpenFile(iniFileName); // if filename is not set we assume we are creating defaults without an INI bool opened = ini->Open(iniStream); if (iniFileName[0] && !opened) { Log(WARNING, "Core", "Unable to read defaults from '%s'. Using GemRB default values.", iniFileName); } else { overrides = ini.get(); } if (!opened || iniFileName[0] == 0) { delete iniStream; // Open deletes it itself on success } PluginHolder gemINI(IE_INI_CLASS_ID); DataStream* gemINIStream = gamedata->GetResource( "defaults", IE_INI_CLASS_ID ); if (!gemINIStream || !gemINI->Open(gemINIStream)) { Log(WARNING, "Core", "Unable to load GemRB default values."); defaults = ini.get(); } else { defaults = gemINI.get(); } if (!overrides) { overrides = defaults; } for (int i = 0; i < defaults->GetTagsCount(); i++) { const char* tag = defaults->GetTagNameByIndex(i); for (int j = 0; j < defaults->GetKeysCount(tag); j++) { ieDword nothing; const char* key = defaults->GetKeyNameByIndex(tag, j); //skip any existing entries. GemRB.cfg has priority if (!vars->Lookup(key, nothing)) { ieDword defaultVal = defaults->GetKeyAsInt(tag, key, 0); vars->SetAt(key, overrides->GetKeyAsInt(tag, key, defaultVal)); } } } // handle a few special cases if (!overrides->GetKeyAsInt("Config", "Sound", 1)) AudioDriverName = "null"; if (overrides->GetKeyAsInt("Game Options", "Cheats", 1)) { EnableCheatKeys(1); } // copies if (!overrides->GetKeyAsInt("Game Options", "Darkvision", 1)) { vars->SetAt("Infravision", (ieDword)0); } if (!Width || !Height) { Height = overrides->GetKeyAsInt("Config", "ConfigHeight", Height); int tmpWidth = overrides->GetKeyAsInt("Config", "ConfigWidth", 0); if (!tmpWidth) { // Resolution is stored as width only. assume 4|3 ratio. Width = overrides->GetKeyAsInt("Program Options", "Resolution", Width); Height = 0.75 * Width; } } return true; } /** Saves the gemrb config variables from the whitelist to gem-INIConfig * If GamePath is not writable, it tries SavePath */ bool Interface::SaveConfig() { char ini_path[_MAX_PATH] = { '\0' }; char gemrbINI[_MAX_PATH+4] = { '\0' }; if (strncmp(INIConfig, "gem-", 4)) { snprintf(gemrbINI, sizeof(gemrbINI), "gem-%s", INIConfig); } PathJoin(ini_path, GamePath, gemrbINI, nullptr); FileStream *fs = new FileStream(); if (!fs->Create(ini_path)) { PathJoin(ini_path, SavePath, gemrbINI, nullptr); if (!fs->Create(ini_path)) { delete fs; return false; } } PluginHolder defaultsINI(IE_INI_CLASS_ID); DataStream* INIStream = gamedata->GetResource( "defaults", IE_INI_CLASS_ID ); if (INIStream && defaultsINI->Open(INIStream)) { // dump the formatted default config options to the file StringBuffer contents; for (int i = 0; i < defaultsINI->GetTagsCount(); i++) { const char* tag = defaultsINI->GetTagNameByIndex(i); // write section header contents.appendFormatted("[%s]\n", tag); for (int j = 0; j < defaultsINI->GetKeysCount(tag); j++) { const char* key = defaultsINI->GetKeyNameByIndex(tag, j); ieDword value = 0; bool found = vars->Lookup(key, value); assert(found); contents.appendFormatted("%s = %d\n", key, value); } } fs->Write(contents.get().c_str(), contents.get().size()); } else { Log(ERROR, "Core", "Unable to open GemRB defaults. Cannot determine what values to write to %s.", ini_path); } delete fs; return true; } // this is more of a workaround than anything else // needed for cases where the script runner is gone before finishing his queue void Interface::SetCutSceneRunner(Scriptable *runner) { CutSceneRunner = runner; } /** Enables/Disables the Cut Scene Mode */ void Interface::SetCutSceneMode(bool active) { GameControl *gc = GetGameControl(); if (gc) { gc->SetCutSceneMode( active ); } ToggleViewsVisible(!active, "HIDE_CUT"); if (active) { GetGUIScriptEngine()->RunFunction("GUICommonWindows", "CloseTopWindow"); } else { SetCutSceneRunner(NULL); } } /** returns true if in dialogue or cutscene */ bool Interface::InCutSceneMode() const { const GameControl *gc = GetGameControl(); if (!gc || (gc->GetDialogueFlags()&DF_IN_DIALOG) || (gc->GetScreenFlags()&SF_CUTSCENE)) { return true; } return false; } /** Updates the Game Script Engine State */ bool Interface::GSUpdate(bool update_scripts) { if(update_scripts) { return timer.Update(); } else { timer.Freeze(); return false; } } void Interface::QuitGame(int BackToMain) { SetCutSceneMode(false); //shutting down ingame music //(do it before deleting the game) if (music) { music->HardEnd(); } // stop any ambients which are still enqueued if (AudioDriver) { AmbientMgr *ambim = AudioDriver->GetAmbientMgr(); if (ambim) ambim->deactivate(); AudioDriver->Stop(); // also kill sounds } //delete game, worldmap if (game) { delete game; game=NULL; } if (worldmap) { delete worldmap; worldmap=NULL; } if (BackToMain) { SetNextScript("Start"); } GSUpdate(true); } void Interface::SetupLoadGame(Holder sg, int ver_override) { LoadGameIndex = sg; VersionOverride = ver_override; QuitFlag |= QF_LOADGAME; } void Interface::LoadGame(SaveGame *sg, int ver_override) { // This function has rather painful error handling, // as it should swap all the objects or none at all // and the loading can fail for various reasons // Yes, it uses goto. Other ways seemed too awkward for me. gamedata->SaveAllStores(); strings->CloseAux(); tokens->RemoveAll(NULL); //clearing the token dictionary if(calendar) delete calendar; calendar = new Calendar; DataStream* gam_str = NULL; DataStream* sav_str = NULL; DataStream* wmp_str1 = NULL; DataStream* wmp_str2 = NULL; Game* new_game = NULL; WorldMapArray* new_worldmap = NULL; LoadProgress(10); if (!KeepCache) DelTree((const char *) CachePath, true); LoadProgress(15); if (sg == NULL) { //Load the Default Game gam_str = gamedata->GetResource( GameNameResRef, IE_GAM_CLASS_ID ); sav_str = NULL; wmp_str1 = gamedata->GetResource( WorldMapName[0], IE_WMP_CLASS_ID ); if (!WorldMapName[1].IsEmpty()) { wmp_str2 = gamedata->GetResource( WorldMapName[1], IE_WMP_CLASS_ID ); } } else { gam_str = sg->GetGame(); sav_str = sg->GetSave(); wmp_str1 = sg->GetWmap(0); if (!WorldMapName[1].IsEmpty()) { wmp_str2 = sg->GetWmap(1); if (!wmp_str2) { //upgrade an IWD game to HOW wmp_str2 = gamedata->GetResource( WorldMapName[1], IE_WMP_CLASS_ID ); } } } // These are here because of the goto PluginHolder gam_mgr(IE_GAM_CLASS_ID); PluginHolder wmp_mgr(IE_WMP_CLASS_ID); AmbientMgr *ambim = core->GetAudioDrv()->GetAmbientMgr(); if (!gam_str || !(wmp_str1 || wmp_str2) ) goto cleanup; // Load GAM file if (!gam_mgr) goto cleanup; if (!gam_mgr->Open(gam_str)) goto cleanup; new_game = gam_mgr->LoadGame(new Game(), ver_override); if (!new_game) goto cleanup; gam_str = NULL; // Load WMP (WorldMap) file if (!wmp_mgr) goto cleanup; if (!wmp_mgr->Open(wmp_str1, wmp_str2)) goto cleanup; new_worldmap = wmp_mgr->GetWorldMapArray( ); wmp_str1 = NULL; wmp_str2 = NULL; LoadProgress(20); // Unpack SAV (archive) file to Cache dir if (sav_str) { PluginHolder ai(IE_SAV_CLASS_ID); if (ai) { if (ai->DecompressSaveGame(sav_str) != GEM_OK) { goto cleanup; } } delete sav_str; sav_str = NULL; } // rarely caused crashes while loading, so stop the ambients if (ambim) { ambim->reset(); } // Let's assume that now is everything loaded OK and swap the objects delete game; delete worldmap; game = new_game; worldmap = new_worldmap; strings->OpenAux(); LoadProgress(70); return; cleanup: // Something went wrong, so try to clean after itself delete new_game; delete new_worldmap; delete gam_str; delete wmp_str1; delete wmp_str2; delete sav_str; error("Core", "Unable to load game."); } /* replace the current world map but sync areas available in old and new */ void Interface::UpdateWorldMap(ResRef wmResRef) { DataStream* wmp_str = gamedata->GetResource(wmResRef, IE_WMP_CLASS_ID); PluginHolder wmp_mgr(IE_WMP_CLASS_ID); if (!wmp_str || !wmp_mgr || !wmp_mgr->Open(wmp_str, NULL)) { Log(ERROR, "Core", "Could not update world map %s", wmResRef.CString()); return; } WorldMapArray *new_worldmap = wmp_mgr->GetWorldMapArray(); WorldMap *wm = worldmap->GetWorldMap(0); WorldMap *nwm = new_worldmap->GetWorldMap(0); unsigned int i, ni; unsigned int ec = wm->GetEntryCount(); //update status of the previously existing areas for(i=0;iGetEntry(i); WMPAreaEntry *nae = nwm->GetArea(ae->AreaResRef, ni); if (nae != NULL) { nae->SetAreaStatus(ae->GetAreaStatus(), OP_SET); } } delete worldmap; worldmap = new_worldmap; WorldMapName[0] = wmResRef; } /* swapping out old resources */ void Interface::UpdateMasterScript() { if (game) { game->SetScript( GlobalScript, 0 ); } PluginHolder wmp_mgr(IE_WMP_CLASS_ID); if (! wmp_mgr) return; if (worldmap) { DataStream *wmp_str1 = gamedata->GetResource( WorldMapName[0], IE_WMP_CLASS_ID ); DataStream *wmp_str2 = gamedata->GetResource( WorldMapName[1], IE_WMP_CLASS_ID ); if (!wmp_mgr->Open(wmp_str1, wmp_str2)) { delete wmp_str1; delete wmp_str2; } delete worldmap; worldmap = wmp_mgr->GetWorldMapArray(); } } bool Interface::InitItemTypes() { int i; if (slotmatrix) { free(slotmatrix); } AutoTable it("itemtype"); ItemTypes = 0; if (it) { ItemTypes = it->GetRowCount(); //number of itemtypes if (ItemTypes<0) { ItemTypes = 0; } int InvSlotTypes = it->GetColumnCount(); if (InvSlotTypes > 32) { //bit count limit InvSlotTypes = 32; } //make sure unsigned int is 32 bits slotmatrix = (ieDword *) malloc(ItemTypes * sizeof(ieDword) ); for (i=0;iQueryField(i,j),NULL,0) ) { value |= k; } k <<= 1; } //we let any items in the inventory slotmatrix[i] = value | SLOT_INVENTORY; } } //itemtype data stores (armor failure and critical damage multipliers), critical range itemtypedata.reserve(ItemTypes); for (i=0;i(4)); //default values in case itemdata is missing (it is needed only for iwd2) if (slotmatrix[i] & SLOT_WEAPON) { itemtypedata[i][IDT_FAILURE] = 0; // armor malus itemtypedata[i][IDT_CRITRANGE] = 20; // crit range itemtypedata[i][IDT_CRITMULTI] = 2; // crit multiplier itemtypedata[i][IDT_SKILLPENALTY] = 0; // skill check malus } } AutoTable af("itemdata"); if (af) { int armcount = af->GetRowCount(); int colcount = af->GetColumnCount(); int j; for (i = 0; i < armcount; i++) { int itemtype = (ieWord) atoi( af->QueryField(i,0) ); if (itemtypeQueryField(i, j+1)); } } } } //slottype describes the inventory structure Inventory::Init(); AutoTable st("slottype"); if (slottypes) { free(slottypes); slottypes = NULL; } SlotTypes = 0; if (st) { SlotTypes = st->GetRowCount(); //make sure unsigned int is 32 bits slottypes = (SlotType *) malloc(SlotTypes * sizeof(SlotType) ); memset(slottypes, -1, SlotTypes * sizeof(SlotType) ); for (unsigned int row = 0; row < SlotTypes; row++) { bool alias; unsigned int i = (ieDword) strtol(st->GetRowName(row),NULL,0 ); if (i>=SlotTypes) continue; if (slottypes[i].sloteffects!=0xffffffffu) { slottypes[row].slot = i; i=row; alias = true; } else { slottypes[row].slot = i; alias = false; } slottypes[i].slottype = (ieDword) strtol(st->QueryField(row,0),NULL,0 ); slottypes[i].slotid = (ieDword) strtol(st->QueryField(row,1),NULL,0 ); strnlwrcpy( slottypes[i].slotresref, st->QueryField(row,2), 8 ); slottypes[i].slottip = (ieDword) strtol(st->QueryField(row,3),NULL,0 ); slottypes[i].slotflags = (ieDword) strtol(st->QueryField(row,5),NULL,0 ); //don't fill sloteffects for aliased slots (pst) if (alias) { continue; } slottypes[i].sloteffects = (ieDword) strtol(st->QueryField(row,4),NULL,0 ); //setting special slots if (slottypes[i].slottype&SLOT_ITEM) { if (slottypes[i].slottype&SLOT_INVENTORY) { Inventory::SetInventorySlot(i); } else { Inventory::SetQuickSlot(i); } } switch (slottypes[i].sloteffects) { //fist slot, not saved, default weapon case SLOT_EFFECT_FIST: Inventory::SetFistSlot(i); break; //magic weapon slot, overrides all weapons case SLOT_EFFECT_MAGIC: Inventory::SetMagicSlot(i); break; //weapon slot, Equipping marker is relative to it case SLOT_EFFECT_MELEE: Inventory::SetWeaponSlot(i); break; //ranged slot case SLOT_EFFECT_MISSILE: Inventory::SetRangedSlot(i); break; //right hand case SLOT_EFFECT_LEFT: Inventory::SetShieldSlot(i); break; //head (for averting critical hit) case SLOT_EFFECT_HEAD: Inventory::SetHeadSlot(i); break; //armor slot case SLOT_EFFECT_ITEM: Inventory::SetArmorSlot(i); break; default:; } } } return (it && st); } ieDword Interface::FindSlot(unsigned int idx) const { ieDword i; for (i=0;i=SlotTypes) { return 0; } return slottypes[idx].slot; } ieDword Interface::QuerySlotType(unsigned int idx) const { if (idx>=SlotTypes) { return 0; } return slottypes[idx].slottype; } ieDword Interface::QuerySlotID(unsigned int idx) const { if (idx>=SlotTypes) { return 0; } return slottypes[idx].slotid; } ieDword Interface::QuerySlottip(unsigned int idx) const { if (idx>=SlotTypes) { return 0; } return slottypes[idx].slottip; } ieDword Interface::QuerySlotEffects(unsigned int idx) const { if (idx>=SlotTypes) { return 0; } return slottypes[idx].sloteffects; } ieDword Interface::QuerySlotFlags(unsigned int idx) const { if (idx>=SlotTypes) { return 0; } return slottypes[idx].slotflags; } const char *Interface::QuerySlotResRef(unsigned int idx) const { if (idx>=SlotTypes) { return ""; } return slottypes[idx].slotresref; } int Interface::GetArmorFailure(unsigned int itemtype) const { if (itemtype>=(unsigned int) ItemTypes) { return 0; } if (slotmatrix[itemtype]&SLOT_ARMOUR) return itemtypedata[itemtype][IDT_FAILURE]; return 0; } int Interface::GetShieldFailure(unsigned int itemtype) const { if (itemtype>=(unsigned int) ItemTypes) { return 0; } if (slotmatrix[itemtype]&SLOT_SHIELD) return itemtypedata[itemtype][IDT_FAILURE]; return 0; } int Interface::GetArmorPenalty(unsigned int itemtype) const { if (itemtype>=(unsigned int) ItemTypes) { return 0; } if (slotmatrix[itemtype]&SLOT_ARMOUR) return itemtypedata[itemtype][IDT_SKILLPENALTY]; return 0; } int Interface::GetShieldPenalty(unsigned int itemtype) const { if (itemtype>=(unsigned int) ItemTypes) { return 0; } if (slotmatrix[itemtype]&SLOT_SHIELD) return itemtypedata[itemtype][IDT_SKILLPENALTY]; return 0; } int Interface::GetCriticalMultiplier(unsigned int itemtype) const { if (itemtype>=(unsigned int) ItemTypes) { return 2; } if (slotmatrix[itemtype]&SLOT_WEAPON) return itemtypedata[itemtype][IDT_CRITMULTI]; return 2; } int Interface::GetCriticalRange(unsigned int itemtype) const { if (itemtype>=(unsigned int) ItemTypes) { return 20; } if (slotmatrix[itemtype]&SLOT_WEAPON) return itemtypedata[itemtype][IDT_CRITRANGE]; return 20; } // checks the itemtype vs. slottype, and also checks the usability flags // vs. Actor's stats (alignment, class, race, kit etc.) int Interface::CanUseItemType(int slottype, const Item *item, const Actor *actor, bool feedback, bool equipped) const { //inventory is a special case, we allow any items to enter it if ( slottype==-1 ) { return SLOT_INVENTORY; } //if we look for ALL slot types, then SLOT_SHIELD shouldn't interfere //with twohandedness //As long as this is an Item, use the ITEM constant //switch for IE_INV_ITEM_* if it is a CREItem if (item->Flags&IE_ITEM_TWO_HANDED) { //if the item is twohanded and there are more slots, drop the shield slot if (slottype&~SLOT_SHIELD) { slottype&=~SLOT_SHIELD; } if (slottype&SLOT_SHIELD) { //cannot equip twohanded in offhand if (feedback) displaymsg->DisplayConstantString(STR_NOT_IN_OFFHAND, DMC_WHITE); return 0; } } if ( (unsigned int) item->ItemType>=(unsigned int) ItemTypes) { //invalid itemtype if (feedback) displaymsg->DisplayConstantString(STR_WRONGITEMTYPE, DMC_WHITE); return 0; } //if actor is supplied, check its usability fields if (actor) { //constant strings int idx = actor->Unusable(item); if (idx) { if (feedback) displaymsg->DisplayConstantString(idx, DMC_WHITE); return 0; } //custom strings ieStrRef str = actor->Disabled(item->Name, item->ItemType); if (str && !equipped) { if (feedback) displaymsg->DisplayString(str, DMC_WHITE, 0); return 0; } } //if any bit is true, the answer counts as true int ret = (slotmatrix[item->ItemType]&slottype); if (!ret) { if (feedback) displaymsg->DisplayConstantString(STR_WRONGITEMTYPE, DMC_WHITE); return 0; } //this warning comes only when feedback is enabled if (feedback) { //this was, but that disabled equipping of amber earrings in PST //if (slotmatrix[item->ItemType]&(SLOT_QUIVER|SLOT_WEAPON|SLOT_ITEM)) { if (ret&(SLOT_QUIVER|SLOT_WEAPON|SLOT_ITEM)) { //don't ruin the return variable, it contains the usable slot bits int flg = 0; if (ret&SLOT_QUIVER) { if (item->GetWeaponHeader(true)) flg = 1; } if (ret&SLOT_WEAPON) { //melee if (item->GetWeaponHeader(false)) flg = 1; //ranged if (item->GetWeaponHeader(true)) flg = 1; } if (ret&SLOT_ITEM) { if (item->GetEquipmentHeaderNumber(0)!=0xffff) flg = 1; } if (!flg) { displaymsg->DisplayConstantString(STR_UNUSABLEITEM, DMC_WHITE); return 0; } } } return ret; } Label* Interface::GetMessageLabel() const { return GetControl