From 46a08e97e9f88db7c64da3610c3ecef85edccd8b Mon Sep 17 00:00:00 2001 From: Torhan Bartel Date: Sat, 17 Oct 2026 04:04:21 +0200 Subject: [PATCH] Teleportationstrank: yellow potions open the portal window - Every yellow potion (potion kind 5, all stages) is a Teleportationstrank: drinking it posts the teleporter's event to the busy dialog with the hero's known portals; the potion is used up only when the hero teleports from that window (closing it keeps the potion) - Names replaced in every game text (ReplaceInTexts), so item names and the bottom bar's quick-select tooltip read Teleportationstrank; GameText reads the game's own text - HeroIndex / HeroInventory moved to skillslots for shared use - docs/teleport-potion.md; walking animation after loading noted in todo Co-Authored-By: Claude Opus 5.5 (1M context) --- CMakeLists.txt | 1 + docs/teleport-potion.md | 31 ++++++ docs/todo.md | 7 ++ src/game/engine/items.h | 10 ++ src/game/ui/texts.cpp | 69 ++++++++++-- src/game/ui/texts.h | 8 ++ src/mods/itemstack.cpp | 8 +- src/mods/runetrade.cpp | 12 +-- src/mods/skillslots.cpp | 14 +++ src/mods/skillslots.h | 5 + src/mods/teleportpotion.cpp | 206 ++++++++++++++++++++++++++++++++++++ src/mods/teleportpotion.h | 12 +++ tools/names.txt | 7 ++ 13 files changed, 368 insertions(+), 22 deletions(-) create mode 100644 docs/teleport-potion.md create mode 100644 src/mods/teleportpotion.cpp create mode 100644 src/mods/teleportpotion.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c1c37b..ebfec3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,6 +62,7 @@ add_library(sacred_re SHARED src/mods/megamap.cpp src/mods/respec.cpp src/mods/runetrade.cpp + src/mods/teleportpotion.cpp src/mods/guards.cpp ) target_include_directories(sacred_re PRIVATE src) diff --git a/docs/teleport-potion.md b/docs/teleport-potion.md new file mode 100644 index 0000000..bf99f31 --- /dev/null +++ b/docs/teleport-potion.md @@ -0,0 +1,31 @@ +# Teleportationstrank + +Request (2026-10-17): the yellow potion becomes "Teleportationstrank"; +drinking it opens the window of known portals and the hero can teleport to +one of them. + +## Measured + +- The yellow potions come in stages (small / medium / large; seen types + 0x1434 and 0x141a), all potion kind 5 (logged in + cCreature::UseItem 0x54a8f0). +- Touching a teleporter logs `cInterpretSQW::Teleporter(hex=N)` (0x4ae330): + it sets bit N in the hero's known portals (hero +0x578) and posts a UI event + (constructor 0x553080) of type 0x39 to `DEFAULT_BUSYDLG` with +8 = 2, + +0xc = 5 (mode: portals; 6 for underworld portals above 0xd) and +0x10 = the + mask. cUI_BusyDlg::HandleEvent (0x720c10) calls SetMode (0x720180). +- Choosing a portal: the engine's portal case (0x619671, log `EE_USEPORTAL`) + teleports the hero with cCreature::Teleport (0x54d840), returning to 0x6197c8. + +## Mod (src/mods/teleportpotion.cpp) + +- Every item type of potion kind 5 counts. Their names (text 0x428700 of each + type) are replaced in every game text (`game::ReplaceInTexts`): the item + names and the bottom bar's quick-select tooltip ("Trank der Konzentration + [R]", its own text id). The shortest name is replaced, so stage words stay. +- cCreature::UseItem (hooked in itemstack.cpp) asks `teleportpotion::UseItem` + first: for the hero's Teleportationstrank it posts the teleporter's event + with the hero's mask instead of the drink. +- The potion is used up (flag, RemoveItem, DestroyObject as UseItem does; a + stack loses one) only when cCreature::Teleport is called from the portal + case while it is pending; closing the window keeps it. diff --git a/docs/todo.md b/docs/todo.md index fcda16e..50444f3 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -134,3 +134,10 @@ stack in one inventory cell, up to 20 per stack (a full stack starts a new one). Drag, sell and drop move the whole stack, as with any other item. The counts on the potion slots then add up the stacks. In progress, see docs/item-stacks.md (open: sale price per stack, splitting on the ground). + +## Walking animation after loading (2026-10-17) + +The hero sometimes plays a walking animation after a save game load while +standing still. Not reproduced yet: a load log (hero mode, action, animation +fields, orders) showed nothing unusual; right after a load the game itself +sends one walk order (0x4) while the hero is idle. Measure again when it shows. diff --git a/src/game/engine/items.h b/src/game/engine/items.h index c46f4bc..32717f8 100644 --- a/src/game/engine/items.h +++ b/src/game/engine/items.h @@ -14,6 +14,7 @@ constexpr uint32_t kObjectUsedUp = 0x8000000; // kObjectFlags: set by cPotion3D // The item type table at *0xaa9564 (ecx of ItemPotionKind). constexpr uint32_t kItemTypeTable = 0x00aa9564; +constexpr uint32_t kItemTypeCount = 0x7e60; // types 1..0x7e5f (bounds check in 0x428700) // ItemPotionKind (0x43f630): the potion kind (1..5, the bottom bar's potion // slots; 1 = healing) of item type `type`, 0 if it is no potion. @@ -46,6 +47,15 @@ inline bool ItemIsConsumable(uint32_t type) { return reinterpret_cast(0x0043f5f0)(type); } +// 0x428700 (thiscall on the type table, (type), ret 4): the text id of the +// item type's name (entry +0x20), which cItemBase::GetName (0x44a420) shows +// unless the object has its own (+0x3c). +inline uint32_t ItemNameText(uint32_t type) { + using Fn = uint32_t(__fastcall*)(void* table, void* edx, uint32_t type); + void* table = *reinterpret_cast(kItemTypeTable); + return table ? reinterpret_cast(0x00428700)(table, nullptr, type) : 0; +} + inline uint32_t ItemType(const void* item) { return *reinterpret_cast(static_cast(item) + kItemType); } diff --git a/src/game/ui/texts.cpp b/src/game/ui/texts.cpp index 6a8f25f..e1c5e4a 100644 --- a/src/game/ui/texts.cpp +++ b/src/game/ui/texts.cpp @@ -8,11 +8,17 @@ #include #include #include +#include #include #include "re/hook.h" namespace game { + +// GetText (0x672f70, ret 4): (id) -> string; ours first. +const void* __fastcall TextManager_GetText(void* self, void* edx, uint32_t id); +RE_HOOK(0x00672f70, TextManager_GetText); + namespace { // Our ids: above the game's (resource ids are small; bit 31 means "by index"). @@ -26,10 +32,13 @@ struct GameString { const wchar_t* capacity; }; -struct Capitalized { +// A game text as we show it: made from `source` (the game's) when that +// changes; `changed` false if ours is the same. +struct Shown { std::wstring source; std::wstring text; GameString string; + bool changed = false; }; std::mutex g_lock; @@ -41,8 +50,12 @@ std::unordered_set& CapitalizedIds() { static std::unordered_set ids; return ids; } +std::vector>& Replacements() { + static std::vector> replacements; + return replacements; +} // By id; nodes stay put, so the strings handed out stay valid. -std::unordered_map g_capitalized; +std::unordered_map g_shown; std::wstring Capitalize(std::wstring text) { bool wordStart = true; @@ -63,6 +76,21 @@ std::wstring Capitalize(std::wstring text) { return text; } +std::wstring Transform(uint32_t id, std::wstring text) { + for (const auto& [from, to] : Replacements()) { + for (size_t at = text.find(from); at != std::wstring::npos; at = text.find(from, at + to.size())) { + text.replace(at, from.size(), to); + } + } + return CapitalizedIds().count(id) ? Capitalize(std::move(text)) : text; +} + +const GameString* GameTextString(uint32_t id) { + using ManagerFn = void*(__cdecl*)(); + void* manager = reinterpret_cast(0x006728c0)(); + return manager ? static_cast(TextManager_GetText_original(manager, nullptr, id)) : nullptr; +} + } // namespace uint32_t RegisterText(const wchar_t* text) { @@ -75,11 +103,29 @@ uint32_t RegisterText(const wchar_t* text) { void CapitalizeText(uint32_t id) { std::lock_guard guard(g_lock); CapitalizedIds().insert(id); + g_shown.erase(id); +} + +void ReplaceInTexts(const std::wstring& from, const std::wstring& to) { + if (from.empty() || from == to) { + return; + } + std::lock_guard guard(g_lock); + for (const auto& replacement : Replacements()) { + if (replacement.first == from) { + return; + } + } + Replacements().emplace_back(from, to); + g_shown.clear(); +} + +std::wstring GameText(uint32_t id) { + const GameString* string = GameTextString(id); + return string && string->begin && string->end > string->begin ? std::wstring(string->begin, string->end) + : std::wstring(); } -// GetText (0x672f70, ret 4): (id) -> string; ours first. -const void* __fastcall TextManager_GetText(void* self, void* edx, uint32_t id); -RE_HOOK(0x00672f70, TextManager_GetText); const void* __fastcall TextManager_GetText(void* self, void* edx, uint32_t id) { if (id >= kFirstOwnText && id < 0x80000000) { std::lock_guard guard(g_lock); @@ -90,22 +136,23 @@ const void* __fastcall TextManager_GetText(void* self, void* edx, uint32_t id) { } const void* original = TextManager_GetText_original(self, edx, id); std::lock_guard guard(g_lock); - if (!original || !CapitalizedIds().count(id)) { + if (!original || (Replacements().empty() && !CapitalizedIds().count(id))) { return original; } const auto* string = static_cast(original); if (!string->begin || string->end <= string->begin) { return original; } - Capitalized& entry = g_capitalized[id]; - if (entry.source.compare(0, std::wstring::npos, string->begin, string->end - string->begin) != 0 || - entry.text.empty()) { + Shown& entry = g_shown[id]; + if (entry.text.empty() || + entry.source.compare(0, std::wstring::npos, string->begin, string->end - string->begin) != 0) { entry.source.assign(string->begin, string->end); - entry.text = Capitalize(entry.source); + entry.text = Transform(id, entry.source); + entry.changed = entry.text != entry.source; const wchar_t* text = entry.text.c_str(); entry.string = {text, text + entry.text.size(), text + entry.text.size()}; } - return &entry.string; + return entry.changed ? &entry.string : original; } } // namespace game diff --git a/src/game/ui/texts.h b/src/game/ui/texts.h index 70606ce..d0c5277 100644 --- a/src/game/ui/texts.h +++ b/src/game/ui/texts.h @@ -4,6 +4,7 @@ // tab headers), and changes to the game's own texts. #include +#include namespace game { @@ -15,4 +16,11 @@ uint32_t RegisterText(const wchar_t* text); // "Rucksack"). Call at startup. void CapitalizeText(uint32_t id); +// Shows `to` in place of `from` wherever a game text contains it (item names, +// tooltips, ...). +void ReplaceInTexts(const std::wstring& from, const std::wstring& to); + +// The game's text `id` without our changes (empty if there is none). +std::wstring GameText(uint32_t id); + } // namespace game diff --git a/src/mods/itemstack.cpp b/src/mods/itemstack.cpp index b6378cf..1c7b7dc 100644 --- a/src/mods/itemstack.cpp +++ b/src/mods/itemstack.cpp @@ -31,6 +31,7 @@ // (0x5dc3f0; cell rects from CellRect 0x5dd530). #include "mods/itemstack.h" +#include "mods/teleportpotion.h" #include @@ -308,11 +309,16 @@ uint32_t __fastcall ObjMgr_DestroyObject(void* self, void* edx, uint32_t id, uin return ObjMgr_DestroyObject_original(self, edx, id, a, b, c); } -// cCreature::UseItem (0x54a8f0, ret 4): a new drink starts. + +// cCreature::UseItem (0x54a8f0, ret 4): a new drink starts (a +// Teleportationstrank opens the portal window instead, teleportpotion.cpp). char __fastcall Creature_UseItem(void* self, void* edx, uint32_t id); RE_HOOK(0x0054a8f0, Creature_UseItem); char __fastcall Creature_UseItem(void* self, void* edx, uint32_t id) { g_drunk = 0; + if (mods::teleportpotion::UseItem(self, id)) { + return 0; + } return Creature_UseItem_original(self, edx, id); } diff --git a/src/mods/runetrade.cpp b/src/mods/runetrade.cpp index 26be10d..d45f36a 100644 --- a/src/mods/runetrade.cpp +++ b/src/mods/runetrade.cpp @@ -160,11 +160,8 @@ int RuneCount(uint8_t* window) { return count; } -int HeroIndex() { - void* hero = game::GetPlayerHero(); - const int index = hero ? game::CreatureField(hero, game::kCreatureId) : -1; - return index >= 0 && index < game::kChestInventory ? index : -1; -} +using skillslots::HeroIndex; +using skillslots::HeroInventory; // The hero's object type (hero +0x10), which doLottoSelect takes. uint32_t HeroType() { @@ -172,11 +169,6 @@ uint32_t HeroType() { return hero ? game::ItemType(hero) : 0; } -uint8_t* HeroInventory() { - const int hero = HeroIndex(); - return hero >= 0 ? skillslots::Inventory(static_cast(hero)) : nullptr; -} - // Free space for a 1 x 1 item in `inventory` (cInventoryEntry::FindFreeSpace 0x5dab60). bool FindFreeSpace(uint8_t* inventory, uint16_t* cell) { using FindFn = char(__fastcall*)(void*, void*, uint32_t, uint32_t, uint16_t*, uint32_t); diff --git a/src/mods/skillslots.cpp b/src/mods/skillslots.cpp index 2b11a63..4889f12 100644 --- a/src/mods/skillslots.cpp +++ b/src/mods/skillslots.cpp @@ -21,6 +21,9 @@ #include +#include "game/engine/items.h" +#include "game/engine/objects.h" +#include "game/engine/orders.h" #include "re/hook.h" namespace mods::skillslots { @@ -51,6 +54,17 @@ uint8_t* Inventory(unsigned index) { return base && index < kInventories ? base + index * kInventorySize : nullptr; } +int HeroIndex() { + void* hero = game::GetPlayerHero(); + const int index = hero ? game::CreatureField(hero, game::kCreatureId) : -1; + return index >= 0 && index < game::kChestInventory ? index : -1; +} + +uint8_t* HeroInventory() { + const int hero = HeroIndex(); + return hero >= 0 ? Inventory(static_cast(hero)) : nullptr; +} + uint8_t* ShadowFor(const void* inventory, bool create) { const int index = IndexOf(inventory); if (index < 0) { diff --git a/src/mods/skillslots.h b/src/mods/skillslots.h index 61bc1bc..b78d7ec 100644 --- a/src/mods/skillslots.h +++ b/src/mods/skillslots.h @@ -23,6 +23,11 @@ constexpr size_t kSelected = 0xd58; // u16 [0] weapon set, [1] art slot // Inventory `index` (0-31) of cInventoryManager's block, or null. uint8_t* Inventory(unsigned index); +// The local player hero's inventory index (its object id; 0..0x10), or -1; +// and its inventory, or null. +int HeroIndex(); +uint8_t* HeroInventory(); + // The shadow inventory holding slots 6-7 (at indices 0-1) of `inventory`, or // null if it has none yet and `create` is false. uint8_t* ShadowFor(const void* inventory, bool create); diff --git a/src/mods/teleportpotion.cpp b/src/mods/teleportpotion.cpp new file mode 100644 index 0000000..b06bb57 --- /dev/null +++ b/src/mods/teleportpotion.cpp @@ -0,0 +1,206 @@ +// Teleportationstrank (request of 2026-10-17): the yellow potion is renamed +// and, instead of its own effect, opens the window of known portals; the +// potion is used up when the hero teleports from it (closing the window +// keeps it). +// +// Measured (2026-10-17): the yellow potions (small / medium / large, e.g. +// types 0x141a, 0x1434) are potion kind 5 (the bottom bar's fifth slot, +// whose tooltip names the potion too). Touching a teleporter runs +// cInterpretSQW::Teleporter (0x4ae330): it adds the portal to the hero's known ones (bit mask at hero +0x578) and posts +// a UI event (type 0x39) to DEFAULT_BUSYDLG with +8 = 2, +0xc = 5 (portal +// mode; 6 for the underworld portals above 0xd) and +0x10 = the mask; +// cUI_BusyDlg::HandleEvent (0x720c10) then calls SetMode (0x720180, (mode, a, +// b), ret 0xc). Choosing a portal sends the engine an event whose case +// (0x619671, "EE_USEPORTAL") teleports the hero with cCreature::Teleport +// (0x54d840, (x, y, 0, 1), ret 0x10), returning to 0x6197c8. +// +// cCreature::UseItem (0x54a8f0) is hooked by mods/itemstack.cpp, which asks +// UseItem() here first. + +#include "mods/teleportpotion.h" + +#include + +#include +#include +#include +#include + +#include "game/engine/events.h" +#include "game/engine/items.h" +#include "game/engine/objects.h" +#include "game/engine/orders.h" +#include "game/ui/texts.h" +#include "mods/skillslots.h" +#include "re/hook.h" +#include "re/log.h" + +namespace mods::teleportpotion { +namespace { + +constexpr int kTeleportPotionKind = 5; // the yellow potions (game::ItemPotionKind) +constexpr const wchar_t* kName = L"Teleportationstrank"; + +bool IsTeleportPotion(uint32_t type) { + return game::ItemPotionKind(type) == kTeleportPotionKind; +} + +constexpr size_t kHeroKnownPortals = 0x578; // u32 bit mask, bit = portal index + +// The UI event cInterpretSQW::Teleporter posts (built with the UI event +// constructor 0x553080: (type, target name, sender name, a, b, c, d, e, f), +// thiscall, ret 0x24; its wide string at +0x68 is freed with 0x6e85d0). +constexpr uint32_t kUiEventBusyDialog = 0x39; +constexpr uintptr_t kBusyDialogName = 0x0094d700; // "DEFAULT_BUSYDLG" +constexpr uintptr_t kNoSender = 0x00aa24bc; // the game's empty name +constexpr uint32_t kBusyDialogSetMode = 2; +constexpr uint32_t kBusyModePortals = 5; +constexpr size_t kUiEventSize = 0x80; +constexpr size_t kUiEventString = 0x68; + +// cCreature::Teleport's return address in the engine's portal case. +constexpr uintptr_t kPortalTeleportReturn = 0x006197c8; + +constexpr size_t kControlFlags = 0x10; +constexpr uint32_t kControlVisible = 1; + +// The potion that opened the portal window, until it is used or the window +// closes; and the dialog, once it showed for it. +uint32_t g_pending = 0; +uint8_t* g_dialog = nullptr; +bool g_shown = false; + +void OpenPortalWindow(uint32_t knownPortals) { + alignas(4) uint8_t event[kUiEventSize] = {}; + using CtorFn = void*(__fastcall*)(void*, void*, uint32_t, const char*, const char*, uint32_t, uint32_t, uint32_t, + uint32_t, uint32_t, uint32_t); + reinterpret_cast(0x00553080)(event, nullptr, kUiEventBusyDialog, reinterpret_cast(kBusyDialogName), + reinterpret_cast(kNoSender), + kBusyDialogSetMode, kBusyModePortals, knownPortals, 0, 0, 0); + game::PostEvent(event); + using FreeFn = void(__fastcall*)(void*, void*); + reinterpret_cast(0x006e85d0)(event + kUiEventString, nullptr); +} + +// Uses up the potion as cCreature::UseItem does after a drink (flag, remove +// from the inventory, destroy); mods/itemstack.cpp takes one off a stack. +void UseUp(uint32_t id) { + auto* item = static_cast(game::GetObjectById(id)); + if (!item || !IsTeleportPotion(game::ItemType(item))) { + return; + } + *reinterpret_cast(item + game::kObjectFlags) |= game::kObjectUsedUp; + if (uint8_t* inventory = skillslots::HeroInventory()) { + using RemoveFn = int(__fastcall*)(void*, void*, uint32_t, uint32_t); + reinterpret_cast(0x005da5f0)(inventory, nullptr, id, 1); // cInventoryEntry::RemoveItem + } + game::DestroyObject(id); +} + +void OnTick(uint8_t*) { + if (!g_pending || !g_dialog) { + return; + } + const bool visible = (*reinterpret_cast(g_dialog + kControlFlags) & kControlVisible) != 0; + if (visible) { + g_shown = true; + } else if (g_shown) { + g_pending = 0; // closed without a teleport + } +} + +// The yellow potions' names become kName in every text (names, the bottom +// bar's tooltip): the shortest name, so stage words in front ("Großer ...") +// stay, and any name that does not contain it. +void OnGameStart() { + static bool renamed = false; + if (renamed) { + return; + } + std::vector names; + for (uint32_t type = 1; type < game::kItemTypeCount; ++type) { + if (IsTeleportPotion(type)) { + std::wstring name = game::GameText(game::ItemNameText(type)); + if (!name.empty() && std::find(names.begin(), names.end(), name) == names.end()) { + names.push_back(std::move(name)); + } + } + } + std::sort(names.begin(), names.end(), [](const auto& a, const auto& b) { return a.size() < b.size(); }); + std::vector replaced; + for (const std::wstring& name : names) { + const bool covered = std::any_of(replaced.begin(), replaced.end(), + [&](const std::wstring& r) { return name.find(r) != std::wstring::npos; }); + if (!covered) { + game::ReplaceInTexts(name, kName); + replaced.push_back(name); + } + } + renamed = !names.empty(); + re::Log("teleportpotion: %zu potion names, %zu replaced", names.size(), replaced.size()); +} + +void OnGameEnd() { + g_pending = 0; + g_dialog = nullptr; +} + +struct Registration { + Registration() { + game::OnGameStart(&OnGameStart); + game::OnGameEnd(&OnGameEnd); + game::OnEngineTick(&OnTick); + } +} g_registration; + +} // namespace + +bool UseItem(void* creature, uint32_t id) { + const void* item = id ? game::GetObjectById(id) : nullptr; + if (!item || !IsTeleportPotion(game::ItemType(item)) || creature != game::GetPlayerHero()) { + return false; + } + g_pending = id; + g_shown = false; + OpenPortalWindow( + *reinterpret_cast(static_cast(creature) + kHeroKnownPortals)); + re::Log("teleportpotion: portal window for potion %u", id); + return true; +} + +} // namespace mods::teleportpotion + +namespace game { + +// cUI_BusyDlg::SetMode (0x720180, (mode, a, b), ret 0xc): remembers the dialog +// when it shows the portals for a potion. +void __fastcall BusyDlg_SetMode(void* self, void* edx, uint32_t mode, uint32_t a, uint32_t b); +RE_HOOK(0x00720180, BusyDlg_SetMode); +void __fastcall BusyDlg_SetMode(void* self, void* edx, uint32_t mode, uint32_t a, uint32_t b) { + using namespace mods::teleportpotion; + if (g_pending) { + g_dialog = static_cast(self); + if (mode != kBusyModePortals) { + g_pending = 0; + } + } + BusyDlg_SetMode_original(self, edx, mode, a, b); +} + +// cCreature::Teleport (0x54d840, (x, y, 0, 1), ret 0x10): a teleport from the +// portal window uses up the potion that opened it. +uint32_t __fastcall Creature_Teleport(void* self, void* edx, uint32_t x, uint32_t y, uint32_t a, uint32_t b); +RE_HOOK(0x0054d840, Creature_Teleport); +uint32_t __fastcall Creature_Teleport(void* self, void* edx, uint32_t x, uint32_t y, uint32_t a, uint32_t b) { + using namespace mods::teleportpotion; + const uint32_t result = Creature_Teleport_original(self, edx, x, y, a, b); + if (g_pending && reinterpret_cast(_ReturnAddress()) == kPortalTeleportReturn) { + const uint32_t id = g_pending; + g_pending = 0; + UseUp(id); + re::Log("teleportpotion: potion %u used for a teleport", id); + } + return result; +} + +} // namespace game diff --git a/src/mods/teleportpotion.h b/src/mods/teleportpotion.h new file mode 100644 index 0000000..90ec17d --- /dev/null +++ b/src/mods/teleportpotion.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace mods::teleportpotion { + +// cCreature::UseItem (mods/itemstack.cpp) asks first: true if `id` is a +// Teleportationstrank used by the player hero; it then opened the portal +// window instead of the drink. +bool UseItem(void* creature, uint32_t id); + +} // namespace mods::teleportpotion diff --git a/tools/names.txt b/tools/names.txt index 041a087..47d2823 100644 --- a/tools/names.txt +++ b/tools/names.txt @@ -245,3 +245,10 @@ 0x004ec170 cCreature::CanCollectItem # (item object), ret 4; pickup filter, no distance check 0x00592ef0 WideString_Append # thiscall (first, last, 0), ret 0xc; {begin,end,cap} 0x00589270 cCreatureArts::ArtTooltip # (art, text*, flag), ret 0xc +0x004ae330 cInterpretSQW::Teleporter # script: adds the portal bit (hero +0x578), posts busy dialog mode 5/6 +0x00720180 cUI_BusyDlg::SetMode # (mode, a, b), ret 0xc; 5 = known portals +0x00720c10 cUI_BusyDlg::HandleEvent # UI event 0x39 to DEFAULT_BUSYDLG, +8 == 2: SetMode(+0xc) +0x0054d840 cCreature::Teleport # (x, y, 0, 1), ret 0x10 +0x00553080 cEvent_UI::cEvent_UI # (type, target, sender, a..f), ret 0x24 +0x00428700 cItemTypeTable::NameText # (type), ret 4: text id of the type's name (+0x20) +0x0054a8f0 cCreature::UseItem # (id), ret 4: event 0x10 to the item, removes it if used up