Skills, auto-attack, aura and chain lightning rework, Esc, cache fix

- Skill bar: no art selection; slot 0 is the right-click art, keys 0-6 cast
  slots directly; cooldown labels in exact seconds; slot 0 set apart.
- Arts regenerate on their own (casting no longer refills the group).
- Auto-attack after targeting or casting at a hostile creature, at the speed
  of a held button.
- Auras can be recast while active (renew, no second visual).
- Blicke wie Klingen hits every enemy in range, physical, with life leech.
- Blitzschlag steers the game's own chain: 3 + level/5 hits, 1 + level/10
  chains, re-hits a lone enemy.
- Weapon sets: click selects, drag only from the selected set; the leftmost
  set is clickable (signed hit tests).
- Esc closes open windows before opening the game menu.
- HUD: standalone buttons and texts are centred.
- Record cache sized for widened views (town lag while walking).
- Shared game code: orders, fx events, text drawing, cooldowns, effects.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
khallaandClaude Opus 5.5 committed 2026-10-16 00:11:38 +02:00
1 parent f721d2a486
commit 1fd89524d3
41 files changed
+2347 -214

No files matched your search

+14 -34
View File
@@ -12,62 +12,42 @@
#include <cstdint>
#include <cstring>
#include "game/engine/arts.h"
#include "game/engine/events.h"
#include "game/engine/fx.h"
#include "game/engine/objects.h"
#include "game/engine/orders.h"
#include "re/log.h"
namespace mods::aura_restore {
namespace {
constexpr uint32_t kAuraEffectType = 842; // cObjectFX: aura following its owner
constexpr uint16_t kAuraArts[] = {0x3f7, 0x3f8}; // Heldenmut, Klingenblick
constexpr uint32_t kPlayerIds = 0x11; // object ids below this are player heroes
constexpr uint16_t kAuraArts[] = {game::kArtHeldenmut, game::kArtKlingenblick};
constexpr int kTicksAfterLoad = 5; // wait for the load to finish (0.5 s)
// Active effects of a creature: vector of 0x20-byte entries at
// (creature + 0x3a8) + 0x11a / + 0x11e (cCreatureArts, AddEffect 0x582c10).
constexpr size_t kCreatureArts = 0x3a8;
constexpr size_t kEffectsBegin = 0x11a;
constexpr size_t kEffectsEnd = 0x11e;
constexpr size_t kEffectSize = 0x20;
constexpr size_t kEffectKind = 0x0;
constexpr size_t kEffectRemaining = 0x18; // float, seconds
constexpr size_t kEffectArt = 0x1c; // u16
constexpr uint32_t kKindCooldown = 0x22; // the art's cooldown (0x539ce0), not the buff
// cEventFX (0x7c bytes, built by 0x41e0a0).
constexpr uint32_t kEventFXVtable = 0x0088e9b4;
constexpr uint32_t kFxAttach = 0x42;
using namespace game; // effect layout (arts.h)
int g_ticksUntilRestore = -1;
// cObjectManager::CreateAt (0x5fbaa0): object `type` at `position`; its id.
uint32_t CreateAt(uint32_t type, const void* position) {
using Fn = uint32_t(__fastcall*)(void* manager, void* edx, uint32_t, const void*, uint32_t, uint32_t,
uint32_t);
void* manager = *reinterpret_cast<void**>(0x00ad3bc0);
return manager ? reinterpret_cast<Fn>(0x005fbaa0)(manager, nullptr, type, position, 0, 0, 0) : 0;
}
void AttachAura(uint8_t* hero, uint16_t art, float remaining) {
const uint32_t heroId = *reinterpret_cast<uint32_t*>(hero + 0xc);
const uint32_t id = CreateAt(kAuraEffectType, hero + 0x18); // the hero's position
const uint32_t heroId = *reinterpret_cast<uint32_t*>(hero + kCreatureId);
const uint32_t id = CreateObjectAt(kAuraEffectType, hero + 0x18); // the hero's position
auto* effect = static_cast<uint8_t*>(game::GetObjectById(id));
if (!effect) {
return;
}
uint32_t event[0x1f] = {};
event[0] = kEventFXVtable;
event[1] = kFxAttach;
uint32_t event[kFxEventDwords] = {};
event[0] = kFxEventVtable;
event[1] = kFxEventAttachAura;
event[2] = heroId;
std::memcpy(&event[11], &remaining, 4);
event[15] = art;
using ReceiveFn = int(__fastcall*)(void*, void*, void*);
reinterpret_cast<ReceiveFn>((*reinterpret_cast<void***>(effect))[6])(effect, nullptr, event);
SendEvent(effect, event);
re::Log("aura: restored art %x on hero %u (%.1f s left)", art, heroId, remaining);
}
// Longest remaining time of the buffs (any kind but the cooldown) of `art`.
// Longest remaining time of the buffs (any kind but the lock) of `art`.
float RemainingBuff(uint8_t* hero, uint16_t art) {
uint8_t* arts = hero + kCreatureArts;
auto* entry = *reinterpret_cast<uint8_t**>(arts + kEffectsBegin);
@@ -76,7 +56,7 @@ float RemainingBuff(uint8_t* hero, uint16_t art) {
for (; entry && entry < end; entry += kEffectSize) {
float remaining;
std::memcpy(&remaining, entry + kEffectRemaining, 4);
if (*reinterpret_cast<uint32_t*>(entry + kEffectKind) != kKindCooldown &&
if (*reinterpret_cast<uint32_t*>(entry + kEffectKind) != kEffectKindArtLock &&
*reinterpret_cast<uint16_t*>(entry + kEffectArt) == art && remaining > longest) {
longest = remaining;
}
@@ -85,7 +65,7 @@ float RemainingBuff(uint8_t* hero, uint16_t art) {
}
void RestoreAuras() {
for (uint32_t id = 1; id < kPlayerIds; ++id) {
for (uint32_t id = 1; id < kPlayerHeroIdEnd; ++id) {
auto* hero = static_cast<uint8_t*>(game::GetCreature(id));
if (!hero) {
continue;