Emu/source/WorldServer/EffectFlags.h
Emagi 1bc87d6617 LUA RemoveSpell(Spawn, spell_id, ..) added, Illusion is a control effect, Redesign of control effects, immunities, effect flags away from mutex lists
RemoveSpell(Spawn, SpellID, MaintainedSpell, ReasonString) - maintainedspell is a boolean, default false it will remove an effect (per target) otherwise setting to true will remove the entire spell, maintained, if that is the caster.  Reason String is "canceled" by default unless overwritten

Illusions made to a control effect so we can enforce an existing illusion
2025-08-25 09:18:34 -04:00

442 lines
17 KiB
C++

/*
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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <array>
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <shared_mutex>
#include <cstdint>
#include <optional>
#include <string>
#include <unordered_map>
#include <variant>
#include <vector>
class LuaSpell;
// ----------------------------- Core bitflags --------------------------------
template <std::size_t NBits>
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<std::size_t>(__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<std::uint64_t, kWords> words_;
};
// --------------------------- Thread-safe wrapper ----------------------------
template <std::size_t NBits>
class TSBitFlags {
public:
using Snapshot = BitFlags<NBits>;
// 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<std::size_t> bits) {
std::unique_lock lk(m_); for (auto b : bits) bits_.set(b);
}
inline void reset_many(std::initializer_list<std::size_t> 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<kEffectBits>;
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<std::monostate, int64_t, double, std::string>;
explicit EffectRegistryPerSpell(Code max_code_inclusive)
: max_code_(max_code_inclusive),
slots_(static_cast<size_t>(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 <typename T>
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<LuaSpell*>(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<LuaSpell*> Snapshot(Code code) const {
std::vector<LuaSpell*> 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<SpellWithValue> SnapshotWithValues(Code code) const {
std::vector<SpellWithValue> 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 <typename T>
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 <typename T>
std::optional<T> 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<LuaSpell*>(spell));
if (it == s.members.end()) return std::nullopt;
if (auto p = std::get_if<T>(&it->second.val)) return *p;
return std::nullopt;
}
// Convenience typed getters
std::optional<int64_t> GetInt (Code code, const LuaSpell* s) const { return GetValue<int64_t>(code, s); }
std::optional<double> GetFloat (Code code, const LuaSpell* s) const { return GetValue<double> (code, s); }
std::optional<std::string> GetString(Code code, const LuaSpell* s) const { return GetValue<std::string>(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<LuaSpell*, Entry> 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<int64_t>(v)}; }
static Value to_variant(uint32_t v) { return Value{static_cast<int64_t>(v)}; }
static Value to_variant(float v) { return Value{static_cast<double>(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<Slot> 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 <typename T> 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<LuaSpell*> Snapshot(uint8_t type) const { return reg_.Snapshot(type); }
auto SnapshotWithValues(uint8_t type) const { return reg_.SnapshotWithValues(type); }
template <typename T> 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 <typename T> std::optional<T> GetValue(uint8_t t, const LuaSpell* s) const { return reg_.GetValue<T>(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 <typename T> 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<LuaSpell*> Snapshot(uint8_t type) const { return reg_.Snapshot(type); }
auto SnapshotWithValues(uint8_t type) const { return reg_.SnapshotWithValues(type); }
template <typename T> 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 <typename T> std::optional<T> GetValue(uint8_t t, const LuaSpell* s) const { return reg_.GetValue<T>(t, s); }
private:
EffectRegistryPerSpell reg_;
};