diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index 3c220c02..cb70397d 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -196,6 +196,11 @@ WorldMapEncounterRate=5 ;Setting this greater than 17 requires a replacement background FRM, or you'll get glitched graphics WorldMapSlots=0 +;Set to 1 to use the expanded world map interface +;Requires High Resolution Patch v4.1.8 and a new worldmap.frm file in art\intrface\ (included in sfall.dat) +;The fullscreen resolution of hi-res patch must be set to at least 890x720 +WorldMapInterface=0 + ;To start a new game somewhere other than artemple.map, uncomment the next line and set it to the map you want to load ;StartingMap= diff --git a/artifacts/sfall.dat b/artifacts/sfall.dat new file mode 100644 index 00000000..60e5fc39 Binary files /dev/null and b/artifacts/sfall.dat differ diff --git a/sfall/Modules/LoadOrder.cpp b/sfall/Modules/LoadOrder.cpp index 374794d6..87e352eb 100644 --- a/sfall/Modules/LoadOrder.cpp +++ b/sfall/Modules/LoadOrder.cpp @@ -325,6 +325,9 @@ static void RemoveSavFiles() { } void LoadOrder::init() { + // Load external sfall resource file + patchFiles.push_back("sfall.dat"); + GetExtraPatches(); if (GetConfigInt("Misc", "DataLoadOrderPatch", 0)) { diff --git a/sfall/Modules/Worldmap.cpp b/sfall/Modules/Worldmap.cpp index 6d07c7f1..39ee4686 100644 --- a/sfall/Modules/Worldmap.cpp +++ b/sfall/Modules/Worldmap.cpp @@ -651,6 +651,243 @@ long __fastcall Worldmap::GetRestMapLevel(long elev, int mapId) { return -1; } +////////////////////////////// WORLDMAP INTERFACE ////////////////////////////// + +#define WMAP_WIN_WIDTH (890) +#define WMAP_WIN_HEIGHT (720) +#define WMAP_TOWN_BUTTONS (15) + +static DWORD wmTownMapSubButtonIds[WMAP_TOWN_BUTTONS + 1]; // replace _wmTownMapSubButtonIds (index 0 - unused element) + +// Window width +static const DWORD wmWinWidth[] = { + // wmInterfaceInit_ + 0x4C239E, 0x4C247A, + // wmInterfaceRefresh_ + 0x4C38EA, 0x4C3978, + // wmInterfaceDrawCircleOverlay_ + 0x4C3FCA, 0x4C408E, + // wmRefreshInterfaceDial_ + 0x4C5757, + // wmInterfaceRefreshDate_ + 0x4C3D0A, 0x4C3D79, 0x4C3DBB, 0x4C3E87, 0x4C3E1F, + // wmRefreshTabs_ + 0x4C52BF, 0x4C53F5, 0x4C55A5, 0x4C557E, 0x4C54B2, 0x4C53E8, + // wmRefreshInterfaceOverlay_ + 0x4C50FD, 0x4C51CF, 0x4C51F8, 0x4C517F, + // wmInterfaceRefreshCarFuel_ + 0x4C528B, 0x4C529F, 0x4C52AA, + // wmInterfaceDrawSubTileList_ + 0x4C41C1, 0x4C41D2, + // wmTownMapRefresh_ + 0x4C4BDF, + // wmDrawCursorStopped_ + 0x4C42EE, 0x4C43C8, 0x4C445F, + // wmTownMapRefresh_ + 0x4C4BDF +}; + +// Right limit of the viewport (450) +static const DWORD wmViewportEndRight[] = { + 0x4C3937, 0x4C393E, 0x4C39BB, 0x4C3B2F, 0x4C3B36, 0x4C3C4B, // wmInterfaceRefresh_ + 0x4C4288, 0x4C436A, 0x4C4409, // wmDrawCursorStopped_ + 0x4BC91F, // wmWorldMap_init_ + 0x4C44B4, // wmCursorIsVisible_ +}; + +// Bottom limit of viewport (443) +static const DWORD wmViewportEndBottom[] = { + 0x4C3963, 0x4C38D7, 0x4C39DA, 0x4C3B62, 0x4C3AE7, 0x4C3C74, // wmInterfaceRefresh_ + 0x4C429A, 0x4C4378, 0x4C4413, // wmDrawCursorStopped_ + 0x4BC947, // wmWorldMap_init_ + 0x4C44BE, // wmCursorIsVisible_ +}; + +static const DWORD wmInterfaceInit_Ret = 0x4C23A7; +static void __declspec(naked) wmInterfaceInit_hack() { + __asm { + push eax; + mov eax, 640 - WMAP_WIN_WIDTH; + mov edx, 480 - WMAP_WIN_HEIGHT; + jmp wmInterfaceInit_Ret; + } +} + +static void __declspec(naked) wmInterfaceDrawSubTileList_hack() { + __asm { + mov edx, [esp + 0x10 - 0x10 + 4]; + imul edx, WMAP_WIN_WIDTH; + add edx, ecx; + retn; + } +} + +static void __declspec(naked) wmInterfaceDrawCircleOverlay_hack() { + __asm { + mov eax, ecx; + imul eax, WMAP_WIN_WIDTH; + retn; + } +} + +static void __declspec(naked) wmDrawCursorStopped_hack0() { + __asm { + mov ebx, ecx; + imul ebx, WMAP_WIN_WIDTH; + retn; + } +} + +static void __declspec(naked) wmDrawCursorStopped_hack1() { + __asm { + mov ebx, eax; + imul ebx, WMAP_WIN_WIDTH; + retn; + } +} + +static void __declspec(naked) wmRefreshTabs_hook() { + __asm { + mov eax, edx; + imul eax, WMAP_WIN_WIDTH; + sub ebp, eax; + retn; + } +} + +static void __declspec(naked) wmTownMapRefresh_hook() { + __asm { + cmp edx, 700; //_wmTownWidth + jl scale; + cmp ebx, 682; //_wmTownHeight + jl scale; + jmp fo::funcoffs::buf_to_buf_; +scale: + push WMAP_WIN_WIDTH; // to_width + push 684; // height + push 702; // width + push eax; // to_buff + mov ecx, edx; // from_width + mov eax, esi; // from_buff + call fo::funcoffs::cscale_; + retn; // don't delete + } +} + +void WorldmapViewportPatch() { + // Fix images for up/down buttons + SafeWrite32(0x4C2C0A, 199); // index of UPARWOFF.FRM + SafeWrite8(0x4C2C7C, 0x43); // dec ebx > inc ebx + SafeWrite32(0x4C2C92, 181); // index of DNARWOFF.FRM + SafeWrite8(0x4C2D04, 0x46); // dec esi > inc esi + + // check enabled HRP + if (*(DWORD*)0x4E4480 == 0x278805C7 || GetConfigInt("Misc", "WorldMapInterface", 0) == 0 || GetPrivateProfileIntA("MAIN", "SCR_HEIGHT", 0, ".\\f2_res.ini") < WMAP_WIN_HEIGHT) return; + dlog("Applying world map interface patch.", DL_INIT); + + mapSlotsScrollMax -= 216; + if (mapSlotsScrollMax < 0) mapSlotsScrollMax = 0; + + SafeWriteBatch(135, {0x4C23BD, 0x4C2408}); // use unused worldmap.frm for new world map interface (wmInterfaceInit_) + + // x/y axis offset of interface window + MakeJump(0x4C23A2, wmInterfaceInit_hack); + // size of the created window/buffer + SafeWriteBatch(WMAP_WIN_WIDTH, wmWinWidth); // width + SafeWrite32(0x4C238B, WMAP_WIN_HEIGHT); // height (wmInterfaceInit_) + + // Mouse scrolling area (wmMouseBkProc_) + SafeWrite32(0x4C331D, WMAP_WIN_WIDTH - 1); + SafeWrite32(0x4C3337, WMAP_WIN_HEIGHT - 1); + + MakeCall(0x4C41A4, wmInterfaceDrawSubTileList_hack); // 640 * 21 + MakeCall(0x4C4082, wmInterfaceDrawCircleOverlay_hack); // 640 * y + MakeCall(0x4C4452, wmDrawCursorStopped_hack1); + MakeCalls(wmDrawCursorStopped_hack0, {0x4C43BB, 0x4C42E1}); + MakeCall(0x4C5325, wmRefreshTabs_hook); + HookCall(0x4C4BFF, wmTownMapRefresh_hook); + + // up/down buttons of the location list (wmInterfaceInit_) + SafeWriteBatch(WMAP_WIN_WIDTH - (640 - 480), { // offset by X (480) + 0x4C2D3C, + 0x4C2D7A + }); + + // town/world button (wmInterfaceInit_) + SafeWrite32(0x4C2B9B, WMAP_WIN_HEIGHT - (480 - 439)); // offset by Y (439) + SafeWrite32(0x4C2BAF, WMAP_WIN_WIDTH - (640 - 519)); // offset by X (508) + + // viewport size for mouse click + SafeWriteBatch(WMAP_WIN_HEIGHT - (480 - 465), { // height/offset by Y (465 - 21 = 444 (443)) + 0x4C0154, 0x4C02BA, // wmWorldMap_ + 0x4C3A47, // wmInterfaceRefresh_ + }); + SafeWriteBatch(WMAP_WIN_WIDTH - (640 - 472), { // width/offset by X (472 - 22 = 450) + 0x4C0159, 0x4C02BF, // wmWorldMap_ + 0x4C3A3A, // wmInterfaceRefresh_ + 0x4C417C, 0x4C4184 // wmInterfaceDrawSubTileList_ + }); + SafeWriteBatch(WMAP_WIN_HEIGHT - (480 - 464), { // width/offset by X (464) + 0x4C3FED, // wmInterfaceDrawCircleOverlay_ + 0x4C4157, 0x4C415F, // wmInterfaceDrawSubTileList_ + }); + // right limit of the viewport (450) + SafeWriteBatch(WMAP_WIN_WIDTH - (640 - 450), wmViewportEndRight); // 890 - 190 = 700 + 22 = 722 + // bottom limit of the viewport (443) + SafeWriteBatch(WMAP_WIN_HEIGHT - (480 - 443), wmViewportEndBottom); // 720 - 37 = 683 + 21 = 704 + + // Night/Day frm (wmRefreshInterfaceDial_) + SafeWrite32(0x4C577F, WMAP_WIN_WIDTH - (640 - 532)); // X offset (532) + SafeWrite32(0x4C575D, (WMAP_WIN_WIDTH * 49) - ((640 * 49) - 31252)); // start offset in buffer (31252 / 640 = 49) + + // Date/Time frm + SafeWrite32(0x4C3EC7, WMAP_WIN_WIDTH - (640 - 487)); // start offset by X (487) + SafeWrite32(0x4C3ED1, WMAP_WIN_WIDTH - (640 - 630)); // end offset by X (630) + SafeWrite32(0x4C3D10, (WMAP_WIN_WIDTH * 13) - ((640 * 13) - 8167)); // 8167 start offset in buffer (12327) + SafeWrite32(0x4C3DC1, WMAP_WIN_WIDTH + (666 - 640)); // 666 + + // WMCARMVE/WMGLOBE/WMSCREEN frms (wmRefreshInterfaceOverlay_) + SafeWrite32(0x4C51D5, (WMAP_WIN_WIDTH * 577) - ((640 * 337) - 215554)); // start offset for image WMCARMVE + SafeWrite32(0x4C5184, (WMAP_WIN_WIDTH * 571) - ((640 * 331) - 211695)); // start offset for image WMGLOBE + SafeWrite32(0x4C51FD, (WMAP_WIN_WIDTH * 571) - ((640 * 331) - 211699)); // start offset for image WMSCREEN + // Car gas indicator + SafeWrite32(0x4C527E, (WMAP_WIN_WIDTH * 580) - ((640 * 340) - 217460)); // start offset in buffer (217460 / 640 = 340 + (720 - 480) = 580 Y-axes) + + // WMTABS.frm + SafeWriteBatch((WMAP_WIN_WIDTH * 136) - ((640 * 136) - 86901), { + 0x4C52C4, 0x4C55AA // wmRefreshTabs_ + }); + SafeWrite32(0x4C52FF, (WMAP_WIN_WIDTH * 139) - ((640 * 139) - 88850)); // wmRefreshTabs_ + SafeWriteBatch((WMAP_WIN_WIDTH * 27), { // start offset in buffer (17280) + 0x4C54DE, 0x4C5424 // wmRefreshTabs_ + }); + SafeWrite32(0x4C52E5, WMAP_WIN_HEIGHT - 480 + 178 - 19); // height (178) + + // Buttons of cities, now 15 + SafeWrite32(0x4C2BD9, WMAP_WIN_WIDTH - (640 - 508)); // offset of the buttons by X (508) + SafeWriteBatch(WMAP_TOWN_BUTTONS * 4, {0x4C2C01, 0x4C21B6, 0x4C2289}); // number of buttons (28 = 7 * 4) + int btn = (mapSlotsScrollLimit) ? WMAP_TOWN_BUTTONS : WMAP_TOWN_BUTTONS + 1; + SafeWrite32(0x4C21FD, 27 * btn); // scroll limit for buttons + // number of city labels (6) wmRefreshTabs_ + SafeWriteBatch(WMAP_TOWN_BUTTONS - 1, {0x4C54F6, 0x4C542A}); + SafeWrite8(0x4C555E, 0); + SafeWrite32(0x4C0348, 350 + WMAP_TOWN_BUTTONS); // buttons input code (wmWorldMap_) + + SafeWrite32(0x4C2BFB, (DWORD)&wmTownMapSubButtonIds[0]); // wmInterfaceInit_ + SafeWriteBatch((DWORD)&wmTownMapSubButtonIds[1], { + // 0x4C22DD, 0x4C230A, // wmInterfaceScrollTabsUpdate_ (never called) + 0x4C227B, // wmInterfaceScrollTabsStop_ + 0x4C21A8 // wmInterfaceScrollTabsStart_ + }); + // WMTBEDGE.frm (wmRefreshTabs_) + BlockCall(0x4C55BF); + + // Town map frm images (wmTownMapRefresh_) + SafeWrite32(0x4C4BE4, (WMAP_WIN_WIDTH * 21) + 22); // start offset for town map image (13462) + + dlogr(" Done", DL_INIT); +} + void Worldmap::init() { PathfinderFixInit(); StartingStatePatches(); @@ -660,6 +897,7 @@ void Worldmap::init() { WorldmapFpsPatch(); WorldMapFontPatch(); PipBoyAutomapsPatch(); + WorldmapViewportPatch(); // must be located after WorldMapSlots patch LoadGameHook::OnGameReset() += []() { SetCarInterfaceArt(433); // set index