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

652 lines
No EOL
25 KiB
C#

// 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<INpcMonsterSkill> npcMonsterSkills, IBattleEntityAlgorithmService battleEntityAlgorithmService)
{
UniqueId = builder?.GeneratedGuid ?? Guid.NewGuid();
BCardComponent = bCardComponent;
_castingComponent = new CastingComponent();
Damagers = new ThreadSafeHashSet<IBattleEntity>();
_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<long, int> PlayersDamage { get; } = new();
public DateTime LastMpRegen { get; set; }
public Position? GoToBossPosition { get; set; }
public bool IsInstantBattle { get; set; }
public IEnumerable<DropChance> RaidDrop { get; }
public DateTime LastBonusEffectTime { get; set; }
public DateTime AttentionTime { get; set; }
public ConcurrentDictionary<byte, Waypoint> 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<INpcMonsterSkill> NotBasicSkills { get; }
public IReadOnlyList<INpcMonsterSkill> SkillsWithoutDashSkill { get; }
public INpcMonsterSkill ReplacedBasicSkill { get; }
public INpcMonsterSkill DashSkill { get; }
public IBCardComponent BCardComponent { get; }
public IChargeComponent ChargeComponent { get; }
public ThreadSafeHashSet<Guid> 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<IBattleEntity> Damagers { 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 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<IBattleEntitySkill> 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>(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 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<DropDTO> Drops { get; }
public bool CanSeeInvisible { get; }
public IReadOnlyList<BCardDTO> BCards { get; }
public IReadOnlyList<BCardDTO> ModeBCards { get; }
public IReadOnlyList<INpcMonsterSkill> 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<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);
}
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
};
}
}
}