Megamap: the zoom button zooms on the hero and follows him

The zoom button never set the zoom point, so it zoomed on the last
magnifier point (or 0,0). While the button's zoom is on, the point is put on
the hero's map pixel every engine tick; the held mouse button still zooms on
the mouse. game::UiWindow / UiWindowShown shared from windows.h.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
khallaandClaude Opus 5.5 committed 2026-10-25 11:54:59 +01:00
1 parent 1d16fc65c4
commit c7d54a6cfd
6 files changed
+98 -20

No files matched your search

+4 -12
View File
@@ -261,7 +261,6 @@ void OnTick(uint8_t* engine) {
// letter switch (upper-case character - 'C': index bytes at 0x75868c, targets
// at 0x758660) sends M to index 10, "no hotkey", and the closing branch
// compares with -1 instead of 'M' (cmp eax, 4Dh at 0x757f86).
constexpr int kMegamapSlot = 0x25;
constexpr int kMapBlockingSlots[] = {0x2b, 0x2c, 0x2f};
constexpr uint32_t kMapBlockedFlag = 0x017e82c4;
constexpr uint32_t kMapDisabledFlag = 0x017e8264;
@@ -270,32 +269,25 @@ constexpr uint32_t kUiLetterIndices = 0x0075868c;
constexpr uint8_t kUiLetterMap = 5;
constexpr uint8_t kUiNoHotkey = 10;
constexpr uint32_t kUiMapCloseCompare = 0x00757f86;
constexpr size_t kControlFlagsOffset = 0x10; // bit 0: visible
bool WindowShown(const uint32_t* ui, int slot) {
const auto* window = reinterpret_cast<const uint8_t*>(static_cast<uintptr_t>(ui[slot]));
return window && (*reinterpret_cast<const uint32_t*>(window + kControlFlagsOffset) & 1) != 0;
}
bool Flag(uint32_t address) {
return *reinterpret_cast<const uint8_t*>(static_cast<uintptr_t>(address)) != 0;
}
void ToggleMap() {
uint32_t* ui = game::UiManager();
if (!ui || !ui[kMegamapSlot]) {
if (!ui || !ui[game::kUiSlotMegamap]) {
return;
}
if (WindowShown(ui, kMegamapSlot)) {
if (game::UiWindowShown(ui, game::kUiSlotMegamap)) {
game::SendWindowCommand(ui, "UI_WND_MEGAMAP", game::WindowCommand::Hide);
return;
}
for (const int slot : kMapBlockingSlots) {
if (WindowShown(ui, slot)) {
if (game::UiWindowShown(ui, slot)) {
return;
}
}
if (!Flag(kMapBlockedFlag) && !Flag(kMapDisabledFlag) && !WindowShown(ui, kMapDisabledSlot)) {
if (!Flag(kMapBlockedFlag) && !Flag(kMapDisabledFlag) && !game::UiWindowShown(ui, kMapDisabledSlot)) {
game::SendWindowCommand(ui, "UI_WND_MEGAMAP", game::WindowCommand::Show);
}
}
+63
View File
@@ -9,6 +9,11 @@
// to the right screen edge and its top and bottom tiles stretch across, and
// the zoom buttons at the bottom stay centred. The window is drawn and clicked
// unshifted (mods/hud.cpp, Anchor::Screen). See docs/hud.md.
//
// Zoom (request of 2026-10-25: the zoomed view did not start centred on the
// hero): held mouse button zooms on the mouse (the game's magnifier); the zoom
// button now zooms on the hero and follows him (the game kept the last
// magnifier point, or 0,0).
#include <algorithm>
#include <cstdint>
@@ -16,6 +21,10 @@
#include <unordered_map>
#include "game/addresses.h"
#include "game/engine/events.h"
#include "game/engine/objects.h"
#include "game/engine/orders.h"
#include "game/ui/windows.h"
#include "mods/hud.h"
#include "mods/megamap.h"
#include "mods/widescreen.h"
@@ -71,6 +80,60 @@ constexpr uint32_t kBandRightOuter[] = {0x006ca774, 0x006ca808};
constexpr uint32_t kBandRightInner[] = {0x006ca7bc, 0x006ca84c};
constexpr int kBandWidth = 70;
// The zoom (cUI_Megamap's render 0x6c9ce0, 0x6ca442..0x6ca5cf): flags +0xc0154
// bit 0x100 set by the zoom button (0x6c7fac); +0xc06bc set while the mouse
// button is held on the map (0x6c8274). The zoom point +0xc0438 / +0xc043c
// (screen) follows the mouse only while held; the view zooms on point + scroll
// (+0xc016c / +0xc0170), the map pixel.
constexpr size_t kMapFlags = 0xc0154;
constexpr uint32_t kZoomedByButton = 0x100;
constexpr size_t kZoomHeld = 0xc06bc;
constexpr size_t kZoomPointX = 0xc0438;
constexpr size_t kZoomPointY = 0xc043c;
constexpr size_t kScrollX = 0xc016c;
constexpr size_t kScrollY = 0xc0170;
template <typename T>
T& Field(uint8_t* p, size_t offset) {
return *reinterpret_cast<T*>(p + offset);
}
// cUI_Megamap 0x6cb130 (thiscall (out x, y, 0, world x, y, level), ret 8): the
// map pixel of a world position, as opening the map centres on the hero
// (0x6c7bad..).
struct Vec3 {
float x, y, z;
};
Vec3 MapPixel(uint8_t* megamap, const Vec3& world) {
Vec3 pixel = {};
using Fn = void(__fastcall*)(uint8_t*, void*, Vec3*, const Vec3*);
reinterpret_cast<Fn>(0x006cb130)(megamap, nullptr, &pixel, &world);
return pixel;
}
// Every tick while the zoom button's zoom is on: the zoom point on the hero.
void ZoomOnHero(uint8_t*) {
uint8_t* megamap = game::UiWindow(game::UiManager(), game::kUiSlotMegamap);
if (!game::UiWindowShown(game::UiManager(), game::kUiSlotMegamap) ||
!(Field<uint32_t>(megamap, kMapFlags) & kZoomedByButton) || Field<uint8_t>(megamap, kZoomHeld)) {
return;
}
auto* hero = static_cast<uint8_t*>(game::GetPlayerHero());
if (!hero) {
return;
}
const Vec3 world = {static_cast<float>(game::CreatureField(hero, game::kCreatureX)),
static_cast<float>(game::CreatureField(hero, game::kCreatureY)),
static_cast<float>(hero[game::kObjectLevel])};
const Vec3 pixel = MapPixel(megamap, world);
Field<int32_t>(megamap, kZoomPointX) = static_cast<int32_t>(pixel.x) - Field<int32_t>(megamap, kScrollX);
Field<int32_t>(megamap, kZoomPointY) = static_cast<int32_t>(pixel.y) - Field<int32_t>(megamap, kScrollY);
}
struct Registration {
Registration() { game::OnEngineTick(&ZoomOnHero); }
} g_registration;
float g_halfWidth = 512.0f;
float g_zoomCentre = 512.0f;
float g_zoomMarkerX = 768.0f;