using System; using System.Linq; using ACE.DatLoader; using ACE.DatLoader.Entity; using ACE.Entity; using ACE.Entity.Enum; using ACE.Entity.Enum.Properties; using ACE.Entity.Models; using ACE.Server.Entity; using ACE.Server.Network.GameEvent.Events; using ACE.Server.Network.GameMessages.Messages; using ACE.Server.WorldObjects.Entity; namespace ACE.Server.WorldObjects { public class SkillAlterationDevice : WorldObject { public enum SkillAlterationType { Undef = 0, Specialize = 1, Lower = 2, } public SkillAlterationType TypeOfAlteration { get => (SkillAlterationType)(GetProperty(PropertyInt.TypeOfAlteration) ?? 0); set { if (value == 0) RemoveProperty(PropertyInt.TypeOfAlteration); else SetProperty(PropertyInt.TypeOfAlteration, (int)value); } } public Skill SkillToBeAltered { get => (Skill)(GetProperty(PropertyInt.SkillToBeAltered) ?? 0); set { if (value == 0) RemoveProperty(PropertyInt.SkillToBeAltered); else SetProperty(PropertyInt.SkillToBeAltered, (int)value); } } /// /// A new biota be created taking all of its values from weenie. /// public SkillAlterationDevice(Weenie weenie, ObjectGuid guid) : base(weenie, guid) { SetEphemeralValues(); } /// /// Restore a WorldObject from the database. /// public SkillAlterationDevice(Biota biota) : base(biota) { SetEphemeralValues(); } private void SetEphemeralValues() { } public override void ActOnUse(WorldObject activator) { ActOnUse(activator, false); } public void ActOnUse(WorldObject activator, bool confirmed) { if (!(activator is Player player)) return; // verify skill var skill = player.GetCreatureSkill(SkillToBeAltered); if (skill == null) { player.Session.Network.EnqueueSend(new GameEventWeenieError(player.Session, WeenieError.YouFailToAlterSkill)); return; } // get skill training / specialization costs var skillBase = DatManager.PortalDat.SkillTable.SkillBaseHash[(uint)skill.Skill]; if (!VerifyRequirements(player, skill, skillBase)) return; if (!confirmed) { var msg = "This action will "; switch (TypeOfAlteration) { case SkillAlterationType.Specialize: msg += $"specialize your {skill.Skill.ToSentence()} skill and cost {skillBase.UpgradeCostFromTrainedToSpecialized} credits."; break; case SkillAlterationType.Lower: msg += $"lower your {skill.Skill.ToSentence()} skill from {(skill.AdvancementClass == SkillAdvancementClass.Specialized ? "specialized to trained" : "trained to untrained")} and refund the skill credits and experience invested in this skill."; break; } if (!player.ConfirmationManager.EnqueueSend(new Confirmation_AlterSkill(player.Guid, Guid), msg)) player.SendWeenieError(WeenieError.ConfirmationInProgress); return; } AlterSkill(player, skill, skillBase); } public bool VerifyRequirements(Player player, CreatureSkill skill, SkillBase skillBase) { switch (TypeOfAlteration) { // Gem of Enlightenment case SkillAlterationType.Specialize: // ensure skill is trained if (skill.AdvancementClass != SkillAdvancementClass.Trained) { player.Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(player.Session, WeenieErrorWithString.Your_SkillMustBeTrained, skill.Skill.ToSentence())); return false; } // ensure player has enough available skill credits if (player.AvailableSkillCredits < skillBase.UpgradeCostFromTrainedToSpecialized) { player.Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(player.Session, WeenieErrorWithString.NotEnoughSkillCreditsToSpecialize, skill.Skill.ToSentence())); return false; } // ensure player won't exceed limit of 70 specialized credits after operation var specializedCost = skillBase.SpecializedCost; if (DatManager.PortalDat.CharGen.HeritageGroups.TryGetValue((uint)player.Heritage, out var heritageGroup)) { // check for adjusted costs of Specialization due to player's heritage (e.g. Arcane Lore) var heritageAdjustedCost = heritageGroup.Skills.FirstOrDefault(i => i.SkillNum == (int)skill.Skill); if (heritageAdjustedCost != null) specializedCost = heritageAdjustedCost.PrimaryCost; } if (GetTotalSpecializedCredits(player) + specializedCost > 70) { player.Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(player.Session, WeenieErrorWithString.TooManyCreditsInSpecializedSkills, skill.Skill.ToSentence())); return false; } break; // Gem of Forgetfulness case SkillAlterationType.Lower: // ensure skill is trained or specialized if (skill.AdvancementClass < SkillAdvancementClass.Trained) { player.Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(player.Session, WeenieErrorWithString.Your_SkillIsAlreadyUntrained, skill.Skill.ToSentence())); return false; } // Check for equipped items that have requirements in the skill we're lowering if (CheckWieldedItems(player)) { // Items are wielded which might be affected by a lowering operation player.Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(player.Session, WeenieErrorWithString.CannotLowerSkillWhileWieldingItem, skill.Skill.ToSentence())); return false; } break; } return true; } public void AlterSkill(Player player, CreatureSkill skill, SkillBase skillBase) { switch (TypeOfAlteration) { // Gem of Enlightenment case SkillAlterationType.Specialize: if (player.SpecializeSkill(skill.Skill, skillBase.UpgradeCostFromTrainedToSpecialized, false)) { var updateSkill = new GameMessagePrivateUpdateSkill(player, skill); var availableSkillCredits = new GameMessagePrivateUpdatePropertyInt(player, PropertyInt.AvailableSkillCredits, player.AvailableSkillCredits ?? 0); var msg = new GameEventWeenieErrorWithString(player.Session, WeenieErrorWithString.YouHaveSucceededSpecializing_Skill, skill.Skill.ToSentence()); player.Session.Network.EnqueueSend(updateSkill, availableSkillCredits, msg); player.TryConsumeFromInventoryWithNetworking(this, 1); } break; // Gem of Forgetfulness case SkillAlterationType.Lower: // specialized => trained if (skill.AdvancementClass == SkillAdvancementClass.Specialized) { var specializedViaAugmentation = player.IsSkillSpecializedViaAugmentation(skill.Skill, out var playerHasAugmentation) && playerHasAugmentation; if (player.UnspecializeSkill(skill.Skill, skillBase.UpgradeCostFromTrainedToSpecialized)) { var updateSkill = new GameMessagePrivateUpdateSkill(player, skill); var availableSkillCredits = new GameMessagePrivateUpdatePropertyInt(player, PropertyInt.AvailableSkillCredits, player.AvailableSkillCredits ?? 0); var msg = specializedViaAugmentation ? WeenieErrorWithString.YouSucceededRecoveringXPFromSkill_AugmentationNotUntrainable : WeenieErrorWithString.YouHaveSucceededUnspecializing_Skill; var message = new GameEventWeenieErrorWithString(player.Session, msg, skill.Skill.ToSentence()); player.Session.Network.EnqueueSend(updateSkill, availableSkillCredits, message); player.TryConsumeFromInventoryWithNetworking(this, 1); } } // trained => untrained // in the case of skills which can't be untrained, // keep trained, but recover the xp spent else if (skill.AdvancementClass == SkillAdvancementClass.Trained) { var untrainable = Player.IsSkillUntrainable(skill.Skill); if (player.UntrainSkill(skill.Skill, skillBase.TrainedCost)) { var updateSkill = new GameMessagePrivateUpdateSkill(player, skill); var availableSkillCredits = new GameMessagePrivateUpdatePropertyInt(player, PropertyInt.AvailableSkillCredits, player.AvailableSkillCredits ?? 0); var msg = untrainable ? WeenieErrorWithString.YouHaveSucceededUntraining_Skill : WeenieErrorWithString.CannotUntrain_SkillButRecoveredXP; var message = new GameEventWeenieErrorWithString(player.Session, msg, skill.Skill.ToSentence()); player.Session.Network.EnqueueSend(updateSkill, availableSkillCredits, message); player.TryConsumeFromInventoryWithNetworking(this, 1); } } break; } } /// /// Calculates and returns the current total number of specialized credits /// private int GetTotalSpecializedCredits(Player player) { var specializedCreditsTotal = 0; foreach (var kvp in player.Skills) { if (kvp.Value.AdvancementClass == SkillAdvancementClass.Specialized) { switch (kvp.Key) { // exclude None/Undef skill case Skill.None: // exclude aug specs case Skill.ArmorTinkering: case Skill.ItemTinkering: case Skill.MagicItemTinkering: case Skill.WeaponTinkering: case Skill.Salvaging: continue; } var skill = DatManager.PortalDat.SkillTable.SkillBaseHash[(uint)kvp.Key]; var specializedCost = skill.SpecializedCost; if (DatManager.PortalDat.CharGen.HeritageGroups.TryGetValue((uint)player.Heritage, out var heritageGroup)) { // check for adjusted costs of Specialization due to player's heritage (e.g. Arcane Lore) var heritageAdjustedCost = heritageGroup.Skills.FirstOrDefault(i => i.SkillNum == (int)kvp.Key); if (heritageAdjustedCost != null) specializedCost = heritageAdjustedCost.PrimaryCost; } specializedCreditsTotal += specializedCost; } } return specializedCreditsTotal; } /// /// Checks wielded items and their requirements to see if they'd be violated by an impending skill lowering operation /// private bool CheckWieldedItems(Player player) { foreach (var equippedItem in player.EquippedObjects.Values) { if (CheckWieldRequirement(player, equippedItem.WieldRequirements, equippedItem.WieldSkillType, equippedItem.WieldDifficulty) || CheckWieldRequirement(player, equippedItem.WieldRequirements2, equippedItem.WieldSkillType2, equippedItem.WieldDifficulty2) || CheckWieldRequirement(player, equippedItem.WieldRequirements3, equippedItem.WieldSkillType3, equippedItem.WieldDifficulty3) || CheckWieldRequirement(player, equippedItem.WieldRequirements4, equippedItem.WieldSkillType4, equippedItem.WieldDifficulty4)) //|| //CheckActivationRequirements(player, equippedItem)) { return true; } } return false; } private bool CheckWieldRequirement(Player player, WieldRequirement itemWieldReq, int? wieldSkillType, int? wieldSkillDifficulty) { if (itemWieldReq == WieldRequirement.Training) { var skill = player.ConvertToMoASkill((Skill)(wieldSkillType ?? 0)); if (skill != SkillToBeAltered) return false; var creatureSkill = player.GetCreatureSkill(skill, false); if (creatureSkill == null || wieldSkillDifficulty is null) return false; return (SkillAdvancementClass)wieldSkillDifficulty >= creatureSkill.AdvancementClass; } if (itemWieldReq != WieldRequirement.RawSkill && itemWieldReq != WieldRequirement.Skill) return false; return player.ConvertToMoASkill((Skill)(wieldSkillType ?? 0)) == SkillToBeAltered; } private bool CheckActivationRequirements(Player player, WorldObject equippedItem) { if (equippedItem.ItemDifficulty > 0 && SkillToBeAltered == Skill.ArcaneLore) return true; if (player.ConvertToMoASkill(equippedItem.ItemSkillLimit ?? 0) == SkillToBeAltered) return true; if (player.ConvertToMoASkill(equippedItem.ItemSpecializedOnly ?? 0) == SkillToBeAltered) return true; if (player.ConvertToMoASkill((Skill)(equippedItem.UseRequiresSkill ?? 0)) == SkillToBeAltered) return true; if (player.ConvertToMoASkill((Skill)(equippedItem.UseRequiresSkillSpec ?? 0)) == SkillToBeAltered) return true; return false; } } }