server-master/srcs/WingsAPI.Game/BCard/BCardComponent.cs
2026-02-10 18:21:30 +01:00

481 lines
No EOL
15 KiB
C#

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<BCardDTO> _allBCards = new();
private readonly ConcurrentDictionary<(BCardType, byte), ThreadSafeHashSet<BCardDTO>> _bCards = new();
private readonly ConcurrentDictionary<EquipmentType, List<BCardDTO>> _bCardsByEquipmentType = new();
private readonly ConcurrentDictionary<Guid, HashSet<BCardDTO>> _buffBCards = new();
private readonly List<(int casterLevel, BCardDTO bCard)> _buffBCardsCasterLevel = new();
private readonly ConcurrentDictionary<(BCardType, byte), Dictionary<Guid, (int casterLevel, BCardDTO bCard)>> _buffInformation = new();
private readonly ThreadSafeHashSet<BCardDTO> _chargeBCards = new();
private readonly ConcurrentDictionary<(BCardType, byte), byte> _hasBCardCache = new();
private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.SupportsRecursion);
private readonly List<BCardDTO> _onAttack = new();
private readonly List<BCardDTO> _onDefense = new();
private readonly IRandomGenerator _randomGenerator;
private readonly ConcurrentDictionary<bool, List<BCardDTO>> _shellTriggerOnAttack = new();
public BCardComponent(IRandomGenerator randomGenerator) => _randomGenerator = randomGenerator;
public void AddEquipmentBCards(EquipmentType equipmentType, IEnumerable<BCardDTO> bCards)
{
_lock.EnterWriteLock();
try
{
List<BCardDTO> existingBCards = _bCardsByEquipmentType.GetOrDefault(equipmentType);
if (existingBCards == null)
{
existingBCards = new List<BCardDTO>();
_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<BCardDTO> bCards);
if (bCards == null)
{
return;
}
foreach (BCardDTO bCard in bCards)
{
RemoveBCard(bCard);
}
}
finally
{
_lock.ExitWriteLock();
}
}
public IReadOnlyList<BCardDTO> GetEquipmentBCards(EquipmentType equipmentType)
{
List<BCardDTO> eqBCards = _bCardsByEquipmentType.GetOrDefault(equipmentType);
if (eqBCards == null)
{
return Array.Empty<BCardDTO>();
}
return eqBCards;
}
public IReadOnlyDictionary<EquipmentType, List<BCardDTO>> 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<BCardDTO> 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<Guid, (int casterLevel, BCardDTO bCard)> 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<BCardDTO> 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<BCardDTO> existingBCards = _bCards.GetOrAdd(key, new ThreadSafeHashSet<BCardDTO>());
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<BCardDTO> 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<BCardDTO> 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<BCardDTO> hashSet))
{
hashSet = new HashSet<BCardDTO>();
_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<Guid, (int casterLevel, BCardDTO bCard)> newDictionary))
{
newDictionary = new Dictionary<Guid, (int casterLevel, BCardDTO bCard)>();
_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<BCardDTO> hashSet))
{
hashSet = new HashSet<BCardDTO>();
_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<Guid, (int casterLevel, BCardDTO bCard)> newDictionary))
{
newDictionary = new Dictionary<Guid, (int casterLevel, BCardDTO bCard)>();
_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<BCardDTO> bCards))
{
return;
}
foreach (BCardDTO bCard in bCards)
{
(BCardType, byte SubType) key = ((BCardType)bCard.Type, bCard.SubType);
if (!_buffInformation.TryGetValue(key, out Dictionary<Guid, (int casterLevel, BCardDTO bCard)> 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<BCardDTO> 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<BCardDTO> GetTriggerBCards(BCardTriggerType triggerType)
=> triggerType == BCardTriggerType.ATTACK ? _onAttack : _onDefense;
public void AddShellTrigger(bool isMainWeapon, List<BCardDTO> bCards)
{
if (!_shellTriggerOnAttack.TryGetValue(isMainWeapon, out List<BCardDTO> list))
{
list = new List<BCardDTO>();
_shellTriggerOnAttack[isMainWeapon] = list;
}
list.AddRange(bCards);
}
public void ClearShellTrigger(bool isMainWeapon)
{
if (!_shellTriggerOnAttack.TryGetValue(isMainWeapon, out List<BCardDTO> list))
{
return;
}
list.Clear();
}
public IReadOnlyList<BCardDTO> GetShellTriggers(bool isMainWeapon) => !_shellTriggerOnAttack.TryGetValue(isMainWeapon, out List<BCardDTO> list) ? Array.Empty<BCardDTO>() : 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<BCardDTO> GetChargeBCards() => _chargeBCards;
}