using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Foundatio.Utility; using WingsEmu.DTOs.Skills; using WingsEmu.Game; using WingsEmu.Game._enum; using WingsEmu.Game.Battle; using WingsEmu.Game.Characters; using WingsEmu.Game.Configurations; using WingsEmu.Game.Entities; using WingsEmu.Game.Extensions; using WingsEmu.Game.Extensions.Mates; using WingsEmu.Game.Helpers.Damages; using WingsEmu.Game.Maps; using WingsEmu.Game.Mates; using WingsEmu.Game.Networking; using WingsEmu.Game.Skills; using WingsEmu.Packets.ClientPackets; using WingsEmu.Packets.Enums; namespace WingsEmu.Plugins.PacketHandling.Game.Mate; public class SuctlPacketHandler : GenericGamePacketHandlerBase { private readonly IPartnerSpecialistBasicConfig _partnerSpecialistBasic; private readonly IRandomGenerator _randomGenerator; public SuctlPacketHandler(IRandomGenerator randomGenerator, IPartnerSpecialistBasicConfig partnerSpecialistBasic) { _randomGenerator = randomGenerator; _partnerSpecialistBasic = partnerSpecialistBasic; } protected override async Task HandlePacketAsync(IClientSession session, SuctlPacket packet) { if (session.IsMuted()) { session.SendMuteMessage(); return; } if (packet == null) { session.SendDebugMessage("packet == null"); return; } // check, if owner of mate is in PvP cell if (session.CurrentMapInstance.IsPvp && session.CurrentMapInstance.PvpZone(session.PlayerEntity.PositionX, session.PlayerEntity.PositionY)) { session.SendDebugMessage("Character in No-PvP zone"); return; } if (session.PlayerEntity.IsOnVehicle) { session.SendDebugMessage("Mate - session.IsVehicled"); return; } if (session.PlayerEntity.Invisible) { session.SendDebugMessage("Mate - session.PlayerEntity.Invisible"); return; } if (session.PlayerEntity.IsInvisible()) { session.SendDebugMessage("Mate - session.PlayerEntity.IsInvisible()"); return; } if (session.PlayerEntity.CheatComponent.IsInvisible) { session.SendDebugMessage("Mate - session.IsVehicled, InvisibleGm"); return; } if (session.PlayerEntity.IsInvisible()) { return; } INpcEntity npc = session.PlayerEntity.MapInstance.GetNpcById(packet.MateTransportId); if (npc != null) { await CheckNpcAttack(session, npc, packet); return; } IMateEntity attacker = session.PlayerEntity.MateComponent.GetMate(x => x.Id == packet.MateTransportId); if (attacker == null) { session.SendDebugMessage("Mate == null"); return; } if (!attacker.IsAlive()) { session.SendDebugMessage("Mate is dead"); return; } if (attacker.IsCastingSkill) { session.SendDebugMessage("Mate IsCasting"); return; } if (attacker.IsSitting) { session.SendDebugMessage("Mate IsSitting"); return; } if (!attacker.CanAttack()) { return; } // check, if mate is in PvP cell if (attacker.MapInstance.IsPvp && attacker.MapInstance.PvpZone(attacker.PositionX, attacker.PositionY)) { session.SendDebugMessage("Mate in No-PvP zone"); return; } IBattleEntity target = session.PlayerEntity.MapInstance.GetBattleEntity(packet.TargetType, packet.TargetId); switch (target) { case null: session.SendDebugMessage("Target == null"); return; case IMonsterEntity { MonsterRaceType: MonsterRaceType.Fixed }: case IPlayerEntity { IsSeal: true }: return; } // check, if target is in PvP cell if (target.MapInstance.IsPvp && target.MapInstance.PvpZone(target.PositionX, target.PositionY)) { session.SendDebugMessage("Target in No-PvP zone"); return; } // check, if target owner is in PvP cell IPlayerEntity targetMateOwner = (target as IMateEntity)?.Owner; if (targetMateOwner != null && target.MapInstance.PvpZone(targetMateOwner.PositionX, targetMateOwner.PositionY) && target.MapInstance.IsPvp) { session.SendDebugMessage("Target Mate Owner in No-PvP zone"); return; } if (!target.IsAlive()) { session.SendDebugMessage("Target is dead"); return; } if (target is IMonsterEntity mob && mob.SummonerId != 0 && mob.SummonerType == VisualType.Player && !mob.IsMateTrainer) { return; } IEnumerable mateSkills = attacker.Skills; if (mateSkills == null) { session.SendDebugMessage("mateSkills == null"); return; } IBattleEntitySkill ski = mateSkills.FirstOrDefault(s => s.Skill.SkillType == SkillType.MonsterSkill); SkillDTO skill = null; if (ski is NpcMonsterSkill npcMonsterSkill) { if (npcMonsterSkill.IsBasicAttack) { skill = npcMonsterSkill.Skill; } else if (npcMonsterSkill.Rate == 0 || _randomGenerator.RandomNumber() <= npcMonsterSkill.Rate) { skill = ski.Skill; } } SkillInfo skillInfo = skill == null ? attacker.BasicSkill.DeepClone() : skill.GetInfo(battleEntity: attacker); if (attacker.IsUsingSp && attacker.Specialist != null) { skillInfo.Element = attacker.Specialist.GameItem.Element; } if (skillInfo.Vnum != 0 && !attacker.SkillCanBeUsed(ski, DateTime.UtcNow)) { skill = null; skillInfo = attacker.BasicSkill; } if (session.PlayerEntity.IsAllyWith(target)) { return; } if (attacker.Mp < skill?.MpCost) { skill = null; skillInfo = attacker.BasicSkill; } if (skillInfo.Vnum == 0 && attacker.LastBasicSkill > DateTime.UtcNow) { return; } if (!session.PlayerEntity.CheatComponent.HasGodMode && skill != null) { attacker.RemoveEntityMp(skill.MpCost, skill); } int cellSizeBonus = 3; if (target is INpcMonsterEntity npcMonsterEntity) { cellSizeBonus += npcMonsterEntity.CellSize; } if (!attacker.Position.IsInRange(target.Position, skillInfo.Range + cellSizeBonus)) { return; } session.SendMateEffect(attacker, EffectType.PetAttack); session.SendMateLife(attacker); DateTime castTime = attacker.GenerateSkillCastTime(skillInfo); attacker.LastSkillUse = DateTime.UtcNow; if (skillInfo.Vnum == 0) { if (skillInfo.Cooldown > 10) // Cooldown is > 1 second { skillInfo.Cooldown = 10; } attacker.LastBasicSkill = DateTime.UtcNow.AddMilliseconds(attacker.ApplyCooldownReduction(skillInfo) * 100); if (attacker.IsUsingSp && attacker.Specialist != null) { skillInfo.HitEffect = _partnerSpecialistBasic.GetAttackEffect(attacker.Specialist.GameItem.Morph); } } await attacker.EmitEventAsync(new BattleExecuteSkillEvent(attacker, target, skillInfo, castTime)); } private async Task CheckNpcAttack(IClientSession session, INpcEntity npc, SuctlPacket packet) { if (npc.MapInstance.MapInstanceType != MapInstanceType.TimeSpaceInstance) { return; } if (!npc.IsTimeSpaceMate) { return; } List partners = session.PlayerEntity.TimeSpaceComponent.Partners; INpcEntity partner = partners.FirstOrDefault(x => x.MonsterVNum == npc.MonsterVNum); if (partner == null || !npc.CharacterPartnerId.HasValue || npc.Id != partner.Id) { return; } if (!npc.IsAlive()) { session.SendDebugMessage("Mate is dead"); return; } if (npc.IsCastingSkill) { session.SendDebugMessage("Mate IsCasting"); return; } if (npc.IsSitting) { session.SendDebugMessage("Mate IsSitting"); return; } if (!npc.CanPerformAttack()) { return; } IBattleEntity target = session.PlayerEntity.MapInstance.GetBattleEntity(packet.TargetType, packet.TargetId); switch (target) { case null: session.SendDebugMessage("Target == null"); return; case IMonsterEntity { MonsterRaceType: MonsterRaceType.Fixed }: return; } if (!target.IsAlive()) { session.SendDebugMessage("Target is dead"); return; } if (target is IMonsterEntity mob && mob.SummonerId != 0 && mob.SummonerType == VisualType.Player) { return; } IBattleEntitySkill skillToUse = null; DateTime now = DateTime.UtcNow; foreach (IBattleEntitySkill skill in npc.Skills) { if (skill is not NpcMonsterSkill npcMonsterSkill) { continue; } if (npcMonsterSkill.IsBasicAttack && npc.SkillCanBeUsed(npcMonsterSkill, now)) { skillToUse = npcMonsterSkill; break; } if (!npc.SkillCanBeUsed(npcMonsterSkill, now)) { continue; } skillToUse = npcMonsterSkill; break; } SkillInfo skillInfo = skillToUse?.Skill.GetInfo() ?? npc.BasicSkill; if (session.PlayerEntity.IsAllyWith(target)) { return; } if (skillInfo.Vnum == 0 && npc.Skills.Any(x => x is NpcMonsterSkill { IsBasicAttack: true })) { return; } if (npc.LastBasicAttack > DateTime.UtcNow && skillInfo.Vnum == 0) { return; } int cellSizeBonus = 3; if (target is INpcMonsterEntity npcMonsterEntity) { cellSizeBonus += npcMonsterEntity.CellSize; } if (!npc.Position.IsInRange(target.Position, skillInfo.Range + cellSizeBonus)) { return; } session.SendNpcEffect(npc, EffectType.PetAttack); DateTime castTime = npc.GenerateSkillCastTime(skillInfo); npc.LastBasicAttack = DateTime.UtcNow.AddMilliseconds(npc.ApplyCooldownReduction(npc.BasicSkill) * 100); await npc.EmitEventAsync(new BattleExecuteSkillEvent(npc, target, skillInfo, castTime)); } }