// WingsEmu // // Developed by NosWings Team using System; 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.Maps; using WingsEmu.DTOs.NpcMonster; using WingsEmu.DTOs.Skills; using WingsEmu.Game; using WingsEmu.Game._enum; using WingsEmu.Game.Battle; using WingsEmu.Game.Battle.Managers; using WingsEmu.Game.Buffs; using WingsEmu.Game.Characters; using WingsEmu.Game.Configurations; using WingsEmu.Game.Entities; using WingsEmu.Game.Extensions; using WingsEmu.Game.Helpers.Damages; using WingsEmu.Game.Managers.StaticData; using WingsEmu.Game.Maps; using WingsEmu.Game.RainbowBattle; using WingsEmu.Game.Shops; using WingsEmu.Game.Skills; using WingsEmu.Packets.Enums; using WingsEmu.Packets.Enums.Battle; namespace Plugin.CoreImpl.Entities { public class NpcEntity : INpcEntity { private readonly IAsyncEventPipeline _asyncEventPipeline; private readonly ICastingComponent _castingComponent; private readonly IEndBuffDamageComponent _endBuffDamageComponent; private readonly IEventTriggerContainer _eventContainer; public NpcEntity(IEventTriggerContainer eventContainer, IBCardComponent bCardComponent, IAsyncEventPipeline asyncEventPipeline, IMonsterData monsterData, IMapInstance mapInstance, ISkillsManager skillsManager, MapNpcDTO npcDto = null, ShopNpc shopNpc = null, int? id = null, INpcAdditionalData npcAdditionalData = null, short? optionalDialog = null) { UniqueId = Guid.NewGuid(); BCardComponent = bCardComponent; _castingComponent = new CastingComponent(); ChargeComponent = new ChargeComponent(); Damagers = new ThreadSafeHashSet(); _endBuffDamageComponent = new EndBuffDamageComponent(); _eventContainer = eventContainer; BuffComponent = new BuffComponent(); _asyncEventPipeline = asyncEventPipeline; ShouldRespawn = true; IsHostile = RawHostility != (int)HostilityType.NOT_HOSTILE; if (npcDto != null) { Id = npcDto.Id; Dialog = npcDto.Dialog; QuestDialog = npcDto.QuestDialog; Direction = npcDto.Direction; Effect = npcDto.Effect; EffectDelay = TimeSpan.FromMilliseconds(npcDto.EffectDelay); IsDisabled = npcDto.IsDisabled; IsMoving = npcDto.IsMoving; IsSitting = npcDto.IsSitting; PositionX = npcDto.MapX; PositionY = npcDto.MapY; MapId = npcDto.MapId; CanAttack = npcDto.CanAttack; HasGodMode = npcDto.HasGodMode; CustomName = npcDto.CustomName; IsHostile = npcDto.CanAttack; } Dialog = optionalDialog ?? Dialog; 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; CanWalk = monsterData.CanWalk; 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; BaseDamageMaximum = monsterData.BaseDamageMaximum; BaseDamageMinimum = monsterData.BaseDamageMinimum; BaseDarkResistance = monsterData.BaseDarkResistance; DeathEffect = monsterData.DeathEffect; BaseMaxHp = monsterData.BaseMaxHp; BaseMaxMp = monsterData.BaseMaxMp; DefenceDodge = monsterData.DefenceDodge; DefenceUpgrade = monsterData.DefenceUpgrade; DisappearAfterHitting = monsterData.DisappearAfterHitting; DisappearAfterSeconds = monsterData.DisappearAfterSeconds; DisappearAfterSecondsMana = monsterData.DisappearAfterSecondsMana; DistanceDefence = monsterData.DistanceDefence; DistanceDefenceDodge = monsterData.DistanceDefenceDodge; Drops = monsterData.Drops; 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; Level = BaseLevel; if (npcAdditionalData != null) { IsTimeSpaceMate = npcAdditionalData.IsTimeSpaceMate; IsProtected = npcAdditionalData.IsProtected; MinilandOwner = npcAdditionalData.MinilandOwner; ShouldRespawn = npcAdditionalData.NpcShouldRespawn; CanMove = npcAdditionalData.CanMove; IsMoving = npcAdditionalData.CanMove; CanAttack = npcAdditionalData.CanAttack; Direction = npcAdditionalData.NpcDirection; IsHostile = npcAdditionalData.IsHostile; Faction = npcAdditionalData.FactionType; TimeSpaceInfo = npcAdditionalData.TimeSpaceInfo; TimeSpaceOwnerId = npcAdditionalData.TimeSpaceOwnerId; RainbowFlag = npcAdditionalData.RainbowFlag; if (npcAdditionalData.HpMultiplier.HasValue) { BaseMaxHp = (int)(BaseMaxHp * npcAdditionalData.HpMultiplier.Value); Hp = BaseMaxHp; } if (npcAdditionalData.MpMultiplier.HasValue) { BaseMaxMp = (int)(BaseMaxMp * npcAdditionalData.MpMultiplier.Value); Mp = BaseMaxMp; } if (npcAdditionalData.CustomLevel.HasValue) { Level = npcAdditionalData.CustomLevel.Value; } } LastEffect = Death = SpawnDate = DateTime.UtcNow; FirstX = PositionX; FirstY = PositionY; IsStillAlive = true; Speed = BaseSpeed; Hp = BaseMaxHp; Mp = BaseMaxMp; Killer = null; Target = null; MonsterSkills = monsterData.MonsterSkills; this.InitializeBCards(); ShopNpc = shopNpc; CurrentCollection = MaxTries; CanSeeInvisible = monsterData.CanSeeInvisible; // pure puke, needs proper entity rework :( if (monsterData is NpcMonsterDto npcMonsterDto) { var skills = new List(); foreach (NpcMonsterSkillDTO skill in npcMonsterDto.Skills) { var monsterSkill = new NpcMonsterSkill(skillsManager.GetSkill(skill.SkillVNum), skill.Rate, skill.IsBasicAttack, skill.IsIgnoringHitChance); skills.Add(monsterSkill); Skills.Add(monsterSkill); if (monsterSkill.IsBasicAttack) { ReplacedBasicSkill = monsterSkill; } } NotBasicSkills = skills.Where(x => !x.IsBasicAttack).ToArray(); if (HasDash) { DashSkill = skills.Count != 0 ? skills[0] : null; } SkillsWithoutDashSkill = skills.Where(x => DashSkill != null && DashSkill.Skill.Id != x.Skill.Id).ToArray(); } BasicSkill = new SkillInfo { Vnum = default, AttackType = AttackType, Range = BasicRange, CastTime = BasicCastTime, Cooldown = BasicCooldown, HitEffect = AttackEffect, Element = BaseElement, HitChance = BasicHitChance, HitAnimation = 11, TargetType = TargetType.Target, TargetAffectedEntities = TargetAffectedEntities.Enemies, SkillType = SkillType.MonsterSkill }; if (id != null) { Id = id.Value; } if (Id == default) { Id = mapInstance.GenerateEntityId(); } MapInstance = mapInstance; } public IBCardComponent BCardComponent { get; } public IChargeComponent ChargeComponent { get; } public IBuffComponent BuffComponent { get; } public byte Size { get; set; } = 10; public DateTime SpawnDate { get; set; } public VisualType Type => VisualType.Npc; 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 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); } #region Properties 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; } 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; } public short BaseConcentrate { get; } public short BaseCriticalChance { get; } public short BaseCriticalRate { get; } public bool DamagedOnlyLastJajamaruSkill { get; } public int BaseDamageMaximum { get; } public int BaseDamageMinimum { get; } public short BaseDarkResistance { get; } public short DeathEffect { get; } public int BaseMaxHp { get; } public int BaseMaxMp { get; } public short DefenceDodge { get; } public byte DefenceUpgrade { get; } public bool DisappearAfterHitting { get; } public bool DisappearAfterSeconds { get; } public bool DisappearAfterSecondsMana { get; } public short DistanceDefence { get; } public short DistanceDefenceDodge { get; } 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; } public byte BaseLevel { get; } public short BaseLightResistance { get; } public short MagicDefence { get; } public short MagicMpFactor { get; } public short MeleeHpFactor { get; } public sbyte MinimumAttackRange { get; } public int MonsterVNum { get; } public string Name { get; } public byte NoticeRange { get; } public bool OnDefenseOnlyOnce { get; } public short PermanentEffect { get; } public MonsterRaceType MonsterRaceType { get; } 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; } 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 bool TeleportBackOnNoticeRangeExceed { get; } public int BaseXp { get; } public int BaseJobXp { get; } public FactionType Faction { get; } = FactionType.Neutral; public byte Speed { get; set; } public bool ShouldRespawn { get; set; } public short FirstX { get; set; } public short FirstY { get; set; } public Guid UniqueId { get; } public bool IsHostile { get; } public float? HpMultiplier { get; } public float? MpMultiplier { get; } public byte? CustomLevel { get; } public FactionType FactionType { get; } public long? TimeSpaceOwnerId { get; } public TimeSpaceFileConfiguration TimeSpaceInfo { get; } public bool IsTimeSpaceMate { get; set; } public bool IsProtected { get; set; } public IPlayerEntity MinilandOwner { get; } public bool NpcShouldRespawn { get; } public bool CanMove { get; } public ThreadSafeHashSet Targets { get; } = new(); public ThreadSafeHashSet<(VisualType, long)> TargetsByVisualTypeAndId { get; } = new(); public DateTime LastTargetsRefresh { get; set; } public DateTime Death { get; set; } public DateTime LastEffect { get; set; } public DateTime LastSkill { get; set; } public DateTime LastSpecialHpDecrease { get; set; } public IMapInstance MapInstance { get; private set; } public IBattleEntity Killer { get; set; } 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 (VisualType, long) LastAttackedEntity { get; set; } public bool IsRunningAway { get; set; } public byte ReturnTimeOut { get; set; } public IReadOnlyList NotBasicSkills { get; } public IReadOnlyList SkillsWithoutDashSkill { get; } public INpcMonsterSkill ReplacedBasicSkill { get; } public INpcMonsterSkill DashSkill { get; } public byte Level { get; set; } public int Hp { get; set; } public int MaxHp { get => this.GetMaxHp(BaseMaxHp); set => throw new NotImplementedException(); } public int Mp { get; set; } public int MaxMp { get => this.GetMaxMp(BaseMaxMp); set => throw new NotImplementedException(); } public SkillInfo BasicSkill { get; } public ThreadSafeHashSet Damagers { get; } public IReadOnlyList Drops { get; } public bool CanSeeInvisible { get; } public IReadOnlyList BCards { get; } public IReadOnlyList ModeBCards { get; } private IReadOnlyList _monsterSkills; public IReadOnlyList MonsterSkills { get => _monsterSkills; private set { _monsterSkills = value; Skills = value.Cast().ToList(); } } public List Skills { get; private set; } public byte Element { get; set; } public int ElementRate { get; set; } public int FireResistance { get; set; } public int WaterResistance { get; set; } public int LightResistance { get; set; } public int DarkResistance { get; set; } public int DamagesMinimum { get; set; } public int DamagesMaximum { get; set; } public Position Position { get => new(PositionX, PositionY); set { Position pos = value; PositionX = pos.X; PositionY = pos.Y; } } public short PositionX { get; private set; } public short PositionY { get; private set; } public bool IsStillAlive { get; set; } 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 bool HasGodMode { get; } public byte CurrentCollection { get; set; } public DateTime LastCollection { get; set; } = DateTime.UtcNow; public string CustomName { get; } public long? CharacterPartnerId { get; set; } public DateTime LastBasicAttack { get; set; } public DateTime LastTimeSpaceHeal { get; set; } public RainBowFlag RainbowFlag { get; set; } public void ChangeMapInstance(IMapInstance mapInstance) { MapInstance = mapInstance; } public bool CanAttack { get; set; } public byte NpcDirection { get; } public ThreadSafeHashSet AggroedEntities { get; } = new(); public int Id { get; } public short Dialog { get; } public int? QuestDialog { get; } public short Effect { get; } public TimeSpan EffectDelay { get; } public bool IsDisabled { get; } public bool IsMoving { get; } public bool IsSitting { get; } public int MapId { get; } public int NpcVNum => MonsterVNum; public ShopNpc ShopNpc { get; set; } public byte Direction { get; set; } #endregion } }