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();