diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index 2c0b0a5e..195eac38 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -133,6 +133,15 @@ WorldMapTravelMarkers=0 ;Set to 1 to display terrain types when hovering the cursor over the player's marker on the world map WorldMapTerrainInfo=0 +;Set to 1 to enable tall trade/barter window with 4 item slots per table instead of 3 +;Requires the high-res mode with resolution of at least 640x528 +;Requires new frm files in art\intrface\: barter_e.frm (640x239), trade_e.frm (640x238) (included in sfall.dat) +ExpandedBarter=0 + +;Set to 1 to enable tall inventory/loot/item select windows with 8 vertical slots instead of 6 +;Requires new frm files in art\intrface\: invbox_e.frm (499x467), loot_e.frm (537x464) and use_e.frm (292x464) files (included in sfall.dat) +ExpandedInventory=0 + ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [Sound] ;Sets the number of allowed simultaneous sound effects diff --git a/artifacts/sfall_dat/dat_en/art/intrface/barter_e.frm b/artifacts/sfall_dat/dat_en/art/intrface/barter_e.frm new file mode 100644 index 00000000..c793d823 Binary files /dev/null and b/artifacts/sfall_dat/dat_en/art/intrface/barter_e.frm differ diff --git a/artifacts/sfall_dat/dat_en/art/intrface/invbox_e.frm b/artifacts/sfall_dat/dat_en/art/intrface/invbox_e.frm new file mode 100644 index 00000000..fa201709 Binary files /dev/null and b/artifacts/sfall_dat/dat_en/art/intrface/invbox_e.frm differ diff --git a/artifacts/sfall_dat/dat_en/art/intrface/loot_e.frm b/artifacts/sfall_dat/dat_en/art/intrface/loot_e.frm new file mode 100644 index 00000000..18393795 Binary files /dev/null and b/artifacts/sfall_dat/dat_en/art/intrface/loot_e.frm differ diff --git a/artifacts/sfall_dat/dat_en/art/intrface/trade_e.frm b/artifacts/sfall_dat/dat_en/art/intrface/trade_e.frm new file mode 100644 index 00000000..33df9feb Binary files /dev/null and b/artifacts/sfall_dat/dat_en/art/intrface/trade_e.frm differ diff --git a/artifacts/sfall_dat/dat_en/art/intrface/use_e.frm b/artifacts/sfall_dat/dat_en/art/intrface/use_e.frm new file mode 100644 index 00000000..cf4b2b35 Binary files /dev/null and b/artifacts/sfall_dat/dat_en/art/intrface/use_e.frm differ diff --git a/artifacts/sfall_dat/dat_ru/art/intrface/BARTER_E.frm b/artifacts/sfall_dat/dat_ru/art/intrface/BARTER_E.frm new file mode 100644 index 00000000..956d1481 Binary files /dev/null and b/artifacts/sfall_dat/dat_ru/art/intrface/BARTER_E.frm differ diff --git a/artifacts/sfall_dat/dat_ru/art/intrface/INVBOX_E.frm b/artifacts/sfall_dat/dat_ru/art/intrface/INVBOX_E.frm new file mode 100644 index 00000000..230060c7 Binary files /dev/null and b/artifacts/sfall_dat/dat_ru/art/intrface/INVBOX_E.frm differ diff --git a/artifacts/sfall_dat/dat_ru/art/intrface/LOOT_E.frm b/artifacts/sfall_dat/dat_ru/art/intrface/LOOT_E.frm new file mode 100644 index 00000000..eea4fd02 Binary files /dev/null and b/artifacts/sfall_dat/dat_ru/art/intrface/LOOT_E.frm differ diff --git a/artifacts/sfall_dat/dat_ru/art/intrface/TRADE_E.frm b/artifacts/sfall_dat/dat_ru/art/intrface/TRADE_E.frm new file mode 100644 index 00000000..3759875c Binary files /dev/null and b/artifacts/sfall_dat/dat_ru/art/intrface/TRADE_E.frm differ diff --git a/artifacts/sfall_dat/dat_ru/art/intrface/USE_E.frm b/artifacts/sfall_dat/dat_ru/art/intrface/USE_E.frm new file mode 100644 index 00000000..6d4f060b Binary files /dev/null and b/artifacts/sfall_dat/dat_ru/art/intrface/USE_E.frm differ diff --git a/sfall/FalloutEngine/EngineUtils.cpp b/sfall/FalloutEngine/EngineUtils.cpp index 9f03b808..7b0c8530 100644 --- a/sfall/FalloutEngine/EngineUtils.cpp +++ b/sfall/FalloutEngine/EngineUtils.cpp @@ -409,7 +409,7 @@ fo::GameObject* __fastcall MultiHexMoveIsBlocking(fo::GameObject* source, long d // Returns the terrain type of the sub-tile at the specified coordinates on the world map long wmGetTerrainType(long xPos, long yPos) { - long* terrainId; + long* terrainId{}; __asm { lea ebx, terrainId; mov edx, yPos; @@ -667,105 +667,5 @@ void RefreshGNW(bool skipOwner) { fo::var::setInt(FO_VAR_doing_refresh_all) = 0; } -//////////////////////////// UNLISTED FRM FUNCTIONS //////////////////////////// - -static bool LoadFrmHeader(fo::UnlistedFrm *frmHeader, fo::DbFile* frmStream) { - if (fo::func::db_freadInt(frmStream, &frmHeader->version) == -1) - return false; - else if (fo::func::db_freadShort(frmStream, &frmHeader->FPS) == -1) - return false; - else if (fo::func::db_freadShort(frmStream, &frmHeader->actionFrame) == -1) - return false; - else if (fo::func::db_freadShort(frmStream, &frmHeader->numFrames) == -1) - return false; - else if (fo::func::db_freadShortCount(frmStream, frmHeader->xCentreShift, 6) == -1) - return false; - else if (fo::func::db_freadShortCount(frmStream, frmHeader->yCentreShift, 6) == -1) - return false; - else if (fo::func::db_freadIntCount(frmStream, frmHeader->oriOffset, 6) == -1) - return false; - else if (fo::func::db_freadInt(frmStream, &frmHeader->frameAreaSize) == -1) - return false; - - return true; -} - -static bool LoadFrmFrame(fo::UnlistedFrm::Frame *frame, fo::DbFile* frmStream) { - //FRMframe *frameHeader = (FRMframe*)frameMEM; - //BYTE* frameBuff = frame + sizeof(FRMframe); - - if (fo::func::db_freadShort(frmStream, &frame->width) == -1) - return false; - else if (fo::func::db_freadShort(frmStream, &frame->height) == -1) - return false; - else if (fo::func::db_freadInt(frmStream, &frame->size) == -1) - return false; - else if (fo::func::db_freadShort(frmStream, &frame->x) == -1) - return false; - else if (fo::func::db_freadShort(frmStream, &frame->y) == -1) - return false; - - frame->indexBuff = new BYTE[frame->size]; - if (fo::func::db_fread(frame->indexBuff, 1, frame->size, frmStream) != frame->size) - return false; - - return true; -} - -fo::UnlistedFrm *LoadUnlistedFrm(char *frmName, unsigned int folderRef) { - if (folderRef > fo::OBJ_TYPE_SKILLDEX) return nullptr; - - const char *artfolder = fo::var::art[folderRef].path; // address of art type name - char frmPath[MAX_PATH]; - - if (fo::var::use_language) { - sprintf_s(frmPath, MAX_PATH, "art\\%s\\%s\\%s", (const char*)fo::var::language, artfolder, frmName); - } else { - sprintf_s(frmPath, MAX_PATH, "art\\%s\\%s", artfolder, frmName); - } - - fo::UnlistedFrm *frm = new fo::UnlistedFrm; - - auto frmStream = fo::func::db_fopen(frmPath, "rb"); - - if (!frmStream && fo::var::use_language) { - sprintf_s(frmPath, MAX_PATH, "art\\%s\\%s", artfolder, frmName); - frmStream = fo::func::db_fopen(frmPath, "rb"); - } - - if (frmStream != nullptr) { - if (!LoadFrmHeader(frm, frmStream)) { - fo::func::db_fclose(frmStream); - delete frm; - return nullptr; - } - - DWORD oriOffset_1st = frm->oriOffset[0]; - DWORD oriOffset_new = 0; - frm->frames = new fo::UnlistedFrm::Frame[6 * frm->numFrames]; - for (int ori = 0; ori < 6; ori++) { - if (ori == 0 || frm->oriOffset[ori] != oriOffset_1st) { - frm->oriOffset[ori] = oriOffset_new; - for (int fNum = 0; fNum < frm->numFrames; fNum++) { - if (!LoadFrmFrame(&frm->frames[oriOffset_new + fNum], frmStream)) { - fo::func::db_fclose(frmStream); - delete frm; - return nullptr; - } - } - oriOffset_new += frm->numFrames; - } else { - frm->oriOffset[ori] = 0; - } - } - - fo::func::db_fclose(frmStream); - } else { - delete frm; - return nullptr; - } - return frm; -} - } } diff --git a/sfall/FalloutEngine/EngineUtils.h b/sfall/FalloutEngine/EngineUtils.h index 579e084f..aa5f0ca5 100644 --- a/sfall/FalloutEngine/EngineUtils.h +++ b/sfall/FalloutEngine/EngineUtils.h @@ -33,6 +33,20 @@ namespace fo namespace util { +// To safely unlock cache entries after using art_ptr_lock and similar functions +struct ArtCacheLock { + DWORD entryPtr = 0; + + ArtCacheLock() { } + ArtCacheLock(DWORD _lock) : entryPtr(_lock) { } + ~ArtCacheLock() { + if (entryPtr != 0) { + fo::func::art_ptr_unlock(entryPtr); + entryPtr = 0; + } + } +}; + __inline void DisplayPrint(const std::string& str) { fo::func::display_print(str.c_str()); } @@ -191,7 +205,5 @@ void RedrawObject(fo::GameObject* obj); // Redraws all windows void RefreshGNW(bool skipOwner = false); -fo::UnlistedFrm *LoadUnlistedFrm(char *frmName, unsigned int folderRef); - } } diff --git a/sfall/FalloutEngine/Enums.h b/sfall/FalloutEngine/Enums.h index b7b77c72..2f7872ba 100644 --- a/sfall/FalloutEngine/Enums.h +++ b/sfall/FalloutEngine/Enums.h @@ -927,4 +927,31 @@ enum DialogOutFlags : long DIALOGOUT_CLEAN = 0x20 // no buttons }; +enum InventoryWindowType : unsigned long +{ + // Normal inventory window with quick character sheet. + INVENTORY_WINDOW_TYPE_NORMAL, + + // Narrow inventory window with just an item scroller that's shown when + // a "Use item on" is selected from context menu. + INVENTORY_WINDOW_TYPE_USE_ITEM_ON, + + // Looting/strealing interface. + INVENTORY_WINDOW_TYPE_LOOT, + + // Barter interface. + INVENTORY_WINDOW_TYPE_TRADE, + + // Supplementary "Move items" window. Used to set quantity of items when + // moving items between inventories. + INVENTORY_WINDOW_TYPE_MOVE_ITEMS, + + // Supplementary "Set timer" window. Internally it's implemented as "Move + // items" window but with timer overlay and slightly different adjustment + // mechanics. + INVENTORY_WINDOW_TYPE_SET_TIMER, + + INVENTORY_WINDOW_TYPE_COUNT, +}; + } diff --git a/sfall/FalloutEngine/Structs.h b/sfall/FalloutEngine/Structs.h index 8e3e3247..86f1ed2e 100644 --- a/sfall/FalloutEngine/Structs.h +++ b/sfall/FalloutEngine/Structs.h @@ -29,8 +29,8 @@ namespace sfall struct Rectangle { long x, y, width, height; - long right() { return x + (width - 1); } - long bottom() { return y + (height - 1); } + long right() const { return x + (width - 1); } + long bottom() const { return y + (height - 1); } }; } @@ -208,25 +208,25 @@ struct GameObject { long rads; long poison; - inline bool IsDead() { + inline bool IsDead() const { return ((damageFlags & DamageFlag::DAM_DEAD) != 0); } - inline bool IsNotDead() { + inline bool IsNotDead() const { return ((damageFlags & DamageFlag::DAM_DEAD) == 0); } - inline bool IsActive() { + inline bool IsActive() const { return ((damageFlags & (DamageFlag::DAM_KNOCKED_OUT | DamageFlag::DAM_LOSE_TURN)) == 0); } - inline bool IsNotActive() { + inline bool IsNotActive() const { return ((damageFlags & (DamageFlag::DAM_KNOCKED_OUT | DamageFlag::DAM_LOSE_TURN)) != 0); } - inline bool IsActiveNotDead() { + inline bool IsActiveNotDead() const { return ((damageFlags & (DamageFlag::DAM_DEAD | DamageFlag::DAM_KNOCKED_OUT | DamageFlag::DAM_LOSE_TURN)) == 0); } - inline bool IsNotActiveOrDead() { + inline bool IsNotActiveOrDead() const { return ((damageFlags & (DamageFlag::DAM_DEAD | DamageFlag::DAM_KNOCKED_OUT | DamageFlag::DAM_LOSE_TURN)) != 0); } - inline bool IsFleeing() { + inline bool IsFleeing() const { return ((combatState & CombatStateFlag::InFlee) != 0); } @@ -255,23 +255,23 @@ struct GameObject { GameObject* owner; // not saved long scriptIndex; - inline char Type() { + inline char Type() const { return (protoId >> 24); } - inline char TypeFid() { + inline char TypeFid() const { return ((artFid >> 24) & 0x0F); } - inline bool IsCritter() { + inline bool IsCritter() const { return (Type() == fo::ObjType::OBJ_TYPE_CRITTER); } - inline bool IsNotCritter() { + inline bool IsNotCritter() const { return (Type() != fo::ObjType::OBJ_TYPE_CRITTER); } - inline bool IsItem() { + inline bool IsItem() const { return (Type() == fo::ObjType::OBJ_TYPE_ITEM); } - inline bool IsNotItem() { + inline bool IsNotItem() const { return (Type() != fo::ObjType::OBJ_TYPE_ITEM); } }; @@ -498,58 +498,6 @@ struct TileFrmFile : public FrmHeaderData { static_assert(sizeof(TileFrmFile) == 2954, "Incorrect TileFrmFile definition."); -// structures for loading unlisted frms -struct UnlistedFrm { - DWORD version; - WORD FPS; - WORD actionFrame; - WORD numFrames; - WORD xCentreShift[6]; - WORD yCentreShift[6]; - DWORD oriOffset[6]; - DWORD frameAreaSize; - - struct Frame { - WORD width; - WORD height; - DWORD size; - WORD x; - WORD y; - BYTE *indexBuff; - - Frame() { - width = 0; - height = 0; - size = 0; - x = 0; - y = 0; - indexBuff = nullptr; - } - ~Frame() { - if (indexBuff != nullptr) - delete[] indexBuff; - } - } *frames; - - UnlistedFrm() { - version = 0; - FPS = 0; - actionFrame = 0; - numFrames = 0; - for (int i = 0; i < 6; i++) { - xCentreShift[i] = 0; - yCentreShift[i] = 0; - oriOffset[i] = 0; - } - frameAreaSize = 0; - frames = nullptr; - } - - ~UnlistedFrm() { - if (frames != nullptr) delete[] frames; - } -}; - //for holding a message struct MessageNode { long number; diff --git a/sfall/FalloutEngine/VariableOffsets.h b/sfall/FalloutEngine/VariableOffsets.h index 3d8e5a68..b1f8d8ff 100644 --- a/sfall/FalloutEngine/VariableOffsets.h +++ b/sfall/FalloutEngine/VariableOffsets.h @@ -169,6 +169,7 @@ #define FO_VAR_intface_full_width 0x56FB4C #define FO_VAR_intfaceEnabled 0x518F10 #define FO_VAR_intotal 0x43E95C +#define FO_VAR_inven_cur_disp 0x519054 #define FO_VAR_inven_dude 0x519058 #define FO_VAR_inven_pid 0x51905C #define FO_VAR_inven_scroll_dn_bid 0x5190E8 diff --git a/sfall/FalloutEngine/Variables_def.h b/sfall/FalloutEngine/Variables_def.h index 9d7f90ca..823da7df 100644 --- a/sfall/FalloutEngine/Variables_def.h +++ b/sfall/FalloutEngine/Variables_def.h @@ -18,6 +18,7 @@ VAR_(BlueColor, BYTE) VAR_(bottom_line, DWORD) VAR_(btable, DWORD) VAR_(btncnt, DWORD) +VAR_(buf_length_2, long) VARD(cap, fo::AIcap) // dynamic array VAR_(carCurrentArea, DWORD) VAR_(carGasAmount, long) // from 0 to 80000 diff --git a/sfall/HRP/Dialog.cpp b/sfall/HRP/Dialog.cpp index 05b8feda..adce5efa 100644 --- a/sfall/HRP/Dialog.cpp +++ b/sfall/HRP/Dialog.cpp @@ -6,6 +6,7 @@ #include "..\main.h" #include "..\FalloutEngine\Fallout2.h" +#include "..\Modules\ExtraArt.h" #include "..\Modules\LoadGameHook.h" #include "Init.h" @@ -22,7 +23,7 @@ namespace sf = sfall; static const long width = 640; // art static long scr_width = 639; -static fo::UnlistedFrm* altDialogArt; +static fo::FrmFile* altDialogArt; bool Dialog::DIALOG_SCRN_ART_FIX = true; bool Dialog::DIALOG_SCRN_BACKGROUND = false; @@ -172,20 +173,16 @@ static bool loadAltDialogArt = false; static void __cdecl talk_to_refresh_background_window_hook_buf_to_buf(BYTE* src, long w, long h, long srcWidth, BYTE* dst, long dstWidth) { if (!loadAltDialogArt) { loadAltDialogArt = true; - altDialogArt = fo::util::LoadUnlistedFrm("HR_ALLTLK.frm", fo::ArtType::OBJ_TYPE_INTRFACE); + altDialogArt = sf::LoadUnlistedFrmCached("HR_ALLTLK.frm", fo::ArtType::OBJ_TYPE_INTRFACE); } if (altDialogArt) { - src = altDialogArt->frames->indexBuff; - srcWidth = altDialogArt->frames->width; + src = altDialogArt->frameData[0].data; + srcWidth = altDialogArt->frameData[0].width; } fo::func::buf_to_buf(src, w, h, srcWidth, dst, dstWidth); } static void UnloadDialogArt() { - if (altDialogArt) { - delete altDialogArt; - altDialogArt = nullptr; - } loadAltDialogArt = false; } @@ -273,7 +270,7 @@ void Dialog::init() { fo::var::backgrndRects[i].offy += 5; } - sf::LoadGameHook::OnGameExit() += UnloadDialogArt; + sf::LoadGameHook::OnGameReset() += UnloadDialogArt; } } diff --git a/sfall/HRP/InterfaceBar.cpp b/sfall/HRP/InterfaceBar.cpp index 83e09987..8e1f2bba 100644 --- a/sfall/HRP/InterfaceBar.cpp +++ b/sfall/HRP/InterfaceBar.cpp @@ -6,6 +6,7 @@ #include "..\main.h" #include "..\FalloutEngine\Fallout2.h" +#include "..\Modules\ExtraArt.h" #include "..\Modules\LoadGameHook.h" #include "Init.h" @@ -45,22 +46,20 @@ static class Panels { long rightBarID; void LoadFRMImage(char* name, long winId) { - auto* frm = fo::util::LoadUnlistedFrm(name, fo::ArtType::OBJ_TYPE_INTRFACE); - if (!frm) return; + fo::FrmFile* frm = sf::LoadUnlistedFrmCached(name, fo::ArtType::OBJ_TYPE_INTRFACE); + if (frm == nullptr) return; fo::Window* win = fo::func::GNW_find(winId); long width = win->width; - if (width > frm->frames->width) width = frm->frames->width; - BYTE* scr = frm->frames->indexBuff; + if (width > frm->frameData[0].width) width = frm->frameData[0].width; + BYTE* scr = frm->frameData[0].data; // set the position to the right side - if (!IFaceBar::IFACE_BAR_SIDES_ORI && win->wRect.left <= 0) scr += (frm->frames->width - win->width); - if (IFaceBar::IFACE_BAR_SIDES_ORI && win->wRect.left > xPosition) scr += (frm->frames->width - win->width); + if (!IFaceBar::IFACE_BAR_SIDES_ORI && win->wRect.left <= 0) scr += (frm->frameData[0].width - win->width); + if (IFaceBar::IFACE_BAR_SIDES_ORI && win->wRect.left > xPosition) scr += (frm->frameData[0].width - win->width); - fo::func::cscale(scr, width, frm->frames->height, frm->frames->width, win->surface, win->width, win->height, win->width); - - delete frm; + fo::func::cscale(scr, width, frm->frameData[0].height, frm->frameData[0].width, win->surface, win->width, win->height, win->width); } public: @@ -205,24 +204,21 @@ static long __cdecl InterfaceArt(BYTE* scr, long w, long h, long srcWidth, BYTE* char file[33]; std::sprintf(file, "HR_IFACE_%i%s.frm", IFaceBar::IFACE_BAR_WIDTH, ((expandAPBar) ? "E" : "")); - auto* frm = fo::util::LoadUnlistedFrm(file, fo::ArtType::OBJ_TYPE_INTRFACE); - if (frm && frm->frames->width == IFaceBar::IFACE_BAR_WIDTH) { - h = frm->frames->height; + fo::FrmFile* frm = sf::LoadUnlistedFrmCached(file, fo::ArtType::OBJ_TYPE_INTRFACE); + if (frm != nullptr && frm->frameData[0].width == IFaceBar::IFACE_BAR_WIDTH) { + h = frm->frameData[0].height; if (h > 100) h = 100; - fo::func::buf_to_buf(frm->frames->indexBuff, frm->frames->width, h, frm->frames->width, dst, IFaceBar::IFACE_BAR_WIDTH); - - delete frm; + fo::func::buf_to_buf(frm->frameData[0].data, frm->frameData[0].width, h, frm->frameData[0].width, dst, IFaceBar::IFACE_BAR_WIDTH); return 0; } // no required file, use the default one provided by HRP - if (!frm) frm = fo::util::LoadUnlistedFrm(((expandAPBar) ? "HR_IFACE_800E.frm" : "HR_IFACE_800.frm"), fo::ArtType::OBJ_TYPE_INTRFACE); + if (frm == nullptr) frm = sf::LoadUnlistedFrmCached(((expandAPBar) ? "HR_IFACE_800E.frm" : "HR_IFACE_800.frm"), fo::ArtType::OBJ_TYPE_INTRFACE); - if (frm) { + if (frm != nullptr) { // scale the 800px wide interface to the width of IFACE_BAR_WIDTH - InterfaceArtScale(frm->frames->indexBuff, frm->frames->width, frm->frames->height, dst, h); - delete frm; + InterfaceArtScale(frm->frameData[0].data, frm->frameData[0].width, frm->frameData[0].height, dst, h); } else { // scale the vanilla interface to 640px wide (640-460=180) InterfaceArtScale(scr, w, h, dst, h); diff --git a/sfall/HRP/Inventory.cpp b/sfall/HRP/Inventory.cpp index f4045c8e..a9fc44f9 100644 --- a/sfall/HRP/Inventory.cpp +++ b/sfall/HRP/Inventory.cpp @@ -24,7 +24,8 @@ static long __fastcall CreateWin(long height, long yPos, long xPos, long width, if (!setPosition[mode]) { setPosition[mode] = true; long x = (Setting::ScreenWidth() - width) / 2; - long y = (fo::var::getInt(FO_VAR_buf_length_2) - height) / 2; + long y = (fo::var::buf_length_2 - height) / 2; + if (y < 0) y = 0; fo::var::iscr_data[mode].x = x; fo::var::iscr_data[mode].y = y; } diff --git a/sfall/HRP/MainMenu.cpp b/sfall/HRP/MainMenu.cpp index f1e17436..3d58dc23 100644 --- a/sfall/HRP/MainMenu.cpp +++ b/sfall/HRP/MainMenu.cpp @@ -7,6 +7,7 @@ #include "..\main.h" #include "..\FalloutEngine\Fallout2.h" +#include "..\Modules\ExtraArt.h" #include "..\Modules\LoadGameHook.h" #include "..\Modules\Graphics.h" #include "..\Modules\MainMenu.h" @@ -31,8 +32,8 @@ bool MainMenuScreen::SCALE_BUTTONS_AND_TEXT_MENU; // if the value is false and U long MainMenuScreen::MENU_BG_OFFSET_X = 30; long MainMenuScreen::MENU_BG_OFFSET_Y = 19; -static fo::UnlistedFrm* mainBackgroundFrm; -static fo::UnlistedFrm* btnBackgroundFrm; +static fo::FrmFile* mainBackgroundFrm; +static fo::FrmFile* btnBackgroundFrm; static long mainmenuWidth = 640; @@ -49,9 +50,9 @@ static void __cdecl main_menu_create_hook_buf_to_buf(BYTE* src, long sw, long sh dstW = w; if (mainBackgroundFrm) { - src = mainBackgroundFrm->frames->indexBuff; - sh = mainBackgroundFrm->frames->height; - sw = mainBackgroundFrm->frames->width; + src = mainBackgroundFrm->frameData[0].data; + sh = mainBackgroundFrm->frameData[0].height; + sw = mainBackgroundFrm->frameData[0].width; } bool stretch = (MainMenuScreen::MAIN_MENU_SIZE == 1 || MainMenuScreen::MAIN_MENU_SIZE == 2); @@ -81,13 +82,13 @@ static void __cdecl main_menu_create_hook_buf_to_buf(BYTE* src, long sw, long sh if (y < 0) y = 0; dst += (y * dstW) + x; - sh = btnBackgroundFrm->frames->height; - sw = btnBackgroundFrm->frames->width; + sh = btnBackgroundFrm->frameData[0].height; + sw = btnBackgroundFrm->frameData[0].width; if (MainMenuScreen::SCALE_BUTTONS_AND_TEXT_MENU) { - fo::func::trans_cscale(btnBackgroundFrm->frames->indexBuff, sw, sh, sw, dst, (long)(sw * scaleFactor), (long)(sh * scaleFactor), dstW); + fo::func::trans_cscale(btnBackgroundFrm->frameData[0].data, sw, sh, sw, dst, (long)(sw * scaleFactor), (long)(sh * scaleFactor), dstW); } else { - fo::func::trans_buf_to_buf(btnBackgroundFrm->frames->indexBuff, sw, sh, sw, dst, dstW); // direct copy + fo::func::trans_buf_to_buf(btnBackgroundFrm->frameData[0].data, sw, sh, sw, dst, dstW); // direct copy } } } @@ -101,13 +102,13 @@ static long __fastcall main_menu_create_hook_add_win(long h, long y, long color, sf::Graphics::BackgroundClearColor(0); if (MainMenuScreen::USE_HIRES_IMAGES) { - if (!mainBackgroundFrm) { - mainBackgroundFrm = fo::util::LoadUnlistedFrm("HR_MAINMENU.frm", fo::ArtType::OBJ_TYPE_INTRFACE); - btnBackgroundFrm = fo::util::LoadUnlistedFrm("HR_MENU_BG.frm", fo::ArtType::OBJ_TYPE_INTRFACE); + if (mainBackgroundFrm == nullptr) { + mainBackgroundFrm = sf::LoadUnlistedFrmCached("HR_MAINMENU.frm", fo::ArtType::OBJ_TYPE_INTRFACE); + btnBackgroundFrm = sf::LoadUnlistedFrmCached("HR_MENU_BG.frm", fo::ArtType::OBJ_TYPE_INTRFACE); } - if (mainBackgroundFrm) { - sw = mainBackgroundFrm->frames->width; - sh = mainBackgroundFrm->frames->height; + if (mainBackgroundFrm != nullptr) { + sw = mainBackgroundFrm->frameData[0].width; + sh = mainBackgroundFrm->frameData[0].height; w = sw; h = sh; } @@ -274,14 +275,9 @@ static void __declspec(naked) main_menu_create_hook_register_button() { } static void FreeMainMenuImages() { - if (mainBackgroundFrm) { - delete mainBackgroundFrm; - mainBackgroundFrm = nullptr; - } - if (btnBackgroundFrm) { - delete btnBackgroundFrm; - btnBackgroundFrm = nullptr; - } + // Reset FRM pointers so they can be loaded again after Art cache is reset. + mainBackgroundFrm = nullptr; + btnBackgroundFrm = nullptr; if (buttonImageData) { delete[] buttonImageData; buttonImageData = nullptr; diff --git a/sfall/Modules/BugFixes.cpp b/sfall/Modules/BugFixes.cpp index 1dde7a2f..19349a5f 100644 --- a/sfall/Modules/BugFixes.cpp +++ b/sfall/Modules/BugFixes.cpp @@ -867,7 +867,7 @@ next: pop eax; jnz found; inc edx; - cmp edx, 6; + cmp edx, ds:[FO_VAR_inven_cur_disp]; jb next; end: push 0x47125C; diff --git a/sfall/Modules/ExtraArt.cpp b/sfall/Modules/ExtraArt.cpp new file mode 100644 index 00000000..20024b88 --- /dev/null +++ b/sfall/Modules/ExtraArt.cpp @@ -0,0 +1,123 @@ +/* + * sfall + * Copyright (C) 2008-2024 The sfall team + * + * 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 3 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, see . + */ + +#include "..\main.h" +#include "..\FalloutEngine\Fallout2.h" +#include "..\FalloutEngine\Structs.h" +#include "LoadGameHook.h" + +#include "ExtraArt.h" + +namespace sfall +{ + +typedef std::unordered_map TFrmCache; +typedef std::unordered_map TPcxCache; + +static TFrmCache frmFileCache; +static TPcxCache pcxFileCache; + +static PcxFile LoadPcxFile(const char* file) { + PcxFile pcx; + pcx.pixelData = fo::func::loadPCX(file, &pcx.width, &pcx.height, fo::var::pal); + if (pcx.pixelData == nullptr) return PcxFile(); + + fo::func::datafileConvertData(pcx.pixelData, fo::var::pal, pcx.width, pcx.height); + return pcx; +} + +fo::FrmFile* LoadFrmFileCached(const char* file) { + fo::FrmFile* frmPtr = nullptr; + auto cacheHit = frmFileCache.find(file); + if (cacheHit != frmFileCache.end()) { + frmPtr = cacheHit->second; + } else { + if (fo::func::load_frame(file, &frmPtr)) { + frmPtr = nullptr; + } + frmFileCache.emplace(file, frmPtr); + } + return frmPtr; +} + +PcxFile LoadPcxFileCached(const char* file) { + auto cacheHit = pcxFileCache.find(file); + if (cacheHit != pcxFileCache.end()) { + return cacheHit->second; + } + return pcxFileCache.emplace(file, LoadPcxFile(file)).first->second; +} + +static void GetUnlistedFrmPath(const char* frmName, unsigned int folderRef, bool useLanguage, char* pathBuf) { + + const char* artfolder = fo::var::art[folderRef].path; // address of art type name + if (useLanguage) { + sprintf_s(pathBuf, MAX_PATH, "art\\%s\\%s\\%s", (const char*)fo::var::language, artfolder, frmName); + } else { + sprintf_s(pathBuf, MAX_PATH, "art\\%s\\%s", artfolder, frmName); + } +} + +bool UnlistedFrmExists(const char* frmName, unsigned int folderRef) { + if (folderRef > fo::OBJ_TYPE_SKILLDEX) return nullptr; + + char frmPath[MAX_PATH]; + GetUnlistedFrmPath(frmName, folderRef, fo::var::use_language != 0, frmPath); + + bool exists = fo::func::db_access(frmPath); + if (!exists && fo::var::use_language) { + GetUnlistedFrmPath(frmName, folderRef, false, frmPath); + exists = fo::func::db_access(frmPath); + } + return exists; +} + +fo::FrmFile* LoadUnlistedFrmCached(const char* frmName, unsigned int folderRef) { + if (folderRef > fo::OBJ_TYPE_SKILLDEX) return nullptr; + + char frmPath[MAX_PATH]; + + GetUnlistedFrmPath(frmName, folderRef, fo::var::use_language != 0, frmPath); + + fo::FrmFile* frm = LoadFrmFileCached(frmPath); + if (frm == nullptr && fo::var::use_language) { + GetUnlistedFrmPath(frmName, folderRef, false, frmPath); + frm = LoadFrmFileCached(frmPath); + } + return frm; +} + +static void ClearInterfaceArtCache() { + for (auto &pair : pcxFileCache) { + fo::func::freePtr_invoke(pair.second.pixelData); + } + pcxFileCache.clear(); + + for (auto &pair : frmFileCache) { + fo::func::mem_free(pair.second); + } + frmFileCache.clear(); +} + +void ExtraArt::init() { + LoadGameHook::OnGameReset() += []() { + ClearInterfaceArtCache(); + }; +} + +} \ No newline at end of file diff --git a/sfall/Modules/ExtraArt.h b/sfall/Modules/ExtraArt.h new file mode 100644 index 00000000..6f6a68c7 --- /dev/null +++ b/sfall/Modules/ExtraArt.h @@ -0,0 +1,51 @@ +/* + * sfall + * Copyright (C) 2008-2024 The sfall team + * + * 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 3 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, see . + */ + +#pragma once + +#include "Module.h" + +namespace fo +{ + struct FrmFile; +} + +namespace sfall +{ + +struct PcxFile { + unsigned char* pixelData; + long width; + long height; + + PcxFile() : pixelData(nullptr), width(0), height(0) {} +}; + +class ExtraArt : public Module { +public: + const char* name() { return "ExtraArt"; } + void init(); +}; + +fo::FrmFile* LoadFrmFileCached(const char* file); +PcxFile LoadPcxFileCached(const char* file); + +bool UnlistedFrmExists(const char* frmName, unsigned int folderRef); +fo::FrmFile* LoadUnlistedFrmCached(const char* file, unsigned int folderRef); + +} diff --git a/sfall/Modules/HeroAppearance.cpp b/sfall/Modules/HeroAppearance.cpp index a30541e1..22b09be4 100644 --- a/sfall/Modules/HeroAppearance.cpp +++ b/sfall/Modules/HeroAppearance.cpp @@ -21,6 +21,7 @@ #include "..\Translate.h" #include "Inventory.h" +#include "ExtraArt.h" #include "LoadGameHook.h" #include "LoadOrder.h" #include "PartyControl.h" @@ -585,10 +586,9 @@ static void DrawCharNote(bool style, int winRef, DWORD xPosWin, DWORD yPosWin, B BYTE *PadSurface = new BYTE [280 * 168]; surface_draw(280, 168, widthBG, xPosBG, yPosBG, BGSurface, 280, 0, 0, PadSurface); - fo::UnlistedFrm *frm = fo::util::LoadUnlistedFrm((style) ? "AppStyle.frm" : "AppRace.frm", fo::OBJ_TYPE_SKILLDEX); - if (frm) { - fo::util::DrawToSurface(frm->frames[0].width, frm->frames[0].height, 0, 0, frm->frames[0].width, frm->frames[0].indexBuff, 136, 37, 280, 168, PadSurface, 0); // cover buttons pics bottom - delete frm; + fo::FrmFile *frm = LoadUnlistedFrmCached((style) ? "AppStyle.frm" : "AppRace.frm", fo::OBJ_TYPE_SKILLDEX); + if (frm != nullptr) { + fo::util::DrawToSurface(frm->frameData[0].width, frm->frameData[0].height, 0, 0, frm->frameData[0].width, frm->frameData[0].data, 136, 37, 280, 168, PadSurface, 0); // cover buttons pics bottom } int oldFont = GetFont(); // store current font @@ -653,7 +653,7 @@ static void __stdcall DrawCharNoteNewChar(bool type) { void __stdcall HeroSelectWindow(int raceStyleFlag) { if (!HeroAppearance::appModEnabled) return; - fo::UnlistedFrm *frm = fo::util::LoadUnlistedFrm("AppHeroWin.frm", fo::OBJ_TYPE_INTRFACE); + fo::FrmFile *frm = LoadUnlistedFrmCached("AppHeroWin.frm", fo::OBJ_TYPE_INTRFACE); if (frm == nullptr) { fo::func::debug_printf("\nApperanceMod: art\\intrface\\AppHeroWin.frm file not found."); return; @@ -665,7 +665,6 @@ void __stdcall HeroSelectWindow(int raceStyleFlag) { int winRef = fo::func::win_add(resWidth / 2 - 242, (resHeight - 100) / 2 - 65, 484, 230, 100, 0x4); if (winRef == -1) { - delete frm; return; } @@ -677,8 +676,7 @@ void __stdcall HeroSelectWindow(int raceStyleFlag) { BYTE *winSurface = fo::func::win_get_buf(winRef); BYTE *mainSurface = new BYTE [484 * 230]; - surface_draw(484, 230, 484, 0, 0, frm->frames[0].indexBuff, 484, 0, 0, mainSurface); - delete frm; + surface_draw(484, 230, 484, 0, 0, frm->frameData[0].data, 484, 0, 0, mainSurface); DWORD MenuUObj, MenuDObj; BYTE *MenuUSurface = fo::func::art_ptr_lock_data(BuildFrmId(6, 299), 0, 0, &MenuUObj); // MENUUP Frm @@ -1149,11 +1147,9 @@ static void __declspec(naked) FixCharScrnBack() { if (charScrnBackSurface == nullptr) { charScrnBackSurface = new BYTE [640 * 480]; - fo::UnlistedFrm *frm = fo::util::LoadUnlistedFrm((fo::var::glblmode) ? "AppChCrt.frm" : "AppChEdt.frm", fo::OBJ_TYPE_INTRFACE); - + fo::FrmFile *frm = LoadUnlistedFrmCached((fo::var::glblmode) ? "AppChCrt.frm" : "AppChEdt.frm", fo::OBJ_TYPE_INTRFACE); if (frm != nullptr) { - surface_draw(640, 480, 640, 0, 0, frm->frames[0].indexBuff, 640, 0, 0, charScrnBackSurface); - delete frm; + surface_draw(640, 480, 640, 0, 0, frm->frameData[0].data, 640, 0, 0, charScrnBackSurface); } else { BYTE* oldCharScrnBackSurface = fo::var::bckgnd; // char screen background frm surface diff --git a/sfall/Modules/Interface.cpp b/sfall/Modules/Interface.cpp index 80f5a5f2..c05fa6da 100644 --- a/sfall/Modules/Interface.cpp +++ b/sfall/Modules/Interface.cpp @@ -22,6 +22,7 @@ #include "..\FalloutEngine\Fallout2.h" #include "..\SimplePatch.h" #include "..\Utils.h" +#include "ExtraArt.h" #include "Graphics.h" #include "LoadGameHook.h" #include "Worldmap.h" @@ -96,13 +97,39 @@ fo::Window* Interface::GetWindow(long winType) { return (winID > 0) ? fo::func::GNW_find(winID) : nullptr; } +struct InterfaceCustomFrm { + fo::FrmFile* frm; + const char* frmName; + bool isLoaded; + + InterfaceCustomFrm(const char* _frmName) : frm(nullptr), frmName(_frmName), isLoaded(false) {} + InterfaceCustomFrm(InterfaceCustomFrm &other) = delete; + + bool ArtExists() const { + return UnlistedFrmExists(frmName, fo::ArtType::OBJ_TYPE_INTRFACE); + } + + BYTE* LoadFrmData() { + if (!isLoaded) { + frm = LoadUnlistedFrmCached(frmName, fo::ArtType::OBJ_TYPE_INTRFACE); + isLoaded = true; + } + return frm != nullptr + ? frm->frameData[0].data + : nullptr; + } + + void Reset() { + frm = nullptr; + isLoaded = false; + } +}; + static BYTE movePointBackground[16 * 9 * 5]; -static fo::UnlistedFrm* ifaceFrm = nullptr; +static InterfaceCustomFrm ifaceFrm{ "IFACE_E.frm" }; static void* LoadIfaceFrm() { - ifaceFrm = fo::util::LoadUnlistedFrm("IFACE_E.frm", fo::OBJ_TYPE_INTRFACE); - if (!ifaceFrm) return nullptr; - return ifaceFrm->frames[0].indexBuff; + return ifaceFrm.LoadFrmData(); } static void __declspec(naked) intface_init_hook_lock() { @@ -1104,6 +1131,246 @@ static void UIAnimationSpeedPatch() { SimplePatch(&addrs[4], 2, "Misc", "PipboyTimeAnimDelay", 50, 0, 127); } +static InterfaceCustomFrm barterTallFrm { "barter_e.frm" }; +static InterfaceCustomFrm tradeTallFrm { "trade_e.frm" }; + +static std::array inventoryTallFrms { "invbox_e.frm", "use_e.frm", "loot_e.frm"}; + +static DWORD findInventoryWindowTypeByFid(DWORD fid) { + fid &= 0xFFF; + for (int i = 0; i < fo::INVENTORY_WINDOW_TYPE_TRADE; ++i) { + if (fid == fo::var::iscr_data[i].artIndex) + return i; + } + return -1; +} + +static BYTE* __fastcall inventory_get_art_data(DWORD fid) { + DWORD windowType = findInventoryWindowTypeByFid(fid); + if (windowType > fo::INVENTORY_WINDOW_TYPE_LOOT) return nullptr; + + return inventoryTallFrms[windowType].LoadFrmData(); +} + +static BYTE* __fastcall gdialog_barter_get_art_data() { + return (fo::var::dialog_target_is_party ? tradeTallFrm : barterTallFrm).LoadFrmData(); +} + +static DWORD __fastcall gdialog_barter_get_art_height() { + fo::FrmFile* frm = fo::var::dialog_target_is_party + ? tradeTallFrm.frm + : barterTallFrm.frm; + + return frm != nullptr + ? frm->frameData[0].height + : 0; +} + +// replace art data for dialog barter window +static void __declspec(naked) gdialog_barter_create_win__art_frame_data_hook() { + __asm { + pushadc; + call gdialog_barter_get_art_data; + test eax, eax; + jz skipCall; + pop ecx; + pop edx; + add esp, 4; + retn; +skipCall: + popadc; + jmp fo::funcoffs::art_frame_data_; + } +} + +// replace art height (length) for dialog barter window +static void __declspec(naked) gdialog_barter_create_win__art_frame_length_hook() { + __asm { + pushadc; + call gdialog_barter_get_art_height; + test eax, eax; + jz skipCall; + pop ecx; + pop edx; + add esp, 4; + retn; +skipCall: + popadc; + jmp fo::funcoffs::art_frame_length_; + } +} + +// replace art data for dialog barter window when scrolling down +static void __declspec(naked) gdialog_barter_destroy_win__art_ptr_lock_data_hook() { + __asm { + call fo::funcoffs::art_ptr_lock_data_; + pushadc; + call gdialog_barter_get_art_data; + test eax, eax; + jz skipCall; + pop ecx; + pop edx; + add esp, 4; + retn; +skipCall: + popadc; + retn; + } +} + +// Replace art data for 6-slot inventory windows +static void __declspec(naked) inventory__art_ptr_lock_data_hook() { + __asm { + push eax; + call fo::funcoffs::art_ptr_lock_data_; + pushadc; + mov ecx, [esp + 12]; // FID + call inventory_get_art_data; + test eax, eax; + jz skipCall; + pop ecx; + pop edx; + add esp, 8; + retn; +skipCall: + popadc; + add esp, 4; + retn; + } +} + +constexpr int numExtraBarterSlots = 1; +constexpr int extraBarterHeight = 48 * numExtraBarterSlots; + +// This call draws pixels from alltlk.frm onto the barter window prior to scroll animation. +// Because we expanded barter window downwards beyond the background window, it will try to copy pixels beyond source FRM buffer. +// To fix this, we reduce the height parameter passed to buf_to_buf. +static void __declspec(naked) gdialog_barter_create__win_buf_to_buf_hook() { + __asm { + mov eax, [esp + 12]; // height to copy + sub eax, extraBarterHeight; + mov [esp + 12], eax; + jmp fo::funcoffs::buf_to_buf_; + } +} +// Same issue as above, this time we reduce height passed to gdialog_scroll_subwin to avoid reading beyond alltlk.frm height. +static void __declspec(naked) gdialog_barter_destroy_win__gdialog_scroll_subwin_hook() { + __asm { + push eax; + mov eax, [esp + 12]; // height to copy + sub eax, extraBarterHeight; + mov [esp + 12], eax; + pop eax; + jmp fo::funcoffs::gdialog_scroll_subwin_; + } +} + +// Expands barter/trade window vertically with 4 slots per table instead of 3. +static void ExpandedBarterPatch() { + if (IniReader::GetConfigInt("Interface", "ExpandedBarter", 0) == 0) return; + + const int dialogWindowHeight = 480 + extraBarterHeight; + if (Graphics::GetGameHeightRes() < dialogWindowHeight) { + dlog_f("Skipping expanded barter screen patch. Screen height = %d < %d\n", DL_INIT, Graphics::GetGameHeightRes(), dialogWindowHeight); + return; + } + if (HRP::Setting::ExternalEnabled() && !HRP::Setting::VersionIsValid) { + dlogr("Skipping expanded barter screen patch. Incompatible version of High-Resolution Patch (f2_res.dll) found.", DL_INIT); + return; + } + if (!barterTallFrm.ArtExists() || !tradeTallFrm.ArtExists()) { + dlog_f("Skipping expanded barter screen patch. Missing required FRM files: %s, %s.\n", DL_INIT, + barterTallFrm.frmName, tradeTallFrm.frmName); + return; + } + + dlogr("Applying expanded barter screen patch.", DL_INIT); + SafeWrite32(0x46EDA4, 3 + numExtraBarterSlots); // Trade window slot count 3 -> 4 + fo::var::iscr_data[fo::INVENTORY_WINDOW_TYPE_TRADE].height = 180 + extraBarterHeight; // Trade sub-window height + SafeWriteBatch(180 + extraBarterHeight, { // Trade sub-window height + 0x46EDAB, 0x46EE13, // setup_inventory + }); + if (HRP::Setting::VersionIsValid) { + // HRP overrides window creation code setup_inventory, so need to write correct max Y value into HRP itself. + SafeWrite32(HRP::Setting::GetAddress(0x1001220C), 470 + extraBarterHeight); + } else { + SafeWrite32(0x46EDD4, 470 + extraBarterHeight); // Trade window max Y = Y pos + height = 290 + 180 = 470 + } + SafeWriteBatch(dialogWindowHeight, { // Game dialog BG window height (for Y calculation only) + 0x44831E, // gdialog_barter_create_win_ + 0x4485A7, // gdialog_barter_destroy_win_ + }); + // Transparent inventory windows create issues, see comment in ExpandedInventoryPatch() + //SafeWrite8(0x44831C, *(BYTE*)0x44831C | fo::WinFlags::Transparent); // add transparentcy flag to trade/barter window + //SafeWrite8(0x46EDA9, *(BYTE*)0x46EDA9 | fo::WinFlags::Transparent); // add transparent flag 0x20 to inventory (inner part) window + + HookCall(0x4482EA, gdialog_barter_create_win__art_frame_data_hook); + HookCall(0x4482FF, gdialog_barter_create_win__art_frame_length_hook); + HookCall(0x448603, gdialog_barter_destroy_win__art_ptr_lock_data_hook); + HookCall(0x448374, gdialog_barter_create__win_buf_to_buf_hook); + HookCall(0x448628, gdialog_barter_destroy_win__gdialog_scroll_subwin_hook); +} + +// Expands inventory/loot/item select windows vertically with 8 vertical slots instead of 6 +static void ExpandedInventoryPatch() { + if (IniReader::GetConfigInt("Interface", "ExpandedInventory", 0) == 0) return; + + for (size_t i = 0; i < inventoryTallFrms.size(); i++) { + if (!inventoryTallFrms[i].ArtExists()) { + dlog_f("Skipping expanded inventory screen patch. Missing required FRM file: %s.\n", DL_INIT, inventoryTallFrms[i].frmName); + return; + } + } + + dlogr("Applying expanded inventory screen patch.", DL_INIT); + const int numExtraSlots = 2; + const int slotHeight = 48; + const int extraHeight = slotHeight * numExtraSlots - 8; // shorten by a few pixels to reduce empty space below the last item slot + SafeWrite32(0x46EC9E, 6 + numExtraSlots); // All other inventory windows slot count 6 -> 8 + fo::var::iscr_data[fo::INVENTORY_WINDOW_TYPE_NORMAL].height = 377 + extraHeight; + fo::var::iscr_data[fo::INVENTORY_WINDOW_TYPE_USE_ITEM_ON].height = 376 + extraHeight; + fo::var::iscr_data[fo::INVENTORY_WINDOW_TYPE_LOOT].height = 376 + extraHeight; + + // Shift Done buttons down: + SafeWrite32(0x46F26C, 329 + extraHeight); // Normal + SafeWrite32(0x46F29C, 328 + extraHeight); // Use Item On + SafeWrite32(0x46F2CC, 331 + extraHeight); // Loot + + // Transparent inventory windows create issues: + // - Subtle flickering when dragging items + // - Crashes when opening action window at certain coordinates and screen resolutions (suspect relation with height of active area - above interface panel) + //SafeWrite8(0x46ECE9, (*(BYTE*)0x46ECE9) | fo::WinFlags::Transparent); //add transparency for all tall inventory windows + + HookCalls(inventory__art_ptr_lock_data_hook, { + 0x46ED56, // setup_inventory + 0x46FE51, 0x46FF8E, 0x46FFF9, // display_inventory_ + 0x4703B5, // display_target_inventory_ + 0x470FE7, // inven_pickup + 0x473479, // inven_action_cursor + 0x474825, // move_inventory + }); + + // Shift event codes for main inventory buttons (armor, item1, item2) by number of extra slots to make room. + SafeWrite32(0x46EB89, 1008 + numExtraSlots); // upper range value in handle_inventory + SafeWriteBatch(1006 + numExtraSlots, { // Item2 slot + 0x46F104, 0x46F10B, // setup_inventory + 0x470E0B, // inven_pickup, + 0x472B79, // inven_from_button + }); + SafeWriteBatch(1007 + numExtraSlots, { // Item1 slot + 0x46F14C, 0x46F153, // setup_inventory + 0x470DF0, // inven_pickup + 0x472B67, // inven_from_button + }); + SafeWriteBatch(1008 + numExtraSlots, { // Armor slot + 0x46F195, 0x46F19C, // setup_inventory + 0x470DFA, // inven_pickup + 0x472B70, // inven_from_button + }); + SafeWrite32(0x46EF1A, 2005 + numExtraSlots); // loot target slots upper index range fix (setup_inventory) + SafeWrite32(0x46EF1F, 277 + numExtraSlots * slotHeight); // adjust starting Y for loot target slot positions +} + void Interface::init() { InterfaceWindowPatch(); InventoryCharacterRotationSpeedPatch(); @@ -1139,10 +1406,22 @@ void Interface::init() { ammoBarXPos -= 2; } } + LoadGameHook::OnGameInit() += []() { + // Needs to be invoked in OnGameInit when screen height is already known and db is initialized. + ExpandedBarterPatch(); + ExpandedInventoryPatch(); + }; + LoadGameHook::OnGameReset() += []() { + ifaceFrm.Reset(); + barterTallFrm.Reset(); + tradeTallFrm.Reset(); + for (size_t i = 0; i < inventoryTallFrms.size(); ++i) { + inventoryTallFrms[i].Reset(); + } + }; } void Interface::exit() { - if (ifaceFrm) delete ifaceFrm; if (dotStyle) delete[] dotStyle; } diff --git a/sfall/Modules/Inventory.cpp b/sfall/Modules/Inventory.cpp index f91333de..277eedad 100644 --- a/sfall/Modules/Inventory.cpp +++ b/sfall/Modules/Inventory.cpp @@ -414,10 +414,13 @@ scroll: push ebx; mov ebx, [eax + 8 + 0]; // ebx = _i_wid.rect.x mov ecx, [eax + 8 + 4]; // ecx = _i_wid.rect.y + mov eax, 48; + mul ds:[FO_VAR_inven_cur_disp]; + add ecx, 37; + mov edx, ecx; // y_start + add ecx, eax; // y_end lea eax, [ebx + 297]; // x_start add ebx, 297 + 64; // x_end - lea edx, [ecx + 37]; // y_start - add ecx, 37 + 6 * 48; // y_end call fo::funcoffs::mouse_click_in_; pop ebx; pop ecx; @@ -455,10 +458,13 @@ scroll: mov edi, [eax + 8 + 4]; mov ebx, ebp; // ebx = _i_wid.rect.x mov ecx, edi; // ecx = _i_wid.rect.y + mov eax, 48; + mul ds:[FO_VAR_inven_cur_disp]; + add ecx, 35; + mov edx, ecx; // y_start + add ecx, eax; // y_end lea eax, [ebp + 395]; // x_start add ebx, 395 + 64; // x_end - lea edx, [edi + 35]; // y_start - add ecx, 35 + 3 * 48; // y_end call fo::funcoffs::mouse_click_in_; test eax, eax; jz notTargetScroll; @@ -472,10 +478,13 @@ targetDown: notTargetScroll: mov ebx, ebp; mov ecx, edi; + mov eax, 48; + mul ds:[FO_VAR_inven_cur_disp]; + add ecx, 20; + mov edx, ecx; // y_start + add ecx, eax; // y_end lea eax, [ebp + 250]; // x_start add ebx, 250 + 64; // x_end - lea edx, [edi + 20]; // y_start - add ecx, 20 + 3 * 48; // y_end call fo::funcoffs::mouse_click_in_; test eax, eax; jz notTargetBarter; @@ -489,10 +498,13 @@ barterTargetDown: notTargetBarter: mov ebx, ebp; mov ecx, edi; + mov eax, 48; + mul ds:[FO_VAR_inven_cur_disp]; + add ecx, 20; + mov edx, ecx; // y_start + add ecx, eax; // y_end lea eax, [ebp + 165]; // x_start add ebx, 165 + 64; // x_end - lea edx, [edi + 20]; // y_start - add ecx, 20 + 3 * 48; // y_end call fo::funcoffs::mouse_click_in_; test eax, eax; jz end; diff --git a/sfall/Modules/Scripting/Handlers/Interface.cpp b/sfall/Modules/Scripting/Handlers/Interface.cpp index e8a7edd8..47d43ca6 100644 --- a/sfall/Modules/Scripting/Handlers/Interface.cpp +++ b/sfall/Modules/Scripting/Handlers/Interface.cpp @@ -17,10 +17,12 @@ */ #include "..\..\..\FalloutEngine\AsmMacros.h" +#include "..\..\..\FalloutEngine\EngineUtils.h" #include "..\..\..\FalloutEngine\Fallout2.h" #include "..\..\..\InputFuncs.h" #include "..\..\BarBoxes.h" +#include "..\..\ExtraArt.h" #include "..\..\LoadGameHook.h" #include "..\..\ScriptExtender.h" #include "..\..\Interface.h" @@ -502,70 +504,29 @@ struct FrameData { } // Data from PCX file. - FrameData(BYTE* data, long w, long h) { - pixelData = data; - width = (short)w; - height = (short)h; + FrameData(PcxFile pcx) { + pixelData = pcx.pixelData; + width = (short)pcx.width; + height = (short)pcx.height; } }; -static FrameData LoadPCXFile(const char* file) { - long w, h; - BYTE* pixelData = fo::func::loadPCX(file, &w, &h, fo::var::pal); - if (pixelData == nullptr) return FrameData(); - - fo::func::datafileConvertData(pixelData, fo::var::pal, w, h); - return FrameData(pixelData, w, h); -} - static bool IsPCXFile(const char* file) { const char* pos = strrchr(file, '.'); return pos && _stricmp(++pos, "PCX") == 0; } -typedef std::unordered_map TFRMCache; -typedef std::unordered_map TPCXCache; - -static TFRMCache frmFileCache; -static TPCXCache pcxFileCache; - //static fo::FrmFile* LoadArtFileCached(const char* file, long frame, long direction, fo::FrmFrameData* &framePtr, bool checkPCX) { static FrameData LoadFrameDataCached(const char* file, long frame, long direction) { if (IsPCXFile(file)) { - auto cacheHit = pcxFileCache.find(file); - if (cacheHit != pcxFileCache.end()) { - return cacheHit->second; - } - return pcxFileCache.emplace(file, LoadPCXFile(file)).first->second; + return LoadPcxFileCached(file); } - - fo::FrmFile* frmPtr = nullptr; - auto cacheHit = frmFileCache.find(file); - if (cacheHit != frmFileCache.end()) { - frmPtr = cacheHit->second; - } else { - if (fo::func::load_frame(file, &frmPtr)) { - frmPtr = nullptr; - } - frmFileCache.emplace(file, frmPtr); - } - return (frmPtr != nullptr) + fo::FrmFile* frmPtr = LoadFrmFileCached(file); + return frmPtr != nullptr ? FrameData(frmPtr, direction, frame) : FrameData(); } -void ClearInterfaceArtCache() { - for (auto &pair : pcxFileCache) { - fo::func::freePtr_invoke(pair.second.pixelData); - } - pcxFileCache.clear(); - - for (auto &pair : frmFileCache) { - fo::func::mem_free(pair.second); - } - frmFileCache.clear(); -} - static long GetArtFIDFile(long fid, char* outFilePath) { long direction = 0; long _fid = fid & 0xFFFFFFF; @@ -590,20 +551,7 @@ static long GetArtFIDFile(long fid, char* outFilePath) { return direction; } -struct ArtCacheLock { - DWORD entryPtr = 0; - - ArtCacheLock() {} - ArtCacheLock(DWORD _lock) : entryPtr(_lock) {} - ~ArtCacheLock() { - if (entryPtr != 0) { - fo::func::art_ptr_unlock(entryPtr); - entryPtr = 0; - } - } -}; - -static FrameData LockFrameData(unsigned long fid, ArtCacheLock& lock, long direction, long frame) { +static FrameData LockFrameData(unsigned long fid, fo::util::ArtCacheLock& lock, long direction, long frame) { long objType = (fid >> 24) & 0xF; if (direction < 0) { // If direction is not specified, take it from FID. @@ -623,7 +571,7 @@ static long DrawImage(OpcodeContext& ctx, bool isScaled) { return 0; } FrameData frm; - ArtCacheLock cacheLock; + fo::util::ArtCacheLock cacheLock; bool isID = ctx.arg(0).isInt(); long frame = ctx.arg(1).rawValue(); @@ -740,7 +688,7 @@ static long InterfaceDrawImage(OpcodeContext& ctx, fo::Window* ifaceWin) { if (size > 2) h = sArray->val[2].intVal; } } - ArtCacheLock cacheLock; + fo::util::ArtCacheLock cacheLock; FrameData frm; if (isID) { // art id long fid = ctx.arg(1).rawValue(); diff --git a/sfall/Modules/Scripting/Opcodes.cpp b/sfall/Modules/Scripting/Opcodes.cpp index 5e0b87c2..2bddff5d 100644 --- a/sfall/Modules/Scripting/Opcodes.cpp +++ b/sfall/Modules/Scripting/Opcodes.cpp @@ -293,7 +293,6 @@ void Opcodes::InitNew() { PipboyAvailableRestore(); ForceEncounterRestore(); // restore if the encounter did not happen ResetIniCache(); - ClearInterfaceArtCache(); }; if (int unsafe = IniReader::GetIntDefaultConfig("Debugging", "AllowUnsafeScripting", 0)) { diff --git a/sfall/ddraw.vcxproj b/sfall/ddraw.vcxproj index 2a8f76df..8b480592 100644 --- a/sfall/ddraw.vcxproj +++ b/sfall/ddraw.vcxproj @@ -401,6 +401,7 @@ + @@ -542,6 +543,7 @@ + diff --git a/sfall/ddraw.vcxproj.filters b/sfall/ddraw.vcxproj.filters index e46e2bf0..fbfea8af 100644 --- a/sfall/ddraw.vcxproj.filters +++ b/sfall/ddraw.vcxproj.filters @@ -457,6 +457,7 @@ Modules\Scripting\Handlers + @@ -834,6 +835,7 @@ Modules\Scripting\Handlers + diff --git a/sfall/main.cpp b/sfall/main.cpp index cf6e5b88..a8151adb 100644 --- a/sfall/main.cpp +++ b/sfall/main.cpp @@ -39,6 +39,7 @@ #include "Modules\Elevators.h" #include "Modules\EngineTweaks.h" #include "Modules\Explosions.h" +#include "Modules\ExtraArt.h" #include "Modules\ExtraSaveSlots.h" #include "Modules\FileSystem.h" #include "Modules\Graphics.h" @@ -117,6 +118,7 @@ static void InitModules() { manager.add(); manager.add(); manager.add(); + manager.add(); manager.add(); manager.add();