From 9d5bfaa05922ab17becffca289798c384ac56425 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Wed, 23 Dec 2020 11:46:17 +0800 Subject: [PATCH] Added "interface_overlay" script function * for creating an additional overlay on the top of the interface window. (document WIP) --- artifacts/scripting/headers/sfall.h | 3 + sfall/FalloutEngine/Structs.h | 26 +-- sfall/Modules/Graphics.h | 41 ++++- .../Modules/Scripting/Handlers/Interface.cpp | 53 +++++- sfall/Modules/Scripting/Handlers/Interface.h | 2 + sfall/Modules/Scripting/Handlers/Metarule.cpp | 1 + sfall/Modules/SubModules/GameRender.cpp | 173 ++++++++++++++---- sfall/Modules/SubModules/GameRender.h | 6 + sfall/main.h | 7 + 9 files changed, 263 insertions(+), 49 deletions(-) diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index c7ae0cf8..3378c682 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -355,6 +355,9 @@ #define obj_under_cursor(onlyCritter, includeDude) sfall_func2("obj_under_cursor", onlyCritter, includeDude) #define objects_in_radius(tile, radius, elev, type) sfall_func4("objects_in_radius", tile, radius, elev, type) #define outlined_object sfall_func0("outlined_object") +#define overlay_create(winType) sfall_func2("interface_overlay", winType, 1) +#define overlay_clear(winType) sfall_func2("interface_overlay", winType, 2) +#define overlay_clear_rectangle(winType, x, y, w, h) sfall_func6("interface_overlay", winType, 2, x, y, w, h) #define real_dude_obj sfall_func0("real_dude_obj") #define remove_all_timer_events sfall_func0("remove_timer_event") #define remove_timer_event(fixedParam) sfall_func1("remove_timer_event", fixedParam) diff --git a/sfall/FalloutEngine/Structs.h b/sfall/FalloutEngine/Structs.h index 8f3b1fc8..3dc39a29 100644 --- a/sfall/FalloutEngine/Structs.h +++ b/sfall/FalloutEngine/Structs.h @@ -738,28 +738,28 @@ struct ScriptListInfoItem { //for holding window info struct Window { - long wID; + long wID; // window position in the _window_index array long flags; union { RECT wRect; BoundRect rect; }; - long width; - long height; - long clearColour; - long randX; - long randY; - BYTE *surface; // bytes frame data ref to palette - long *buttonsList; - long buttonT1; // buttonptr? - long buttonT2; - long *menuBar; - void (__cdecl *drawFunc)(BYTE* src, long width, long height, long src_width, BYTE* dst, long dst_width); // trans_buf_to_buf_ + long width; + long height; + long clearColour; + long randX; // not used by engine + long* randY; // used by sfall for additional surfaces + BYTE* surface; // bytes frame data ref to palette + long* buttonsList; + long buttonT1; // buttonptr? + long buttonT2; + long* menuBar; + void (__cdecl *drawFunc)(BYTE* src, long width, long height, long src_width, BYTE* dst, long dst_width); // trans_buf_to_buf_ }; struct sWindow { char name[32]; - long wID; + long wID; // window position in the _window_index array long width; long height; long region1; diff --git a/sfall/Modules/Graphics.h b/sfall/Modules/Graphics.h index d9ce8fae..69b4ea65 100644 --- a/sfall/Modules/Graphics.h +++ b/sfall/Modules/Graphics.h @@ -32,6 +32,24 @@ extern IDirect3DDevice9* d3d9Device; extern IDirectDrawSurface* primaryDDSurface; extern bool DeviceLost; +/* +static void DDSurfaceToDXTexture(BYTE* src, long width, long height, long src_width, DWORD* dst, long dst_width) { + if (width <= 0 || height <= 0) return; + + dst_width /= 4; + size_t s_pitch = src_width - width; + size_t d_pitch = dst_width - width; + + while (height--) { + while (width--) { + *dst++ = *src++ << 24; // write palette color index to texture alpha channel + } + dst += d_pitch; + src += s_pitch; + } +} +*/ + class Graphics : public Module { public: const char* name() { return "Graphics"; } @@ -66,13 +84,32 @@ public: static void RefreshGraphics(); static __forceinline void UpdateDDSurface(BYTE* surface, int width, int height, int widthFrom, RECT* rect) { + long x = rect->left; + long y = rect->top; + if (Graphics::mode == 0) { + __asm { + xor eax, eax; + push y; + push x; + push height; + push width; + push eax; // yFrom + push eax; // xFrom + push eax; // heightFrom + push widthFrom; + push surface; + call ds:[FO_VAR_scr_blit]; // GNW95_ShowRect_(int from, int widthFrom, int heightFrom, int xFrom, int yFrom, int width, int height, int x, int y) + add esp, 9*4; + } + return; + } if (!DeviceLost) { DDSURFACEDESC desc; - RECT lockRect = { rect->left, rect->top, rect->right + 1, rect->bottom + 1 }; + RECT lockRect = { x, y, rect->right + 1, rect->bottom + 1 }; primaryDDSurface->Lock(&lockRect, &desc, 0, 0); - if (Graphics::GPUBlt == 0) desc.lpSurface = (BYTE*)desc.lpSurface + (desc.lPitch * rect->top) + rect->left; + if (Graphics::GPUBlt == 0) desc.lpSurface = (BYTE*)desc.lpSurface + (desc.lPitch * y) + x; fo::func::buf_to_buf(surface, width, height, widthFrom, (BYTE*)desc.lpSurface, desc.lPitch); primaryDDSurface->Unlock(desc.lpSurface); diff --git a/sfall/Modules/Scripting/Handlers/Interface.cpp b/sfall/Modules/Scripting/Handlers/Interface.cpp index 4253f16a..57584266 100644 --- a/sfall/Modules/Scripting/Handlers/Interface.cpp +++ b/sfall/Modules/Scripting/Handlers/Interface.cpp @@ -29,6 +29,8 @@ #include "..\..\HookScripts\InventoryHs.h" +#include "..\..\SubModules\GameRender.h" + #include "Interface.h" namespace sfall @@ -582,7 +584,7 @@ void mf_draw_image_scaled(OpcodeContext& ctx) { ctx.setReturn(DrawImage(ctx, true)); } -static long InterfaceDrawImage(OpcodeContext& ctx, fo::Window* interfaceWin) { +static long InterfaceDrawImage(OpcodeContext& ctx, fo::Window* ifaceWin) { const char* file = nullptr; bool useShift = false; long direction = -1, w = -1, h = -1; @@ -628,12 +630,14 @@ static long InterfaceDrawImage(OpcodeContext& ctx, fo::Window* interfaceWin) { int width = (w >= 0) ? w : framePtr->width; int height = (h >= 0) ? h : framePtr->height; + BYTE* surface = (ifaceWin->randY) ? GameRender::GetOverlaySurface(ifaceWin) : ifaceWin->surface; + fo::func::trans_cscale(((frmPtr->id == 'PCX') ? frmPtr->pixelData : framePtr->data), framePtr->width, framePtr->height, framePtr->width, - interfaceWin->surface + (y * interfaceWin->width) + x, width, height, interfaceWin->width + surface + (y * ifaceWin->width) + x, width, height, ifaceWin->width ); if (!(ctx.arg(0).rawValue() & 0x1000000)) { - fo::func::GNW_win_refresh(interfaceWin, &interfaceWin->rect, 0); + fo::func::GNW_win_refresh(ifaceWin, &ifaceWin->rect, 0); } FreeArtFile(frmPtr); @@ -785,12 +789,21 @@ void mf_interface_print(OpcodeContext& ctx) { // same as vanilla PrintRect __asm call fo::funcoffs::windowGetTextColor_; // set from SetTextColor __asm mov byte ptr color, al; } + + BYTE* surface; + if (win->randY) { // if a surface was created, the engine will draw on it + surface = win->surface; + win->surface = GameRender::GetOverlaySurface(win); // replace the surface for the windowWrapLineWithSpacing_ function + } + if (color & 0x10000) { // shadow (textshadow) fo::func::windowWrapLineWithSpacing(win->wID, text, width, maxHeight, x, y, 0x201000F, 0, 0); color ^= 0x10000; } ctx.setReturn(fo::func::windowWrapLineWithSpacing(win->wID, text, width, maxHeight, x, y, color, 0, 0)); // returns count of lines printed + if (win->randY) win->surface = surface; + // no redraw (textdirect) if (!(color & 0x1000000)) fo::func::GNW_win_refresh(win, &win->rect, 0); } @@ -814,5 +827,39 @@ void mf_win_fill_color(OpcodeContext& ctx) { } } +void mf_interface_overlay(OpcodeContext& ctx) { + fo::Window* win = nullptr; + long winType = ctx.arg(0).rawValue(); + + if (ctx.arg(1).rawValue()) { + win = Interface::GetWindow(winType); + if (!win || (int)win == -1) return; + } + + switch (ctx.arg(1).rawValue()) { + case 1: + GameRender::CreateOverlaySurface(win, winType); + break; + case 2: // clear + if (ctx.numArgs() > 2) { + long w = ctx.arg(4).rawValue(); + long h = ctx.arg(5).rawValue(); + if (w <= 0 || h <= 0) return; + + long x = ctx.arg(2).rawValue(); + long y = ctx.arg(3).rawValue(); + if (x < 0 || y < 0) return; + + Rectangle rect = { x, y, w, h }; + GameRender::ClearOverlay(win, rect); + } else { + GameRender::ClearOverlay(win); + } + break; + //case 0: // unused (reserved) + // GameRender::DestroyOverlaySurface(winType); + } +} + } } diff --git a/sfall/Modules/Scripting/Handlers/Interface.h b/sfall/Modules/Scripting/Handlers/Interface.h index fda2a9c8..9d725886 100644 --- a/sfall/Modules/Scripting/Handlers/Interface.h +++ b/sfall/Modules/Scripting/Handlers/Interface.h @@ -121,5 +121,7 @@ void mf_interface_print(OpcodeContext&); void mf_win_fill_color(OpcodeContext&); +void mf_interface_overlay(OpcodeContext&); + } } diff --git a/sfall/Modules/Scripting/Handlers/Metarule.cpp b/sfall/Modules/Scripting/Handlers/Metarule.cpp index 3794d01d..66a07160 100644 --- a/sfall/Modules/Scripting/Handlers/Metarule.cpp +++ b/sfall/Modules/Scripting/Handlers/Metarule.cpp @@ -102,6 +102,7 @@ static const SfallMetarule metarules[] = { {"has_fake_trait_npc", mf_has_fake_trait_npc, 2, 2, 0, {ARG_OBJECT, ARG_STRING}}, {"hide_window", mf_hide_window, 0, 1, -1, {ARG_STRING}}, {"interface_art_draw", mf_interface_art_draw, 4, 6, -1, {ARG_INT, ARG_INTSTR, ARG_INT, ARG_INT, ARG_INT, ARG_INT}}, + {"interface_overlay", mf_interface_overlay, 2, 6, -1, {ARG_INT, ARG_INT, ARG_INT, ARG_INT, ARG_INT, ARG_INT}}, {"interface_print", mf_interface_print, 5, 6, -1, {ARG_STRING, ARG_INT, ARG_INT, ARG_INT, ARG_INT, ARG_INT}}, {"intface_hide", mf_intface_hide, 0, 0}, {"intface_is_hidden", mf_intface_is_hidden, 0, 0}, diff --git a/sfall/Modules/SubModules/GameRender.cpp b/sfall/Modules/SubModules/GameRender.cpp index f33f8e42..4cb8adf4 100644 --- a/sfall/Modules/SubModules/GameRender.cpp +++ b/sfall/Modules/SubModules/GameRender.cpp @@ -25,10 +25,125 @@ namespace sfall { +class OverlaySurface +{ +private: + long size = 0; + long surfWidth; + long allocSize; + BYTE* surface = nullptr; + +public: + //long winType = -1; + + BYTE* Surface() { return surface; } + + void CreateSurface(fo::Window* win, long winType) { + //this->winType = winType; + this->surfWidth = win->width; + this->size = win->height * win->width; + + if (surface != nullptr) { + if (size <= allocSize) { + std::memset(surface, 0, size); + return; + } + delete[] surface; + } + + this->allocSize = size; + surface = new BYTE[size](); + } + + void ClearSurface() { + if (surface != nullptr) std::memset(surface, 0, size); + } + + void ClearSurface(Rectangle &rect) { + if (surface != nullptr) { + if (rect.width > surfWidth || rect.height > (size / surfWidth)) return; // going beyond the surface size + BYTE* surf = surface + (surfWidth * rect.y) + rect.x; + + size_t sizeD = rect.width >> 2; + size_t sizeB = rect.width & 3; + size_t stride = sizeD << 2; + + long height = rect.height; + while (height--) { + if (sizeD) { + __stosd((DWORD*)surf, 0, sizeD); + surf += stride; + } + if (sizeB) { + __stosb(surf, 0, sizeB); + surf += sizeB; + } + }; + } + } + + /*void DestroySurface() { + delete[] surface; + surface = nullptr; + }*/ + + ~OverlaySurface() { + delete[] surface; + } +} overlaySurfaces[5]; + +static long indexPosition = 0; + +void GameRender::CreateOverlaySurface(fo::Window* win, long winType) { + overlaySurfaces[indexPosition].CreateSurface(win, winType); + win->randY = reinterpret_cast(&overlaySurfaces[indexPosition]); + if (++indexPosition == 5) indexPosition = 0; +}; + +BYTE* GameRender::GetOverlaySurface(fo::Window* win) { + return reinterpret_cast(win->randY)->Surface(); +}; + +void GameRender::ClearOverlay(fo::Window* win) { + if (win->randY) reinterpret_cast(win->randY)->ClearSurface(); +}; + +void GameRender::ClearOverlay(fo::Window* win, Rectangle &rect) { + if (win->randY) reinterpret_cast(win->randY)->ClearSurface(rect); +}; + +/*void GameRender::DestroyOverlaySurface(long winType) { + for (size_t i = 0; i < 5; i++) { + if (overlaySurfaces[i].winType == winType) { + overlaySurfaces[i].DestroySurface(); + overlaySurfaces[i].winType = -1; + //break; + } + } +};*/ + static BYTE* GetBuffer() { return (BYTE*)*(DWORD*)FO_VAR_screen_buffer; } +static void Draw(fo::Window* win, BYTE* surface, long width, long height, long widthFrom, BYTE* toBuffer, long toWidth, RECT &rect, RECT* updateRect) { + auto drawFunc = (win->flags & fo::WinFlags::Transparent && win->wID) ? fo::func::trans_buf_to_buf : fo::func::buf_to_buf; + if (toBuffer) { + drawFunc(surface, width, height, widthFrom, &toBuffer[rect.left - updateRect->left] + ((rect.top - updateRect->top) * toWidth), toWidth); + } else { + drawFunc(surface, width, height, widthFrom, &GetBuffer()[rect.left] + (rect.top * toWidth), toWidth); // copy to buffer instead of DD surface (buffering) + } + + if (!win->randY) return; + surface = &GameRender::GetOverlaySurface(win)[rect.left - win->rect.x] + ((rect.top - win->rect.y) * win->width); + + if (toBuffer) { + fo::func::trans_buf_to_buf(surface, width, height, widthFrom, &toBuffer[rect.left - updateRect->left] + ((rect.top - updateRect->top) * toWidth), toWidth); + } else { + fo::func::trans_buf_to_buf(surface, width, height, widthFrom, &GetBuffer()[rect.left] + (rect.top * toWidth), toWidth); + } +} + static void __fastcall sf_GNW_win_refresh(fo::Window* win, RECT* updateRect, BYTE* toBuffer) { if (win->flags & fo::WinFlags::Hidden) return; fo::RectList* rects; @@ -74,9 +189,9 @@ static void __fastcall sf_GNW_win_refresh(fo::Window* win, RECT* updateRect, BYT Graphics::UpdateDDSurface(&GetBuffer()[rects->wRect.left - updateRect->left] + (rects->wRect.top - updateRect->top) * w, wRect, hRect, w, &rects->wRect); - fo::RectList* next = rects->nextRect; - fo::sf_rect_free(rects); - rects = next; + fo::RectList* free = rects; + rects = rects->nextRect; + fo::sf_rect_free(free); } } return; @@ -89,18 +204,17 @@ static void __fastcall sf_GNW_win_refresh(fo::Window* win, RECT* updateRect, BYT rects->rect = { updateRect->left, updateRect->top, updateRect->right, updateRect->bottom }; rects->nextRect = nullptr; - RECT &rect = rects->wRect; /* If the border of the updateRect rectangle is located outside the window, then assign to rects->rect the border of the window rectangle Otherwise, rects->rect contains the borders from the update rectangle (updateRect) */ - if (win->wRect.left >= rect.left) rect.left = win->wRect.left; - if (win->wRect.top >= rect.top) rect.top = win->wRect.top; - if (win->wRect.right <= rect.right) rect.right = win->wRect.right; - if (win->wRect.bottom <= rect.bottom) rect.bottom = win->wRect.bottom; + if (rects->wRect.left < win->wRect.left) rects->wRect.left = win->wRect.left; + if (rects->wRect.top < win->wRect.top) rects->wRect.top = win->wRect.top; + if (rects->wRect.right > win->wRect.right) rects->wRect.right = win->wRect.right; + if (rects->wRect.bottom > win->wRect.bottom) rects->wRect.bottom = win->wRect.bottom; - if (rect.right < rect.left || rect.bottom < rect.top) { + if (rects->wRect.right < rects->wRect.left || rects->wRect.bottom < rects->wRect.top) { fo::sf_rect_free(rects); return; } @@ -112,9 +226,8 @@ static void __fastcall sf_GNW_win_refresh(fo::Window* win, RECT* updateRect, BYT fo::RectList* currRect = rects; while (currRect) { - RECT &crect = currRect->wRect; - int width = (crect.right - crect.left) + 1; // for current rectangle - int height = (crect.bottom - crect.top) + 1;; // for current rectangle + int width = (currRect->wRect.right - currRect->wRect.left) + 1; // for current rectangle + int height = (currRect->wRect.bottom - currRect->wRect.top) + 1;; // for current rectangle BYTE* surface; if (win->wID > 0) { @@ -123,20 +236,14 @@ static void __fastcall sf_GNW_win_refresh(fo::Window* win, RECT* updateRect, BYT mov edx, currRect; call fo::funcoffs::GNW_button_refresh_; } - surface = &win->surface[crect.left - win->rect.x] + ((crect.top - win->rect.y) * win->width); + surface = &win->surface[currRect->wRect.left - win->rect.x] + ((currRect->wRect.top - win->rect.y) * win->width); } else { surface = new BYTE[height * width](); // black background widthFrom = width; // replace with rectangle } - auto drawFunc = (win->flags & fo::WinFlags::Transparent && win->wID) ? fo::func::trans_buf_to_buf : fo::func::buf_to_buf; - if (toBuffer) { - drawFunc(surface, width, height, widthFrom, &toBuffer[crect.left - updateRect->left] + ((crect.top - updateRect->top) * toWidth), toWidth); - } else { - // copy to buffer instead of DD surface (buffering) - drawFunc(surface, width, height, widthFrom, &GetBuffer()[crect.left] + (crect.top * toWidth), toWidth); - //Graphics::UpdateDDSurface(surface, width, height, widthFrom, crect); - } + Draw(win, surface, width, height, widthFrom, toBuffer, toWidth, currRect->wRect, updateRect); + if (win->wID == 0) delete[] surface; currRect = currRect->nextRect; @@ -201,17 +308,21 @@ void GameRender::init() { MakeJump(0x4D3704, fo::func::trans_buf_to_buf); // trans_buf_to_buf_ // Enable support for transparent interface windows - SafeWrite16(0x4D5D46, 0x9090); // win_init_ (create screen_buffer) + SafeWriteBatch(0x9090, { + 0x4D5D46, // win_init_ (create screen_buffer) + 0x4D75E6 // win_clip_ (remove _buffering checking) + }); SafeWrite8(0x42F869, fo::WinFlags::MoveOnTop | fo::WinFlags::OwnerFlag); // addWindow_ (remove Transparent flag) - if (Graphics::mode) { - // custom implementation of the GNW_win_refresh function - MakeJump(0x4D6FD9, GNW_win_refresh_hack, 1); - SafeWrite16(0x4D75E6, 0x9090); // win_clip_ (remove _buffering checking) - SafeWrite32(0x4C8FD1, FO_VAR_screen_buffer); // replace screendump_buf - } else { // for default or HRP graphics mode - SafeWrite8(0x4D5DAB, 0x1D); // ecx > ebx (enable _buffering) - BlockCall(0x431076); // dialogMessage_ - } + + // Custom implementation of the GNW_win_refresh function + MakeJump(0x4D6FD9, GNW_win_refresh_hack, 1); + // Replace _screendump_buf with _screen_buffer for creating screenshots + SafeWriteBatch(FO_VAR_screen_buffer, {0x4C8FD1, 0x4C900D}); + + // Disable unused code for the RandX and RandY window structure fields (these fields can now be used for other purposes) + SafeWrite32(0x4D630C, 0x9090C031); // xor eax, eax + SafeWrite8(0x4D6310, 0x90); + BlockCall(0x4D6319); } } diff --git a/sfall/Modules/SubModules/GameRender.h b/sfall/Modules/SubModules/GameRender.h index f3857b17..cd11efcd 100644 --- a/sfall/Modules/SubModules/GameRender.h +++ b/sfall/Modules/SubModules/GameRender.h @@ -24,6 +24,12 @@ namespace sfall class GameRender { public: static void init(); + + static void CreateOverlaySurface(fo::Window* win, long winType); +// static void DestroyOverlaySurface(long winType); + static void ClearOverlay(fo::Window* win); + static void ClearOverlay(fo::Window* win, Rectangle &rect); + static BYTE* GetOverlaySurface(fo::Window* win); }; } diff --git a/sfall/main.h b/sfall/main.h index 8b7f377c..60a2b953 100644 --- a/sfall/main.h +++ b/sfall/main.h @@ -80,6 +80,13 @@ namespace sfall #define pushadc __asm push eax __asm push edx __asm push ecx #define popadc __asm pop ecx __asm pop edx __asm pop eax +struct Rectangle { + long x, y, width, height; + + long right() { return x + width; } + long bottom() { return y + height; } +}; + // Gets the integer value from given INI file. int iniGetInt(const char* section, const char* setting, int defaultValue, const char* iniFile);