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

+103
View File
@@ -0,0 +1,103 @@
// Arts regenerate on their own: casting an art no longer puts the other arts
// of its group into regeneration.
//
// After an art is used, the cast code computes its regeneration
// (cCreatureArts::RegenFor 0x582710, (art) -> amount) and passes the amount
// to cCreatureArts::AddGroupRegen (0x562330, (amount, group, filter)), which
// adds it to the remaining time of every cooldown entry of that group (1 or
// 2), capped at each entry's total. For player heroes the amount now goes to
// the cast art's entry only. AddGroupRegen calls that do not directly follow
// a RegenFor (other callers, negative amounts, the combo branch) are left to
// the game. See docs/art-regen.md.
#include <cstdint>
#include "game/engine/arts.h"
#include "game/engine/orders.h"
#include "re/hook.h"
namespace mods::artregen {
namespace {
// The art whose regeneration RegenFor computed last (the cast code calls
// AddGroupRegen right after it, on the same thread).
struct Pending {
void* arts = nullptr;
uint16_t art = 0;
};
thread_local Pending g_pending;
uint8_t* Owner(uint8_t* arts) {
return *reinterpret_cast<uint8_t**>(arts + game::kArtsCreature);
}
bool IsPlayerHero(const uint8_t* creature) {
return game::IsPlayerHeroId(*reinterpret_cast<const uint32_t*>(creature + game::kCreatureId));
}
} // namespace
void OnRegenFor(void* arts, uint32_t art) {
g_pending = {arts, static_cast<uint16_t>(art)};
}
// AddGroupRegen for the cast art only; false to let the game handle the call.
bool AddToCastArt(void* self, float amount, uint32_t group, uint32_t filter) {
const Pending pending = g_pending;
g_pending = {};
auto* arts = static_cast<uint8_t*>(self);
// Only the game's normal branch (no combo running, not the combo group),
// without a filter.
uint8_t* creature = Owner(arts);
if (pending.arts != self || pending.art == 0 || amount < 0.0f || group == game::kRegenGroupCombo ||
filter != 0 || !creature || creature[game::kCreatureComboStep] != 0 || arts[game::kArtsComboRunning] != 0 ||
!IsPlayerHero(creature)) {
return false;
}
uint8_t* entry = game::FindCooldownEntry(arts, pending.art);
if (!entry) {
return true; // nothing to regenerate; the other arts stay untouched
}
// As the game: the flags shorten one group's regeneration to 80 %.
const bool magic = game::InMagicRegenGroup(*reinterpret_cast<uint32_t*>(entry + game::kCooldownGroup),
*reinterpret_cast<uint16_t*>(entry + game::kCooldownArt));
const uint32_t flags = *reinterpret_cast<uint32_t*>(arts + game::kArtsFlags);
if ((magic && (flags & game::kArtsFlagFasterMagicRegen)) ||
(!magic && (flags & game::kArtsFlagFasterCombatRegen))) {
amount *= 0.8f;
}
float& remaining = game::Float(entry, game::kCooldownRemaining);
remaining += amount;
const float total = game::Float(entry, game::kCooldownTotal);
if (remaining > total) {
remaining = total;
}
if (remaining < 0.0f) {
remaining = 0.0f;
}
return true;
}
} // namespace mods::artregen
namespace game {
// cCreatureArts::RegenFor (0x582710, ret 4): the regeneration of art `art`.
float __fastcall Arts_RegenFor(void* self, void* edx, uint32_t art);
RE_HOOK(0x00582710, Arts_RegenFor);
float __fastcall Arts_RegenFor(void* self, void* edx, uint32_t art) {
mods::artregen::OnRegenFor(self, art & 0xffff);
return Arts_RegenFor_original(self, edx, art);
}
// cCreatureArts::AddGroupRegen (0x562330, ret 0xc).
uint32_t __fastcall Arts_AddGroupRegen(void* self, void* edx, float amount, uint32_t group, uint32_t filter);
RE_HOOK(0x00562330, Arts_AddGroupRegen);
uint32_t __fastcall Arts_AddGroupRegen(void* self, void* edx, float amount, uint32_t group, uint32_t filter) {
if (self && mods::artregen::AddToCastArt(self, amount, group, filter)) {
return 0;
}
return Arts_AddGroupRegen_original(self, edx, amount, group, filter);
}
} // namespace game