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) <noreply@anthropic.com>
This commit is contained in:
khallaandClaude Opus 5.5 committed 2026-10-17 04:04:21 +02:00
1 parent 6ca96d54b4
commit 46a08e97e9
13 files changed
+368 -22

No files matched your search

+1
View File
@@ -62,6 +62,7 @@ add_library(sacred_re SHARED
src/mods/megamap.cpp src/mods/megamap.cpp
src/mods/respec.cpp src/mods/respec.cpp
src/mods/runetrade.cpp src/mods/runetrade.cpp
src/mods/teleportpotion.cpp
src/mods/guards.cpp src/mods/guards.cpp
) )
target_include_directories(sacred_re PRIVATE src) target_include_directories(sacred_re PRIVATE src)
+31
View File
@@ -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.
+7
View File
@@ -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 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 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). 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.
+10
View File
@@ -14,6 +14,7 @@ constexpr uint32_t kObjectUsedUp = 0x8000000; // kObjectFlags: set by cPotion3D
// The item type table at *0xaa9564 (ecx of ItemPotionKind). // The item type table at *0xaa9564 (ecx of ItemPotionKind).
constexpr uint32_t kItemTypeTable = 0x00aa9564; 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 // 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. // 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<bool(__stdcall*)(uint32_t)>(0x0043f5f0)(type); return reinterpret_cast<bool(__stdcall*)(uint32_t)>(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<void**>(kItemTypeTable);
return table ? reinterpret_cast<Fn>(0x00428700)(table, nullptr, type) : 0;
}
inline uint32_t ItemType(const void* item) { inline uint32_t ItemType(const void* item) {
return *reinterpret_cast<const uint32_t*>(static_cast<const uint8_t*>(item) + kItemType); return *reinterpret_cast<const uint32_t*>(static_cast<const uint8_t*>(item) + kItemType);
} }
+58 -11
View File
@@ -8,11 +8,17 @@
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <utility>
#include <vector> #include <vector>
#include "re/hook.h" #include "re/hook.h"
namespace game { 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 { namespace {
// Our ids: above the game's (resource ids are small; bit 31 means "by index"). // 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; 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 source;
std::wstring text; std::wstring text;
GameString string; GameString string;
bool changed = false;
}; };
std::mutex g_lock; std::mutex g_lock;
@@ -41,8 +50,12 @@ std::unordered_set<uint32_t>& CapitalizedIds() {
static std::unordered_set<uint32_t> ids; static std::unordered_set<uint32_t> ids;
return ids; return ids;
} }
std::vector<std::pair<std::wstring, std::wstring>>& Replacements() {
static std::vector<std::pair<std::wstring, std::wstring>> replacements;
return replacements;
}
// By id; nodes stay put, so the strings handed out stay valid. // By id; nodes stay put, so the strings handed out stay valid.
std::unordered_map<uint32_t, Capitalized> g_capitalized; std::unordered_map<uint32_t, Shown> g_shown;
std::wstring Capitalize(std::wstring text) { std::wstring Capitalize(std::wstring text) {
bool wordStart = true; bool wordStart = true;
@@ -63,6 +76,21 @@ std::wstring Capitalize(std::wstring text) {
return 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<ManagerFn>(0x006728c0)();
return manager ? static_cast<const GameString*>(TextManager_GetText_original(manager, nullptr, id)) : nullptr;
}
} // namespace } // namespace
uint32_t RegisterText(const wchar_t* text) { uint32_t RegisterText(const wchar_t* text) {
@@ -75,11 +103,29 @@ uint32_t RegisterText(const wchar_t* text) {
void CapitalizeText(uint32_t id) { void CapitalizeText(uint32_t id) {
std::lock_guard guard(g_lock); std::lock_guard guard(g_lock);
CapitalizedIds().insert(id); 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) { const void* __fastcall TextManager_GetText(void* self, void* edx, uint32_t id) {
if (id >= kFirstOwnText && id < 0x80000000) { if (id >= kFirstOwnText && id < 0x80000000) {
std::lock_guard guard(g_lock); 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); const void* original = TextManager_GetText_original(self, edx, id);
std::lock_guard guard(g_lock); std::lock_guard guard(g_lock);
if (!original || !CapitalizedIds().count(id)) { if (!original || (Replacements().empty() && !CapitalizedIds().count(id))) {
return original; return original;
} }
const auto* string = static_cast<const GameString*>(original); const auto* string = static_cast<const GameString*>(original);
if (!string->begin || string->end <= string->begin) { if (!string->begin || string->end <= string->begin) {
return original; return original;
} }
Capitalized& entry = g_capitalized[id]; Shown& entry = g_shown[id];
if (entry.source.compare(0, std::wstring::npos, string->begin, string->end - string->begin) != 0 || if (entry.text.empty() ||
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.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(); const wchar_t* text = entry.text.c_str();
entry.string = {text, text + entry.text.size(), text + entry.text.size()}; entry.string = {text, text + entry.text.size(), text + entry.text.size()};
} }
return &entry.string; return entry.changed ? &entry.string : original;
} }
} // namespace game } // namespace game
+8
View File
@@ -4,6 +4,7 @@
// tab headers), and changes to the game's own texts. // tab headers), and changes to the game's own texts.
#include <cstdint> #include <cstdint>
#include <string>
namespace game { namespace game {
@@ -15,4 +16,11 @@ uint32_t RegisterText(const wchar_t* text);
// "Rucksack"). Call at startup. // "Rucksack"). Call at startup.
void CapitalizeText(uint32_t id); 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 } // namespace game
+7 -1
View File
@@ -31,6 +31,7 @@
// (0x5dc3f0; cell rects from CellRect 0x5dd530). // (0x5dc3f0; cell rects from CellRect 0x5dd530).
#include "mods/itemstack.h" #include "mods/itemstack.h"
#include "mods/teleportpotion.h"
#include <windows.h> #include <windows.h>
@@ -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); 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); char __fastcall Creature_UseItem(void* self, void* edx, uint32_t id);
RE_HOOK(0x0054a8f0, Creature_UseItem); RE_HOOK(0x0054a8f0, Creature_UseItem);
char __fastcall Creature_UseItem(void* self, void* edx, uint32_t id) { char __fastcall Creature_UseItem(void* self, void* edx, uint32_t id) {
g_drunk = 0; g_drunk = 0;
if (mods::teleportpotion::UseItem(self, id)) {
return 0;
}
return Creature_UseItem_original(self, edx, id); return Creature_UseItem_original(self, edx, id);
} }
+2 -10
View File
@@ -160,11 +160,8 @@ int RuneCount(uint8_t* window) {
return count; return count;
} }
int HeroIndex() { using skillslots::HeroIndex;
void* hero = game::GetPlayerHero(); using skillslots::HeroInventory;
const int index = hero ? game::CreatureField(hero, game::kCreatureId) : -1;
return index >= 0 && index < game::kChestInventory ? index : -1;
}
// The hero's object type (hero +0x10), which doLottoSelect takes. // The hero's object type (hero +0x10), which doLottoSelect takes.
uint32_t HeroType() { uint32_t HeroType() {
@@ -172,11 +169,6 @@ uint32_t HeroType() {
return hero ? game::ItemType(hero) : 0; return hero ? game::ItemType(hero) : 0;
} }
uint8_t* HeroInventory() {
const int hero = HeroIndex();
return hero >= 0 ? skillslots::Inventory(static_cast<unsigned>(hero)) : nullptr;
}
// Free space for a 1 x 1 item in `inventory` (cInventoryEntry::FindFreeSpace 0x5dab60). // Free space for a 1 x 1 item in `inventory` (cInventoryEntry::FindFreeSpace 0x5dab60).
bool FindFreeSpace(uint8_t* inventory, uint16_t* cell) { bool FindFreeSpace(uint8_t* inventory, uint16_t* cell) {
using FindFn = char(__fastcall*)(void*, void*, uint32_t, uint32_t, uint16_t*, uint32_t); using FindFn = char(__fastcall*)(void*, void*, uint32_t, uint32_t, uint16_t*, uint32_t);
+14
View File
@@ -21,6 +21,9 @@
#include <windows.h> #include <windows.h>
#include "game/engine/items.h"
#include "game/engine/objects.h"
#include "game/engine/orders.h"
#include "re/hook.h" #include "re/hook.h"
namespace mods::skillslots { namespace mods::skillslots {
@@ -51,6 +54,17 @@ uint8_t* Inventory(unsigned index) {
return base && index < kInventories ? base + index * kInventorySize : nullptr; 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<unsigned>(hero)) : nullptr;
}
uint8_t* ShadowFor(const void* inventory, bool create) { uint8_t* ShadowFor(const void* inventory, bool create) {
const int index = IndexOf(inventory); const int index = IndexOf(inventory);
if (index < 0) { if (index < 0) {
+5
View File
@@ -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. // Inventory `index` (0-31) of cInventoryManager's block, or null.
uint8_t* Inventory(unsigned index); 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 // The shadow inventory holding slots 6-7 (at indices 0-1) of `inventory`, or
// null if it has none yet and `create` is false. // null if it has none yet and `create` is false.
uint8_t* ShadowFor(const void* inventory, bool create); uint8_t* ShadowFor(const void* inventory, bool create);
+206
View File
@@ -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 <intrin.h>
#include <algorithm>
#include <cstdint>
#include <string>
#include <vector>
#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<CtorFn>(0x00553080)(event, nullptr, kUiEventBusyDialog, reinterpret_cast<const char*>(kBusyDialogName),
reinterpret_cast<const char*>(kNoSender),
kBusyDialogSetMode, kBusyModePortals, knownPortals, 0, 0, 0);
game::PostEvent(event);
using FreeFn = void(__fastcall*)(void*, void*);
reinterpret_cast<FreeFn>(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<uint8_t*>(game::GetObjectById(id));
if (!item || !IsTeleportPotion(game::ItemType(item))) {
return;
}
*reinterpret_cast<uint32_t*>(item + game::kObjectFlags) |= game::kObjectUsedUp;
if (uint8_t* inventory = skillslots::HeroInventory()) {
using RemoveFn = int(__fastcall*)(void*, void*, uint32_t, uint32_t);
reinterpret_cast<RemoveFn>(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<uint32_t*>(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<std::wstring> 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<std::wstring> 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<const uint32_t*>(static_cast<const uint8_t*>(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<uint8_t*>(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<uintptr_t>(_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
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include <cstdint>
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
+7
View File
@@ -245,3 +245,10 @@
0x004ec170 cCreature::CanCollectItem # (item object), ret 4; pickup filter, no distance check 0x004ec170 cCreature::CanCollectItem # (item object), ret 4; pickup filter, no distance check
0x00592ef0 WideString_Append # thiscall (first, last, 0), ret 0xc; {begin,end,cap} 0x00592ef0 WideString_Append # thiscall (first, last, 0), ret 0xc; {begin,end,cap}
0x00589270 cCreatureArts::ArtTooltip # (art, text*, flag), ret 0xc 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