using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using PhoenixLib.Logging; using WingsAPI.Packets.Enums.Shells; using WingsEmu.DTOs.BCards; using WingsEmu.DTOs.Maps; using WingsEmu.DTOs.Skills; using WingsEmu.Game._enum; using WingsEmu.Game._i18n; using WingsEmu.Game.Battle; using WingsEmu.Game.Buffs; using WingsEmu.Game.Characters; using WingsEmu.Game.Entities; using WingsEmu.Game.Helpers.Damages; using WingsEmu.Game.Maps; using WingsEmu.Game.Mates; using WingsEmu.Game.Networking; using WingsEmu.Game.Networking.Broadcasting; using WingsEmu.Game.RainbowBattle; using WingsEmu.Game.Skills; using WingsEmu.Packets.Enums; using WingsEmu.Packets.Enums.Battle; using WingsEmu.Packets.Enums.Character; using WingsEmu.Packets.Enums.Chat; using WingsEmu.Packets.ServerPackets.Battle; namespace WingsEmu.Game.Extensions; public static class BattleEntityExtension { /// /// Check if the BattleEntity is in the given Range. /// /// The BattleEntity from which position check /// The X coordinate on the Map of the object to check. /// The Y coordinate on the Map of the object to check. /// The maximum distance of the object to check. /// True if the BattleEntity is in range, False if not. public static bool IsInRange(this IBattleEntity battleEntity, short mapX, short mapY, byte distance) => battleEntity.Position.GetDistance(mapX, mapY) <= distance; public static bool IsInvisible(this IBattleEntity seen) { if (seen is IPlayerEntity player && player.CheatComponent.IsInvisible) { return true; } return seen.BCardComponent.HasBCard(BCardType.SpecialActions, (byte)AdditionalTypes.SpecialActions.Hide) || seen.BCardComponent.HasBCard(BCardType.FalconSkill, (byte)AdditionalTypes.FalconSkill.Hide) || seen.BCardComponent.HasBCard(BCardType.FalconSkill, (byte)AdditionalTypes.FalconSkill.Ambush); } public static bool HasGodMode(this IBattleEntity entity) { if (entity is IPlayerEntity player && player.CheatComponent.HasGodMode) { return true; } if (entity.BCardComponent.HasBCard(BCardType.TimeCircleSkills, (byte)AdditionalTypes.TimeCircleSkills.DisableHPConsumption)) { return true; } if (entity.BCardComponent.HasBCard(BCardType.NoDefeatAndNoDamage, (byte)AdditionalTypes.NoDefeatAndNoDamage.NeverReceiveDamage)) { return true; } return entity.BCardComponent.HasBCard(BCardType.HideBarrelSkill, (byte)AdditionalTypes.HideBarrelSkill.NoHPConsumption); } public static int ApplyCooldownReduction(this IBattleEntity battleEntity, SkillInfo skill) { short cooldown = skill.Cooldown; if (battleEntity.NoCooldown()) { return 0; } if (ResetByFairyWings(battleEntity, skill)) { return 0; } (int firstData, int secondData) cooldownDecrease = battleEntity.BCardComponent.GetAllBCardsInformation(BCardType.Morale, (byte)AdditionalTypes.Morale.SkillCooldownDecreased, battleEntity.Level); (int firstData, int secondData) cooldownIncrease = battleEntity.BCardComponent.GetAllBCardsInformation(BCardType.Morale, (byte)AdditionalTypes.Morale.SkillCooldownIncreased, battleEntity.Level); double increaseMultiplier = cooldownIncrease.firstData * 0.01; double decreaseMultiplier = cooldownDecrease.firstData * 0.01; double newCooldown = cooldown * increaseMultiplier * decreaseMultiplier; cooldown = (short)newCooldown; return cooldown < 0 ? 0 : cooldown; } private static bool ResetByFairyWings(IBattleEntity battleEntity, SkillInfo skill) { if (!battleEntity.BCardComponent.HasBCard(BCardType.EffectSummon, (byte)AdditionalTypes.EffectSummon.CooldownResetChance)) { return false; } if (skill.TargetAffectedEntities != TargetAffectedEntities.Enemies) { return false; } if (skill.CastId == 0) { return false; } if (battleEntity is not IPlayerEntity playerEntity) { return false; } int firstData = playerEntity.BCardComponent.GetAllBCardsInformation(BCardType.EffectSummon, (byte)AdditionalTypes.EffectSummon.CooldownResetChance, playerEntity.Level).firstData; if (StaticRandomGenerator.Instance.RandomNumber() > firstData) { return false; } if (!playerEntity.SkillComponent.SkillsResets.TryGetValue(skill.Vnum, out byte usages)) { usages = 0; } if (playerEntity.SkillComponent.LastSkillVnum.HasValue && playerEntity.SkillComponent.LastSkillVnum.Value == skill.Vnum) { usages += 1; } else { playerEntity.SkillComponent.SkillsResets.Clear(); } if (usages > 1) { return false; } playerEntity.SkillComponent.LastSkillVnum = skill.Vnum; playerEntity.SkillComponent.SkillsResets[skill.Vnum] = usages; playerEntity.BroadcastEffectInRange(EffectType.FairyResetBuff); return true; } private static bool NoCooldown(this IBattleEntity entity) => entity is IPlayerEntity character && character.CheatComponent.HasNoCooldown; public static IEnumerable GetEnemiesInRange(this IBattleEntity sender, IBattleEntity caster, short range) => sender.Position.GetEnemiesInRange(caster, range); public static IEnumerable GetAlliesInRange(this IBattleEntity sender, IBattleEntity caster, short range) => sender.Position.GetAlliesInRange(caster, range); public static List GetCellsInLine(this Position pos1, Position pos2) { var cells = new List(); short x0 = pos1.X; short y0 = pos1.Y; short x1 = pos2.X; short y1 = pos2.Y; int dx = Math.Abs(x1 - x0); int sx = x0 < x1 ? 1 : -1; int dy = -Math.Abs(y1 - y0); int sy = y0 < y1 ? 1 : -1; int err = dx + dy; while (true) { cells.Add(new Position(x0, y0)); if (x0 == x1 && y0 == y1) { break; } int e2 = 2 * err; if (e2 >= dy) { err += dy; x0 += (short)sx; } if (e2 > dx) { continue; } err += dx; y0 += (short)sy; } return cells; } public static void ChangeSize(this IBattleEntity battleEntity, byte size) { battleEntity.Size = size; battleEntity.MapInstance.Broadcast(battleEntity.GenerateScal()); } public static int GetHpPercentage(this IBattleEntity target) { if (target.Hp == 0 || target.MaxHp == 0) { return 0; } int hpPercentage = (int)(target.Hp / (float)target.MaxHp * 100); return hpPercentage == 0 ? 1 : hpPercentage; } public static bool ShouldSendScal(this IBattleEntity battleEntity) => battleEntity.Size != 10; public static string GenerateScal(this IBattleEntity battleEntity) => $"char_sc {((byte)battleEntity.Type).ToString()} {battleEntity.Id.ToString()} {battleEntity.Size.ToString()}"; public static string GenerateEffectPacket(this IBattleEntity battleEntity, int effectVnum) => $"eff {(byte)battleEntity.Type} {battleEntity.Id} {effectVnum}"; public static string GenerateEffectPacket(this IBattleEntity battleEntity, EffectType effectType) => battleEntity.GenerateEffectPacket((int)effectType); public static void BroadcastEffectInRange(this IBattleEntity entity, EffectType effectType) { if (entity.IsInvisibleGm()) { return; } entity.MapInstance.Broadcast(entity.GenerateEffectPacket((int)effectType), new RangeBroadcast(entity.PositionX, entity.PositionY)); } public static void BroadcastEffectInRange(this IBattleEntity entity, int effectId) { if (entity.IsInvisibleGm()) { return; } entity.MapInstance.Broadcast(entity.GenerateEffectPacket(effectId), new RangeBroadcast(entity.PositionX, entity.PositionY)); } public static string GenerateMvPacket(this IBattleEntity entity, int speed = default) => $"mv {(byte)entity.Type} {entity.Id} {entity.PositionX} {entity.PositionY} {(speed == default ? entity.Speed : speed).ToString()}"; public static string GenerateStPacket(this IBattleEntity target) { return $"st {(byte)target.Type} {target.Id} {target.Level} {(target is IPlayerEntity character ? character.HeroLevel : 0)} {target.HpPercentage} {target.MpPercentage} {target.Hp} {target.Mp}{target.BuffComponent.GetAllBuffs().Aggregate(string.Empty, (current, buff) => current + $" {buff.CardId}.{buff.CasterLevel}")}"; } public static string GenerateTeleportPacket(this IBattleEntity entity, short x, short y) => $"tp {(byte)entity.Type} {entity.Id} {x} {y} 0"; public static void TeleportOnMap(this IBattleEntity entity, short x, short y, bool teleportMates = false) { var newPosition = new Position(x, y); switch (entity) { case IPlayerEntity playerEntity: if (!teleportMates) { entity.ChangePosition(newPosition); entity.MapInstance.Broadcast(entity.GenerateTeleportPacket(x, y)); return; } IReadOnlyList nosMate = playerEntity.MateComponent.TeamMembers(); foreach (IMateEntity mate in nosMate) { mate.ChangePosition(newPosition); playerEntity.MapInstance.Broadcast(mate.GenerateTeleportPacket(x, y)); } break; } entity.ChangePosition(newPosition); entity.MapInstance.Broadcast(entity.GenerateTeleportPacket(x, y)); } public static int GetMpPercentage(this IBattleEntity target) { if (target.Mp == 0 || target.MaxMp == 0) { return 0; } int mpPercentage = (int)(target.Mp / (float)target.MaxMp * 100); return mpPercentage == 0 ? 1 : mpPercentage; } public static bool IsPvpActivated(this IBattleEntity entity, IBattleEntity target) { if (!target.IsPlayer() && !target.IsMate()) { return false; } if (entity.Id == target.Id && entity.Type == target.Type) { return false; } if (entity.BuffComponent.HasBuff((short)BuffVnums.PVP) && !entity.MapInstance.IsPvp) { // BIGU SWITCHU 2021 KEKW // 1 -> when target is Player (A) and A have PvP buff and entity isn't in the same group with A // 2 -> when target is Mate (B) and owner (C) have PvP buff and entity isn't in the same group with C // 3 -> when caster is Mate and target is Player (D) and Mate Owner (E) have PvP buff and D have PvP buff and E isn't in the same group with D // 4 => when caster is Mate (F - Owner) and target is Mate (G - Owner) and F and G have PvP buff and F isn't in the same group with G return entity switch { IPlayerEntity character when target is IPlayerEntity characterTarget => characterTarget.BuffComponent.HasBuff((short)BuffVnums.PVP) && character.IsInGroupOf(characterTarget) == false, IPlayerEntity character when target is IMateEntity mateTarget => mateTarget.Owner.Id != character.Id && mateTarget.Owner.BuffComponent.HasBuff((short)BuffVnums.PVP) && character.IsInGroupOf(mateTarget.Owner) == false, IMateEntity mate when target is IPlayerEntity character => mate.Owner.Id != character.Id && character.BuffComponent.HasBuff((short)BuffVnums.PVP) && mate.Owner.BuffComponent.HasBuff((short)BuffVnums.PVP) && mate.Owner.IsInGroupOf(character) == false, IMateEntity mate when target is IMateEntity mateTarget => mate.Owner.Id != mateTarget.Owner.Id && mate.Owner.BuffComponent.HasBuff((short)BuffVnums.PVP) && mateTarget.Owner.BuffComponent.HasBuff((short)BuffVnums.PVP) && mate.Owner.IsInGroupOf(mateTarget.Owner) == false, _ => false }; } if (entity.MapInstance.MapInstanceType == MapInstanceType.RainbowBattle) { switch (entity) { case IPlayerEntity character when target is IPlayerEntity characterTarget: RainbowBattleParty rainbowParty = character.RainbowBattleComponent.RainbowBattleParty; return rainbowParty is { Started: true, FinishTime: null } && !characterTarget.RainbowBattleComponent.IsFrozen && !character.RainbowBattleComponent.IsFrozen && characterTarget.RainbowBattleComponent.Team != character.RainbowBattleComponent.Team; case IPlayerEntity character when target is IMateEntity mateTarget: RainbowBattleParty rainbowPartyTwo = character.RainbowBattleComponent.RainbowBattleParty; return rainbowPartyTwo is { Started: true, FinishTime: null } && mateTarget.Owner.Id != character.Id && !mateTarget.Owner.RainbowBattleComponent.IsFrozen && !character.RainbowBattleComponent.IsFrozen && mateTarget.Owner.RainbowBattleComponent.Team != character.RainbowBattleComponent.Team; case IMateEntity mate when target is IPlayerEntity character: RainbowBattleParty rainbowPartyThree = mate.Owner.RainbowBattleComponent.RainbowBattleParty; return rainbowPartyThree is { Started: true, FinishTime: null } && !mate.Owner.RainbowBattleComponent.IsFrozen && !character.RainbowBattleComponent.IsFrozen && mate.Owner.Id != character.Id && mate.Owner.RainbowBattleComponent.Team != character.RainbowBattleComponent.Team; case IMateEntity mate when target is IMateEntity mateTarget: RainbowBattleParty rainbowPartyFour = mate.Owner.RainbowBattleComponent.RainbowBattleParty; return rainbowPartyFour is { Started: true, FinishTime: null } && !mate.Owner.RainbowBattleComponent.IsFrozen && !mateTarget.Owner.RainbowBattleComponent.IsFrozen && mate.Owner.Id != mateTarget.Owner.Id && mate.Owner.RainbowBattleComponent.Team != mateTarget.Owner.RainbowBattleComponent.Team; default: return false; } } switch (entity.MapInstance.IsPvp) { case false: case true when !target.IsInPvpZone(): return false; } if (entity.MapInstance.HasMapFlag(MapFlags.ACT_4)) { return entity.Faction != target.Faction; } return entity switch { IPlayerEntity character when target is IPlayerEntity characterTarget => !character.ArenaImmunity.HasValue && !characterTarget.ArenaImmunity.HasValue && character.IsInGroupOf(characterTarget) == false, IPlayerEntity character when target is IMateEntity mateTarget => mateTarget.Owner.IsInPvpZone() && !character.ArenaImmunity.HasValue && !mateTarget.Owner.ArenaImmunity.HasValue && mateTarget.Owner.Id != character.Id && character.IsInGroupOf(mateTarget.Owner) == false, IMateEntity mate when target is IPlayerEntity character => mate.Owner.IsInPvpZone() && !character.ArenaImmunity.HasValue && !mate.Owner.ArenaImmunity.HasValue && mate.Owner.Id != character.Id && mate.Owner.IsInGroupOf(character) == false, IMateEntity mate when target is IMateEntity mateTarget => mateTarget.Owner.IsInPvpZone() && mate.Owner.IsInPvpZone() && !mateTarget.Owner.ArenaImmunity.HasValue && !mate.Owner.ArenaImmunity.HasValue && mate.Owner.Id != mateTarget.Owner.Id && mate.Owner.IsInGroupOf(mateTarget.Owner) == false, _ => false }; } public static bool IsEnemyWith(this IBattleEntity entity, IBattleEntity target) { if (entity.IsSameEntity(target)) { return false; } switch (target) { case IPlayerEntity playerEntity: if (playerEntity.IsSeal) { return false; } break; case IMateEntity mateEntity: if (mateEntity.Owner.IsOnVehicle) { return false; } break; } switch (entity) { case IPlayerEntity: if (target is not IMonsterEntity monsterEntitySummoner) { return target.IsMonster() || entity.IsPvpActivated(target); } if (entity.Id == monsterEntitySummoner.SummonerId && entity.Type == monsterEntitySummoner.SummonerType && !monsterEntitySummoner.IsMateTrainer) { return false; } if (monsterEntitySummoner.SummonerType is VisualType.Player && !monsterEntitySummoner.IsMateTrainer) { if (!monsterEntitySummoner.MapInstance.IsPvp || !monsterEntitySummoner.SummonerId.HasValue) { return false; } IPlayerEntity player = monsterEntitySummoner.MapInstance.GetCharacterById(monsterEntitySummoner.SummonerId.Value); return player is null || player.IsPvpActivated(entity); } if (monsterEntitySummoner.Faction == entity.Faction && monsterEntitySummoner.MapInstance.HasMapFlag(MapFlags.ACT_4)) { return false; } return target.IsMonster() || entity.IsPvpActivated(target); case IMateEntity mate: if (target is not IMonsterEntity monsterSummoner) { return entity.IsPvpActivated(target); } if (monsterSummoner.IsMateTrainer) { return true; } int mateOwnerId = mate.Owner.Id; if (monsterSummoner.SummonerType is not VisualType.Player) { return true; } if (!monsterSummoner.SummonerId.HasValue) { return true; } if (!monsterSummoner.MapInstance.IsPvp) { return monsterSummoner.SummonerId.Value != mateOwnerId; } IPlayerEntity playerMate = monsterSummoner.MapInstance.GetCharacterById(monsterSummoner.SummonerId.Value); return playerMate is null || playerMate.IsPvpActivated(mate.Owner); } if (entity.IsNpc()) { return !(target.IsNpc() || target.IsPlayer() || target.IsMate()); } if (entity is not IMonsterEntity mob) { return false; } if (mob.MapInstance.HasMapFlag(MapFlags.ACT_4)) { if (target.Faction == mob.Faction) { return false; } } if (mob.IsMateTrainer || !mob.SummonerId.HasValue || !mob.SummonerType.HasValue || mob.MonsterVNum == (short)MonsterVnum.ONYX_MONSTER) { return target.IsPlayer() || target.IsNpc() || target.IsMate() || target.CheckIfIsSummonedMonster(mob); } if (mob.SummonerId == target.Id && mob.SummonerType == target.Type) { return false; } if (target is IMonsterEntity monsterEntity) { if (mob.SummonerId == monsterEntity.SummonerId) { return false; } if (monsterEntity.IsMateTrainer) { return false; } if (monsterEntity.SummonerType is null or VisualType.Monster && mob.SummonerType is null or VisualType.Monster) { return false; } } IBattleEntity summoner = mob.MapInstance?.GetBattleEntity(mob.SummonerType.Value, mob.SummonerId.Value); if (summoner == null && mob.SummonerType.Value == VisualType.Player) { return false; } return summoner switch { IPlayerEntity character => character.IsEnemyWith(target), _ => target.IsPlayer() || target.IsMate() || target.IsMonster() || target.IsNpc() }; } public static bool CanMonsterBeAttacked(this IBattleEntity entity, IMonsterEntity monsterEntity) { if (monsterEntity.SummonerType is not VisualType.Player) { return true; } if (monsterEntity.SummonerId is null) { return true; } IPlayerEntity summoner = monsterEntity.MapInstance.GetCharacterById(monsterEntity.SummonerId.Value); return summoner == null || summoner.IsEnemyWith(entity); } private static bool CheckIfIsSummonedMonster(this IBattleEntity entity, IMonsterEntity monster) { if (entity is not IMonsterEntity monsterEntity) { return false; } if (monsterEntity.SummonerType is not VisualType.Player) { return false; } if (monster.SummonerType == monsterEntity.SummonerType && monsterEntity.SummonerId == monster.SummonerId) { return false; } IPlayerEntity player = monster.SummonerId.HasValue ? entity.MapInstance.GetCharacterById(monster.SummonerId.Value) : null; return player == null || player.IsEnemyWith(monster); } public static bool IsAllyWith(this IBattleEntity entity, IBattleEntity target) { if (target.IsMate()) { if (target is IMateEntity mateEntity && mateEntity.Owner.IsOnVehicle) { return false; } } if (entity.IsPlayer()) { if (target is IMonsterEntity monster) { if (monster.Faction == entity.Faction && monster.MapInstance != null && monster.MapInstance.HasMapFlag(MapFlags.ACT_4)) { return true; } } return !target.IsMonster() && (target.IsNpc() || entity.IsPvpActivated(target) == false); } if (entity.IsMate()) { if (target is IMonsterEntity monster) { if (monster.Faction == entity.Faction && monster.MapInstance != null && monster.MapInstance.HasMapFlag(MapFlags.ACT_4)) { return true; } } return !target.IsMonster() && (target.IsNpc() || entity.IsPvpActivated(target) == false); } if (entity.IsMonster()) { return target.IsMonster(); } if (!entity.IsNpc()) { return false; } if (target is not IMonsterEntity mons) { return true; } if (mons.IsMateTrainer) { return true; } return mons.SummonerType is VisualType.Player; } public static void BroadcastCastPacket(this IBattleEntity caster, IBattleEntity target, SkillInfo skillInfo) { caster.MapInstance.Broadcast(caster.GenerateCtPacket(target, skillInfo)); } public static string GenerateCtPacket(this IBattleEntity caster, IBattleEntity target, SkillInfo skill) => $"ct {(byte)caster.Type} {caster.Id} {(target == null ? 0 : (byte)target.Type)} {target?.Id ?? -1} {(skill.CastAnimation == 0 ? -1 : skill.CastAnimation)} {(skill.CastEffect == 0 ? -1 : skill.CastEffect)} {skill.Vnum}"; public static string GenerateCleanSuPacket(this IBattleEntity entity, IBattleEntity target, int damage) => $"su {(byte)entity.Type} {entity.Id} {(byte)target.Type} {target.Id} -1 0 -1 0 0 0 {(target.IsAlive() ? 1 : 0)} {target.HpPercentage} {damage} 0 0"; public static string GenerateSuCapturePacket(this IBattleEntity caster, IBattleEntity target, SkillInfo skill, bool failed) => $"su {(byte)caster.Type} {caster.Id} {(byte)target.Type} {target.Id} {skill.Vnum} {skill.Cooldown} {(failed ? 16 : 15)} -1 -1 -1 1 0 0 -1 0"; public static string GenerateSuPacket(this IBattleEntity caster, IBattleEntity target, SkillInfo skill, int damages, SuPacketHitMode hitMode, Position position, bool isReflection, bool isFirst) { bool isArchMageSkill = skill.Vnum == (short)SkillsVnums.HOLY_EXPLOSION; return "su " + $"{(byte)caster.Type} " + $"{caster.Id} " + $"{(byte)target.Type} " + $"{target.Id} " + $"{(isReflection ? -1 : skill.Vnum)} " + $"{(isReflection ? 0 : skill.Cooldown)} " + $"{(isReflection || skill.Vnum == (short)SkillsVnums.SPY_OUT_SKILL ? -1 : skill.HitAnimation)} " + $"{(isReflection && isArchMageSkill ? 1047 : skill.HitEffect)} " + "0 " + "0 " + $"{(target.IsAlive() ? 1 : 0)} " + $"{target.HpPercentage} " + $"{damages} " + $"{(sbyte)hitMode} " + $"{(isReflection ? 1 : 0)}"; } public static string GenerateSuDashPacket(this IBattleEntity caster, IBattleEntity target, SkillInfo skill, int damages, SuPacketHitMode hitMode, bool isReflection = false, bool isFirst = false) { if (isReflection) { return "su " + $"{(byte)caster.Type} " + $"{caster.Id} " + $"{(byte)target.Type} " + $"{target.Id} " + $"{(isFirst ? -1 : skill.Vnum)} " + "0 " + "-1 " + $"{(isFirst ? skill.HitEffect : -1)} " + "-1 " + "-1 " + $"{(target.IsAlive() ? 1 : 0)} " + $"{target.GetHpPercentage()} " + $"{damages} " + $"{(sbyte)hitMode} " + "1"; } return "su " + $"{(byte)caster.Type} " + $"{caster.Id} " + $"{(byte)target.Type} " + $"{target.Id} " + $"{(!isFirst ? -1 : skill.Vnum)} " + $"{skill.Cooldown} " + $"{(isFirst ? skill.HitAnimation : -1)} " + $"{(isFirst ? skill.HitEffect : -1)} " + $"{(isFirst ? caster.PositionX : -1)} " + $"{(isFirst ? caster.PositionY : -1)} " + $"{(target.IsAlive() ? 1 : 0)} " + $"{target.GetHpPercentage()} " + $"{damages} " + $"{(sbyte)hitMode} " + $"{0}"; } public static string GenerateIcon(this IBattleEntity entity, bool isItem, int vnum) => $"icon {(byte)entity.Type} {entity.Id} {(isItem ? 1 : 2)} {vnum}"; public static void SendIconPacket(this IBattleEntity entity, bool isItem, int vnum) { switch (entity) { case IPlayerEntity character: character.Session.SendPacket(character.GenerateIcon(isItem, vnum)); break; case IMateEntity mateEntity: mateEntity.Owner?.Session.SendPacket(mateEntity.GenerateIcon(isItem, vnum)); break; } } public static void BroadcastCastNonTarget(this IBattleEntity caster, SkillInfo skillInfo) { caster.MapInstance.Broadcast(caster.GenerateCastNonTargetPacket(skillInfo)); } public static void BroadcastNonTargetSkill(this IBattleEntity caster, Position position, SkillInfo skill) { caster.MapInstance.Broadcast(caster.GenerateNonTargetSkill(position, skill)); } public static string GenerateNonTargetSkill(this IBattleEntity caster, Position position, SkillInfo skill) => $"bs 1 {caster.Id} {position.X} {position.Y} {skill.Vnum} {skill.Cooldown} {skill.HitAnimation} {skill.HitEffect} 0 0 1 1 0 0 0"; public static string GenerateCastNonTargetPacket(this IBattleEntity caster, SkillInfo skill) => $"ct_n 1 {caster.Id} 3 -1 {skill.CastAnimation} {skill.CastEffect} {skill.Vnum}"; public static void BroadcastSuPacket(this IBattleEntity caster, IBattleEntity target, SkillInfo skill, int damages, SuPacketHitMode hitMode, bool isReflection = false, bool isFirst = false) { if (skill.AttackType == AttackType.Dash) { caster.MapInstance.Broadcast(caster.GenerateSuDashPacket(target, skill, damages, hitMode, isReflection, isFirst)); return; } caster.MapInstance.Broadcast(caster.GenerateSuPacket(target, skill, damages, hitMode, target.Position, isReflection, isFirst)); } public static void BroadcastCleanSuPacket(this IBattleEntity caster, IBattleEntity target, int damage) => caster.MapInstance.Broadcast(caster.GenerateCleanSuPacket(target, damage)); public static bool CharacterCanCastOrCancel(this IBattleEntity entity, CharacterSkill skill, IGameLanguageService gameLanguage, SkillInfo skillInfo, bool removeAmmo) { var character = (IPlayerEntity)entity; if (character.Mp < skillInfo.ManaCost) { character.Session.SendCancelPacket(CancelType.NotInCombatMode); character.Session.SendSound(SoundType.NO_MANA); character.Session.SendChatMessage(gameLanguage.GetLanguage(GameDialogKey.INFORMATION_CHATMESSAGE_NOT_ENOUGH_MP, character.Session.UserLanguage), ChatMessageColorType.Yellow); return false; } if (!character.WeaponLoaded(skill, gameLanguage, removeAmmo)) { character.Session.SendDebugMessage("[BATTLE_DEBUG] No Weapon loaded"); character.Session.SendCancelPacket(CancelType.NotInCombatMode); return false; } if (character.SkillCanBeUsed(skill)) { return true; } character.Session.SendDebugMessage("[BATTLE_DEBUG] Skill under cooldown"); character.Session.SendCancelPacket(CancelType.NotInCombatMode); return false; } public static bool CharacterCanCastPartnerSkill(this IBattleEntity entity, IBattleEntitySkill skill, SkillInfo skillInfo) { var mateEntity = (IMateEntity)entity; return mateEntity.Mp >= skill.Skill.MpCost && mateEntity.SkillCanBeUsed(skill, DateTime.UtcNow); } public static void SendTargetConstBuffEffect(this IClientSession session, IBattleEntity battleEntity, Buff buff, int time) { session.SendPacket(battleEntity.GenerateConstBuffEffect(buff, time)); } public static void SendTargetConstBuffEffects(this IClientSession session, IBattleEntity battleEntity) { session.SendPackets(battleEntity.GenerateConstBuffEffects()); } public static void BroadcastConstBuffEffect(this IBattleEntity battleEntity, Buff buff, int time) { battleEntity.MapInstance?.Broadcast(battleEntity.GenerateConstBuffEffect(buff, time)); } public static void BroadcastConstBuffEffects(this IBattleEntity battleEntity) { battleEntity.MapInstance?.Broadcast(battleEntity.GenerateConstBuffEffects()); } public static IEnumerable GenerateConstBuffEffects(this IBattleEntity battleEntity) { return battleEntity.BuffComponent.GetAllBuffs(b => b.IsConstEffect).Select(buff => battleEntity.GenerateConstBuffEffect(buff, (int)buff.Duration.TotalMilliseconds)); } public static string GenerateConstBuffEffect(this IBattleEntity battleEntity, Buff buff, int time) => $"bf_e {((byte)battleEntity.Type).ToString()} {battleEntity.Id.ToString()} {buff.CardId.ToString()} {time.ToString()}"; public static async Task RemoveNegativeBuffs(this IBattleEntity entity, int level) { IReadOnlyList buffs = entity.BuffComponent.GetAllBuffs(x => x.Level <= level && x.BuffGroup == BuffGroup.Bad && x.IsNormal()); await entity.RemoveBuffAsync(false, buffs.ToArray()); } public static async Task RemovePositiveBuffs(this IBattleEntity entity, int level) { IReadOnlyList buffs = entity.BuffComponent.GetAllBuffs(x => x.Level <= level && x.BuffGroup == BuffGroup.Good && x.IsNormal()); await entity.RemoveBuffAsync(false, buffs.ToArray()); } public static async Task RemoveNeutralBuffs(this IBattleEntity entity, int level) { IReadOnlyList buffs = entity.BuffComponent.GetAllBuffs(x => x.Level <= level && x.BuffGroup == BuffGroup.Neutral && x.IsNormal()); await entity.RemoveBuffAsync(false, buffs.ToArray()); } public static async Task RemoveInvisibility(this IBattleEntity damager) { if (!damager.BuffComponent.HasAnyBuff()) { return; } if (damager is IPlayerEntity { Invisible: false }) { return; } if (!damager.BCardComponent.HasBCard(BCardType.SpecialActions, (byte)AdditionalTypes.SpecialActions.Hide)) { return; } if (damager is not IPlayerEntity charDamager) { return; } IReadOnlyList buffs = charDamager.BuffComponent.GetAllBuffs(b => b.BCards.Any(t => t.Type == (short)BCardType.SpecialActions && t.SubType == (byte)AdditionalTypes.SpecialActions.Hide)); foreach (Buff buff in buffs) { await damager.RemoveBuffAsync(false, buff); charDamager.Session.UpdateVisibility(); } foreach (IMateEntity mateEntity in charDamager.MateComponent.TeamMembers()) { if (!mateEntity.BuffComponent.HasAnyBuff()) { continue; } IReadOnlyList mateBuffs = mateEntity.BuffComponent.GetAllBuffs(b => b.BCards.Any(t => t.Type == (short)BCardType.SpecialActions && t.SubType == (byte)AdditionalTypes.SpecialActions.Hide)); foreach (Buff mateBuff in mateBuffs) { await mateEntity.RemoveBuffAsync(false, mateBuff); } } } public static bool CanPerformMove(this IBattleEntity entity) { switch (entity) { case IPlayerEntity playerEntity: RainbowBattleParty rainbowBattleParty = playerEntity.RainbowBattleComponent.RainbowBattleParty; if (rainbowBattleParty != null) { if (!playerEntity.RainbowBattleComponent.RainbowBattleParty.Started) { return false; } if (playerEntity.RainbowBattleComponent.IsFrozen) { return false; } } break; case IMateEntity mateEntity: if (mateEntity.MapInstance is { MapInstanceType: MapInstanceType.RainbowBattle }) { return false; } break; } return !entity.BCardComponent.HasBCard(BCardType.Move, (byte)AdditionalTypes.Move.MovementImpossible) && !entity.BuffComponent.HasBuff((int)BuffVnums.ETERNAL_ICE) && entity.IsAlive(); } public static bool CanPerformAttack(this IBattleEntity entity) { if (entity.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.NoAttack)) { return false; } if (entity.BuffComponent.HasBuff((int)BuffVnums.ETERNAL_ICE)) { return false; } if (!entity.IsAlive()) { return false; } switch (entity) { case IPlayerEntity playerEntity: if (playerEntity.IsSeal) { return false; } if (playerEntity.RainbowBattleComponent.IsFrozen) { return false; } break; case IMonsterEntity mapMonster: switch (mapMonster.AttackType) { case AttackType.Melee: if (mapMonster.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.MeleeDisabled)) { return false; } break; case AttackType.Ranged: if (mapMonster.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.RangedDisabled)) { return false; } break; case AttackType.Magical: if (mapMonster.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.MagicDisabled)) { return false; } break; } break; case IMateEntity mateEntity: if (mateEntity.MapInstance is { MapInstanceType: MapInstanceType.RainbowBattle }) { return false; } switch (mateEntity.AttackType) { case AttackType.Melee: if (mateEntity.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.MeleeDisabled)) { return false; } break; case AttackType.Ranged: if (mateEntity.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.RangedDisabled)) { return false; } break; case AttackType.Magical: if (mateEntity.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.MagicDisabled)) { return false; } break; } break; case INpcEntity mapNpc: switch (mapNpc.AttackType) { case AttackType.Melee: if (mapNpc.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.MeleeDisabled)) { return false; } break; case AttackType.Ranged: if (mapNpc.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.RangedDisabled)) { return false; } break; case AttackType.Magical: if (mapNpc.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.MagicDisabled)) { return false; } break; } break; } return true; } public static bool CanPerformAttack(this IPlayerEntity playerEntity, SkillInfo skillInfo) { return skillInfo.AttackType switch { AttackType.Melee => !playerEntity.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.MeleeDisabled), AttackType.Ranged => !playerEntity.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.RangedDisabled), AttackType.Magical => !playerEntity.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.MagicDisabled), _ => true }; } public static void ApplyAttackBCard(this IBattleEntity attacker, IBattleEntity defender, SkillInfo skillInfo, IBCardEffectHandlerContainer bCardHandler) { EquipmentType eqType = skillInfo.IsUsingSecondWeapon ? EquipmentType.MainWeapon : EquipmentType.SecondaryWeapon; bool isPlayer = attacker.IsPlayer(); if (isPlayer) { IReadOnlyList shellBCards = attacker.BCardComponent.GetShellTriggers(!skillInfo.IsUsingSecondWeapon); foreach (BCardDTO shellBCard in shellBCards) { bCardHandler.Execute(defender, attacker, shellBCard); } byte executeMainOrSecond = skillInfo.IsUsingSecondWeapon ? (byte)AdditionalTypes.SpecialEffects2.SecondaryWeaponCausingChance : (byte)AdditionalTypes.SpecialEffects2.MainWeaponCausingChance; foreach ((int _, BCardDTO bCard) in attacker.BCardComponent.GetBuffBCards(x => x.Item2.Type == (short)BCardType.SpecialEffects2 && x.Item2.SubType == executeMainOrSecond)) { bCardHandler.Execute(defender, attacker, bCard); } IReadOnlyDictionary> dictionary = attacker.BCardComponent.GetEquipmentBCards(); foreach (KeyValuePair> bCardDictionary in dictionary) { EquipmentType equipmentType = bCardDictionary.Key; if (equipmentType == eqType) { continue; } if (equipmentType != EquipmentType.MainWeapon && equipmentType != EquipmentType.SecondaryWeapon) { continue; } if (bCardDictionary.Value == null) { continue; } foreach (BCardDTO bCard in bCardDictionary.Value) { var castType = (CastType)bCard.CastType; switch (castType) { case CastType.ATTACK: bCardHandler.Execute(defender, attacker, bCard); break; case CastType.SELF: case CastType.DEFENSE: bCardHandler.Execute(attacker, attacker, bCard); break; } } } return; } if (attacker.IsMate()) { IReadOnlyDictionary> mateEq = attacker.BCardComponent.GetEquipmentBCards(); foreach (KeyValuePair> bCardDictionary in mateEq) { EquipmentType equipmentType = bCardDictionary.Key; if (equipmentType == eqType) { continue; } if (equipmentType != EquipmentType.MainWeapon && equipmentType != EquipmentType.SecondaryWeapon) { continue; } if (bCardDictionary.Value == null) { continue; } foreach (BCardDTO bCard in bCardDictionary.Value) { var castType = (CastType)bCard.CastType; switch (castType) { case CastType.ATTACK: bCardHandler.Execute(defender, attacker, bCard); break; case CastType.SELF: case CastType.DEFENSE: bCardHandler.Execute(attacker, attacker, bCard); break; } } } } IReadOnlyList attackerBCards = attacker.BCardComponent.GetTriggerBCards(BCardTriggerType.ATTACK); if (!attackerBCards.Any()) { return; } if (attacker.BCardComponent.HasBCard(BCardType.Mode, (byte)AdditionalTypes.Mode.DirectDamage)) { return; } foreach (BCardDTO bCard in attackerBCards.ToList()) { switch ((CastType)bCard.CastType) { case CastType.ATTACK: // during attacking on enemy (the target) bCardHandler.Execute(defender, attacker, bCard); break; case CastType.DEFENSE: // during attacking on self bCardHandler.Execute(attacker, attacker, bCard); break; default: continue; } } } public static void ApplyDefenderBCard(this IBattleEntity defender, IBattleEntity attacker, SkillInfo skillInfo, IBCardEffectHandlerContainer bCardHandler) { bool isPlayer = defender.IsPlayer(); if (isPlayer) { IReadOnlyDictionary> dictionary = defender.BCardComponent.GetEquipmentBCards(); foreach ((EquipmentType equipmentType, List value) in dictionary) { if (equipmentType is EquipmentType.MainWeapon or EquipmentType.SecondaryWeapon) { continue; } if (value == null) { continue; } foreach (BCardDTO bCard in value) { var castType = (CastType)bCard.CastType; switch (castType) { case CastType.ATTACK: bCardHandler.Execute(attacker, defender, bCard, skillInfo); break; case CastType.SELF: case CastType.DEFENSE: bCardHandler.Execute(defender, defender, bCard, skillInfo); break; } } } return; } IReadOnlyList defenderBCards = defender.BCardComponent.GetTriggerBCards(BCardTriggerType.DEFENSE); if (!defenderBCards.Any()) { return; } foreach (BCardDTO bCard in defenderBCards.ToList()) { switch ((CastType)bCard.CastType) { case CastType.ATTACK: // on getting hit on enemy (the attacker) bCardHandler.Execute(attacker, defender, bCard); break; case CastType.DEFENSE: // on getting hit on self bCardHandler.Execute(defender, defender, bCard); break; } } } public static void InitializeBCards(this IBattleEntity entity) { var attackBCards = new List(); var defenseBCards = new List(); IReadOnlyList entityBCards = entity switch { IMonsterEntity mapMonster => mapMonster.BCards, IMateEntity mateEntity => mateEntity.BCards, INpcEntity mapNpc => mapNpc.BCards, _ => null }; if (entityBCards == null) { Log.Warn($"Couldn't initialize BCards of this BattleEntity -> VisualType: '{entity.Type.ToString()}' Id: '{entity.Id.ToString()}'"); return; } foreach (BCardDTO bCard in entityBCards) { switch (bCard.NpcTriggerType) { case BCardNpcTriggerType.ON_ATTACK: if ((CastType)bCard.CastType == CastType.SELF) { entity.BCardComponent.AddBCard(bCard); } else { attackBCards.Add(bCard); } break; case BCardNpcTriggerType.ON_DEFENSE: if ((CastType)bCard.CastType == CastType.SELF) { entity.BCardComponent.AddBCard(bCard); } else { defenseBCards.Add(bCard); } break; default: entity.BCardComponent.AddBCard(bCard); break; } } entity.BCardComponent.AddTriggerBCards(BCardTriggerType.ATTACK, attackBCards); entity.BCardComponent.AddTriggerBCards(BCardTriggerType.DEFENSE, defenseBCards); if (entity is not IMonsterEntity monsterEntity) { return; } monsterEntity.RefreshStats(); } public static bool IsSameEntity(this IBattleEntity entity, IBattleEntity target) => entity != null && target != null && entity.Id == target.Id && entity.Type == target.Type; public static void SetSkillCooldown(this IBattleEntity caster, SkillInfo skill) { short cooldownReduction = skill.Cooldown; List skills = caster.Skills; IBattleEntitySkill battleEntitySkill; if (caster.IsMonster() || caster.IsNpc()) { battleEntitySkill = skills.FirstOrDefault(x => x.Skill.Id == skill.Vnum); } else { if (caster.IsPlayer()) { battleEntitySkill = skills.FirstOrDefault(x => x.Skill.CastId == skill.CastId && x.Skill.SkillType == SkillType.NormalPlayerSkill); } else { battleEntitySkill = skills.FirstOrDefault(x => x.Skill.CastId == skill.CastId); } } if (battleEntitySkill == null) { return; } DateTime time = DateTime.UtcNow.AddMilliseconds(cooldownReduction * 100); battleEntitySkill.LastUse = time; switch (caster) { case IPlayerEntity character: character.AddSkillCooldown(time, skill.CastId); break; case IMateEntity mateEntity: if (skill.Vnum == 0) { return; } mateEntity.Owner?.AddMateSkillCooldown(time, skill.CastId, mateEntity.MateType); break; } } public static string GenerateDiePacket(this IBattleEntity entity) => $"die {(byte)entity.Type} {entity.Id} {(byte)entity.Type} {entity.Id}"; public static void BroadcastDie(this IBattleEntity entity) => entity.MapInstance.Broadcast(entity.GenerateDiePacket()); public static string GeneratePushPacket(this IBattleEntity entity, short x, short y, int value) => $"guri 3 {(byte)entity.Type} {entity.Id} {x} {y} 3 {value} 2 -1"; public static string GenerateDashGuriPacket(this IBattleEntity entity, short x, short y, int value) => $"guri 3 {(byte)entity.Type} {entity.Id} {x} {y} 3 2 2 {value}"; public static async Task RemoveSacrifice(this IBattleEntity caster, IBattleEntity target, ISacrificeManager sacrificeManager, IGameLanguageService gameLanguage) { Buff sacrifice = caster.BuffComponent.GetBuff((short)BuffVnums.SPIRIT_OF_SACRIFICE); await caster.RemoveBuffAsync(false, sacrifice); Buff nobleGesture = target.BuffComponent.GetBuff((short)BuffVnums.NOBLE_GESTURE); await target.RemoveBuffAsync(false, nobleGesture); string message; if (caster is IPlayerEntity character) { message = gameLanguage.GetLanguage(GameDialogKey.SKILL_SHOUTMESSAGE_SACRIFICE_REMOVED, character.Session.UserLanguage); character.Session.SendMsg(message, MsgMessageType.SmallMiddle); } if (target is IPlayerEntity targetCharacter) { message = gameLanguage.GetLanguage(GameDialogKey.SKILL_SHOUTMESSAGE_SACRIFICE_REMOVED, targetCharacter.Session.UserLanguage); targetCharacter.Session.SendMsg(message, MsgMessageType.SmallMiddle); } } public static bool CanBeInterrupted(this IPlayerEntity character, SkillInfo skillInfo) { if (character.Class != ClassType.Magician && character.Class != ClassType.Adventurer) { return false; } if (character.GetMaxWeaponShellValue(ShellEffectType.AntiMagicDisorder) != 0) { return false; } if (skillInfo.AttackType != AttackType.Magical || skillInfo.IsUsingSecondWeapon) { return false; } return !character.SkillComponent.IsSkillInterrupted; } public static string GenerateShadowFigurePacket(this IBattleEntity entity, int firstValue, int secondValue) => $"guri 0 {(byte)entity.Type} {entity.Id} {firstValue} {secondValue}"; public static void BroadcastShadowFigurePacket(this IBattleEntity entity, int firstValue, int secondValue) => entity.MapInstance?.Broadcast(entity.GenerateShadowFigurePacket(firstValue, secondValue)); public static void ShadowAppears(this IBattleEntity battleEntity, bool broadcastZeroValue, Buff buff = null) { if (buff != null && broadcastZeroValue && buff.BCards.Any(x => x.Type == (short)BCardType.SpecialEffects && x.SubType == (byte)AdditionalTypes.SpecialEffects.ShadowAppears)) { battleEntity.BroadcastShadowFigurePacket(0, 0); } if (!battleEntity.BuffComponent.HasAnyBuff()) { return; } if (!battleEntity.BCardComponent.HasBCard(BCardType.SpecialEffects, (byte)AdditionalTypes.SpecialEffects.ShadowAppears)) { battleEntity.BroadcastShadowFigurePacket(0, 0); return; } (int firstData, int secondData) = battleEntity.BCardComponent.GetAllBCardsInformation(BCardType.SpecialEffects, (byte)AdditionalTypes.SpecialEffects.ShadowAppears, battleEntity.Level); if (firstData == 0 && secondData == 0) { battleEntity.BroadcastShadowFigurePacket(0, 0); return; } battleEntity.BroadcastShadowFigurePacket(!broadcastZeroValue ? firstData : 0, secondData); } public static DateTime GenerateSkillCastTime(this IBattleEntity entity, SkillInfo skillInfo, bool isPartnerSkill = false) { DateTime time = DateTime.UtcNow.AddMilliseconds(GenerateSkillCastTimeNumber(entity, skillInfo, isPartnerSkill)); return time; } public static short GenerateSkillCastTimeNumber(this IBattleEntity entity, SkillInfo skillInfo, bool isPartnerSkill = false) { short milliSeconds = skillInfo.CastTime; int toAddMilliSeconds = skillInfo.AttackType switch { AttackType.Melee => (short)entity.BCardComponent.GetAllBCardsInformation(BCardType.JumpBackPush, (byte)AdditionalTypes.JumpBackPush.MeleeDurationIncreased, entity.Level).firstData, AttackType.Ranged => (short)entity.BCardComponent.GetAllBCardsInformation(BCardType.JumpBackPush, (byte)AdditionalTypes.JumpBackPush.RangedDurationIncreased, entity.Level).firstData, AttackType.Magical => (short)entity.BCardComponent.GetAllBCardsInformation(BCardType.JumpBackPush, (byte)AdditionalTypes.JumpBackPush.MagicalDurationIncreased, entity.Level).firstData, _ => 0 }; toAddMilliSeconds -= skillInfo.AttackType switch { AttackType.Melee => (short)entity.BCardComponent.GetAllBCardsInformation(BCardType.JumpBackPush, (byte)AdditionalTypes.JumpBackPush.MeleeDurationDecreased, entity.Level).firstData, AttackType.Ranged => (short)entity.BCardComponent.GetAllBCardsInformation(BCardType.JumpBackPush, (byte)AdditionalTypes.JumpBackPush.RangedDurationDecreased, entity.Level).firstData, AttackType.Magical => (short)entity.BCardComponent.GetAllBCardsInformation(BCardType.JumpBackPush, (byte)AdditionalTypes.JumpBackPush.MagicalDurationDecreased, entity.Level).firstData, _ => 0 }; int toIncrease = entity.BCardComponent.GetAllBCardsInformation(BCardType.Casting, (byte)AdditionalTypes.Casting.EffectDurationIncreased, entity.Level).firstData; int toDecrease = entity.BCardComponent.GetAllBCardsInformation(BCardType.Casting, (byte)AdditionalTypes.Casting.EffectDurationDecreased, entity.Level).firstData; toAddMilliSeconds += (short)((toAddMilliSeconds == 0 ? milliSeconds * 10 : toAddMilliSeconds) * toIncrease * 0.01); toAddMilliSeconds -= (short)((toAddMilliSeconds == 0 ? milliSeconds * 10 : toAddMilliSeconds) * toDecrease * 0.01); milliSeconds += (short)(toAddMilliSeconds / 10); if (isPartnerSkill) { milliSeconds = (short)((milliSeconds - 1) * 2); } return (short)(milliSeconds * 100); } public static int CalculateManaUsage(this IBattleEntity entity, int mana, SkillDTO skill = null) { double manaMultiplier = 1; if (skill is { AttackType: AttackType.Magical }) { manaMultiplier -= entity.BCardComponent.GetAllBCardsInformation(BCardType.Casting, (byte)AdditionalTypes.Casting.ManaForSkillsDecreased, entity.Level).firstData * 0.01; manaMultiplier += entity.BCardComponent.GetAllBCardsInformation(BCardType.Casting, (byte)AdditionalTypes.Casting.ManaForSkillsIncreased, entity.Level).firstData * 0.01; } if (entity is IPlayerEntity playerEntity) { manaMultiplier -= playerEntity.GetJewelsCellonsValue(CellonType.MpConsumption) * 0.01; manaMultiplier -= playerEntity.GetMaxWeaponShellValue(ShellEffectType.ReducedMPConsume) * 0.01; } return (int)(mana * manaMultiplier); } public static void RemoveEntityMp(this IBattleEntity battleEntity, short mana, SkillDTO skill = null) { int mp = battleEntity.CalculateManaUsage(mana, skill); battleEntity.Mp = battleEntity.Mp - mp < 0 ? 0 : battleEntity.Mp - mp; } public static bool IsMateTrainer(this IBattleEntity entity) => entity is IMonsterEntity { IsMateTrainer: true }; public static bool IsInvisibleGm(this IBattleEntity entity) => entity is IPlayerEntity player && player.CheatComponent.IsInvisible; }