From fee4720ef55e9b354b49c2908214f386ef1393b8 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Sat, 11 Dec 2021 09:19:00 +0800 Subject: [PATCH] Added ALTERNATE_AMMO_METRE and NumPathNodes options --- sfall/Game/tilemap.cpp | 225 +++++++++++++++++++++++++++++++++++++ sfall/Game/tilemap.h | 4 + sfall/HRP/Init.cpp | 27 +++-- sfall/HRP/InterfaceBar.cpp | 118 ++++++++++++++++++- sfall/HRP/InterfaceBar.h | 3 + sfall/Modules/Graphics.cpp | 6 +- sfall/main.cpp | 12 +- 7 files changed, 375 insertions(+), 20 deletions(-) diff --git a/sfall/Game/tilemap.cpp b/sfall/Game/tilemap.cpp index 9437f849..797934db 100644 --- a/sfall/Game/tilemap.cpp +++ b/sfall/Game/tilemap.cpp @@ -4,6 +4,8 @@ * */ +#include + #include "..\main.h" #include "..\FalloutEngine\Fallout2.h" @@ -184,9 +186,232 @@ static void __declspec(naked) tile_num_beyond_replacement() { } } +struct sfChild { + long tile; + long from_tile; + long distance; + WORD accumulator; // the higher the value, the less likely it is to use this tile to build a path + char rotation; +}; + +static std::vector m_pathData; +static std::vector m_dadData; +static std::array seenTile; +static long maxPathNodes = 2000; + +static __forceinline fo::GameObject* CheckTileBlocking(void* blockFunc, long tile, fo::GameObject* object) { + using namespace fo::Fields; + __asm { + mov eax, object; + mov edx, tile; + mov ebx, [eax + elevation]; + call blockFunc; + //mov object, eax; + } + //return object; +} + +// same as idist_ +static __inline long DistanceFromPositions(long sX, long sY, long tX, long tY) { + long diffX = std::abs(tX - sX); + long diffY = std::abs(tY - sY); + long minDiff = (diffX <= diffY) ? diffX : diffY; + return (diffX + diffY) - (minDiff >> 1); +} + +// optimized version with added args +// type: 1 - tile path, 0 - rotation path +// maxNodes: limiting the building of a path +long __fastcall Tilemap::make_path_func(fo::GameObject* srcObject, long sourceTile, long targetTile, long type, long maxNodes, void* arrayRef, long checkTargetTile, void* blockFunc) { + if (checkTargetTile && fo::func::obj_blocking_at_wrapper(srcObject, targetTile, srcObject->elevation, blockFunc)) return 0; + + bool inCombat = fo::var::combat_state & fo::CombatStateFlag::InCombat; + int8_t critterType = fo::KillType::KILL_TYPE_men; + + bool isCritter = srcObject->IsCritter(); + if (isCritter) { + critterType = static_cast(fo::func::critter_kill_count_type(srcObject)); + } + + seenTile.fill(0); + seenTile[sourceTile >> 3] = 1 << (sourceTile & 7); + + auto& it = m_pathData.begin(); + it->tile = sourceTile; + it->from_tile = -1; + it->distance = fo::func::tile_idistance(sourceTile, targetTile); // maximum distance + it->rotation = 0; + it->accumulator = 0; + while (++it != m_pathData.end()) it->tile = -1; + + long targetX, targetY; + fo::func::tile_coord(targetTile, &targetX, &targetY); + + auto& dadData = m_dadData.begin(); + size_t pathCounter = 1; + sfChild childData; + + long node = 0; + if (maxNodes > maxPathNodes) maxNodes = maxPathNodes; + + // search path tiles + while (true) { + auto& pathData = m_pathData.begin(); + if (pathCounter > 0) { + size_t counter = 0; + long prevDistance; + + // search for the element with the smallest distance + for (auto currData = pathData; currData != m_pathData.end(); ++currData) { + if (currData->tile != -1) { + long currDistance = currData->distance + currData->accumulator; + if (counter == 0 || currDistance < prevDistance) { + prevDistance = currDistance; + pathData = currData; + } + if (++counter >= pathCounter) break; + } + } + } + childData = *pathData; // copy element data + pathData->tile = -1; // set a free element + pathCounter--; + + if (childData.tile == targetTile) break; // path is built, exit the loop + + *dadData++ = childData; + if (++node >= maxNodes) { //++dadData == m_dadData.end() + return 0; // path cannot be built, reached the end of the array (limit of the maximum path length) + } + + char rotation = 0; + do { + long tile = fo::func::tile_num_in_direction(childData.tile, rotation, 1); + + long seenIndex = tile >> 3; + BYTE seenMask = 1 << (tile & 7); + if (!(seenTile[seenIndex] & seenMask)) { + seenTile[seenIndex] |= seenMask; + + if (tile != targetTile) { + fo::GameObject* objBlock = CheckTileBlocking(blockFunc, tile, srcObject); + if (objBlock) { + // [FIX] for building the path to the central hex of a multihex object (from BugFixes.cpp) + if (objBlock->tile != targetTile) { + if (!fo::func::anim_can_use_door(srcObject, objBlock)) continue; // block - next rotation + } else { + targetTile = tile; // replace the target tile (where the multihex object is located) with the current tile + } + } + } + if (++pathCounter >= 2000) { // limit of the maximum path length? + return 0; // *** do not remove the breakpoint until this is cleared up *** + } + + pathData = m_pathData.begin(); + while (pathData->tile != -1) ++pathData; // search the first free element + + pathData->tile = tile; + pathData->from_tile = childData.tile; + pathData->rotation = rotation; + pathData->accumulator = childData.accumulator + 50; // here childData.accumulator has the value + + if (!inCombat && rotation != childData.rotation) pathData->accumulator += 10; + + long x, y; + fo::func::tile_coord(tile, &x, &y); + pathData->distance = DistanceFromPositions(x, y, targetX, targetY); + + if (isCritter) { + fo::GameObject* object = fo::func::obj_find_first_at_tile(srcObject->elevation, tile); + while (object) { + // TODO: add check for other PIDs + if (object->protoId >= fo::ProtoID::PID_RAD_GOO_1 && object->protoId <= fo::ProtoID::PID_RAD_GOO_4) { + pathData->accumulator += (critterType == fo::KillType::KILL_TYPE_gecko) ? 100 : 400; + break; + } + object = fo::func::obj_find_next_at_tile(); + } + } + } + } while (++rotation < 6); + if (!pathCounter) return 0; // path cannot be built + } + + BYTE* arrayR = reinterpret_cast(arrayRef); + WORD* arrayT = reinterpret_cast(arrayRef); + size_t pathLen = 0; + + // building and calculating the path length + do { + if (childData.tile == sourceTile) break; // reached the source tile + if (arrayRef) { + if (type) { + *arrayT++ = (WORD)childData.tile; + } else { + *arrayR++ = childData.rotation; + } + } + while (childData.from_tile != dadData->tile) --dadData; // search a linked tile 'from -> tile' + childData = *dadData; + } while (++pathLen < 800); + + if (arrayRef && pathLen > 1) { + // reverse the array values + size_t count = pathLen >> 1; + if (type) { + WORD* arrayFront = reinterpret_cast(arrayRef); + do { + WORD last = *--arrayT; + *arrayT = *arrayFront; // last < front + *arrayFront++ = last; + } while (--count); + } else { + BYTE* arrayFront = reinterpret_cast(arrayRef); + do { + BYTE last = *--arrayR; + *arrayR = *arrayFront; // last < front + *arrayFront++ = last; + } while (--count); + } + } + return pathLen; +} + +static void __declspec(naked) make_path_func_replacement() { + __asm { + xchg [esp], ecx; // ret addr <> array + push maxPathNodes; // (sfall addition) + push 0; // type rotation (sfall addition) + push ebx; // target tile + push ecx; // ret addr + mov ecx, eax; + jmp Tilemap::make_path_func; + } +} + +static bool replaced = false; + +void Tilemap::SetPathMaxNodes(long maxNodes) { + maxPathNodes = maxNodes; + + if (!replaced) { + replaced = true; + sf::MakeJump(fo::funcoffs::make_path_func_, make_path_func_replacement); // 0x415EFC + } else { + m_pathData.resize(maxNodes); + m_dadData.resize(maxNodes); + } +} + void Tilemap::init() { // Replace tile_num_beyond_ function sf::MakeJump(fo::funcoffs::tile_num_beyond_ + 1, tile_num_beyond_replacement); // 0x4B1B84 + + //m_pathData.reserve(40000); + //m_dadData.reserve(40000); + m_pathData.resize(maxPathNodes); + m_dadData.resize(maxPathNodes); } } diff --git a/sfall/Game/tilemap.h b/sfall/Game/tilemap.h index 06bf082b..51b516d1 100644 --- a/sfall/Game/tilemap.h +++ b/sfall/Game/tilemap.h @@ -16,6 +16,10 @@ public: static void obj_path_blocking_at_(); static long __fastcall tile_num_beyond(long sourceTile, long targetTile, long maxRange); + + static long __fastcall make_path_func(fo::GameObject* srcObject, long sourceTile, long targetTile, long type, long maxNodes, void* arrayRef, long checkTargetTile, void* blockFunc); + + static void SetPathMaxNodes(long maxNodes); }; } diff --git a/sfall/HRP/Init.cpp b/sfall/HRP/Init.cpp index d9297133..8868e2e8 100644 --- a/sfall/HRP/Init.cpp +++ b/sfall/HRP/Init.cpp @@ -4,6 +4,8 @@ * */ +#pragma comment(lib, "psapi.lib") + #include #include "..\main.h" @@ -13,9 +15,10 @@ #include "..\WinProc.h" #include "..\Modules\Graphics.h" #include "..\Modules\LoadOrder.h" - #include "..\Modules\SubModules\WindowRender.h" +#include "..\Game\tilemap.h" + #include "viewmap\ViewMap.h" #include "SplashScreen.h" #include "MainMenu.h" @@ -56,10 +59,7 @@ long Setting::ScreenHeight() { return SCR_HEIGHT; } long Setting::ColorBits() { return COLOUR_BITS; } static void GetHRPModule() { - static const DWORD loadFunc = 0x4FE1D0; - //HMODULE dll; - __asm call loadFunc; // get HRP loading address - __asm mov baseDLLAddr, eax; + baseDLLAddr = (DWORD)GetModuleHandleA("f2_res.dll"); sf::dlog_f("Loaded f2_res.dll library at the memory address: 0x%x\n", DL_MAIN, baseDLLAddr); } @@ -141,7 +141,11 @@ static __declspec(naked) void combat_turn_run_hook() { static __declspec(naked) void gmouse_bk_process() { __asm { + push edx; + push ecx; call sfall::WinProc::WaitMessageWindow; + pop ecx; + pop edx; jmp fo::funcoffs::gmouse_bk_process_; } } @@ -306,6 +310,10 @@ void Setting::init(const char* exeFileName, std::string &cmdline) { IFaceBar::IFACE_BAR_WIDTH = sf::IniReader::GetInt("IFACE", "IFACE_BAR_WIDTH", (SCR_WIDTH >= 800) ? 800 : 640, f2ResIni); IFaceBar::IFACE_BAR_SIDES_ORI = (sf::IniReader::GetInt("IFACE", "IFACE_BAR_SIDES_ORI", 0, f2ResIni) != 0); + IFaceBar::ALTERNATE_AMMO_METRE = sf::IniReader::GetInt("IFACE", "ALTERNATE_AMMO_METRE", 0, f2ResIni); + IFaceBar::ALTERNATE_AMMO_LIGHT = (BYTE)sf::IniReader::GetInt("IFACE", "ALTERNATE_AMMO_LIGHT", 196, f2ResIni); + IFaceBar::ALTERNATE_AMMO_DARK = (BYTE)sf::IniReader::GetInt("IFACE", "ALTERNATE_AMMO_DARK", 75, f2ResIni); + Dialog::DIALOG_SCRN_ART_FIX = (sf::IniReader::GetInt("OTHER_SETTINGS", "DIALOG_SCRN_ART_FIX", 1, f2ResIni) != 0); Dialog::DIALOG_SCRN_BACKGROUND = (sf::IniReader::GetInt("OTHER_SETTINGS", "DIALOG_SCRN_BACKGROUND", 0, f2ResIni) != 0); @@ -313,6 +321,9 @@ void Setting::init(const char* exeFileName, std::string &cmdline) { sf::WindowRender::EnableRecalculateFadeSteps(); } + int nodes = sf::IniReader::GetInt("MAPS", "NumPathNodes", 1, f2ResIni); + if (nodes > 1) game::Tilemap::SetPathMaxNodes((nodes < 20) ? nodes * 2000 : 40000); + if (sf::IniReader::GetInt("OTHER_SETTINGS", "BARTER_PC_INV_DROP_FIX", 1, f2ResIni)) { // barter_move_from_table_inventory_ if (fo::var::getInt(0x47523D) == 80) sf::SafeWrite32(0x47523D, 100); // x_start @@ -330,9 +341,9 @@ void Setting::init(const char* exeFileName, std::string &cmdline) { sf::HookCall(0x4C73B1, fadeSystemPalette_hook); sf::HookCall(0x4227E5, combat_turn_run_hook); sf::HookCalls(gmouse_bk_process, { - 0x460EB1, 0x460EFF, 0x460F55, 0x460FAE, 0x46101E, 0x4610FA, // intface_rotate_numbers_ - 0x45FA4E, // intface_end_window_open_ - 0x45FBA7, // intface_end_window_close_ + 0x460EB0, 0x460EFE, 0x460F54, 0x460FAD, 0x46101D, 0x4610F9, // intface_rotate_numbers_ + 0x45FA4D, // intface_end_window_open_ + 0x45FBA6, // intface_end_window_close_ }); } diff --git a/sfall/HRP/InterfaceBar.cpp b/sfall/HRP/InterfaceBar.cpp index 72bd6aac..f50f6f64 100644 --- a/sfall/HRP/InterfaceBar.cpp +++ b/sfall/HRP/InterfaceBar.cpp @@ -22,7 +22,13 @@ long IFaceBar::IFACE_BAR_MODE; // 1 - the bottom of the map view window extends long IFaceBar::IFACE_BAR_SIDE_ART; long IFaceBar::IFACE_BAR_WIDTH; bool IFaceBar::IFACE_BAR_SIDES_ORI; // 1 - Iface-bar side graphics extend from the Screen edges to the Iface-Bar + +// 1 - Single colour, the colours used can be set below with the ALTERNATE_AMMO_LIGHT and ALTERNATE_AMMO_DARK options +// 2 - Changes colour depending how much ammo remains in your current weapon +// 3 - Divides the metre into several differently coloured sections long IFaceBar::ALTERNATE_AMMO_METRE; +BYTE IFaceBar::ALTERNATE_AMMO_LIGHT; +BYTE IFaceBar::ALTERNATE_AMMO_DARK; static long xPosition; static long yPosition; @@ -361,6 +367,112 @@ static void __declspec(naked) intface_draw_ammo_lights_hack() { } } +///////////////////////////// Alternate Ammo Metre ///////////////////////////// + +static void GetAmmoMetreColors(long yPercent, BYTE &outClr1, BYTE &outClr2) { + if (IFaceBar::ALTERNATE_AMMO_METRE == 1) { + outClr1 = IFaceBar::ALTERNATE_AMMO_LIGHT; + outClr2 = IFaceBar::ALTERNATE_AMMO_DARK; + return; + } + + if (yPercent < 15) { // 20% + outClr1 = 136; + outClr2 = 181; + } else if (yPercent < 29) { // 40% + outClr1 = 132; + outClr2 = 140; + } else if (yPercent < 43) { // 60% + outClr1 = 145; + outClr2 = 154; + } else if (yPercent < 57) { // 80% + outClr1 = 58; + outClr2 = 66; + } else { + outClr1 = 215; + outClr2 = 75; + } +} + +static void __fastcall DrawAlternateAmmoMetre(long x, long y) { + fo::Window* win = fo::func::GNW_find(fo::var::interfaceWindow); + + x += xOffset - 2; + long startOffset = x + (25 * win->width); + BYTE* surface = win->surface + startOffset; + + *(DWORD*)surface = 0x0F0F0F0F; + surface[4] = 15; + surface += win->width; + + if (y < 70) { + // empty + long count = ((69 - y) / 2) + 1; + do { + surface[0] = 11; + surface[1] = 13; + surface[2] = 13; + surface[3] = 13; + surface[4] = 15; + surface += win->width; + + surface[0] = 11; + surface[1] = 15; + surface[2] = 15; + surface[3] = 15; + surface[4] = 15; + surface += win->width; + } while (--count); + } + + if (y > 0) { + BYTE lColor, dColor; + GetAmmoMetreColors(y, lColor, dColor); + + do { + surface[0] = 11; + surface[1] = lColor; + surface[2] = lColor; + surface[3] = lColor; + surface[4] = dColor; + surface += win->width; + + surface[0] = 11; + surface[1] = dColor; + surface[2] = dColor; + surface[3] = dColor; + surface[4] = dColor; + surface += win->width; + + y -= 2; + if (IFaceBar::ALTERNATE_AMMO_METRE == 3) GetAmmoMetreColors(y, lColor, dColor); + } while (y > 0); + } + + *(DWORD*)surface = 0x0A0A0A0A; + surface[4] = 10; + + fo::BoundRect rect; + rect.x = x; + rect.y = 26; + rect.offx = x + 3; + rect.offy = 26 + 70; + + fo::func::win_draw_rect(fo::var::interfaceWindow, (RECT*)&rect); +} + +static void __declspec(naked) intface_update_ammo_lights_hook() { + __asm { + push ecx; + mov ecx, eax; + call DrawAlternateAmmoMetre; + pop ecx; + retn; + } +} + +//////////////////////////////////////////////////////////////////////////////// + void IFaceBar::Hide() { InterfaceHide(fo::var::getInt(FO_VAR_interfaceWindow)); } @@ -445,8 +557,10 @@ void IFaceBar::init() { 0x45E9FD // intface_hide_ }); -// if (ALTERNATE_AMMO_METRE > 0) { -// } + if (ALTERNATE_AMMO_METRE) { + if (ALTERNATE_AMMO_METRE < 0 || ALTERNATE_AMMO_METRE > 3) ALTERNATE_AMMO_METRE = 1; + sf::HookCall(0x45F954, intface_update_ammo_lights_hook); // replace intface_draw_ammo_lights_ + } if (IFACE_BAR_MODE > 0) { // Set view map height to game resolution diff --git a/sfall/HRP/InterfaceBar.h b/sfall/HRP/InterfaceBar.h index 7492f86e..49a402cf 100644 --- a/sfall/HRP/InterfaceBar.h +++ b/sfall/HRP/InterfaceBar.h @@ -17,7 +17,10 @@ public: static long IFACE_BAR_SIDE_ART; static long IFACE_BAR_WIDTH; static bool IFACE_BAR_SIDES_ORI; + static long ALTERNATE_AMMO_METRE; + static BYTE ALTERNATE_AMMO_LIGHT; + static BYTE ALTERNATE_AMMO_DARK; static long display_width; static char* display_string_buf; diff --git a/sfall/Modules/Graphics.cpp b/sfall/Modules/Graphics.cpp index be873dae..2e3a0225 100644 --- a/sfall/Modules/Graphics.cpp +++ b/sfall/Modules/Graphics.cpp @@ -1171,7 +1171,7 @@ void Graphics::init() { int gMode = IniReader::GetConfigInt("Graphics", "Mode", 4); if (gMode >= 4) Graphics::mode = gMode; - if (Graphics::mode < 0 && Graphics::mode > 6) { + if (Graphics::mode < 0 || Graphics::mode > 6) { Graphics::mode = 0; } IsWindowedMode = (mode == 2 || mode == 3 || mode == 5 || mode == 6); @@ -1180,11 +1180,11 @@ void Graphics::init() { if (Graphics::mode >= 4) { dlog("Applying DX9 graphics patch.", DL_INIT); #define _DLL_NAME "d3dx9_43.dll" - HMODULE h = LoadLibraryEx(_DLL_NAME, 0, LOAD_LIBRARY_AS_DATAFILE); + HMODULE h = LoadLibraryExA(_DLL_NAME, 0, LOAD_LIBRARY_AS_DATAFILE); if (!h) { dlogr(" Failed", DL_INIT); MessageBoxA(0, "You have selected DirectX graphics mode, but " _DLL_NAME " is missing.\n" - "Switch back to DirectDraw mode, or install an up to date version of DirectX 9.0c.", 0, MB_TASKMODAL | MB_ICONERROR); + "Switch back to DirectDraw (Mode=0), or install an up to date version of DirectX 9.0c.", 0, MB_TASKMODAL | MB_ICONERROR); #undef _DLL_NAME ExitProcess(-1); } diff --git a/sfall/main.cpp b/sfall/main.cpp index 922b10c2..47f571fe 100644 --- a/sfall/main.cpp +++ b/sfall/main.cpp @@ -16,8 +16,6 @@ * along with this program. If not, see . */ -#pragma comment(lib, "psapi.lib") - #include "main.h" #include "FalloutEngine\Fallout2.h" @@ -212,17 +210,17 @@ static void SfallInit() { LoggingInit(); + // enabling debugging features + isDebug = (IniReader::GetIntDefaultConfig("Debugging", "Enable", 0) != 0); + + if (!ddraw.dll) dlog("Error: Cannot load the original ddraw.dll library.\n"); + if (!HRP::Setting::CheckExternalPatch()) { WinProc::init(); } else { ShowCursor(0); } - // enabling debugging features - isDebug = (IniReader::GetIntDefaultConfig("Debugging", "Enable", 0) != 0); - - if (!ddraw.dll) dlog("Error: Cannot load the original ddraw.dll library.\n"); - if (!isDebug || !IniReader::GetIntDefaultConfig("Debugging", "SkipCompatModeCheck", 0)) { int is64bit; typedef int (__stdcall *chk64bitproc)(HANDLE, int*);