From 91e7f844cc1b4bd18a334d3edd5531aeaaecfbdc Mon Sep 17 00:00:00 2001 From: Torhan Bartel Date: Mon, 26 Oct 2026 22:21:50 +0100 Subject: [PATCH] =?UTF-8?q?M=C3=B6rsergranate:=20explosion=20radius=20in?= =?UTF-8?q?=20the=20tooltip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/dwarf-arts.md | 6 ++++ src/mods/dwarfarts.cpp | 77 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/docs/dwarf-arts.md b/docs/dwarf-arts.md index c9efccb..2e00766 100644 --- a/docs/dwarf-arts.md +++ b/docs/dwarf-arts.md @@ -70,3 +70,9 @@ Mod: the construct call notes grenade, owner, landing point and radius (`ImpactRadius`, shared with the hit check); after `0x62d530` a disc (alpha 0x40) with a 2 px rim (alpha 0xa0) is drawn there, without depth test, until the impact of that owner's grenade at that point gathers its targets. + +Tooltip (2026-10-26, "the skills tooltip does not show the current radius"): +a game::Art tooltip filter adds "Explosionsradius: N" after the last value +line of each level section (current level, and the next level's in the skill +book), in that line's colour, from the same `ImpactRadiusAt(level)` as the +hit check and the marker. diff --git a/src/mods/dwarfarts.cpp b/src/mods/dwarfarts.cpp index 9ae0f59..630731e 100644 --- a/src/mods/dwarfarts.cpp +++ b/src/mods/dwarfarts.cpp @@ -44,6 +44,7 @@ #include #include +#include "game/engine/art.h" #include "game/engine/arts.h" #include "game/engine/objects.h" #include "game/engine/orders.h" @@ -100,12 +101,79 @@ int32_t TypeSize(uint32_t type) { // The explosion radius of a grenade thrown by `owner` (world units; a // creature is hit when within it plus its own size). -int32_t ImpactRadius(void* owner) { - const unsigned level = owner ? game::ArtLevel(owner, game::kArtMoerser) : 0; +int32_t ImpactRadiusAt(unsigned level) { const int32_t size = TypeSize(kGrenadeImpactType); return static_cast(static_cast(size) * (1.0f + kRadiusGrowth * (level / kLevelsPerStep))); } +int32_t ImpactRadius(void* owner) { + return ImpactRadiusAt(owner ? game::ArtLevel(owner, game::kArtMoerser) : 0); +} + +// The tooltip (game::Art::Tooltip filter, as for Kettenblitz in +// mods/chainlightning.cpp): sections start with the level line ("Aktuelle +// Stufe:", in the skill book also "Nächste erlernte Stufe:") followed by +// "Name: value" lines; each section gets "Explosionsradius: N" after its last +// value, in that line's colour. +constexpr const wchar_t* kCurrentLevelLine = L"Aktuelle Stufe:"; +constexpr const wchar_t* kNextLevelLine = L"Nächste erlernte Stufe:"; +constexpr const wchar_t* kValueSeparator = L": "; +constexpr size_t kColourCodeLength = 10; // "\cAARRGGBB" + +std::wstring LeadingColourCodes(const std::wstring& line) { + size_t end = 0; + while (line.compare(end, 2, L"\\c") == 0 && end + kColourCodeLength <= line.size()) { + end += kColourCodeLength; + } + return line.substr(0, end); +} + +void RewriteTooltip(const game::Art& art, void* creature, std::wstring& text) { + std::vector lines; + for (size_t start = 0; start < text.size();) { + size_t end = text.find(L'\n', start); + end = end == std::wstring::npos ? text.size() : end + 1; + lines.push_back(text.substr(start, end - start)); + start = end; + } + const unsigned current = game::ArtLevel(creature, art.Id()); + std::vector out; + unsigned level = current; + bool inSection = false; + size_t lastValue = SIZE_MAX; // index in `out` + const auto closeSection = [&] { + if (!inSection || lastValue == SIZE_MAX) { + return; + } + std::wstring& after = out[lastValue]; + const bool last = after.empty() || after.back() != L'\n'; + if (last) { + after += L'\n'; + } + out.insert(out.begin() + static_cast(lastValue) + 1, + LeadingColourCodes(after) + L"Explosionsradius: " + std::to_wstring(ImpactRadiusAt(level)) + + (last ? L"" : L"\n")); + }; + for (const std::wstring& line : lines) { + const bool currentHeader = line.find(kCurrentLevelLine) != std::wstring::npos; + const bool nextHeader = line.find(kNextLevelLine) != std::wstring::npos; + if (currentHeader || nextHeader) { + closeSection(); + inSection = true; + level = nextHeader ? current + 1 : current; + lastValue = SIZE_MAX; + } else if (inSection && line.find(kValueSeparator) != std::wstring::npos) { + lastValue = out.size(); + } + out.push_back(line); + } + closeSection(); + text.clear(); + for (const std::wstring& line : out) { + text += line; + } +} + // 0x5ab400 calls 0x5ff820 (thiscall (out, position, type, only, radius), // ret 0x14) with the effect object in ebx. __declspec(naked) void GatherTargetsThunk() { @@ -362,7 +430,10 @@ void Startup() { RE_STARTUP(DwarfArtsStartup, Startup); struct Registration { - Registration() { game::OnCreatureOrder(&OnOrder); } + Registration() { + game::OnCreatureOrder(&OnOrder); + game::AddTooltipFilter(game::kArtMoerser, &RewriteTooltip); + } } g_registration; } // namespace