using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using WingsEmu.Core.Extensions; using WingsEmu.Core.Generics; using WingsEmu.DTOs.BCards; using WingsEmu.Packets.Enums; namespace WingsEmu.Game.Buffs; public class BCardComponent : IBCardComponent { private readonly List _allBCards = new(); private readonly ConcurrentDictionary<(BCardType, byte), ThreadSafeHashSet> _bCards = new(); private readonly ConcurrentDictionary> _bCardsByEquipmentType = new(); private readonly ConcurrentDictionary> _buffBCards = new(); private readonly List<(int casterLevel, BCardDTO bCard)> _buffBCardsCasterLevel = new(); private readonly ConcurrentDictionary<(BCardType, byte), Dictionary> _buffInformation = new(); private readonly ThreadSafeHashSet _chargeBCards = new(); private readonly ConcurrentDictionary<(BCardType, byte), byte> _hasBCardCache = new(); private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.SupportsRecursion); private readonly List _onAttack = new(); private readonly List _onDefense = new(); private readonly IRandomGenerator _randomGenerator; private readonly ConcurrentDictionary> _shellTriggerOnAttack = new(); public BCardComponent(IRandomGenerator randomGenerator) => _randomGenerator = randomGenerator; public void AddEquipmentBCards(EquipmentType equipmentType, IEnumerable bCards) { _lock.EnterWriteLock(); try { List existingBCards = _bCardsByEquipmentType.GetOrDefault(equipmentType); if (existingBCards == null) { existingBCards = new List(); _bCardsByEquipmentType[equipmentType] = existingBCards; } foreach (BCardDTO bCard in bCards) { existingBCards.Add(bCard); AddBCard(bCard); } } finally { _lock.ExitWriteLock(); } } public void ClearEquipmentBCards(EquipmentType equipmentType) { _lock.EnterWriteLock(); try { _bCardsByEquipmentType.TryRemove(equipmentType, out List bCards); if (bCards == null) { return; } foreach (BCardDTO bCard in bCards) { RemoveBCard(bCard); } } finally { _lock.ExitWriteLock(); } } public IReadOnlyList GetEquipmentBCards(EquipmentType equipmentType) { List eqBCards = _bCardsByEquipmentType.GetOrDefault(equipmentType); if (eqBCards == null) { return Array.Empty(); } return eqBCards; } public IReadOnlyDictionary> GetEquipmentBCards() => _bCardsByEquipmentType; public (int firstData, int secondData) GetAllBCardsInformation(BCardType type, byte subType, int level) { int firstData = 0; int secondData = 0; double firstDataMultiply = 1; double secondDataMultiply = 1; if (_bCards.TryGetValue((type, subType), out ThreadSafeHashSet list)) { foreach (BCardDTO bCard in list) { int firstDataValue = bCard.FirstDataValue(level); int secondDataValue = bCard.SecondDataValue(level); if (type == BCardType.Morale) { switch (subType) { case (byte)AdditionalTypes.Morale.SkillCooldownIncreased: firstDataMultiply *= 1 + 0.01 * firstDataValue; secondDataMultiply *= 1 + 0.01 * secondDataValue; break; case (byte)AdditionalTypes.Morale.SkillCooldownDecreased: firstDataMultiply *= 0.01 * (100 - firstDataValue); secondDataMultiply *= 0.01 * (100 - secondDataValue); break; } continue; } firstData += firstDataValue; secondData += secondDataValue; } } if (_buffInformation.TryGetValue((type, subType), out Dictionary buff)) { foreach ((int casterLevel, BCardDTO bCard) in buff.Values) { int firstDataValue = bCard.FirstDataValue(casterLevel); int secondDataValue = bCard.SecondDataValue(casterLevel); if (type == BCardType.Morale) { switch (subType) { case (byte)AdditionalTypes.Morale.SkillCooldownIncreased: firstDataMultiply *= 1 + 0.01 * firstDataValue; secondDataMultiply *= 1 + 0.01 * secondDataValue; break; case (byte)AdditionalTypes.Morale.SkillCooldownDecreased: firstDataMultiply *= 0.01 * (100 - firstDataValue); secondDataMultiply *= 0.01 * (100 - secondDataValue); break; } continue; } firstData += firstDataValue; secondData += secondDataValue; } } if (type == BCardType.Morale && (subType == (byte)AdditionalTypes.Morale.SkillCooldownIncreased || subType == (byte)AdditionalTypes.Morale.SkillCooldownDecreased)) { return ((int)(firstDataMultiply * 100), (int)(secondDataMultiply * 100)); } return (firstData, secondData); } public IReadOnlyList GetAllBCards() { _lock.EnterReadLock(); try { return _allBCards.ToArray(); } finally { _lock.ExitReadLock(); } } public IReadOnlyList<(int casterLevel, BCardDTO bCard)> GetBuffBCards(Func<(int, BCardDTO), bool> predicate = null) { _lock.EnterReadLock(); try { return _buffBCardsCasterLevel.FindAll(x => predicate == null || predicate(x)); } finally { _lock.ExitReadLock(); } } public bool HasBCard(BCardType bCardType, byte subType) => _hasBCardCache.TryGetValue((bCardType, subType), out byte amount) && amount > 0; public bool HasEquipmentsBCard(BCardType bCardType, byte subType) { return _bCardsByEquipmentType.Values.Any(x => x.Any(s => s.Type == (short)bCardType && s.SubType == subType)); } public void AddBCard(BCardDTO bCard) { (BCardType, byte) key = ((BCardType)bCard.Type, bCard.SubType); ThreadSafeHashSet existingBCards = _bCards.GetOrAdd(key, new ThreadSafeHashSet()); existingBCards.Add(bCard); if (!_hasBCardCache.TryGetValue(key, out _)) { _hasBCardCache.TryAdd(key, 1); } else { _hasBCardCache[key]++; } _allBCards.Add(bCard); } public void RemoveBCard(BCardDTO bCard) { (BCardType, byte) key = ((BCardType)bCard.Type, bCard.SubType); ThreadSafeHashSet existingBCards = _bCards.GetOrDefault(key); existingBCards?.Remove(bCard); if (_hasBCardCache.TryGetValue(key, out byte amount)) { amount -= 1; if (amount > 0) { _hasBCardCache[key] = amount; } else { _hasBCardCache.TryRemove(key, out _); } } _allBCards.Remove(bCard); } public void AddBuffBCards(Buff buff, IEnumerable bCards = null) { _lock.EnterWriteLock(); try { if (_buffBCards.ContainsKey(buff.BuffId) && bCards == null) { return; } if (bCards != null) // Second BCards execution { foreach (BCardDTO bCard in bCards) { (BCardType, byte SubType) key = ((BCardType)bCard.Type, bCard.SubType); if (bCard.ProcChance != 0) { int randomNumber = _randomGenerator.RandomNumber(); if (randomNumber > bCard.ProcChance) { continue; } } if (!_buffBCards.TryGetValue(buff.BuffId, out HashSet hashSet)) { hashSet = new HashSet(); _buffBCards[buff.BuffId] = hashSet; } hashSet.Add(bCard); _buffBCardsCasterLevel.Add((buff.CasterLevel, bCard)); if (!_hasBCardCache.TryGetValue(key, out _)) { _hasBCardCache.TryAdd(key, 1); } else { _hasBCardCache[key]++; } if (!_buffInformation.TryGetValue(key, out Dictionary newDictionary)) { newDictionary = new Dictionary(); _buffInformation[((BCardType)bCard.Type, bCard.SubType)] = newDictionary; } newDictionary[bCard.Id] = (buff.CasterLevel, bCard); } } else { foreach (BCardDTO bCard in buff.BCards.Where(x => !x.IsSecondBCardExecution.HasValue || !x.IsSecondBCardExecution.Value)) { (BCardType, byte SubType) key = ((BCardType)bCard.Type, bCard.SubType); if (bCard.ProcChance != 0) { int randomNumber = _randomGenerator.RandomNumber(); if (randomNumber > bCard.ProcChance) { continue; } } if (!_buffBCards.TryGetValue(buff.BuffId, out HashSet hashSet)) { hashSet = new HashSet(); _buffBCards[buff.BuffId] = hashSet; } hashSet.Add(bCard); _buffBCardsCasterLevel.Add((buff.CasterLevel, bCard)); if (!_hasBCardCache.TryGetValue(key, out _)) { _hasBCardCache.TryAdd(key, 1); } else { _hasBCardCache[key]++; } if (!_buffInformation.TryGetValue(key, out Dictionary newDictionary)) { newDictionary = new Dictionary(); _buffInformation[((BCardType)bCard.Type, bCard.SubType)] = newDictionary; } newDictionary[bCard.Id] = (buff.CasterLevel, bCard); } } } finally { _lock.ExitWriteLock(); } } public void RemoveBuffBCards(Buff buff) { _lock.EnterWriteLock(); try { if (!_buffBCards.TryRemove(buff.BuffId, out HashSet bCards)) { return; } foreach (BCardDTO bCard in bCards) { (BCardType, byte SubType) key = ((BCardType)bCard.Type, bCard.SubType); if (!_buffInformation.TryGetValue(key, out Dictionary toRemove)) { _hasBCardCache.TryRemove(key, out _); continue; } toRemove.Remove(bCard.Id); _buffBCardsCasterLevel.Remove((buff.CasterLevel, bCard)); if (_hasBCardCache.TryGetValue(key, out byte amount)) { amount -= 1; if (amount > 0) { _hasBCardCache[key] = amount; continue; } _hasBCardCache.TryRemove(key, out _); _buffInformation.TryRemove(key, out _); continue; } _hasBCardCache.TryRemove(key, out _); _buffInformation.TryRemove(key, out _); } } finally { _lock.ExitWriteLock(); } } public void AddTriggerBCards(BCardTriggerType triggerType, List bCards) { _lock.EnterWriteLock(); try { if (bCards == null) { return; } if (!bCards.Any()) { return; } switch (triggerType) { case BCardTriggerType.ATTACK: _onAttack.AddRange(bCards); break; case BCardTriggerType.DEFENSE: _onDefense.AddRange(bCards); break; } } finally { _lock.ExitWriteLock(); } } public void RemoveAllTriggerBCards(BCardTriggerType triggerType) { _lock.EnterWriteLock(); try { if (triggerType == BCardTriggerType.ATTACK) { _onAttack.Clear(); return; } _onDefense.Clear(); } finally { _lock.ExitWriteLock(); } } public IReadOnlyList GetTriggerBCards(BCardTriggerType triggerType) => triggerType == BCardTriggerType.ATTACK ? _onAttack : _onDefense; public void AddShellTrigger(bool isMainWeapon, List bCards) { if (!_shellTriggerOnAttack.TryGetValue(isMainWeapon, out List list)) { list = new List(); _shellTriggerOnAttack[isMainWeapon] = list; } list.AddRange(bCards); } public void ClearShellTrigger(bool isMainWeapon) { if (!_shellTriggerOnAttack.TryGetValue(isMainWeapon, out List list)) { return; } list.Clear(); } public IReadOnlyList GetShellTriggers(bool isMainWeapon) => !_shellTriggerOnAttack.TryGetValue(isMainWeapon, out List list) ? Array.Empty() : list; public void AddChargeBCard(BCardDTO bCard) { if (_chargeBCards.Contains(bCard)) { return; } _chargeBCards.Add(bCard); } public void RemoveChargeBCard(BCardDTO bCard) { _chargeBCards.Remove(bCard); } public void ClearChargeBCard() { _chargeBCards.Clear(); } public IEnumerable GetChargeBCards() => _chargeBCards; }