server-master/srcs/_plugins/Plugin.CoreImpl/Entities/NpcEntity.cs
2026-02-10 18:21:30 +01:00

582 lines
No EOL
23 KiB
C#

// 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<IBattleEntity>();
_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<INpcMonsterSkill>();
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>(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>(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<short, int> 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<IBattleEntity> 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<INpcMonsterSkill> NotBasicSkills { get; }
public IReadOnlyList<INpcMonsterSkill> 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<IBattleEntity> Damagers { get; }
public IReadOnlyList<DropDTO> Drops { get; }
public bool CanSeeInvisible { get; }
public IReadOnlyList<BCardDTO> BCards { get; }
public IReadOnlyList<BCardDTO> ModeBCards { get; }
private IReadOnlyList<INpcMonsterSkill> _monsterSkills;
public IReadOnlyList<INpcMonsterSkill> MonsterSkills
{
get => _monsterSkills;
private set
{
_monsterSkills = value;
Skills = value.Cast<IBattleEntitySkill>().ToList();
}
}
public List<IBattleEntitySkill> 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<Guid> 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
}
}