Files
sacred_k/src/mods/aura_restore.cpp
T
khallaandClaude Opus 5.5 1fd89524d3 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>
2026-10-16 00:11:38 +02:00

101 lines
3.4 KiB
C++

// Aura visuals after loading a save.
//
// Casting a lasting self aura (Heldenmut 0x3f7, Klingenblick 0x3f8) adds a
// buff to the hero's active effects and, separately, creates an aura effect
// object (cObjectFX type 842) that follows the hero: the art's cast effect
// (0x4feef0) creates it with cObjectManager::CreateAt at the hero and sends it
// a cEventFX 0x42 (owner, duration, art). Saving marks effect objects as dead;
// loading restores the buff but drops the effect, so the aura works but is
// invisible. A few ticks after a save game has loaded, the effect is created
// again for every such buff of a player hero, with the buff's remaining time.
#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[] = {game::kArtHeldenmut, game::kArtKlingenblick};
constexpr int kTicksAfterLoad = 5; // wait for the load to finish (0.5 s)
using namespace game; // effect layout (arts.h)
int g_ticksUntilRestore = -1;
void AttachAura(uint8_t* hero, uint16_t art, float remaining) {
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[kFxEventDwords] = {};
event[0] = kFxEventVtable;
event[1] = kFxEventAttachAura;
event[2] = heroId;
std::memcpy(&event[11], &remaining, 4);
event[15] = art;
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 lock) of `art`.
float RemainingBuff(uint8_t* hero, uint16_t art) {
uint8_t* arts = hero + kCreatureArts;
auto* entry = *reinterpret_cast<uint8_t**>(arts + kEffectsBegin);
auto* end = *reinterpret_cast<uint8_t**>(arts + kEffectsEnd);
float longest = 0.0f;
for (; entry && entry < end; entry += kEffectSize) {
float remaining;
std::memcpy(&remaining, entry + kEffectRemaining, 4);
if (*reinterpret_cast<uint32_t*>(entry + kEffectKind) != kEffectKindArtLock &&
*reinterpret_cast<uint16_t*>(entry + kEffectArt) == art && remaining > longest) {
longest = remaining;
}
}
return longest;
}
void RestoreAuras() {
for (uint32_t id = 1; id < kPlayerHeroIdEnd; ++id) {
auto* hero = static_cast<uint8_t*>(game::GetCreature(id));
if (!hero) {
continue;
}
for (uint16_t art : kAuraArts) {
const float remaining = RemainingBuff(hero, art);
if (remaining > 0.0f) {
AttachAura(hero, art, remaining);
}
}
}
}
void OnLoaded() {
g_ticksUntilRestore = kTicksAfterLoad;
}
void OnTick(uint8_t*) {
if (g_ticksUntilRestore >= 0 && g_ticksUntilRestore-- == 0) {
RestoreAuras();
}
}
struct Registration {
Registration() {
game::OnSaveGameLoaded(&OnLoaded);
game::OnEngineTick(&OnTick);
}
} g_registration;
} // namespace
} // namespace mods::aura_restore