// WingsEmu // WingsEmu // // Developed by NosWings Team using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using PhoenixLib.Events; using WingsAPI.Data.Drops; using WingsEmu.Core.Generics; 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.Battle.Managers; using WingsEmu.Game.Buffs; using WingsEmu.Game.Entities; using WingsEmu.Game.Extensions; using WingsEmu.Game.Helpers.Damages; using WingsEmu.Game.Maps; using WingsEmu.Game.Monster; using WingsEmu.Game.Raids; using WingsEmu.Game.Skills; using WingsEmu.Packets.Enums; using WingsEmu.Packets.Enums.Battle; namespace Plugin.CoreImpl.Entities { public class MonsterEntity : IMonsterEntity { private readonly IAsyncEventPipeline _asyncEventPipeline; private readonly IBattleEntityAlgorithmService _battleEntityAlgorithmService; private readonly ICastingComponent _castingComponent; private readonly IEndBuffDamageComponent _endBuffDamageComponent; private readonly IEventTriggerContainer _eventContainer; private double _enhancedHp = 1; public MonsterEntity(int id, IEventTriggerContainer eventContainer, IBCardComponent bCardComponent, IAsyncEventPipeline asyncEventPipeline, IMonsterData monsterData, IMapInstance mapInstance, MonsterEntityBuilder builder, IReadOnlyList npcMonsterSkills, IBattleEntityAlgorithmService battleEntityAlgorithmService) { UniqueId = builder?.GeneratedGuid ?? Guid.NewGuid(); BCardComponent = bCardComponent; _castingComponent = new CastingComponent(); Damagers = new ThreadSafeHashSet(); _endBuffDamageComponent = new EndBuffDamageComponent(); ChargeComponent = new ChargeComponent(); _eventContainer = eventContainer; BuffComponent = new BuffComponent(); _asyncEventPipeline = asyncEventPipeline; _battleEntityAlgorithmService = battleEntityAlgorithmService; Id = id; AmountRequired = monsterData.AmountRequired; ArmorLevel = monsterData.ArmorLevel; AttackType = monsterData.AttackType; AttackUpgrade = monsterData.AttackUpgrade; BasicCastTime = monsterData.BasicCastTime; BasicCooldown = monsterData.BasicCooldown; BasicRange = monsterData.BasicRange; BCards = monsterData.BCards; AttackEffect = monsterData.AttackEffect; BasicHitChance = monsterData.BasicHitChance; CanBeCollected = monsterData.CanBeCollected; CanBeDebuffed = monsterData.CanBeDebuffed; CanBeCaught = monsterData.CanBeCaught; CanBePushed = monsterData.CanBePushed; CanRegenMp = monsterData.CanRegenMp; CellSize = monsterData.CellSize; CleanDamageMin = monsterData.CleanDamageMin; CleanDamageMax = monsterData.CleanDamageMax; CleanHitRate = monsterData.CleanHitRate; CleanMeleeDefence = monsterData.CleanMeleeDefence; CleanRangeDefence = monsterData.CleanRangeDefence; CleanMagicDefence = monsterData.CleanMagicDefence; CleanDodge = monsterData.CleanDodge; CleanHp = monsterData.CleanHp; CleanMp = monsterData.CleanMp; BaseCloseDefence = monsterData.BaseCloseDefence; BaseConcentrate = monsterData.BaseConcentrate; BaseCriticalChance = monsterData.BaseCriticalChance; BaseCriticalRate = monsterData.BaseCriticalRate; DamagedOnlyLastJajamaruSkill = monsterData.DamagedOnlyLastJajamaruSkill; BaseDamageMinimum = monsterData.BaseDamageMaximum; BaseDamageMaximum = monsterData.BaseDamageMinimum; BaseDarkResistance = monsterData.BaseDarkResistance; DeathEffect = monsterData.DeathEffect; BaseMaxHp = monsterData.BaseMaxHp; BaseMaxMp = monsterData.BaseMaxMp; DefenceDodge = monsterData.DefenceDodge; DefenceUpgrade = monsterData.DefenceUpgrade; Drops = monsterData.Drops; DisappearAfterHitting = monsterData.DisappearAfterHitting; DisappearAfterSeconds = monsterData.DisappearAfterSeconds; DisappearAfterSecondsMana = monsterData.DisappearAfterSecondsMana; DistanceDefence = monsterData.DistanceDefence; DistanceDefenceDodge = monsterData.DistanceDefenceDodge; BaseElement = monsterData.BaseElement; BaseElementRate = monsterData.BaseElementRate; BaseFireResistance = monsterData.BaseFireResistance; GiveDamagePercentage = monsterData.GiveDamagePercentage; GroupAttack = monsterData.GroupAttack; HasMode = monsterData.HasMode; RawHostility = monsterData.RawHostility; IconId = monsterData.IconId; IsPercent = monsterData.IsPercent; JobXp = monsterData.JobXp; BaseLevel = monsterData.BaseLevel; BaseLightResistance = monsterData.BaseLightResistance; MagicDefence = monsterData.MagicDefence; MagicMpFactor = monsterData.MagicMpFactor; MeleeHpFactor = monsterData.MeleeHpFactor; MinimumAttackRange = monsterData.MinimumAttackRange; MonsterVNum = monsterData.MonsterVNum; Name = monsterData.Name; NoticeRange = monsterData.NoticeRange; OnDefenseOnlyOnce = monsterData.OnDefenseOnlyOnce; PermanentEffect = monsterData.PermanentEffect; MonsterRaceType = monsterData.MonsterRaceType; MonsterRaceSubType = monsterData.MonsterRaceSubType; RangeDodgeFactor = monsterData.RangeDodgeFactor; BaseRespawnTime = monsterData.BaseRespawnTime; SpawnMobOrColor = monsterData.SpawnMobOrColor; BaseSpeed = monsterData.BaseSpeed; SpriteSize = monsterData.SpriteSize; TakeDamages = monsterData.TakeDamages; VNumRequired = monsterData.VNumRequired; BaseWaterResistance = monsterData.BaseWaterResistance; WeaponLevel = monsterData.WeaponLevel; WinfoValue = monsterData.WinfoValue; Xp = monsterData.Xp; MaxTries = monsterData.MaxTries; CollectionCooldown = monsterData.CollectionCooldown; CollectionDanceTime = monsterData.CollectionDanceTime; TeleportRemoveFromInventory = monsterData.TeleportRemoveFromInventory; BasicDashSpeed = monsterData.BasicDashSpeed; ModeIsHpTriggered = monsterData.ModeIsHpTriggered; ModeLimiterType = monsterData.ModeLimiterType; ModeRangeTreshold = monsterData.ModeRangeTreshold; ModeCModeVnum = monsterData.ModeCModeVnum; ModeHpTresholdOrItemVnum = monsterData.ModeHpTresholdOrItemVnum; MidgardDamage = monsterData.MidgardDamage; HasDash = monsterData.HasDash; DropToInventory = monsterData.DropToInventory; ModeBCards = monsterData.ModeBCards; BaseXp = monsterData.BaseXp; BaseJobXp = monsterData.BaseJobXp; LastEffect = Death = SpawnDate = DateTime.UtcNow; LastSkill = DateTime.MinValue; IsHostile = RawHostility != (int)HostilityType.NOT_HOSTILE; IsStillAlive = true; Speed = this.GetSpeed(BaseSpeed); Hp = BaseMaxHp; Mp = BaseMaxMp; Level = BaseLevel; CanSeeInvisible = monsterData.CanSeeInvisible; Faction = RawHostility switch { (int)HostilityType.ATTACK_ANGELS_ONLY => FactionType.Demon, (int)HostilityType.ATTACK_DEVILS_ONLY => FactionType.Angel, _ => monsterData.SuggestedFaction ?? FactionType.Neutral }; if (builder != null) { PositionX = FirstX = builder.PositionX; PositionY = FirstY = builder.PositionY; Direction = builder.Direction; CanWalk = builder.IsWalkingAround & monsterData.CanWalk; BaseShouldRespawn = builder.IsRespawningOnDeath; IsMateTrainer = builder.IsMateTrainer; IsBonus = builder.IsBonus; IsBoss = builder.IsBoss; IsTarget = builder.IsTarget; VesselMonster = builder.IsVesselMonster; SummonerId = builder.SummonerId; SummonerType = builder.SummonerType; IsHostile = builder.IsHostile; SummonType = builder.SummonType; GoToBossPosition = builder.GoToBossPosition; IsInstantBattle = builder.IsInstantBattle; RaidDrop = builder.RaidDrop; if (builder.Level.HasValue) { Level = builder.Level.Value; UpdateMonstersBaseStatistics(); Hp = MaxHp; Mp = MaxMp; } if (builder.HpMultiplier.HasValue) { BaseMaxHp = (int)(BaseMaxHp * builder.HpMultiplier.Value); Hp = BaseMaxHp; } if (builder.MpMultiplier.HasValue) { BaseMaxMp = (int)(BaseMaxMp * builder.MpMultiplier.Value); Mp = BaseMaxMp; } Faction = builder.FactionType ?? (SuggestedFaction ?? FactionType.Neutral); } Drops = monsterData.Drops; MonsterSkills = npcMonsterSkills; NotBasicSkills = MonsterSkills.Where(x => !x.IsBasicAttack).ToArray(); if (HasDash) { DashSkill = MonsterSkills.Count != 0 ? MonsterSkills[0] : null; } SkillsWithoutDashSkill = MonsterSkills.Where(x => DashSkill != null && DashSkill.Skill.Id != x.Skill.Id).ToArray(); foreach (INpcMonsterSkill skill in MonsterSkills) { Skills.Add(skill); if (skill.IsBasicAttack) { ReplacedBasicSkill = skill; } } this.InitializeBCards(); BasicSkill = new SkillInfo { Vnum = default, AttackType = AttackType, Range = BasicRange, CastTime = BasicCastTime, Cooldown = BasicCooldown, HitEffect = AttackEffect, Element = BaseElement, HitChance = builder?.SetHitChance ?? BasicHitChance, HitAnimation = 11, TargetType = TargetType.Target, TargetAffectedEntities = TargetAffectedEntities.Enemies, SkillType = SkillType.MonsterSkill }; MapInstance = mapInstance; } public bool IsEnhanced { get; set; } public DateTime LastEnhancedEffect { get; set; } public bool BaseShouldRespawn { get; } public bool TeleportBackOnNoticeRangeExceed { get; } public void RefreshStats() { Speed = this.GetSpeed(BaseSpeed); } public ConcurrentDictionary PlayersDamage { get; } = new(); public DateTime LastMpRegen { get; set; } public Position? GoToBossPosition { get; set; } public bool IsInstantBattle { get; set; } public IEnumerable RaidDrop { get; } public DateTime LastBonusEffectTime { get; set; } public DateTime AttentionTime { get; set; } public ConcurrentDictionary Waypoints { get; set; } public DateTime LastWayPoint { get; set; } public byte CurrentWayPoint { get; set; } public byte ReturnTimeOut { get; set; } public (VisualType, long) LastAttackedEntity { get; set; } public bool IsRunningAway { get; set; } public IReadOnlyList NotBasicSkills { get; } public IReadOnlyList SkillsWithoutDashSkill { get; } public INpcMonsterSkill ReplacedBasicSkill { get; } public INpcMonsterSkill DashSkill { get; } public IBCardComponent BCardComponent { get; } public IChargeComponent ChargeComponent { get; } public ThreadSafeHashSet AggroedEntities { get; } = new(); public IBuffComponent BuffComponent { get; } public bool IsMateTrainer { get; } public int MaxHp { get => this.GetMaxHp((int)(BaseMaxHp * _enhancedHp)); set => throw new NotImplementedException(); } public int MaxMp { get => this.GetMaxMp(BaseMaxMp); set => throw new NotImplementedException(); } public int Hp { get; set; } public int Mp { get; set; } public ThreadSafeHashSet Damagers { get; } public ThreadSafeHashSet Targets { get; } = new(); public ThreadSafeHashSet<(VisualType, long)> TargetsByVisualTypeAndId { get; } = new(); public DateTime LastTargetsRefresh { get; set; } public DateTime Death { get; set; } public bool IsStillAlive { get; set; } public byte Level { get; set; } public byte Element { get => BaseElement; set => throw new NotImplementedException(); } public int ElementRate { get => BaseElementRate; set => throw new NotImplementedException(); } public int FireResistance { get => BaseFireResistance; set => throw new NotImplementedException(); } public int WaterResistance { get => BaseWaterResistance; set => throw new NotImplementedException(); } public int LightResistance { get => BaseLightResistance; set => throw new NotImplementedException(); } public int DarkResistance { get => BaseDarkResistance; set => throw new NotImplementedException(); } public int DamagesMinimum { get; set; } public int DamagesMaximum { get; set; } public Position Position { get => new(PositionX, PositionY); set { PositionX = value.X; PositionY = value.Y; } } public short PositionX { get; private set; } public short PositionY { get; private set; } public FactionType Faction { get; } public byte Speed { get; set; } public byte Size { get; set; } = 10; public bool IsBonus { get; set; } public bool IsBoss { get; } public string Name { get; } public byte NoticeRange { get; } public bool OnDefenseOnlyOnce { get; } public short PermanentEffect { get; } public SummonType? SummonType { get; } public bool IsHostile { get; set; } public bool IsTarget { get; set; } public DateTime LastEffect { get; set; } public DateTime LastSkill { get; set; } public IMapInstance MapInstance { get; set; } public List Skills { get; set; } = new(); public IBattleEntity Target { get; set; } public DateTime NextTick { get; set; } public DateTime NextAttackReady { get; set; } public bool ModeIsActive { get; set; } public short Morph { get; set; } public long ModeDeathsSinceRespawn { get; set; } public short FirstX { get; set; } public short FirstY { get; set; } public bool VesselMonster { get; set; } public short AmountRequired { get; } public byte ArmorLevel { get; } public AttackType AttackType { get; } public byte AttackUpgrade { get; } public byte BasicCastTime { get; } public short BasicCooldown { get; } public byte BasicRange { get; } public short AttackEffect { get; } public short BasicHitChance { get; } public bool CanBeCollected { get; } public bool CanBeDebuffed { get; } public bool CanBeCaught { get; } public bool CanBePushed { get; } public bool CanRegenMp { get; } public bool CanWalk { get; set; } public int CellSize { get; } public int CleanDamageMin { get; } public int CleanDamageMax { get; } public int CleanHitRate { get; } public int CleanMeleeDefence { get; } public int CleanRangeDefence { get; } public int CleanMagicDefence { get; } public int CleanDodge { get; } public int CleanHp { get; } public int CleanMp { get; } public short BaseCloseDefence { get; private set; } public short BaseConcentrate { get; private set; } public short BaseCriticalChance { get; } public short BaseCriticalRate { get; } public bool DamagedOnlyLastJajamaruSkill { get; } public int BaseDamageMaximum { get; private set; } public int BaseDamageMinimum { get; private set; } public short BaseDarkResistance { get; } public short DeathEffect { get; } public int BaseMaxHp { get; private set; } public int BaseMaxMp { get; private set; } public short DefenceDodge { get; private set; } public byte DefenceUpgrade { get; } public bool DisappearAfterHitting { get; } public bool DisappearAfterSeconds { get; } public bool DisappearAfterSecondsMana { get; } public short DistanceDefence { get; private set; } public short DistanceDefenceDodge { get; private set; } public byte BaseElement { get; } public short BaseElementRate { get; } public short BaseFireResistance { get; } public FactionType? SuggestedFaction => Faction; public int GiveDamagePercentage { get; } public int GroupAttack { get; } public bool HasMode { get; } public int RawHostility { get; } public int IconId { get; } public bool IsPercent { get; } public int JobXp { get; private set; } public byte BaseLevel { get; } public short BaseLightResistance { get; } public short MagicDefence { get; private set; } public short MagicMpFactor { get; } public short MeleeHpFactor { get; } public sbyte MinimumAttackRange { get; } public long? SummonerId { get; set; } public VisualType? SummonerType { get; set; } public IBattleEntity Killer { get; set; } public Guid UniqueId { get; } public void GenerateDeath(IBattleEntity killer = null) { IsStillAlive = false; Hp = 0; Mp = 0; Death = DateTime.UtcNow; this.RemoveAllBuffsAsync(true); Target = null; } public VisualType Type => VisualType.Monster; public int Id { get; set; } public byte Direction { get; set; } = 2; bool INpcMonsterEntity.ShouldRespawn => BaseShouldRespawn; public bool ReturningToFirstPosition { get; set; } public bool ShouldFindNewTarget { get; set; } public bool FindNewPositionAroundTarget { get; set; } public bool IsApproachingTarget { get; set; } public bool OnFirstDamageReceive { get; set; } public DateTime SpawnDate { get; set; } public DateTime LastSpecialHpDecrease { get; set; } public bool IsMoving => CanWalk; public int MonsterVNum { get; } public void AddEvent(string key, IAsyncEvent notification, bool removedOnTrigger = false) => _eventContainer.AddEvent(key, notification, removedOnTrigger); public async Task TriggerEvents(string key) => await _eventContainer.TriggerEvents(key); public async Task EmitEventAsync(T eventArgs) where T : IBattleEntityEvent { if (eventArgs.Entity != this) { throw new ArgumentException("An event should be emitted only from the event sender"); } await _asyncEventPipeline.ProcessEventAsync(eventArgs); } public void EmitEvent(T eventArgs) where T : IBattleEntityEvent { EmitEventAsync(eventArgs).ConfigureAwait(false).GetAwaiter().GetResult(); } public MonsterRaceType MonsterRaceType { get; set; } public byte MonsterRaceSubType { get; } public short RangeDodgeFactor { get; } public TimeSpan BaseRespawnTime { get; } public int SpawnMobOrColor { get; } public byte BaseSpeed { get; } public int SpriteSize { get; } public int TakeDamages { get; } public short VNumRequired { get; } public short BaseWaterResistance { get; } public byte WeaponLevel { get; } public byte WinfoValue { get; } public int Xp { get; private set; } public byte MaxTries { get; } public short CollectionCooldown { get; } public byte CollectionDanceTime { get; } public bool TeleportRemoveFromInventory { get; } public short BasicDashSpeed { get; } public bool ModeIsHpTriggered { get; } public byte ModeLimiterType { get; } public short ModeRangeTreshold { get; } public short ModeCModeVnum { get; } public short ModeHpTresholdOrItemVnum { get; } public short MidgardDamage { get; } public bool HasDash { get; } public bool DropToInventory { get; } public int BaseXp { get; } public int BaseJobXp { get; } public IReadOnlyList Drops { get; } public bool CanSeeInvisible { get; } public IReadOnlyList BCards { get; } public IReadOnlyList ModeBCards { get; } public IReadOnlyList MonsterSkills { get; } public SkillInfo BasicSkill { get; } public SkillCast SkillCast => _castingComponent.SkillCast; public bool IsCastingSkill => _castingComponent.IsCastingSkill; public void SetCastingSkill(SkillInfo skill, DateTime time) { _castingComponent.SetCastingSkill(skill, time); } public void RemoveCastingSkill() { _castingComponent.RemoveCastingSkill(); } public IReadOnlyDictionary EndBuffDamages => _endBuffDamageComponent.EndBuffDamages; public void AddEndBuff(short buffVnum, int damage) { _endBuffDamageComponent.AddEndBuff(buffVnum, damage); } public int DecreaseDamageEndBuff(short buffVnum, int damage) => _endBuffDamageComponent.DecreaseDamageEndBuff(buffVnum, damage); public void RemoveEndBuffDamage(short buffVnum) { _endBuffDamageComponent.RemoveEndBuffDamage(buffVnum); } public void ChangeHpMultiplier(double hp = 1) { _enhancedHp = hp; } private void UpdateMonstersBaseStatistics() { BaseMaxHp = _battleEntityAlgorithmService.GetBasicHp((short)MonsterRaceType, Level, MeleeHpFactor, CleanHp); BaseMaxMp = _battleEntityAlgorithmService.GetBasicMp((short)MonsterRaceType, Level, MagicMpFactor, CleanMp); BaseDamageMinimum = _battleEntityAlgorithmService.GetAttack(true, (short)MonsterRaceType, AttackType, WeaponLevel, WinfoValue, Level, GetModifier(), CleanDamageMin); BaseDamageMaximum = _battleEntityAlgorithmService.GetAttack(false, (short)MonsterRaceType, AttackType, WeaponLevel, WinfoValue, Level, GetModifier(), CleanDamageMax); BaseConcentrate = (short)_battleEntityAlgorithmService.GetHitrate((short)MonsterRaceType, AttackType, WeaponLevel, Level, GetModifier(), CleanHitRate); BaseCloseDefence = (short)_battleEntityAlgorithmService.GetDefense((short)MonsterRaceType, AttackType.Melee, ArmorLevel, Level, GetModifier(), CleanMeleeDefence); DistanceDefence = (short)_battleEntityAlgorithmService.GetDefense((short)MonsterRaceType, AttackType.Ranged, ArmorLevel, Level, GetModifier(), CleanRangeDefence); MagicDefence = (short)_battleEntityAlgorithmService.GetDefense((short)MonsterRaceType, AttackType.Magical, ArmorLevel, Level, GetModifier(), CleanMagicDefence); DefenceDodge = (short)_battleEntityAlgorithmService.GetDodge((short)MonsterRaceType, ArmorLevel, Level, GetModifier(), CleanDodge); DistanceDefenceDodge = (short)_battleEntityAlgorithmService.GetDodge((short)MonsterRaceType, ArmorLevel, Level, GetModifier(), CleanDodge); Xp = Level < 20 ? 60 * Level + BaseXp : 70 * Level + BaseXp; JobXp = Level > 60 ? 105 + BaseJobXp : 120 + BaseJobXp; if (Xp < 0) { Xp = 0; } if (JobXp < 0) { JobXp = 0; } } private int GetModifier() { return AttackType switch { AttackType.Melee => MeleeHpFactor, AttackType.Ranged => RangeDodgeFactor, AttackType.Magical => MagicMpFactor, _ => 0 }; } } }