diff --git a/Source/ACE.Entity/Enum/Properties/PropertyBool.cs b/Source/ACE.Entity/Enum/Properties/PropertyBool.cs index d95f6c5047..8f8cb674e5 100644 --- a/Source/ACE.Entity/Enum/Properties/PropertyBool.cs +++ b/Source/ACE.Entity/Enum/Properties/PropertyBool.cs @@ -178,6 +178,14 @@ public enum PropertyBool : ushort UntrainedSkills = 9004, [Ephemeral][ServerOnly] IsEnvoy = 9005, + [ServerOnly] + UnspecializedSkills = 9006, + [ServerOnly] + FreeSkillResetRenewed = 9007, + [ServerOnly] + FreeAttributeResetRenewed = 9008, + [ServerOnly] + SkillTemplesTimerReset = 9009, } public static class PropertyBoolExtensions diff --git a/Source/ACE.Server/Command/Handlers/DeveloperFixCommands.cs b/Source/ACE.Server/Command/Handlers/DeveloperFixCommands.cs index 7c894a2584..e2bdbfdaca 100644 --- a/Source/ACE.Server/Command/Handlers/DeveloperFixCommands.cs +++ b/Source/ACE.Server/Command/Handlers/DeveloperFixCommands.cs @@ -267,38 +267,7 @@ public static void HandleVerifySkills(Session session, params string[] parameter } else { - var augProp = 0; - var augType = AugmentationType.None; - switch (skill.Key) - { - - case Skill.ArmorTinkering: - augType = AugmentationType.ArmorTinkering; - augProp = player.GetProperty(PropertyInt.AugmentationSpecializeArmorTinkering) ?? 0; - break; - - case Skill.ItemTinkering: - augType = AugmentationType.ItemTinkering; - augProp = player.GetProperty(PropertyInt.AugmentationSpecializeItemTinkering) ?? 0; - break; - - case Skill.MagicItemTinkering: - augType = AugmentationType.MagicItemTinkering; - augProp = player.GetProperty(PropertyInt.AugmentationSpecializeMagicItemTinkering) ?? 0; - break; - - case Skill.WeaponTinkering: - augType = AugmentationType.WeaponTinkering; - augProp = player.GetProperty(PropertyInt.AugmentationSpecializeWeaponTinkering) ?? 0; - break; - - case Skill.Salvaging: - augType = AugmentationType.Salvage; - augProp = player.GetProperty(PropertyInt.AugmentationSpecializeSalvaging) ?? 0; - break; - } - - if (skill.Value.InitLevel != 10 && augProp == 0) + if (skill.Value.InitLevel != 10) { Console.WriteLine($"{player.Name} has {sac} skill {skill.Key} with {skill.Value.InitLevel:N0} InitLevel{fixStr}"); foundIssues = true; @@ -307,18 +276,6 @@ public static void HandleVerifySkills(Session session, params string[] parameter { skill.Value.InitLevel = 10; - updated = true; - } - } - else if (skill.Value.InitLevel == 10 && augProp == 1) - { - Console.WriteLine($"{player.Name} has {sac} skill {skill.Key} with {skill.Value.InitLevel:N0} InitLevel as a result of {augType} augmentation{fixStr}"); - foundIssues = true; - - if (fix) - { - skill.Value.InitLevel = 0; - updated = true; } } @@ -405,6 +362,8 @@ public static void HandleVerifySkillCredits(Session session, params string[] par var used = 0; + var specCreditsSpent = 0; + foreach (var skill in new Dictionary(player.Biota.PropertiesSkill)) { var sac = skill.Value.SAC; @@ -435,6 +394,8 @@ public static void HandleVerifySkillCredits(Session session, params string[] par } used += skillInfo.UpgradeCostFromTrainedToSpecialized; + + specCreditsSpent += skillInfo.SpecializedCost; } } @@ -464,6 +425,20 @@ public static void HandleVerifySkillCredits(Session session, params string[] par continue; } + if (specCreditsSpent > 70) + { + // if the player has already spent more skill credits than they should have, + // unfortunately this situation requires a partial reset.. + + Console.WriteLine($"{player.Name} has spent {specCreditsSpent} skill credits on specalization, {specCreditsSpent - 70} over the limit of 70. To fix this situation, specialized skill reset will need to be applied{fixStr}"); + foundIssues = true; + + if (fix) + UnspecializeSkills(player); + + continue; + } + var availableCredits = player.GetProperty(PropertyInt.AvailableSkillCredits) ?? 0; if (availableCredits != targetCredits) @@ -590,6 +565,54 @@ private static void UntrainSkills(OfflinePlayer player, int targetCredits) player.SaveBiotaToDatabase(); } + /// + /// This method is only required if the player is found to be over the spec skill limit of 70 credits + /// + private static void UnspecializeSkills(OfflinePlayer player) + { + long refundXP = 0; + + int refundedCredits = 0; + + foreach (var skill in new Dictionary(player.Biota.PropertiesSkill)) + { + if (!DatManager.PortalDat.SkillTable.SkillBaseHash.TryGetValue((uint)skill.Key, out var skillBase)) + { + Console.WriteLine($"{player.Name}.UntrainSkills({skill.Key}) - unknown skill"); + continue; + } + + var sac = skill.Value.SAC; + + if (sac != SkillAdvancementClass.Specialized || !Player.IsSkillSpecializedViaAugmentation(skill.Key)) + continue; + + refundXP += skill.Value.PP; + + skill.Value.SAC = SkillAdvancementClass.Trained; + skill.Value.InitLevel = 0; + skill.Value.PP = 0; + skill.Value.LevelFromPP = 0; + + refundedCredits += skillBase.UpgradeCostFromTrainedToSpecialized; + } + + var availableExperience = player.GetProperty(PropertyInt64.AvailableExperience) ?? 0; + + player.SetProperty(PropertyInt64.AvailableExperience, availableExperience + refundXP); + + var availableSkillCredits = player.GetProperty(PropertyInt.AvailableSkillCredits) ?? 0; + + player.SetProperty(PropertyInt.AvailableSkillCredits, availableSkillCredits + refundedCredits); + + player.SetProperty(PropertyBool.UnspecializedSkills, true); + + player.SetProperty(PropertyBool.FreeSkillResetRenewed, true); + player.SetProperty(PropertyBool.SkillTemplesTimerReset, true); + + player.SaveBiotaToDatabase(); + } + [CommandHandler("verify-heritage-augs", AccessLevel.Admin, CommandHandlerFlag.ConsoleInvoke, "Verifies all players have their heritage augs.")] public static void HandleVerifyHeritageAugs(Session session, params string[] parameters) diff --git a/Source/ACE.Server/WorldObjects/AugmentationDevice.cs b/Source/ACE.Server/WorldObjects/AugmentationDevice.cs index c45fe99183..7fda82af73 100644 --- a/Source/ACE.Server/WorldObjects/AugmentationDevice.cs +++ b/Source/ACE.Server/WorldObjects/AugmentationDevice.cs @@ -94,7 +94,7 @@ public void DoAugmentation(Player player) { var playerSkill = player.GetCreatureSkill(AugTypeHelper.GetSkill(type)); playerSkill.AdvancementClass = SkillAdvancementClass.Specialized; - //playerSkill.InitLevel = 10; + playerSkill.InitLevel = 10; // adjust rank? // handle overages? // if trained skill is maxed, there will be a ~103m xp overage... diff --git a/Source/ACE.Server/WorldObjects/Player_Networking.cs b/Source/ACE.Server/WorldObjects/Player_Networking.cs index efd0b3a06d..9a535e8a1c 100644 --- a/Source/ACE.Server/WorldObjects/Player_Networking.cs +++ b/Source/ACE.Server/WorldObjects/Player_Networking.cs @@ -91,6 +91,10 @@ public void PlayerEnterWorld() HandleMissingXp(); HandleSkillCreditRefund(); + HandleSkillTemplesReset(); + HandleSkillSpecCreditRefund(); + HandleFreeSkillResetRenewal(); + HandleFreeAttributeResetRenewal(); if (PlayerKillerStatus == PlayerKillerStatus.PKLite && !PropertyManager.GetBool("pkl_server").Item) { diff --git a/Source/ACE.Server/WorldObjects/Player_Skills.cs b/Source/ACE.Server/WorldObjects/Player_Skills.cs index 4101758615..34feff7451 100644 --- a/Source/ACE.Server/WorldObjects/Player_Skills.cs +++ b/Source/ACE.Server/WorldObjects/Player_Skills.cs @@ -585,11 +585,24 @@ public void HandleDBUpdates() Skill.Salvaging }; + public static List AugSpecSkills = new List() + { + Skill.ArmorTinkering, + Skill.ItemTinkering, + Skill.MagicItemTinkering, + Skill.WeaponTinkering, + Skill.Salvaging + }; + public static bool IsSkillUntrainable(Skill skill) { return !AlwaysTrained.Contains(skill); } + public static bool IsSkillSpecializedViaAugmentation(Skill skill) + { + return !AugSpecSkills.Contains(skill); + } public override bool GetHeritageBonus(WorldObject weapon) { @@ -715,6 +728,79 @@ public void HandleSkillCreditRefund() actionChain.EnqueueChain(); } + public void HandleSkillSpecCreditRefund() + { + if (!(GetProperty(PropertyBool.UnspecializedSkills) ?? false)) return; + + var actionChain = new ActionChain(); + actionChain.AddDelaySeconds(5.0f); + actionChain.AddAction(this, () => + { + Session.Network.EnqueueSend(new GameMessageSystemChat("Your specialized skills have been unspecialized due to an error with skill credits.\nYou have received a refund for these skill credits and experience.", ChatMessageType.Broadcast)); + + RemoveProperty(PropertyBool.UnspecializedSkills); + }); + actionChain.EnqueueChain(); + } + + public void HandleFreeSkillResetRenewal() + { + if (!(GetProperty(PropertyBool.FreeSkillResetRenewed) ?? false)) return; + + var actionChain = new ActionChain(); + actionChain.AddDelaySeconds(5.0f); + actionChain.AddAction(this, () => + { + Session.Network.EnqueueSend(new GameMessageSystemChat("Your opportunity to change your skills is renewed! Visit Fianhe to reset your skills.", ChatMessageType.Magic)); + + RemoveProperty(PropertyBool.FreeSkillResetRenewed); + + QuestManager.Erase("UsedFreeSkillReset"); + }); + actionChain.EnqueueChain(); + } + + public void HandleFreeAttributeResetRenewal() + { + if (!(GetProperty(PropertyBool.FreeAttributeResetRenewed) ?? false)) return; + + var actionChain = new ActionChain(); + actionChain.AddDelaySeconds(5.0f); + actionChain.AddAction(this, () => + { + // Your opportunity to change your attributes is renewed! Visit Chafulumisa to reset your skills [sic attributes]. + Session.Network.EnqueueSend(new GameMessageSystemChat("Your opportunity to change your attributes is renewed! Visit Chafulumisa to reset your attributes.", ChatMessageType.Magic)); + + RemoveProperty(PropertyBool.FreeAttributeResetRenewed); + + QuestManager.Erase("UsedFreeAttributeReset"); + }); + actionChain.EnqueueChain(); + } + + public void HandleSkillTemplesReset() + { + if (!(GetProperty(PropertyBool.SkillTemplesTimerReset) ?? false)) return; + + var actionChain = new ActionChain(); + actionChain.AddDelaySeconds(5.0f); + actionChain.AddAction(this, () => + { + Session.Network.EnqueueSend(new GameMessageSystemChat("The Temples of Forgetfulness and Enlightenment have had the timer for their use reset due to skill changes.", ChatMessageType.Magic)); + + RemoveProperty(PropertyBool.SkillTemplesTimerReset); + + QuestManager.Erase("ForgetfulnessGems1"); + QuestManager.Erase("ForgetfulnessGems2"); + QuestManager.Erase("ForgetfulnessGems3"); + QuestManager.Erase("ForgetfulnessGems4"); + QuestManager.Erase("Forgetfulness6days"); + QuestManager.Erase("Forgetfulness13days"); + QuestManager.Erase("Forgetfulness20days"); + }); + actionChain.EnqueueChain(); + } + /// /// Resets the skill, refunds all experience and skill credits, if allowed. /// diff --git a/Source/ACE.Server/WorldObjects/SkillAlterationDevice.cs b/Source/ACE.Server/WorldObjects/SkillAlterationDevice.cs index 6f5617f7e7..afdc729eb3 100644 --- a/Source/ACE.Server/WorldObjects/SkillAlterationDevice.cs +++ b/Source/ACE.Server/WorldObjects/SkillAlterationDevice.cs @@ -111,7 +111,7 @@ public bool VerifyRequirements(Player player, CreatureSkill skill, SkillBase ski } // ensure player won't exceed limit of 70 specialized credits after operation - if (GetTotalSpecializedCredits(player) + skillBase.UpgradeCostFromTrainedToSpecialized > 70) + if (GetTotalSpecializedCredits(player) + skillBase.SpecializedCost > 70) { player.Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(player.Session, WeenieErrorWithString.TooManyCreditsInSpecializedSkills, skill.Skill.ToSentence())); return false; @@ -259,7 +259,7 @@ private int GetTotalSpecializedCredits(Player player) var skill = DatManager.PortalDat.SkillTable.SkillBaseHash[(uint)kvp.Key]; - specializedCreditsTotal += skill.UpgradeCostFromTrainedToSpecialized; + specializedCreditsTotal += skill.SpecializedCost; } } diff --git a/appveyor.yml b/appveyor.yml index 4c0482567a..0689e3cf16 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 1.5.{build} +version: 1.6.{build} pull_requests: do_not_increment_build_number: true skip_tags: true