#pragma once #include "90CN_companion_skill_data.h" namespace dnf90_companion_quickbar { enum class Purpose { Empty, Combat, PartyBuff, Heal, Passive, SupportAwakening, SelfBuff, DefensiveSupport, Unsupported, Summon, SelfHeal }; inline bool IsAssistMode(std::uint8_t behavior) { return behavior != 2; } inline bool SupportsQuickbarPolicy(std::uint8_t job) { return job < 16; } inline bool NativeActionRecent(std::uint32_t now, std::uint32_t started, std::uint32_t window) { return now >= started && now - started < window; } inline bool OrdinarySupportAllowed(std::uint8_t behavior, bool fighting, bool hurt) { return !hurt && (IsAssistMode(behavior) || !fighting); } inline Purpose Classify(std::uint8_t job, std::uint32_t id, std::uint8_t grow = 0) { if (!id) return Purpose::Empty; const auto* r = dnf90_companion_skill_data::Find(job,id); if (!r) return Purpose::Unsupported; // Low nibble is the profession branch; the high nibble is awakening. if (r->passiveGrow & (1u << (grow & 15))) return Purpose::Passive; return static_cast(r->purpose); } inline std::uint32_t CooldownMS(std::uint8_t job, std::uint32_t id) { const auto* r=dnf90_companion_skill_data::Find(job,id); return r ? r->cooldown : 0; } inline bool HasNativeBuffCheck(std::uint8_t job, std::uint32_t id) { const auto* r=dnf90_companion_skill_data::Find(job,id); return r && (r->flags & 2); } inline bool IsAreaCombat(std::uint8_t job, std::uint32_t id) { const auto* r=dnf90_companion_skill_data::Find(job,id); return r && r->purpose==1 && (r->flags & 4); } inline bool CombatPurposeAllowed(std::uint8_t behavior, Purpose purpose) { return !IsAssistMode(behavior) && purpose==Purpose::Combat; } // Accepted dispatch is not an effect receipt. Refresh is a conservative AI // policy. Switches and summons are issued once per native key program lifetime // so another press cannot cancel an existing state. inline bool SupportDispatchHandled(std::uint8_t job, std::uint32_t id, std::uint32_t now, std::uint32_t acceptedAt) { const auto* r=dnf90_companion_skill_data::Find(job,id); if (!r || !acceptedAt) return false; if (r->flags & 1) return true; return r->refresh && NativeActionRecent(now,acceptedAt,r->refresh); } inline bool EligibleSupportBuff(std::uint8_t job, std::uint32_t id, std::uint32_t learned, std::uint8_t grow=0) { const auto p=Classify(job,id,grow); return learned && (p==Purpose::PartyBuff || p==Purpose::SelfBuff || p==Purpose::Summon); } inline bool FirstSupportOption(std::uint8_t job, const std::uint32_t (&quick)[12], const std::uint32_t (&learned)[512], std::size_t slot, std::uint8_t grow) { const auto id=quick[slot]; const auto* r=dnf90_companion_skill_data::Find(job,id); for (std::size_t j=0; jgroup && prev && prev->group==r->group && EligibleSupportBuff(job,quick[j],learned[quick[j]],grow)) return false; } return true; } template int SelectSupportBuff(std::uint8_t job, const std::uint32_t (&quick)[12], const std::uint32_t (&learned)[512], Handled handled, Ready ready, std::uint8_t grow=0) { constexpr Purpose priorities[]={Purpose::PartyBuff,Purpose::SelfBuff,Purpose::Summon}; for (const auto priority:priorities) for (std::size_t i=0;i<12;++i) { const auto id=quick[i]; if (id>=512 || !EligibleSupportBuff(job,id,learned[id],grow) || Classify(job,id,grow)!=priority) continue; if (FirstSupportOption(job,quick,learned,i,grow) && !handled(id) && ready(i)) return static_cast(i); } return -1; } template int SelectSupportAwakening(std::uint8_t job, const std::uint32_t (&quick)[12], const std::uint32_t (&learned)[512], Ready ready, std::uint8_t grow=0) { for (std::size_t i=0;i<12;++i) { const auto id=quick[i]; if (id>=512 || !learned[id] || Classify(job,id,grow)!=Purpose::SupportAwakening) continue; if (FirstSupportOption(job,quick,learned,i,grow) && ready(i)) return static_cast(i); } return -1; } }