- 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>
104 lines
3.9 KiB
C++
104 lines
3.9 KiB
C++
// 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
|