From 2415eb31f2be87c20372a374fd7b8ce288865b2d Mon Sep 17 00:00:00 2001 From: NovaRain Date: Wed, 22 Apr 2026 21:09:32 +0800 Subject: [PATCH] Backported ActionPointsBar from 4.x Updated resource files. --- artifacts/ddraw.ini | 4 + .../dat_en/art/intrface/iface_apbar_e.frm | Bin 0 -> 2466 bytes artifacts/sfall_dat/sfall.dat | Bin 194722 -> 195869 bytes artifacts/sfall_dat/sfall_de.dat | Bin 196503 -> 197650 bytes artifacts/sfall_dat/sfall_fr.dat | Bin 196466 -> 197613 bytes artifacts/sfall_dat/sfall_ru.dat | Bin 200779 -> 201926 bytes artifacts/sfall_dat/sfall_zh.dat | Bin 194763 -> 195910 bytes sfall/FalloutEngine/VarPointers_def.h | 2 + sfall/FalloutEngine/VariableOffsets.h | 2 + sfall/Modules/ExtraArt.cpp | 22 +++++ sfall/Modules/ExtraArt.h | 16 ++++ sfall/Modules/HeroAppearance.cpp | 10 ++- sfall/Modules/Interface.cpp | 85 +++++++++++++++++- 13 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 artifacts/sfall_dat/dat_en/art/intrface/iface_apbar_e.frm diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index 5458b6a8..0c099968 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -105,6 +105,10 @@ FadeMultiplier=100 ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [Interface] +;Set to 1 to expand the number of action points displayed on the interface bar +;Requires a new IFACE_APBAR_E.frm in art\intrface\ (included in sfall.dat) to display correctly +ActionPointsBar=0 + ;Set to 1 to use the expanded world map interface ;Set to 2 to skip correcting the position of entrance markers on town maps ;You can use resized FRMs in 700x682 for town maps in the expanded world map interface diff --git a/artifacts/sfall_dat/dat_en/art/intrface/iface_apbar_e.frm b/artifacts/sfall_dat/dat_en/art/intrface/iface_apbar_e.frm new file mode 100644 index 0000000000000000000000000000000000000000..43da42dec4faeb6120757ff340454151d41c4a39 GIT binary patch literal 2466 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 diff --git a/artifacts/sfall_dat/sfall_de.dat b/artifacts/sfall_dat/sfall_de.dat index 3848e5f7b6e7285801929a41607df6809a5b079b..7bed80e008d0331ebafe03bb942124d1695850af 100644 GIT binary patch delta 1260 zcmbRKpLq=4zD2jqM8cv=k9D5SQj1NQ*k#dnoo$23#tyT)S5*qyc5OV< z%}_1u{$kq$Cn-V2Dbf|Qy1!IUNbxmtKL4drx#9kIxgQT7`ft9OW2gK(@!S5ly)*yX zY6txH%YT&;cyz{U!KWFIBYN~rY}W^-ci+FU@OcGasJp@R;!qhY%@YB8>sMHPvuA4C zl5|J>v4z&c9~<_qGSj$PV0~cK)g4l13~gLyJpDiJrfiAY@AR;A^S$+9+77c{9laL% zwQFBonj?dt>5S$VnHhcBme~dqZKcY4 zn{MUCn)?|PNHT@q&o~yHY!Y}z`jexq!2Z1l72+4`oQzJXWISX$&2ynws;&0pr-zoD z)=%!)_dCm{_Oi!YN!L|uD_a|+H5zYma@|^TvF5hQLxw~T50O2yXGhJRHfI}KH~VE) zmX|C=>RBf*8kFz3R-ZKMRgQ-E^zJE7Z#GBszbFK+n<#coKi zoK{{d&b>8UuVRhLw*4C4!jG;i5fu6m+q;*QmE&lU+5w4grL(*$Sz0{m(^O1*iZv%| z@{0$2l5F%Bo8DgJ(ldM6uWRw~@p~;#yspYQb98@%y7e1|A9~;NyN~J_&MBB=R^sJv zAsb|NoadzH+}TTPnhQApGA-v?7Bfp^&S4dWhRtu*Y;2J*O{uafdlKQtu;%OKDMw@8 z@O>(aP(P^mqU(_5ij3-|CAGN`n)OQi+?;dwT6Iiz_dVI2c`8{;!T#qp>qC=-@9bP7 ztS;TFXSrHMcdE)t#gjLSP8>crLHAIf$gW>~S#Kld`h-`gK3sZeqs%FryVra41Uri( zcCFifLW^fR=b@Qf_SLrvDJ)=9H&J%^X7PA)|6#XD7ammA2+MVBnzresOhfbO#jZgo z108;Ts$RwWN@cyvF6aGG7x%OEosMhWmy#2HtfAV^h2c?%#W6OO>ERklE@x6~Dvt(# zb~$Q1y|VPxv;{ejesmN{dR{cwPkkbr7yoPbC;8jVjl0?lCNpj?n9L-b#mKk4?=O=I zqplnS14Cj_Nla#5Nl{v2a%xN_h>TAxNJ=b2Lor@iWbzIsM;1CN-wM+0)hjGbuAooj2Y4Ka+-vj|vk5I|BnlN@{VkUSqp%r!#KfKb=V`i;;KxEkBpFvHJBI_rhi~&7URkI%f!IM#K189KNAB01+5wA diff --git a/artifacts/sfall_dat/sfall_fr.dat b/artifacts/sfall_dat/sfall_fr.dat index 27af4adf7a2d50d3cad5356d01bdf57bfc2c66ff..958acad35ed5cc4f532d24296c32ade93d677252 100644 GIT binary patch delta 1250 zcmezLkNa&qPs0|*Ba`dHe2Z?IiG)R$9_u`tr52kqvCE?EI@<=5jU8rpuc{QZ?b>*z zo1t3R{l&HiPEvx3Q=}_qb$_Xzkm76PeEv(La>M=aaz7qE^xu3l$4>co;8duRT& z)eiXYm;Wjy@aT-yf=@FZNA&2M*sc#u@4kOy;qwZj%@I(jYi zYvZ1OE9F)%l-jh`o-ey5-P!Q4RNHm`^XG-Hd|uvu`*!=ohmAkl-4jkY1O(p7aDCBc z|Ni~`{^Sq$D(^=q{q3dC+)DI6=flaEkl!J?^~Wf-Rxbl;^NZ z=ae0PFZ|xxFXHs>%vHRNPyU+r?LWA7^NjUQUk|w%_I>|cZhzK(eN+0PFR5OAiib|C z{MCrMr~WMV{?EFuM`2rijM^tUZf)WJ!Zkahh_BhZqbWnyTJit&pSypGw;nmr6rL5og7s;BVfDw)e~$nBo2D6)IoWfo-@4$SWy=be@_U+Z zE!;b6*$&@LD@_-lQM-JC?Ven(TlR8=-78Zy4+&SkwvU>pqR~Dx;l;9Refynm3iN-n z)6s4D9iPE%Avh&EZ1(EA{~rBy>)hw;%2w?%evv$`gJiuiZO$$(%_J+e(%9 zHr>jNHTN?pkYozIpK&ZY*(C6c^e0DIf&F_AD#S0=IT@W&$#}?in&(2VR9o%GPY*3Q zt)JYp?{}6@?PZU*lCG=RR<<@sYc$^CD^PF-fXxYkuZ14J+0$n`QIAr>U6s6l+e_ z_kIL}GXxwDtpG#7CGWm?X)EM}I-oWm*#4V&Mr+1Mgsno?y|_9ViOVa?aeQ;x>G z;rmn;p?*;BMb{zA6&ck_OKNi?H0zc2xjE{_?|gci?s&O~hq2dS&UWX$x{5{pcu^^t@=UpZY{LFaFo=Px7~!8+WxIoXoiW;AE!dnT&kfGygKF zFlx#%Ffb$*mBeJ`l@z5VCa1<^g2?#9f~3Tv_*A{LqFlyBoD2*;EYsKhW0Dd~$pZ_8 zGBYr|U}Rw6oiTmWKPFYCuQR9L{Kurmba(c2#{W#pOi$)bSNqST!Q`YeJ>Wl+7*9VV OGlL-$1H)TpW(ELZNLJMV delta 108 zcmaFc&hzOXcf%IOBa_<&W-x9Sn8CCplaY7&Jw|4!?dKVpxfrK&Ff%JN9h^K}mzi0W zY30=EA*z zo1t3R{l&HiPEvx3Q=}_qb$_Xzkm76PeEv(La>M=aaz7qE^xu3l$4>co;8duRT& z)eiXYm;Wjy@aT-yf=@FZNA&2M*sc#u@4kOy;qwZj%@I(jYi zYvZ1OE9F)%l-jh`o-ey5-P!Q4RNHm`^XG-Hd|uvu`*!=ohmAkl-4jkY1O(p7aDCBc z|Ni~`{^Sq$D(^=q{q3dC+)DI6=flaEkl!J?^~Wf-Rxbl;^NZ z=ae0PFZ|xxFXHs>%vHRNPyU+r?LWA7^NjUQUk|w%_I>|cZhzK(eN+0PFR5OAiib|C z{MCrMr~WMV{?EFuM`2rijM^tUZf)WJ!Zkahh_BhZqbWnyTJit&pSypGw;nmr6rL5og7s;BVfDw)e~$nBo2D6)IoWfo-@4$SWy=be@_U+Z zE!;b6*$&@LD@_-lQM-JC?Ven(TlR8=-78Zy4+&SkwvU>pqR~Dx;l;9Refynm3iN-n z)6s4D9iPE%Avh&EZ1(EA{~rBy>)hw;%2w?%evv$`gJiuiZO$$(%_J+e(%9 zHr>jNHTN?pkYozIpK&ZY*(C6c^e0DIf&F_AD#S0=IT@W&$#}?in&(2VR9o%GPY*3Q zt)JYp?{}6@?PZU*lCG=RR<<@sYc$^CD^PF-fXxYkuZ14J+0$n`QIAr>U6s6l+e_ z_kIL}GXxwDtpG#7CGWm?X)EM}I-oWm*#4V&Mr+1Mgsno?y|_9ViOVa?aeQ;x>G z;rmn;p?*;BMb{zA6&ck_OKNi?H0zc2xjE{_?|gci?s&O~hq2dS&UWX$x{5{pcu^^t@=UpZY{LFaFo=Px7~!8+WyfO<~+FHifCPl2K^;0$yen zMol>e28P6&~X4&bt`Iz~c zl4eYQ!^f<_6hCXa7(cTrlir-^wjlc7-03O&%xX+e=1=bd@n0{Rz8OT{S~C3>KeIB^ u#%0t0^D}FxtTJX|U}s=pNJ%YD*2_#`P-S2M<70x%440W07> delta 165 zcmX@MlIQdSo`x-ql2h7KXE1J0ox#*r$tbw}jUclM<8*srX0_?o!pzds8-$tpnS`fJ zpD4_%%JgVDh!&fE4kWl~=Jfl*%xX+CW`hKUr%Q=23o*@~2j++rWEPYb=%p3qGKMlU zFkEM3VCY*gJye8QnJIPA^ePc%4V5^3CI)r}28NW>;$*$d6b4lW1~9JTWoB5y#K7P! Hz{~&umwqf` diff --git a/artifacts/sfall_dat/sfall_zh.dat b/artifacts/sfall_dat/sfall_zh.dat index 1bbf4df4dc9d22941aecd24d7d6d361a72830c2a..075718fe85f3c14c37c9bd66b6315fbe0d588142 100644 GIT binary patch delta 1239 zcmX^8k^9&$?uIRlLKEtzhWQrVHWLYpE+hFNp7NmalyHKGjo=jb<9pnB!v$MHrzy{2 zm(D3W{$BXKwO_>P-I=R+8=w3&?c0BF?dBQloxUD&Gwl2Rx!nG&{raZ#MPE|A`VK>pyq@6mLCppeZ~neg*5({KD#wpZ^^H`8Q27CUdgqR=;(@LCcmEF6H+$ z-&(kL*0LSGn^u}GKBIQ|1lv8iUbpP!3cFXPY911(;r?+s*v8_1Ot}!QJ(_kCi9bh1g{8R<`svI6_}9#n{5taCCtrIPWG?KIDYUa7X)kDne| za#}yRXW#EEpW4eFZzWwDA%$^-Jd)l0BY~Ada zSy^7P6sc#Oyl7Cqr~X>ftXDZ2;?ujQJiXa)JtAT5lzUpo#qz&js9ovxFm}eQ4tvL7AaJ`B(D%)|l39tD zzlCg&*>Rqeo^xj}v1u;g{L8eQYgx=JkvWG|6dE?aS+lW4!ZfAIs_aRGAH$ljm!}+! zdBgXqEJFRD-ixk7mMb!YKcjl>NEd~3Z*Q^gs627x@ zjj+0Oub$;<72T;SD-}=PEIM)c+yvc2eImPl^<}+{lmP6h@a76t}^3Db>!FsU)c zOqm|_gGrewdHVGFA50o5CuEoy*cliYQc{bP^)gc!R2dk+xaBt!!&F8F2DiUV3;-h5 BO`HG# delta 86 zcmX^1i~ICP?uIRlLKE8KrZ8@go5JLs&d51^)o-S2)7SrDQehI9F#YNuCS@k^$TVWnchfi|frameData[0].width, frm->frameData[0].height, 0, 0, frm->frameData[0].width, frm->frameData[0].dataPtr(), 136, 37, 280, 168, PadSurface, 0); // cover buttons pics bottom - UnloadFrmFile(frm); + { + TempFrmHandle frmHandle( LoadUnlistedFrm((style) ? "AppStyle.frm" : "AppRace.frm", fo::ArtType::OBJ_TYPE_SKILLDEX) ); + if (frmHandle.IsValid()) { + const fo::FrmFile& frm = frmHandle.Frm(); + fo::util::DrawToSurface(frm.frameData[0].width, frm.frameData[0].height, 0, 0, frm.frameData[0].width, frm.frameData[0].dataPtr(), 136, 37, 280, 168, PadSurface, 0); // cover buttons pics bottom + } } int oldFont = GetFont(); // store current font diff --git a/sfall/Modules/Interface.cpp b/sfall/Modules/Interface.cpp index 3c0476c1..d19c74a5 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" @@ -89,6 +90,82 @@ fo::Window* Interface::GetWindow(long winType) { return (winID > 0) ? fo::func::GNW_find(winID) : nullptr; } +static BYTE movePointBackground[16 * 9 * 5]; +static bool hrpAPBarRectSetup = false; + +static void DrawExtendedApBar() { + const char* const ifaceApBarFrm = "iface_apbar_e.frm"; // 183x13 at 266,10 (x = width - 374) + TempFrmHandle frmHandle( LoadUnlistedFrm(ifaceApBarFrm, fo::ArtType::OBJ_TYPE_INTRFACE) ); + if (!frmHandle.IsValid()) return; + + DWORD ifaceWin = *fo::ptr::interfaceWindow; + fo::Window* win = fo::func::GNW_find(ifaceWin); + if (win == nullptr) return; + + const int destOffsetRight = 374; + const int destOffsetTop = 10; + const fo::FrmFrameData& srcFrame = frmHandle.Frm().frameData[0]; + BYTE* dest = *fo::ptr::interfaceBuffer + (win->width * (destOffsetTop + 1)) - destOffsetRight; + fo::func::buf_to_buf(srcFrame.dataPtr(), srcFrame.width, srcFrame.height, srcFrame.width, dest, win->width); +} + +static __declspec(naked) void intface_init_hook_unlock_iface_frm() { + __asm { + pushadc; + call DrawExtendedApBar; + popadc; + jmp fo::funcoffs::art_ptr_unlock_; + } +} + +static __declspec(naked) void intface_init_hack() { + __asm { + add eax, 9276 - (54 / 2); // x offset + mov edx, 144 - 90; // width + add [esp + 4], edx; + add [esp + 0x10 + 4], edx; + retn; + } +} + +static __declspec(naked) void intface_update_move_points_hack() { + static const DWORD intface_update_move_points_Ret = 0x45EE3E; + __asm { + mov eax, 16 * 9 + push eax; + push 5; + push eax; + jmp intface_update_move_points_Ret; + } +} + +static void APBarRectPatch() { + fo::ptr::movePointRect->x -= (54 / 2); // 54 = 144(new width) - 90(old width) + fo::ptr::movePointRect->offx += (54 / 2); +} + +static void ActionPointsBarPatch() { + dlogr("Applying expanded action points bar patch.", DL_INIT); + if (hrpIsEnabled) { + hrpAPBarRectSetup = true; + } else { + APBarRectPatch(); + } + + // intface_init_ + const DWORD movePointBgAddr[] = {0x45E343, 0x45EE3F}; + SafeWriteBatch((DWORD)&movePointBackground, movePointBgAddr); + // intface_update_move_points_ + const DWORD movePointMaxAddr[] = {0x45EE55, 0x45EE7B, 0x45EE82, 0x45EE9C, 0x45EEA0}; + SafeWriteBatch(16, movePointMaxAddr); + const DWORD ifaceBufDestAddr[] = {0x45EE33, 0x45EEC8, 0x45EF16}; + SafeWriteBatch(9276 - (54 / 2), ifaceBufDestAddr); + + HookCall(0x45D962, intface_init_hook_unlock_iface_frm); + MakeCall(0x45E356, intface_init_hack); + MakeJump(0x45EE38, intface_update_move_points_hack, 1); +} + static long costAP = -1; static __declspec(naked) void intface_redraw_items_hack0() { __asm { @@ -391,7 +468,7 @@ static void WorldmapViewportPatch() { HookCall(0x4C4B8F, wmTownMapInit_hook); } // up/down buttons of the location list (wmInterfaceInit_) - const DWORD wmIfaceUpDnBtnXAddr[] = {0x4C2D3C, 0x4C2D7A}; // offset by X (480) + const DWORD wmIfaceUpDnBtnXAddr[] = {0x4C2D3C, 0x4C2D7A}; SafeWriteBatch(WMAP_WIN_WIDTH - (640 - 480), wmIfaceUpDnBtnXAddr); // offset by X (480) // town/world button (wmInterfaceInit_) @@ -1089,6 +1166,9 @@ static void UIAnimationSpeedPatch() { } void Interface::OnAfterGameInit() { + if (hrpAPBarRectSetup) { + APBarRectPatch(); + } if (worldmapInterface) { WorldmapViewportPatch(); // Note: must be applied after WorldMapSlots patch } @@ -1109,6 +1189,9 @@ void Interface::init() { SafeWriteBatch(CodeType::JumpShort, windowRoundingAddr); //} + if (IniReader::GetConfigInt("Interface", "ActionPointsBar", 0)) { + ActionPointsBarPatch(); + } DrawActionPointsNumber(); WorldMapInterfacePatch(); SpeedInterfaceCounterAnimsPatch();