// WingsEmu // // Developed by NosWings Team using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using PhoenixLib.Events; using WingsEmu.Core.Extensions; using WingsEmu.DTOs.BCards; using WingsEmu.DTOs.Skills; using WingsEmu.Game; using WingsEmu.Game._enum; using WingsEmu.Game.Algorithm; using WingsEmu.Game.Battle; using WingsEmu.Game.Buffs; using WingsEmu.Game.Characters; using WingsEmu.Game.Entities; using WingsEmu.Game.Extensions; using WingsEmu.Game.Helpers.Damages; using WingsEmu.Game.Mates; using WingsEmu.Packets.Enums; using WingsEmu.Packets.Enums.Battle; namespace WingsEmu.Plugins.BasicImplementations.Event.Battle; public class ProcessHitEventHandler : IAsyncEventProcessor { private static readonly byte MAX_TARGETS = 50; private readonly IBattleEntityDumpFactory _battleEntityDumpFactory; private readonly IBCardEffectHandlerContainer _bCardEffectHandler; private readonly IDamageAlgorithm _damageAlgorithm; private readonly IRandomGenerator _randomGenerator; private readonly ISkillUsageManager _skillUsageManager; public ProcessHitEventHandler(IBattleEntityDumpFactory battleEntityDumpFactory, IDamageAlgorithm damageAlgorithm, ISkillUsageManager skillUsageManager, IBCardEffectHandlerContainer bCardEffectHandler, IRandomGenerator randomGenerator) { _battleEntityDumpFactory = battleEntityDumpFactory; _damageAlgorithm = damageAlgorithm; _skillUsageManager = skillUsageManager; _bCardEffectHandler = bCardEffectHandler; _randomGenerator = randomGenerator; } public async Task HandleAsync(ProcessHitEvent e, CancellationToken cancellation) { IBattleEntity caster = e.Caster; if (!caster.IsAlive()) { caster.CancelCastingSkill(); return; } IBattleEntity target = e.Target; SkillInfo skill = e.HitInformation.Skill; Position position = e.HitInformation.Position; BCardDTO[] afterAttackAllAllies = skill.BCardsType.TryGetValue(SkillCastType.AFTER_ATTACK_ALL_ALLIES, out HashSet allAllies) ? allAllies.ToArray() : Array.Empty(); BCardDTO[] beforeAttackOnMainTarget = skill.BCardsType.TryGetValue(SkillCastType.BEFORE_ATTACK_ON_MAIN_TARGET, out HashSet beforeAttackMain) ? beforeAttackMain.ToArray() : Array.Empty(); BCardDTO[] beforeAttackSelf = skill.BCardsType.TryGetValue(SkillCastType.BEFORE_ATTACK_SELF, out HashSet beforeAttack) ? beforeAttack.ToArray() : Array.Empty(); BCardDTO[] beforeAttackAllTargets = skill.BCardsType.TryGetValue(SkillCastType.BEFORE_ATTACK_ALL_TARGETS, out HashSet beforeAttackAll) ? beforeAttackAll.ToArray() : Array.Empty(); IBattleEntityDump attacker = caster switch { IPlayerEntity character => _battleEntityDumpFactory.Dump(character, skill), IMonsterEntity monster => _battleEntityDumpFactory.Dump(monster, skill), INpcEntity mapNpc => _battleEntityDumpFactory.Dump(mapNpc, skill), IMateEntity mate => _battleEntityDumpFactory.Dump(mate, skill), _ => null }; if (attacker == null) { caster.CancelCastingSkill(); return; } List entities; TargetHitType hitType = skill.HitType; (int randomChance, int range) = caster.BCardComponent.GetAllBCardsInformation(BCardType.SpecialDamageAndExplosions, (byte)AdditionalTypes.SpecialDamageAndExplosions.SurroundingDamage, caster.Level); if (randomChance != 0 && _randomGenerator.RandomNumber() <= randomChance && hitType is not TargetHitType.SpecialArea) { hitType = TargetHitType.EnemiesInAffectedAoE; skill.AoERange += (byte)range; } if (skill.TargetType == TargetType.NonTarget) { entities = position.GetEnemiesInRange(caster, skill.AoERange).ToList(); if (skill.BCards.Any(x => (BCardType)x.Type == BCardType.FalconSkill && x.SubType == (byte)AdditionalTypes.FalconSkill.FalconFocusLowestHP)) { if (entities.Count != 0) { entities = Lists.Create(entities.OrderBy(x => x.HpPercentage).First()); } } } else { switch (hitType) { case TargetHitType.EnemiesInAffectedAoE: entities = skill.Vnum == (short)SkillsVnums.DOUBLE_RIPPER ? position.GetEnemiesInRange(caster, skill.AoERange).ToList() : target.GetEnemiesInRange(caster, skill.AoERange).ToList(); break; case TargetHitType.SpecialArea: entities = _skillUsageManager.GetMultiTargets(caster.Id) .Select(x => caster.MapInstance.GetBattleEntity(x.Item1, x.Item2)) .Where(x => x != null && caster.Position.IsInAoeZone(x.Position, 10) && caster.IsEnemyWith(x)) .ToList(); _skillUsageManager.ResetMultiTargets(caster.Id); break; case TargetHitType.TargetOnly: entities = Lists.Create(target); break; default: caster.CancelCastingSkill(); return; } if (!caster.Equals(target)) { entities.SetFirst(target); } } if (!caster.IsPlayer() || caster is IPlayerEntity playerEntity && playerEntity.CheatComponent.HasNoTargetLimit == false) { entities = entities.Take(MAX_TARGETS).ToList(); } foreach (BCardDTO bCard in beforeAttackOnMainTarget) { _bCardEffectHandler.Execute(target, caster, bCard, skill, position); } foreach (BCardDTO bCard in beforeAttackSelf) { _bCardEffectHandler.Execute(caster, caster, bCard, skill, position); } var targets = new List<(IBattleEntity, DamageAlgorithmResult)>(); foreach (IBattleEntity entity in entities) { if (caster is IPlayerEntity c) { if (c.CheatComponent.IsInvisible) { continue; } if (entity.IsMonster()) { var monster = entity as IMonsterEntity; sbyte monsterMinRange = monster.MinimumAttackRange; if (monsterMinRange != 0 && skill.Range < monsterMinRange) { continue; } } } foreach (BCardDTO bCard in beforeAttackAllTargets) { _bCardEffectHandler.Execute(entity, caster, bCard, skill, position); } IBattleEntityDump defender = entity switch { IPlayerEntity character => _battleEntityDumpFactory.Dump(character, skill, true, entity.IsSameEntity(target)), IMonsterEntity monster => _battleEntityDumpFactory.Dump(monster, skill, true, entity.IsSameEntity(target)), INpcEntity mapNpc => _battleEntityDumpFactory.Dump(mapNpc, skill, true, entity.IsSameEntity(target)), IMateEntity mate => _battleEntityDumpFactory.Dump(mate, skill, true, entity.IsSameEntity(target)), _ => null }; if (defender == null) { caster.CancelCastingSkill(); continue; } DamageAlgorithmResult algorithmResult = _damageAlgorithm.GenerateDamage(attacker, defender, skill); targets.Add((entity, algorithmResult)); if (caster.IsEnemyWith(entity)) { continue; } foreach (BCardDTO bCard in afterAttackAllAllies) { _bCardEffectHandler.Execute(entity, caster, bCard, skill, position); } } foreach (BCardDTO bCard in afterAttackAllAllies) { _bCardEffectHandler.Execute(caster, caster, bCard, skill, position); } caster.MapInstance.AddHitRequest(new HitRequest(targets, e.HitInformation, target)); } }