ace/Source/ACE.Server/WorldObjects/SkillAlterationDevice.cs
Ty Conner 1de1dc23e7
Update Checks for Attribute Transfers and Skill Alterations (#4476)
* Update AttributeTransferDevice.cs

* Update SkillAlterationDevice.cs

* Update WorldObject_Properties.cs

* Update WorldObject_Use.cs

* Update AttributeTransferDevice.cs

* Update SkillAlterationDevice.cs

* Update AttributeTransferDevice.cs

* Update SkillAlterationDevice.cs

* Update WorldObject_Use.cs

* Update WorldObject_Use.cs

* Update SkillAlterationDevice.cs

* Update AttributeTransferDevice.cs

* Update AttributeTransferDevice.cs
2026-08-13 01:12:28 -04:00

337 lines
15 KiB
C#

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); }
}
/// <summary>
/// A new biota be created taking all of its values from weenie.
/// </summary>
public SkillAlterationDevice(Weenie weenie, ObjectGuid guid) : base(weenie, guid)
{
SetEphemeralValues();
}
/// <summary>
/// Restore a WorldObject from the database.
/// </summary>
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;
}
}
/// <summary>
/// Calculates and returns the current total number of specialized credits
/// </summary>
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;
}
/// <summary>
/// Checks wielded items and their requirements to see if they'd be violated by an impending skill lowering operation
/// </summary>
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;
}
}
}