/* EQ2Emulator: Everquest II Server Emulator Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net) This file is part of EQ2Emulator. EQ2Emulator is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. EQ2Emulator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with EQ2Emulator. If not, see . */ #pragma once #include #include #include #include #include #include #include #include #include #include #include class LuaSpell; // ----------------------------- Core bitflags -------------------------------- template class BitFlags { public: static constexpr std::size_t kWordBits = 64; static constexpr std::size_t kWords = (NBits + kWordBits - 1) / kWordBits; constexpr BitFlags() noexcept : words_{} {} // Single-bit ops inline void set(std::size_t bit) noexcept { words_[word_index(bit)] |= word_mask(bit); } inline void reset(std::size_t bit) noexcept { words_[word_index(bit)] &= ~word_mask(bit); } inline void toggle(std::size_t bit) noexcept { words_[word_index(bit)] ^= word_mask(bit); } inline bool test(std::size_t bit) const noexcept { return (words_[word_index(bit)] & word_mask(bit)) != 0ULL; } // Bulk ops inline void clear() noexcept { for (std::size_t i = 0; i < kWords; ++i) words_[i] = 0ULL; } inline bool any() const noexcept { for (std::size_t i = 0; i < kWords; ++i) if (words_[i]) return true; return false; } inline bool none() const noexcept { return !any(); } inline void import_u32(std::uint32_t legacy) noexcept { if (!legacy) return; const std::size_t lim = (NBits < 32 ? NBits : 32); for (std::size_t i = 0; i < lim; ++i) if ((legacy >> i) & 1u) set(i); } inline std::uint32_t export_u32() const noexcept { std::uint32_t out = 0; const std::size_t lim = (NBits < 32 ? NBits : 32); for (std::size_t i = 0; i < lim; ++i) if (test(i)) out |= (1u << i); return out; } // Import a 64-bit legacy mask (low 64 bits only) inline void import_u64(std::uint64_t legacy) noexcept { if (!legacy) return; const std::size_t lim = (NBits < 64 ? NBits : 64); for (std::size_t i = 0; i < lim; ++i) if ((legacy >> i) & 1ULL) set(i); } // Export low 64 bits into a uint64_t inline std::uint64_t export_u64() const noexcept { std::uint64_t out = 0; const std::size_t lim = (NBits < 64 ? NBits : 64); for (std::size_t i = 0; i < lim; ++i) if (test(i)) out |= (1ULL << i); return out; } // Set by legacy power-of-two value (find index of the single set bit) inline void set_legacy_flag_value(std::uint64_t pow2) noexcept { if (!pow2) return; #if defined(__GNUC__) || defined(__clang__) std::size_t bit = static_cast(__builtin_ctzll(pow2)); #else std::size_t bit = 0; while (bit < 64 && ((pow2 >> bit) & 1ULL) == 0ULL) ++bit; #endif if (bit < NBits) set(bit); } // Direct word access (used by TS wrapper) static constexpr std::size_t words_count() noexcept { return kWords; } inline std::uint64_t& word_ref(std::size_t i) noexcept { return words_[i]; } inline const std::uint64_t& word_ref(std::size_t i) const noexcept { return words_[i]; } private: static constexpr std::size_t word_index(std::size_t bit) noexcept { return bit / kWordBits; } static constexpr std::uint64_t word_mask (std::size_t bit) noexcept { return 1ULL << (bit % kWordBits); } std::array words_; }; // --------------------------- Thread-safe wrapper ---------------------------- template class TSBitFlags { public: using Snapshot = BitFlags; // Single-bit ops inline void set(std::size_t bit) { std::unique_lock lk(m_); bits_.set(bit); } inline void reset(std::size_t bit) { std::unique_lock lk(m_); bits_.reset(bit); } inline void toggle(std::size_t bit) { std::unique_lock lk(m_); bits_.toggle(bit); } inline bool test(std::size_t bit) const { std::shared_lock lk(m_); return bits_.test(bit); } // Convenience inline void add(std::size_t bit) { set(bit); } inline void remove(std::size_t bit) { reset(bit); } inline bool has(std::size_t bit) const { return test(bit); } // Batch ops inline void set_many(std::initializer_list bits) { std::unique_lock lk(m_); for (auto b : bits) bits_.set(b); } inline void reset_many(std::initializer_list bits) { std::unique_lock lk(m_); for (auto b : bits) bits_.reset(b); } // Clear / any / none inline void clear() { std::unique_lock lk(m_); bits_.clear(); } inline bool any() const { std::shared_lock lk(m_); return bits_.any(); } inline bool none() const { std::shared_lock lk(m_); return bits_.none(); } // Legacy helpers inline void import_u32(std::uint32_t legacy) { std::unique_lock lk(m_); bits_.import_u32(legacy); } inline std::uint32_t export_u32() const { std::shared_lock lk(m_); return bits_.export_u32(); } inline void import_u64(std::uint64_t legacy) { std::unique_lock lk(m_); bits_.import_u64(legacy); } inline std::uint64_t export_u64() const { std::shared_lock lk(m_); return bits_.export_u64(); } inline void set_legacy_flag_value(std::uint64_t v){ std::unique_lock lk(m_); bits_.set_legacy_flag_value(v); } // Snapshots (copy out/in without exposing lock) inline Snapshot snapshot() const { std::shared_lock lk(m_); return bits_; } inline void assign_from(const Snapshot& snap) { std::unique_lock lk(m_); for (std::size_t i = 0; i < Snapshot::words_count(); ++i) bits_.word_ref(i) = snap.word_ref(i); } private: mutable std::shared_mutex m_; Snapshot bits_{}; }; // ------------------------- Project-specific typedefs ------------------------ inline constexpr std::size_t kEffectBits = 64; // limited due to DB restrictions at this time using EffectFlags = TSBitFlags; enum : std::size_t { EFFECT_IDX_STUN = 0, EFFECT_IDX_ROOT = 1, EFFECT_IDX_MEZ = 2, EFFECT_IDX_STIFLE = 3, EFFECT_IDX_DAZE = 4, EFFECT_IDX_FEAR = 5, EFFECT_IDX_SPELLBONUS = 6, EFFECT_IDX_SKILLBONUS = 7, EFFECT_IDX_STEALTH = 8, EFFECT_IDX_INVIS = 9, EFFECT_IDX_SNARE = 10, EFFECT_IDX_WATERWALK = 11, EFFECT_IDX_WATERJUMP = 12, EFFECT_IDX_FLIGHT = 13, EFFECT_IDX_GLIDE = 14, EFFECT_IDX_AOE_IMMUNE = 15, EFFECT_IDX_STUN_IMMUNE = 16, EFFECT_IDX_MEZ_IMMUNE = 17, EFFECT_IDX_DAZE_IMMUNE = 18, EFFECT_IDX_ROOT_IMMUNE = 19, EFFECT_IDX_STIFLE_IMMUNE = 20, EFFECT_IDX_FEAR_IMMUNE = 21, EFFECT_IDX_SAFEFALL = 22, EFFECT_IDX_ILLUSION = 23, }; class EffectRegistryPerSpell { public: using Code = uint8_t; // your int8 codes using Value = std::variant; explicit EffectRegistryPerSpell(Code max_code_inclusive) : max_code_(max_code_inclusive), slots_(static_cast(max_code_inclusive) + 1) {} // --- Membership (writers) ------------------------------------------------- // Add a spell to a type. Returns true if inserted (false if already present). bool Add(Code code, LuaSpell* spell) { if (!in_range(code) || !spell) return false; auto& s = slots_[code]; std::unique_lock lk(s.mtx); return s.members.emplace(spell, Entry{spell, std::monostate{}}).second; } // Add or update (set/replace the per-spell value atomically). // Returns true if inserted new, false if updated existing. template bool AddOrUpdate(Code code, LuaSpell* spell, T v) { if (!in_range(code) || !spell) return false; auto& s = slots_[code]; std::unique_lock lk(s.mtx); auto [it, inserted] = s.members.emplace(spell, Entry{spell, std::monostate{}}); it->second.val = to_variant(std::move(v)); return inserted; } // Remove a spell from a type. Returns true if erased. bool Remove(Code code, LuaSpell* spell) { if (!in_range(code) || !spell) return false; auto& s = slots_[code]; std::unique_lock lk(s.mtx); return s.members.erase(spell) > 0; } // Clear all spells at a type void Clear(Code code) { if (!in_range(code)) return; auto& s = slots_[code]; std::unique_lock lk(s.mtx); s.members.clear(); } // --- Queries (readers) ---------------------------------------------------- // Is there any spell registered at this type? bool Has(Code code) const { if (!in_range(code)) return false; auto& s = slots_[code]; std::shared_lock lk(s.mtx); return !s.members.empty(); } // Is this specific spell present under this type? bool Contains(Code code, const LuaSpell* spell) const { if (!in_range(code) || !spell) return false; auto& s = slots_[code]; std::shared_lock lk(s.mtx); return s.members.find(const_cast(spell)) != s.members.end(); } // How many spells are registered at this type? size_t Count(Code code) const { if (!in_range(code)) return 0; auto& s = slots_[code]; std::shared_lock lk(s.mtx); return s.members.size(); } // Snapshot list of spells under a type (copy out while holding a shared lock). std::vector Snapshot(Code code) const { std::vector out; if (!in_range(code)) return out; auto& s = slots_[code]; std::shared_lock lk(s.mtx); out.reserve(s.members.size()); for (auto& kv : s.members) out.push_back(kv.first); return out; } // Snapshot of (spell, value) pairs under a type. struct SpellWithValue { LuaSpell* spell{}; Value value{}; }; std::vector SnapshotWithValues(Code code) const { std::vector out; if (!in_range(code)) return out; auto& s = slots_[code]; std::shared_lock lk(s.mtx); out.reserve(s.members.size()); for (auto& kv : s.members) out.push_back({kv.second.ptr, kv.second.val}); return out; } // --- Per-spell value API -------------------------------------------------- // Set/replace the value for a specific spell under a type. template bool SetValue(Code code, LuaSpell* spell, T v) { if (!in_range(code) || !spell) return false; auto& s = slots_[code]; std::unique_lock lk(s.mtx); auto it = s.members.find(spell); if (it == s.members.end()) return false; it->second.val = to_variant(std::move(v)); return true; } // Clear value for a specific spell under a type (keeps membership). bool ClearValue(Code code, LuaSpell* spell) { if (!in_range(code) || !spell) return false; auto& s = slots_[code]; std::unique_lock lk(s.mtx); auto it = s.members.find(spell); if (it == s.members.end()) return false; it->second.val = std::monostate{}; return true; } // Read value as requested type. nullopt if not set or wrong type. template std::optional GetValue(Code code, const LuaSpell* spell) const { if (!in_range(code) || !spell) return std::nullopt; auto& s = slots_[code]; std::shared_lock lk(s.mtx); auto it = s.members.find(const_cast(spell)); if (it == s.members.end()) return std::nullopt; if (auto p = std::get_if(&it->second.val)) return *p; return std::nullopt; } // Convenience typed getters std::optional GetInt (Code code, const LuaSpell* s) const { return GetValue(code, s); } std::optional GetFloat (Code code, const LuaSpell* s) const { return GetValue (code, s); } std::optional GetString(Code code, const LuaSpell* s) const { return GetValue(code, s); } Code MaxCode() const { return max_code_; } private: struct Entry { LuaSpell* ptr{}; Value val{}; }; struct Slot { mutable std::shared_mutex mtx; std::unordered_map members; // key = spell*, value = (spell*, variant) }; bool in_range(Code code) const { return code <= max_code_; } // normalize to variant static Value to_variant(int64_t v) { return Value{v}; } static Value to_variant(int v) { return Value{static_cast(v)}; } static Value to_variant(uint32_t v) { return Value{static_cast(v)}; } static Value to_variant(float v) { return Value{static_cast(v)}; } static Value to_variant(double v) { return Value{v}; } static Value to_variant(const char* s) { return Value{std::string(s ? s : "")}; } static Value to_variant(std::string s) { return Value{std::move(s)}; } Code max_code_; std::vector slots_; }; #define CONTROL_EFFECT_TYPE_MEZ 1 #define CONTROL_EFFECT_TYPE_STIFLE 2 #define CONTROL_EFFECT_TYPE_DAZE 3 #define CONTROL_EFFECT_TYPE_STUN 4 #define CONTROL_EFFECT_TYPE_ROOT 5 #define CONTROL_EFFECT_TYPE_FEAR 6 #define CONTROL_EFFECT_TYPE_WALKUNDERWATER 7 #define CONTROL_EFFECT_TYPE_JUMPUNDERWATER 8 #define CONTROL_EFFECT_TYPE_INVIS 9 #define CONTROL_EFFECT_TYPE_STEALTH 10 #define CONTROL_EFFECT_TYPE_SNARE 11 #define CONTROL_EFFECT_TYPE_FLIGHT 12 #define CONTROL_EFFECT_TYPE_GLIDE 13 #define CONTROL_EFFECT_TYPE_SAFEFALL 14 #define CONTROL_EFFECT_TYPE_ILLUSION 15 #define CONTROL_MAX_EFFECTS 16 #define IMMUNITY_TYPE_MEZ 1 #define IMMUNITY_TYPE_STIFLE 2 #define IMMUNITY_TYPE_DAZE 3 #define IMMUNITY_TYPE_STUN 4 #define IMMUNITY_TYPE_ROOT 5 #define IMMUNITY_TYPE_FEAR 6 #define IMMUNITY_TYPE_AOE 7 #define IMMUNITY_TYPE_TAUNT 8 #define IMMUNITY_TYPE_RIPOSTE 9 #define IMMUNITY_TYPE_STRIKETHROUGH 10 #define IMMUNITY_MAX_TYPES 11 class ControlEffects { public: ControlEffects() : reg_(CONTROL_MAX_EFFECTS - 1) {} // max code = 14 bool Add(uint8_t type, LuaSpell* s) { return reg_.Add(type, s); } template bool AddOrUpdate(uint8_t t, LuaSpell* s, T v){ return reg_.AddOrUpdate(t, s, std::move(v)); } bool Remove(uint8_t type, LuaSpell* s) { return reg_.Remove(type, s); } void Clear(uint8_t type) { reg_.Clear(type); } bool Has(uint8_t type) const { return reg_.Has(type); } bool Contains(uint8_t type, const LuaSpell* s) const { return reg_.Contains(type, s); } size_t Count(uint8_t type) const { return reg_.Count(type); } std::vector Snapshot(uint8_t type) const { return reg_.Snapshot(type); } auto SnapshotWithValues(uint8_t type) const { return reg_.SnapshotWithValues(type); } template bool SetValue(uint8_t t, LuaSpell* s, T v) { return reg_.SetValue(t, s, std::move(v)); } bool ClearValue(uint8_t t, LuaSpell* s) { return reg_.ClearValue(t, s); } template std::optional GetValue(uint8_t t, const LuaSpell* s) const { return reg_.GetValue(t, s); } private: EffectRegistryPerSpell reg_; }; class Immunities { public: Immunities() : reg_(IMMUNITY_MAX_TYPES) {} // max code = 10 bool Add(uint8_t type, LuaSpell* s) { return reg_.Add(type, s); } template bool AddOrUpdate(uint8_t t, LuaSpell* s, T v){ return reg_.AddOrUpdate(t, s, std::move(v)); } bool Remove(uint8_t type, LuaSpell* s) { return reg_.Remove(type, s); } void Clear(uint8_t type) { reg_.Clear(type); } bool Has(uint8_t type) const { return reg_.Has(type); } bool Contains(uint8_t type, const LuaSpell* s) const { return reg_.Contains(type, s); } size_t Count(uint8_t type) const { return reg_.Count(type); } std::vector Snapshot(uint8_t type) const { return reg_.Snapshot(type); } auto SnapshotWithValues(uint8_t type) const { return reg_.SnapshotWithValues(type); } template bool SetValue(uint8_t t, LuaSpell* s, T v) { return reg_.SetValue(t, s, std::move(v)); } bool ClearValue(uint8_t t, LuaSpell* s) { return reg_.ClearValue(t, s); } template std::optional GetValue(uint8_t t, const LuaSpell* s) const { return reg_.GetValue(t, s); } private: EffectRegistryPerSpell reg_; };