// 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 #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(arts + game::kArtsCreature); } bool IsPlayerHero(const uint8_t* creature) { return game::IsPlayerHeroId(*reinterpret_cast(creature + game::kCreatureId)); } } // namespace void OnRegenFor(void* arts, uint32_t art) { g_pending = {arts, static_cast(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(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(entry + game::kCooldownGroup), *reinterpret_cast(entry + game::kCooldownArt)); const uint32_t flags = *reinterpret_cast(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