From 3b2e23fda65d99a68ff3946e766f05417d980711 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Sun, 26 Jan 2020 09:26:41 +0800 Subject: [PATCH] Backported FO1 worldmap features from 4.x * WorldMapTravelMarkers, WorldMapTerrainInfo options. * set_terrain_name, set_town_title script functions. --- artifacts/ddraw.ini | 14 + artifacts/scripting/headers/sfall.h | 2 + artifacts/scripting/sfall function notes.txt | 7 + sfall/FalloutEngine.cpp | 124 ++++++++ sfall/FalloutEngine.h | 30 ++ sfall/Interface.cpp | 289 ++++++++++++++++++- sfall/Interface.h | 2 + sfall/LoadGameHook.cpp | 2 + sfall/ScriptExtender.cpp | 2 + sfall/ScriptOps/MetaruleOp.hpp | 2 + sfall/ScriptOps/WorldmapOps.hpp | 8 + sfall/Worldmap.cpp | 56 ++++ sfall/Worldmap.h | 9 + sfall/main.cpp | 1 + 14 files changed, 545 insertions(+), 3 deletions(-) diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index 5153c395..aa796ac3 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -89,6 +89,20 @@ AllowDShowMovies=0 ;Default is 100. Decrease/increase this value to speed up/slow down fade effects FadeMultiplier=100 +;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +[Interface] +;Set to 1 to enable drawing a dotted line when traveling on the world map (similar to Fallout 1) +WorldMapTravelMarkers=0 +;Uncomment these lines to change the appearance of the markers +;The color index in Fallout default palette (valid range: 1..228; default is 134) +;TravelMarkerColor=134 +;The length and spacing of the dots in pixels for each type of terrain in worldmap.txt +;Syntax is 'length:spacing', with each pair separated by a comma (valid range: 1..10; default is 2) +;TravelMarkerStyles=2:2,2:2,2:2,2:2 + +;Set to 1 to display terrain types when hovering the cursor over the player's marker on the world map +WorldMapTerrainInfo=0 + ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [Input] ;Set to 1 to enable the mouse scroll wheel to scroll through the inventory, barter, and loot screens diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index 8ec266bc..a5821dac 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -286,6 +286,8 @@ #define set_map_enter_position(tile, elev, rot) sfall_func3("set_map_enter_position", tile, elev, rot) #define set_object_data(obj, offset, value) sfall_func3("set_object_data", obj, offset, value) #define set_outline(obj, color) sfall_func2("set_outline", obj, color) +#define set_terrain_name(x, y, name) sfall_func3("set_terrain_name", x, y, name) +#define set_town_title(areaID, title) sfall_func2("set_town_title", areaID, title) #define set_unique_id(obj) sfall_func1("set_unique_id", obj) #define set_unjam_locks_time(time) sfall_func1("set_unjam_locks_time", time) #define set_window_flag(winID, flag, value) sfall_func3("set_window_flag", winID, flag, value) diff --git a/artifacts/scripting/sfall function notes.txt b/artifacts/scripting/sfall function notes.txt index f1d0eb2d..47db92e9 100644 --- a/artifacts/scripting/sfall function notes.txt +++ b/artifacts/scripting/sfall function notes.txt @@ -608,11 +608,18 @@ optional arguments: - toCase: 0 - lowercase, 1 - uppercase - NOTE: this function works only for English letters of A-Z/a-z +> void sfall_func3("set_terrain_name", int x, int y, string name) +- overrides the terrain type name for the sub-tile on the world map by the specified coordinates + int sfall_func2("get_window_attribute", int winType, int attrType) - returns the attribute of the specified interface window by the attrType argument - winType: the type number of the interface window (see WINTYPE_* constants in sfall.h) - attrType: 0 - X position, 1 - Y position (relative to the top-left corner of the game screen) +> void sfall_func2("set_town_title", int areaID, string title) +- sets a floating text for a town on the world map when hovering the cursor over the player's marker +- areaID: the ID number of the town from city.txt + ------------------------ ------ MORE INFO ------- ------------------------ diff --git a/sfall/FalloutEngine.cpp b/sfall/FalloutEngine.cpp index a103bbda..3262bfaa 100644 --- a/sfall/FalloutEngine.cpp +++ b/sfall/FalloutEngine.cpp @@ -224,6 +224,8 @@ DWORD* ptr_wmAreaInfoList = reinterpret_cast(_wmAreaInfoList const DWORD* ptr_wmBkWin = reinterpret_cast(_wmBkWin); BYTE** ptr_wmBkWinBuf = reinterpret_cast(_wmBkWinBuf); DWORD* ptr_wmLastRndTime = reinterpret_cast(_wmLastRndTime); +MSGList* ptr_wmMsgFile = reinterpret_cast(_wmMsgFile); +DWORD* ptr_wmNumHorizontalTiles = reinterpret_cast(_wmNumHorizontalTiles); long* ptr_wmWorldOffsetX = reinterpret_cast(_wmWorldOffsetX); long* ptr_wmWorldOffsetY = reinterpret_cast(_wmWorldOffsetY); DWORD* ptr_world_xpos = reinterpret_cast(_world_xpos); @@ -374,6 +376,9 @@ const DWORD executeProcedure_ = 0x46DD2C; const DWORD findCurrentProc_ = 0x467160; const DWORD fadeSystemPalette_ = 0x4C7320; const DWORD findVar_ = 0x4410AC; +const DWORD FMtext_char_width_ = 0x4421DC; +const DWORD FMtext_to_buf_ = 0x4422B4; +const DWORD FMtext_width_ = 0x442188; const DWORD folder_print_line_ = 0x43E3D8; const DWORD fprintf_ = 0x4F0D56; const DWORD frame_ptr_ = 0x419880; @@ -717,6 +722,7 @@ const DWORD windowGetBuffer_ = 0x4B82DC; const DWORD windowHide_ = 0x4B7610; const DWORD windowShow_ = 0x4B7648; const DWORD windowWidth_ = 0x4B7734; +const DWORD wmDrawCursorStopped_ = 0x4C41EC; const DWORD wmFindCurSubTileFromPos_ = 0x4C0C00; const DWORD wmInterfaceRefresh_ = 0x4C3830; const DWORD wmInterfaceScrollTabsStart_ = 0x4C219C; @@ -725,6 +731,7 @@ const DWORD wmMarkSubTileRadiusVisited_ = 0x4C3550; const DWORD wmMatchAreaContainingMapIdx_ = 0x4C59A4; const DWORD wmPartyInitWalking_ = 0x4C1E54; const DWORD wmPartyWalkingStep_ = 0x4C1F90; +const DWORD wmRefreshInterfaceOverlay_ = 0x4C50F4; const DWORD wmSubTileMarkRadiusVisited_ = 0x4C35A8; const DWORD wmWorldMapFunc_ = 0x4BFE10; const DWORD wmWorldMapLoadTempData_ = 0x4BD6B4; @@ -1177,6 +1184,10 @@ void __stdcall WinDraw(DWORD winRef) { WRAP_WATCOM_CALL1(win_draw_, winRef) } +void __stdcall WinDrawRect(DWORD winRef, RECT* rect) { + WRAP_WATCOM_CALL2(win_draw_rect_, winRef, rect) +} + void __stdcall WinDelete(DWORD winRef) { WRAP_WATCOM_CALL1(win_delete_, winRef) } @@ -1339,6 +1350,10 @@ long __fastcall ItemAddForce(TGameObj* critter, TGameObj* item, long count) { WRAP_WATCOM_FCALL3(item_add_force_, critter, item, count) } +long __fastcall MessageFind(DWORD* msgFile, long msgNumber, DWORD* outBuf) { + WRAP_WATCOM_FCALL3(message_find_, msgFile, msgNumber, outBuf) +} + long __fastcall MouseClickIn(long x, long y, long x_end, long y_end) { WRAP_WATCOM_FCALL4(mouse_click_in_, x, y, x_end, y_end) } @@ -1351,6 +1366,14 @@ long __stdcall LoadFrame(const char* filename, FrmFile** frmPtr) { WRAP_WATCOM_CALL2(load_frame_, filename, frmPtr) } +long __stdcall FMtextWidth(const char* text) { + WRAP_WATCOM_CALL1(FMtext_width_, text) +} + +void __stdcall WmRefreshInterfaceOverlay(long isRedraw) { + WRAP_WATCOM_CALL1(wmRefreshInterfaceOverlay_, isRedraw) +} + ///////////////////////////////// ENGINE UTILS ///////////////////////////////// static MSGNode messageBuf; @@ -1512,6 +1535,81 @@ void GetObjectsTileRadius(std::vector &objs, long sourceTile, long ra } } +// Returns the type of the terrain sub tile at the the player's position on the world map +long wmGetCurrentTerrainType() { + long* terrainId = *(long**)_world_subtile; + if (terrainId == nullptr) { + __asm { + lea ebx, terrainId; + mov edx, dword ptr ds:[_world_ypos]; + mov eax, dword ptr ds:[_world_xpos]; + call wmFindCurSubTileFromPos_; + } + } + return *terrainId; +} + +//--------------------------------------------------------- +// copy the area from the interface buffer to the data array +void SurfaceCopyToMem(long fromX, long fromY, long width, long height, long fromWidth, BYTE* fromSurface, BYTE* toMem) { + fromSurface += fromY * fromWidth + fromX; + long i = 0; + for (long h = 0; h < height; h++) { + for (long w = 0; w < width; w++) { + toMem[i++] = fromSurface[w]; + } + fromSurface += fromWidth; + } +} + +// copy data from memory to the area of the interface buffer +void DrawToSurface(long toX, long toY, long width, long height, long toWidth, long toHeight, BYTE* toSurface, BYTE* fromMem) { + BYTE* _toSurface = toSurface + (toY * toWidth + toX); + BYTE* endToSurf = (toWidth * toHeight) + toSurface; + long i = 0; + for (long h = 0; h < height; h++) { + for (long w = 0; w < width; w++) { + if (_toSurface + w > endToSurf) return; + if (_toSurface >= toSurface) _toSurface[w] = fromMem[i++]; + } + _toSurface += toWidth; + } +} + +void DrawToSurface(long width, long height, long fromX, long fromY, long fromWidth, BYTE* fromSurf, + long toX, long toY, long toWidth, long toHeight, BYTE* toSurf, int maskRef) +{ + BYTE* _fromSurf = fromSurf + (fromY * fromWidth + fromX); + BYTE* _toSurf = toSurf + (toY * toWidth + toX); + BYTE* endToSurf = (toWidth * toHeight) + toSurf; + + for (long h = 0; h < height; h++) { + for (long w = 0; w < width; w++) { + if (_toSurf + w > endToSurf) return; + if (_toSurf >= toSurf && _fromSurf[w] != maskRef) _toSurf[w] = _fromSurf[w]; + } + _fromSurf += fromWidth; + _toSurf += toWidth; + } +} + +void DrawToSurface(long width, long height, long fromX, long fromY, long fromWidth, BYTE* fromSurf, + long toX, long toY, long toWidth, long toHeight, BYTE* toSurf) +{ + BYTE* _fromSurf = fromSurf + (fromY * fromWidth + fromX); + BYTE* _toSurf = toSurf + (toY * toWidth + toX); + BYTE* endToSurf = (toWidth * toHeight) + toSurf; + + for (long h = 0; h < height; h++) { + for (long w = 0; w < width; w++) { + if (_toSurf + w > endToSurf) return; + if (_toSurf >= toSurf) _toSurf[w] = _fromSurf[w]; + } + _fromSurf += fromWidth; + _toSurf += toWidth; + } +} + //--------------------------------------------------------- // print text to surface void __stdcall PrintText(char* displayText, BYTE colorIndex, DWORD xPos, DWORD yPos, DWORD txtWidth, DWORD toWidth, BYTE* toSurface) { @@ -1529,6 +1627,21 @@ void __stdcall PrintText(char* displayText, BYTE colorIndex, DWORD xPos, DWORD y } } +void __stdcall PrintTextFM(char* displayText, BYTE colorIndex, DWORD xPos, DWORD yPos, DWORD txtWidth, DWORD toWidth, BYTE* toSurface) { + DWORD posOffset = yPos * toWidth + xPos; + __asm { + xor eax, eax; + mov al, colorIndex; + mov edx, displayText; + push eax; + mov ebx, txtWidth; + mov eax, toSurface; + mov ecx, toWidth; + add eax, posOffset; + call FMtext_to_buf_; + } +} + //--------------------------------------------------------- //gets the height of the currently selected font DWORD __stdcall GetTextHeight() { @@ -1549,6 +1662,10 @@ DWORD __stdcall GetTextWidth(const char* TextMsg) { } } +DWORD __stdcall GetTextWidthFM(const char* TextMsg) { + return FMtextWidth(TextMsg); //get text width +} + //--------------------------------------------------------- //get width of Char for current font DWORD __stdcall GetCharWidth(char charVal) { @@ -1558,6 +1675,13 @@ DWORD __stdcall GetCharWidth(char charVal) { } } +DWORD __stdcall GetCharWidthFM(char charVal) { + __asm { + mov al, charVal; + call FMtext_char_width_; + } +} + //--------------------------------------------------------- //get maximum string length for current font - if all characters were maximum width DWORD __stdcall GetMaxTextWidth(const char* TextMsg) { diff --git a/sfall/FalloutEngine.h b/sfall/FalloutEngine.h index bc330922..78f5019e 100644 --- a/sfall/FalloutEngine.h +++ b/sfall/FalloutEngine.h @@ -262,10 +262,13 @@ #define _wmEncounterIconShow 0x672E48 #define _wmLastRndTime 0x51DEA0 #define _wmMaxMapNum 0x51DE10 +#define _wmMsgFile 0x672FB0 +#define _wmNumHorizontalTiles 0x51DDF4 #define _wmRndCursorFid 0x672E58 #define _wmWorldOffsetX 0x51DE2C #define _wmWorldOffsetY 0x51DE30 #define _wmYesNoStrs 0x51DD90 +#define _world_subtile 0x672E14 #define _world_xpos 0x672E0C #define _world_ypos 0x672E10 #define _WorldMapCurrArea 0x672E08 @@ -490,6 +493,8 @@ extern DWORD* ptr_wmAreaInfoList; extern const DWORD* ptr_wmBkWin; extern BYTE** ptr_wmBkWinBuf; extern DWORD* ptr_wmLastRndTime; +extern MSGList* ptr_wmMsgFile; +extern DWORD* ptr_wmNumHorizontalTiles; extern long* ptr_wmWorldOffsetX; extern long* ptr_wmWorldOffsetY; extern DWORD* ptr_world_xpos; @@ -621,6 +626,9 @@ extern const DWORD exec_script_proc_; // unsigned int aScriptID, int aProcI extern const DWORD executeProcedure_; // - programPtr, - procNumber extern const DWORD fadeSystemPalette_; extern const DWORD findVar_; +extern const DWORD FMtext_char_width_; +extern const DWORD FMtext_to_buf_; +extern const DWORD FMtext_width_; extern const DWORD folder_print_line_; extern const DWORD fprintf_; extern const DWORD frame_ptr_; @@ -973,6 +981,7 @@ extern const DWORD windowGetBuffer_; extern const DWORD windowHide_; extern const DWORD windowShow_; extern const DWORD windowWidth_; +extern const DWORD wmDrawCursorStopped_; extern const DWORD wmFindCurSubTileFromPos_; extern const DWORD wmInterfaceRefresh_; extern const DWORD wmInterfaceScrollTabsStart_; @@ -981,6 +990,7 @@ extern const DWORD wmMarkSubTileRadiusVisited_; extern const DWORD wmMatchAreaContainingMapIdx_; extern const DWORD wmPartyInitWalking_; extern const DWORD wmPartyWalkingStep_; +extern const DWORD wmRefreshInterfaceOverlay_; extern const DWORD wmSubTileMarkRadiusVisited_; extern const DWORD wmWorldMapFunc_; extern const DWORD wmWorldMapLoadTempData_; @@ -1189,6 +1199,7 @@ void __stdcall WinShow(DWORD winRef); void __stdcall WinHide(DWORD winRef); BYTE* __stdcall WinGetBuf(DWORD winRef); void __stdcall WinDraw(DWORD winRef); +void __stdcall WinDrawRect(DWORD winRef, RECT* rect); void __stdcall WinDelete(DWORD winRef); long __stdcall WindowWidth(); @@ -1263,12 +1274,18 @@ long __stdcall InvenUnwield(TGameObj* critter, long slot); long __fastcall ItemAddForce(TGameObj* critter, TGameObj* item, long count); +long __fastcall MessageFind(DWORD* msgFile, long msgNumber, DWORD* outBuf); + long __fastcall MouseClickIn(long x, long y, long x_end, long y_end); const char* __stdcall ArtGetName(long artFID); long __stdcall LoadFrame(const char* filename, FrmFile** frmPtr); +long __stdcall FMtextWidth(const char* text); + +void __stdcall WmRefreshInterfaceOverlay(long isRedraw); + ///////////////////////////////// ENGINE UTILS ///////////////////////////////// // returns message string from given file or "Error" when not found @@ -1300,17 +1317,30 @@ WINinfo* GetUIWindow(long winType); void GetObjectsTileRadius(std::vector &objs, long sourceTile, long radius, long elev, long type); +long wmGetCurrentTerrainType(); + +void SurfaceCopyToMem(long fromX, long fromY, long width, long height, long fromWidth, BYTE* fromSurface, BYTE* toMem); + +void DrawToSurface(long toX, long toY, long width, long height, long toWidth, long toHeight, BYTE* toSurface, BYTE* fromMem); + +void DrawToSurface(long width, long height, long fromX, long fromY, long fromWidth, BYTE* fromSurf, long toX, long toY, long toWidth, long toHeight, BYTE* toSurf, int maskRef); + +void DrawToSurface(long width, long height, long fromX, long fromY, long fromWidth, BYTE* fromSurf, long toX, long toY, long toWidth, long toHeight, BYTE* toSurf); + // Print text to surface void __stdcall PrintText(char* displayText, BYTE colorIndex, DWORD xPos, DWORD yPos, DWORD txtWidth, DWORD toWidth, BYTE* toSurface); +void __stdcall PrintTextFM(char* displayText, BYTE colorIndex, DWORD xPos, DWORD yPos, DWORD txtWidth, DWORD toWidth, BYTE* toSurface); // gets the height of the currently selected font DWORD __stdcall GetTextHeight(); // gets the length of a string using the currently selected font DWORD __stdcall GetTextWidth(const char* textMsg); +DWORD __stdcall GetTextWidthFM(const char* textMsg); // get width of Char for current font DWORD __stdcall GetCharWidth(char charVal); +DWORD __stdcall GetCharWidthFM(char charVal); // get maximum string length for current font - if all characters were maximum width DWORD __stdcall GetMaxTextWidth(const char* textMsg); diff --git a/sfall/Interface.cpp b/sfall/Interface.cpp index 5d8d9c30..79c0ead4 100644 --- a/sfall/Interface.cpp +++ b/sfall/Interface.cpp @@ -16,8 +16,12 @@ * along with this program. If not, see . */ +#include + #include "main.h" #include "FalloutEngine.h" +#include "Utils.h" +#include "Worldmap.h" static long costAP = -1; static void __declspec(naked) intface_redraw_items_hack0() { @@ -100,16 +104,250 @@ static void __declspec(naked) wmInterfaceInit_text_font_hook() { } } +// const because no expanded world map patch +static const long wmapWinWidth = 640; +static const long wmapWinHeight = 480; +static const long wmapViewPortWidth = 450; +static const long wmapViewPortHeight = 443; + +///////////////////////// FALLOUT 1 WORLD MAP FEATURES ///////////////////////// +static bool showTerrainType = false; + +enum DotStyleDefault { + STYDEF_DotLen = 2, + STYDEF_SpaceLen = 2 +}; + +enum TerrainHoverImage { + HVRIMG_width = 200, + HVRIMG_height = 15, + HVRIMG_size = HVRIMG_width * HVRIMG_height, + HVRIMG_x_shift = (HVRIMG_width / 4) + 25 // adjust x position +}; + +static std::array wmTmpBuffer; +static bool isHoveringHotspot = false; +static bool backImageIsCopy = false; + +struct DotPosition { + long x; + long y; +}; +static std::vector dots; + +static unsigned char colorDot = 0; +static long spaceLen = STYDEF_SpaceLen; +static long dotLen = STYDEF_DotLen; +static long dot_xpos = 0; +static long dot_ypos = 0; +static size_t terrainCount = 0; + +static struct DotStyle { + long dotLen; + long spaceLen; +} *dotStyle = nullptr; + +static void AddNewDot() { + dot_xpos = *ptr_world_xpos; + dot_ypos = *ptr_world_ypos; + + long* terrain = *(long**)_world_subtile; + size_t id = (terrain) ? *terrain : 0; + + // Reinitialize if current terrain has smaller values than previous + if (id < terrainCount) { + if (dotLen > dotStyle[id].dotLen) dotLen = dotStyle[id].dotLen; + if (spaceLen > dotStyle[id].spaceLen) spaceLen = dotStyle[id].spaceLen; + } + + if (dotLen <= 0 && spaceLen) { + spaceLen--; + if (!spaceLen) { // set dot length + dotLen = (id < terrainCount) ? dotStyle[id].dotLen : STYDEF_DotLen; + }; + return; + } + dotLen--; + spaceLen = (id < terrainCount) ? dotStyle[id].spaceLen : STYDEF_SpaceLen; + + DotPosition dot; + dot.x = dot_xpos; + dot.y = dot_ypos; + dots.push_back(std::move(dot)); +} + +static void __declspec(naked) DrawingDots() { + long x_offset, y_offset; + __asm { + mov ebp, esp; // prolog + sub esp, __LOCAL_SIZE; + } + + if (dot_xpos != *ptr_world_xpos || dot_ypos != *ptr_world_ypos) { + AddNewDot(); + } + x_offset = 22 - *ptr_wmWorldOffsetX; + y_offset = 21 - *ptr_wmWorldOffsetY; + + for (std::vector::const_iterator it = dots.begin(); it != dots.end(); ++it) { // redraws all dots + if (it->x < *ptr_wmWorldOffsetX || it->y < *ptr_wmWorldOffsetY) continue; // the pixel is out of viewport + if (it->x > *ptr_wmWorldOffsetX + wmapViewPortWidth || it->y > *ptr_wmWorldOffsetY + wmapViewPortHeight) continue; + + long wmPixelX = (it->x + x_offset); + long wmPixelY = (it->y + y_offset); + + wmPixelY *= wmapWinWidth; + + BYTE* wmWinBuf = *ptr_wmBkWinBuf; + BYTE* wmWinBuf_xy = (wmPixelY + wmPixelX) + wmWinBuf; + + // put pixel to interface window buffer + if (wmWinBuf_xy > wmWinBuf) *wmWinBuf_xy = colorDot; + + // TODO: fix dots for car travel + } + __asm { + mov esp, ebp; // epilog + retn; + } +} + +static bool PrintHotspotText(long x, long y, bool backgroundCopy = false) { + long area = *ptr_WorldMapCurrArea; + char* text = (area != -1 || !showTerrainType) ? (char*)Wmap_GetCustomAreaTitle(area) : (char*)Wmap_GetCurrentTerrainName(); + if (!text) return false; + + if (backgroundCopy) { // copy background image to memory (size 200 x 15) + backImageIsCopy = true; + SurfaceCopyToMem(x - HVRIMG_x_shift, y, HVRIMG_width, HVRIMG_height, wmapWinWidth, *ptr_wmBkWinBuf, wmTmpBuffer.data()); + } + + long txtWidth = GetTextWidthFM(text); + if (txtWidth > HVRIMG_width) txtWidth = HVRIMG_width; + + // offset text position + y += 4; + x += 25 - (txtWidth / 2); + + // prevent printing text outside of viewport + /*if ((x + txtWidth) > wmapViewPortWidth - 20) { + txtWidth -= (x + txtWidth) - wmapViewPortWidth - 20; + } else if (x < 20) { + long x_cut = abs(20 - x); + long width = 0; + do { + width += GetCharWidthFM(*text++); + } while (width < x_cut); + x += x_cut; + }*/ + + PrintTextFM(text, 228, x, y, txtWidth, wmapWinWidth, *ptr_wmBkWinBuf); // shadow + PrintTextFM(text, 215, x - 1, y - 1, txtWidth, wmapWinWidth, *ptr_wmBkWinBuf); + + if (backgroundCopy) WmRefreshInterfaceOverlay(0); // prevent printing text over the interface + return true; +} + +static void __declspec(naked) wmInterfaceRefresh_hook() { + if (colorDot && *ptr_target_xpos != -1) { + if (*ptr_In_WorldMap) { + DrawingDots(); + } else if (!*ptr_target_xpos && !*ptr_target_ypos) { + // player stops moving + dots.clear(); + // Reinitialize on next AddNewDot + if (terrainCount) + dotLen = spaceLen = 99; + else { + dotLen = STYDEF_DotLen; + spaceLen = STYDEF_SpaceLen; + } + } + } + if (isHoveringHotspot && !*ptr_In_WorldMap) { + PrintHotspotText(*ptr_world_xpos - *ptr_wmWorldOffsetX, *ptr_world_ypos - *ptr_wmWorldOffsetY); + isHoveringHotspot = backImageIsCopy = false; + } + __asm jmp wmDrawCursorStopped_; +} + +static void __fastcall wmDetectHotspotHover(long wmMouseX, long wmMouseY) { + if (!showTerrainType && Wmap_AreaTitlesIsEmpty()) return; + + long deltaX = 20, deltaY = 20; + + // mouse cursor is out of viewport area (the zero values of wmMouseX and wmMouseY correspond to the top-left corner of the worldmap interface) + if ((wmMouseX < 20 || wmMouseY < 20 || wmMouseX > wmapViewPortWidth + 15 || wmMouseY > wmapViewPortHeight + 20) == false) { + deltaX = abs((long)*ptr_world_xpos - (wmMouseX - deltaX + *ptr_wmWorldOffsetX)); + deltaY = abs((long)*ptr_world_ypos - (wmMouseY - deltaY + *ptr_wmWorldOffsetY)); + } + + bool isHovered = isHoveringHotspot; + isHoveringHotspot = deltaX < 8 && deltaY < 6; + if (isHoveringHotspot != isHovered) { // if value has changed + // upper left corner + long y = *ptr_world_ypos - *ptr_wmWorldOffsetY; + long x = *ptr_world_xpos - *ptr_wmWorldOffsetX; + long x_offset = x - HVRIMG_x_shift; + if (!backImageIsCopy) { + if (!PrintHotspotText(x, y, true)) return; + } else { + // restore background image + DrawToSurface(x_offset, y, HVRIMG_width, HVRIMG_height, wmapWinWidth, wmapWinHeight, *ptr_wmBkWinBuf, wmTmpBuffer.data()); + backImageIsCopy = false; + } + // redraw rectangle on worldmap interface + RECT rect; + rect.top = y; + rect.left = x_offset; + rect.right = x + HVRIMG_width; + rect.bottom = y + HVRIMG_height; + WinDrawRect(*ptr_wmBkWin, &rect); + } +} + +static void __declspec(naked) wmWorldMap_hack() { + __asm { + cmp ds:[_In_WorldMap], 1; // player is moving + jne checkHover; + mov eax, dword ptr ds:[_wmWorldOffsetY]; // overwritten code + retn; +checkHover: + cmp esi, 328; + je isScroll; + cmp esi, 331; + je isScroll; + cmp esi, 333; + je isScroll; + cmp esi, 336; + je isScroll; + push ecx; + mov ecx, [esp + 0x38 - 0x30 + 8]; // x + mov edx, [esp + 0x38 - 0x34 + 8]; // y + call wmDetectHotspotHover; + pop ecx; + mov eax, dword ptr ds:[_wmWorldOffsetY]; + retn; +isScroll: + mov isHoveringHotspot, 0; + mov backImageIsCopy, 0; + mov eax, dword ptr ds:[_wmWorldOffsetY]; + retn; + } +} + +//////////////////////////////////////////////////////////////////////////////// + static void __declspec(naked) wmInterfaceRefreshCarFuel_hack_empty() { __asm { mov byte ptr [eax - 1], 13; mov byte ptr [eax + 1], 13; - add eax, 640; + add eax, wmapWinWidth; dec ebx; mov byte ptr [eax], 14; mov byte ptr [eax - 1], 15; mov byte ptr [eax + 1], 15; - add eax, 640; + add eax, wmapWinWidth; retn; } } @@ -118,7 +356,7 @@ static void __declspec(naked) wmInterfaceRefreshCarFuel_hack() { __asm { mov byte ptr [eax - 1], 196; mov byte ptr [eax + 1], 196; - add eax, 640; + add eax, wmapWinWidth; mov byte ptr [eax - 1], 200; mov byte ptr [eax + 1], 200; retn; @@ -156,6 +394,43 @@ static void WorldMapInterfacePatch() { dlogr(" Done", DL_INIT); } + // Fallout 1 features, travel markers and displaying terrain types or town titles + if (GetConfigInt("Interface", "WorldMapTravelMarkers", 0)) { + dlog("Applying world map travel markers patch.", DL_INIT); + + int color = GetConfigInt("Interface", "TravelMarkerColor", 134); // color index in palette: R = 224, G = 0, B = 0 + if (color > 228) color = 228; else if (color < 1) color = 1; // no palette animation colors + colorDot = color; + + std::vector dotList = GetConfigList("Interface", "TravelMarkerStyles", "", 512); + if (!dotList.empty()) { + terrainCount = dotList.size(); + dotStyle = new DotStyle[terrainCount]; + + std::vector pair; + for (size_t i = 0; i < terrainCount; i++) { + split(dotList[i], ':', std::back_inserter(pair), 2); + if (pair.size() >= 2) { + int len = atoi(pair[0].c_str()); + if (len < 1) len = 1; else if (len > 10) len = 10; + dotStyle[i].dotLen = len; + len = atoi(pair[1].c_str()); + if (len < 1) len = 1; else if (len > 10) len = 10; + dotStyle[i].spaceLen = len; + } else { + dotStyle[i].dotLen = STYDEF_DotLen; + dotStyle[i].spaceLen = STYDEF_SpaceLen; + } + pair.clear(); + } + } + dots.reserve(512); + dlogr(" Done", DL_INIT); + } + showTerrainType = (GetConfigInt("Interface", "WorldMapTerrainInfo", 0) != 0); + HookCall(0x4C3C7E, wmInterfaceRefresh_hook); // when calling wmDrawCursorStopped_ + MakeCall(0x4BFE84, wmWorldMap_hack); + // Car fuel gauge graphics patch MakeCall(0x4C528A, wmInterfaceRefreshCarFuel_hack_empty); MakeCall(0x4C529E, wmInterfaceRefreshCarFuel_hack); @@ -236,6 +511,10 @@ void InterfaceGmouseHandleHook() { HookCall(0x44C018, gmouse_handle_event_hook); // replaces hack function from HRP } +void Interface_OnGameLoad() { + dots.clear(); +} + void InterfaceInit() { DrawActionPointsNumber(); WorldMapInterfacePatch(); @@ -247,3 +526,7 @@ void InterfaceInit() { HookCall(0x44B737, gmouse_bk_process_hook); // InterfaceGmouseHandleHook will be run before game initialization } + +void InterfaceExit() { + if (dotStyle) delete[] dotStyle; +} diff --git a/sfall/Interface.h b/sfall/Interface.h index afdafe97..2c68947d 100644 --- a/sfall/Interface.h +++ b/sfall/Interface.h @@ -1,4 +1,6 @@ #pragma once void InterfaceInit(); +void InterfaceExit(); void InterfaceGmouseHandleHook(); +void Interface_OnGameLoad(); diff --git a/sfall/LoadGameHook.cpp b/sfall/LoadGameHook.cpp index 5c9e25f5..b464f470 100644 --- a/sfall/LoadGameHook.cpp +++ b/sfall/LoadGameHook.cpp @@ -89,7 +89,9 @@ static void _stdcall ResetState(DWORD onLoad) { ForceGraphicsRefresh(0); // disable refresh LoadOrder_OnGameLoad(); WipeSounds(); + Interface_OnGameLoad(); RestoreObjUnjamAllLocks(); + Worldmap_OnGameLoad(); Stats_OnGameLoad(); PerksReset(); Combat_OnGameLoad(); diff --git a/sfall/ScriptExtender.cpp b/sfall/ScriptExtender.cpp index 7c4faac0..72a4fda8 100644 --- a/sfall/ScriptExtender.cpp +++ b/sfall/ScriptExtender.cpp @@ -429,6 +429,8 @@ static const SfallOpcodeMetadata opcodeMetaArray[] = { {sf_set_map_enter_position, "set_map_enter_position", {DATATYPE_MASK_INT, DATATYPE_MASK_INT, DATATYPE_MASK_INT}}, {sf_set_object_data, "set_object_data", {DATATYPE_MASK_VALID_OBJ, DATATYPE_MASK_INT, DATATYPE_MASK_INT}}, {sf_set_outline, "set_outline", {DATATYPE_MASK_VALID_OBJ, DATATYPE_MASK_INT}}, + {sf_set_terrain_name, "set_terrain_name", {DATATYPE_MASK_INT, DATATYPE_MASK_INT, DATATYPE_MASK_STR}}, + {sf_set_town_title, "set_town_title", {DATATYPE_MASK_INT, DATATYPE_MASK_STR}}, {sf_set_unique_id, "set_unique_id", {DATATYPE_MASK_VALID_OBJ, DATATYPE_MASK_INT}}, {sf_set_unjam_locks_time, "set_unjam_locks_time", {DATATYPE_MASK_INT}}, {sf_set_window_flag, "set_window_flag", {DATATYPE_MASK_INT | DATATYPE_MASK_STR, DATATYPE_MASK_INT, DATATYPE_MASK_INT}}, diff --git a/sfall/ScriptOps/MetaruleOp.hpp b/sfall/ScriptOps/MetaruleOp.hpp index f55c07f6..b60cc93d 100644 --- a/sfall/ScriptOps/MetaruleOp.hpp +++ b/sfall/ScriptOps/MetaruleOp.hpp @@ -165,6 +165,8 @@ static const SfallMetarule metaruleArray[] = { {"set_map_enter_position", sf_set_map_enter_position, 3, 3}, {"set_object_data", sf_set_object_data, 3, 3}, {"set_outline", sf_set_outline, 2, 2}, + {"set_terrain_name", sf_set_terrain_name, 3, 3}, + {"set_town_title", sf_set_town_title, 2, 2}, {"set_unique_id", sf_set_unique_id, 1, 2}, {"set_unjam_locks_time", sf_set_unjam_locks_time, 1, 1}, {"set_window_flag", sf_set_window_flag, 3, 3}, diff --git a/sfall/ScriptOps/WorldmapOps.hpp b/sfall/ScriptOps/WorldmapOps.hpp index a6b46d70..0869b53b 100644 --- a/sfall/ScriptOps/WorldmapOps.hpp +++ b/sfall/ScriptOps/WorldmapOps.hpp @@ -249,3 +249,11 @@ static void sf_get_map_enter_position() { static void sf_tile_by_position() { opHandler.setReturn(TileNum(opHandler.arg(0).rawValue(), opHandler.arg(1).rawValue())); } + +static void sf_set_terrain_name() { + Wmap_SetTerrainTypeName(opHandler.arg(0).rawValue(), opHandler.arg(1).rawValue(), opHandler.arg(2).strValue()); +} + +static void sf_set_town_title() { + Wmap_SetCustomAreaTitle(opHandler.arg(0).rawValue(), opHandler.arg(1).strValue()); +} diff --git a/sfall/Worldmap.cpp b/sfall/Worldmap.cpp index 2ed374fd..3058642b 100644 --- a/sfall/Worldmap.cpp +++ b/sfall/Worldmap.cpp @@ -29,6 +29,9 @@ static DWORD AutomapPipboyList[AUTOMAP_MAX]; static DWORD ViewportX; static DWORD ViewportY; +std::vector> wmTerrainTypeNames; // pair first: x + y * number of horizontal sub-tiles +std::vector> wmAreaHotSpotTitle; + static DWORD worldMapDelay; static DWORD worldMapTicks; @@ -442,6 +445,59 @@ DWORD GetAddedYears(bool isCheck) { return (isCheck && !addYear) ? 0 : addedYears; } +static const char* GetOverrideTerrainName(long x, long y) { + if (wmTerrainTypeNames.empty()) return nullptr; + + long subTileID = x + y * (*ptr_wmNumHorizontalTiles * 7); + auto it = std::find_if(wmTerrainTypeNames.crbegin(), wmTerrainTypeNames.crend(), + [=](const std::pair &el) + { return el.first == subTileID; } + ); + return (it != wmTerrainTypeNames.crend()) ? it->second.c_str() : nullptr; +} + +// x, y - position of the sub-tile on the world map +void Wmap_SetTerrainTypeName(long x, long y, const char* name) { + long subTileID = x + y * (*ptr_wmNumHorizontalTiles * 7); + wmTerrainTypeNames.push_back(std::make_pair(subTileID, name)); +} +/* +// TODO: someone might need to know the name of a terrain type? +const char* Wmap_GetTerrainTypeName(long x, long y) { + //const char* name = GetOverrideTerrainName(x, y); + //return (name) ? name : fo::GetMessageStr(&fo::var::wmMsgFile, 1000 + fo::wmGetTerrainType(x, y)); + return nullptr; +} +*/ +// Returns the name of the terrain type in the position of the player's marker on the world map +const char* Wmap_GetCurrentTerrainName() { + const char* name = GetOverrideTerrainName(*ptr_world_xpos / 50, *ptr_world_ypos / 50); + return (name) ? name : GetMessageStr(ptr_wmMsgFile, 1000 + wmGetCurrentTerrainType()); +} + +bool Wmap_AreaTitlesIsEmpty() { + return wmAreaHotSpotTitle.empty(); +} + +void Wmap_SetCustomAreaTitle(long areaID, const char* msg) { + wmAreaHotSpotTitle.push_back(std::make_pair(areaID, msg)); +} + +const char* Wmap_GetCustomAreaTitle(long areaID) { + if (wmAreaHotSpotTitle.empty()) return nullptr; + + auto it = std::find_if(wmAreaHotSpotTitle.crbegin(), wmAreaHotSpotTitle.crend(), + [=](const std::pair &el) + { return el.first == areaID; } + ); + return (it != wmAreaHotSpotTitle.crend()) ? it->second.c_str() : nullptr; +} + +void Worldmap_OnGameLoad() { + wmTerrainTypeNames.clear(); + wmAreaHotSpotTitle.clear(); +} + void WorldmapInit() { PathfinderFixInit(); StartingStatePatches(); diff --git a/sfall/Worldmap.h b/sfall/Worldmap.h index 8409c02b..b02ca4ad 100644 --- a/sfall/Worldmap.h +++ b/sfall/Worldmap.h @@ -19,8 +19,17 @@ #pragma once void WorldmapInit(); +void Worldmap_OnGameLoad(); DWORD GetAddedYears(bool isCheck = true); void SetAddedYears(DWORD years); +void Wmap_SetTerrainTypeName(long x, long y, const char* name); +//const char* Wmap_GetTerrainTypeName(long x, long y); +const char* Wmap_GetCurrentTerrainName(); + +bool Wmap_AreaTitlesIsEmpty(); +const char* Wmap_GetCustomAreaTitle(long areaID); +void Wmap_SetCustomAreaTitle(long areaID, const char* msg); + void _stdcall SetMapMulti(float value); diff --git a/sfall/main.cpp b/sfall/main.cpp index 9e5956ba..20baae4a 100644 --- a/sfall/main.cpp +++ b/sfall/main.cpp @@ -768,6 +768,7 @@ static void _stdcall OnExit() { if (scriptDialog) delete[] scriptDialog; GraphicsExit(); MoviesExit(); + InterfaceExit(); SpeedPatchExit(); SkillsExit(); ReputationsExit();