ace/Source/ACE.Server/WorldObjects/AugmentationDevice.cs
Alissa 799fd886f0
Fix: Attributes able to be augmented above 100 innate (#3902)
* potential opt-in style fix for attributes being able to be augmented above 100 innate

* Fix valid attribute query to target valid attributes

* Fix debug message

* Cleanup messages

* more message cleanup

* target attribute must have points distributed to it

* when redistributing attributes, reset the free attribute reset timer

* Clean up message for verify-attributes augmentation fix

* opt-in attribute exploit fix; cleaned up query, comments, debug messages

* opt-in attribute exploit fix; line format debug messages

* Opt-in style fix for attribute augmentation exploit; remove unnecessary file

* Update Source/ACE.Server/Managers/PropertyManager.cs

flip boolean per suggestion and previous discussion

Co-authored-by: Ty Conner <tyconner@itcproductions.com>

Co-authored-by: Ty Conner <tyconner@itcproductions.com>
2022-09-23 20:54:26 -04:00

307 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using ACE.Entity;
using ACE.Entity.Enum;
using ACE.Entity.Enum.Properties;
using ACE.Entity.Models;
using ACE.Server.Entity;
using ACE.Server.Managers;
using ACE.Server.Network.GameMessages.Messages;
namespace ACE.Server.WorldObjects
{
public class AugmentationDevice: WorldObject
{
/// <summary>
/// Indicates that the server should enforce logic to prevent players
/// from augmenting a given attribute's innate value over 100
/// </summary>
public static bool AttributeAugmentationSafetyCapEnabled => PropertyManager.GetBool("attribute_augmentation_safety_cap").Item;
public long? AugmentationCost
{
get => GetProperty(PropertyInt64.AugmentationCost);
set { if (!value.HasValue) RemoveProperty(PropertyInt64.AugmentationCost); else SetProperty(PropertyInt64.AugmentationCost, value.Value); }
}
public int? AugmentationStat
{
get => GetProperty(PropertyInt.AugmentationStat);
set { if (!value.HasValue) RemoveProperty(PropertyInt.AugmentationStat); else SetProperty(PropertyInt.AugmentationStat, value.Value); }
}
/// <summary>
/// A new biota be created taking all of its values from weenie.
/// </summary>
public AugmentationDevice(Weenie weenie, ObjectGuid guid) : base(weenie, guid)
{
SetEphemeralValues();
}
/// <summary>
/// Restore a WorldObject from the database.
/// </summary>
public AugmentationDevice(Biota biota) : base(biota)
{
SetEphemeralValues();
}
private void SetEphemeralValues()
{
}
public override void ActOnUse(WorldObject activator)
{
ActOnUse(activator, false);
}
public void ActOnUse(WorldObject activator, bool confirmed = false)
{
if (!(activator is Player player))
return;
if (!VerifyRequirements(player))
return;
if (!confirmed)
{
if (!player.ConfirmationManager.EnqueueSend(new Confirmation_Augmentation(player.Guid, Guid),
$"This action will augment your character with {Name} and will cost {AugmentationCost:N0} available experience."))
player.SendWeenieError(WeenieError.ConfirmationInProgress);
return;
}
DoAugmentation(player);
}
public void DoAugmentation(Player player)
{
//Console.WriteLine($"{Name}.DoAugmentation({player.Name})");
// set augmentation props for player
var type = (AugmentationType)(AugmentationStat ?? 0);
var augProp = AugProps[type];
var curVal = player.GetProperty(augProp) ?? 0;
var newVal = curVal + 1;
player.SetProperty(augProp, newVal);
if (AugTypeHelper.IsAttribute(type))
{
player.AugmentationInnateFamily++;
var attr = AugTypeHelper.GetAttribute(type);
var playerAttr = player.Attributes[attr];
playerAttr.StartingValue += AttributeAugmentationSafetyCapEnabled ? Math.Min(5, 100 - playerAttr.StartingValue) : 5;
player.Session.Network.EnqueueSend(new GameMessagePrivateUpdateAttribute(player, playerAttr));
}
else if (AugTypeHelper.IsResist(type))
{
player.AugmentationResistanceFamily++;
}
else if (AugTypeHelper.IsSkill(type))
{
var playerSkill = player.GetCreatureSkill(AugTypeHelper.GetSkill(type));
playerSkill.AdvancementClass = SkillAdvancementClass.Specialized;
playerSkill.InitLevel = 10;
// adjust rank?
// handle overages?
// if trained skill is maxed, there will be a ~103m xp overage...
var specRank = Player.CalcSkillRank(SkillAdvancementClass.Specialized, playerSkill.ExperienceSpent);
playerSkill.Ranks = (ushort)specRank;
player.Session.Network.EnqueueSend(new GameMessagePrivateUpdateSkill(player, playerSkill));
}
else if (type == AugmentationType.PackSlot)
{
// still seems to require the client to relog
player.ContainerCapacity++;
player.Session.Network.EnqueueSend(new GameMessagePrivateUpdatePropertyInt(player, PropertyInt.ContainersCapacity, (int)player.ContainerCapacity));
}
else if (type == AugmentationType.BurdenLimit)
{
var capacity = player.GetEncumbranceCapacity();
player.Session.Network.EnqueueSend(new GameMessagePrivateUpdatePropertyInt(player, PropertyInt.EncumbranceCapacity, capacity));
}
// consume xp
player.AvailableExperience -= AugmentationCost;
// consume augmentation gem
player.TryConsumeFromInventoryWithNetworking(this, 1);
// send network messages
var updateProp = new GameMessagePrivateUpdatePropertyInt(player, augProp, newVal);
var updateXP = new GameMessagePrivateUpdatePropertyInt64(player, PropertyInt64.AvailableExperience, player.AvailableExperience ?? 0);
player.Session.Network.EnqueueSend(updateProp, updateXP);
player.SendWeenieErrorWithString(WeenieErrorWithString.YouSuccededAcquiringAugmentation, Name);
// also broadcast to nearby players
player.EnqueueBroadcast(new GameMessageScript(player.Guid, AugTypeHelper.GetEffect(type)));
player.EnqueueBroadcast(new GameMessageSystemChat($"{player.Name} has acquired the {Name} augmentation!", ChatMessageType.Broadcast));
player.SaveBiotaToDatabase();
}
public bool VerifyRequirements(Player player)
{
var availableXP = player.AvailableExperience ?? 0;
var augCost = AugmentationCost ?? 0;
if (AugmentationCost == null)
{
player.EnqueueBroadcast(new GameMessageSystemChat($"{Name} is missing AugmentationCost", ChatMessageType.System));
return false;
}
if (availableXP < augCost)
{
player.SendWeenieError(WeenieError.AugmentationNotEnoughExperience);
return false;
}
var type = (AugmentationType)(AugmentationStat ?? 0);
// per-type checks
if (AugTypeHelper.IsAttribute(type))
{
// innate attributes shared cap
if (player.AugmentationInnateFamily >= MaxAugs[type])
{
player.SendWeenieError(WeenieError.AugmentationTypeUsedTooManyTimes);
return false;
}
var playerAttribute = player.Attributes[AugTypeHelper.GetAttribute(type)];
// check InitLevel
var maxInnateValue = AttributeAugmentationSafetyCapEnabled ? 96 : 100;
if (playerAttribute.StartingValue >= maxInnateValue)
{
player.SendWeenieErrorWithString(WeenieErrorWithString.AugmentationSkillNotTrained, $"You are not able to purchase this augmentation because your {playerAttribute.Attribute.ToString()} is already at the maximum innate level!");
return false;
}
}
else if (AugTypeHelper.IsSkill(type))
{
var playerSkill = player.GetCreatureSkill(AugTypeHelper.GetSkill(type));
if (playerSkill.AdvancementClass != SkillAdvancementClass.Trained)
{
player.SendWeenieErrorWithString(WeenieErrorWithString.AugmentationSkillNotTrained, $"You are not able to purchase this augmentation because you are not trained in {playerSkill.Skill.ToSentence()}!");
return false;
}
}
else if (AugTypeHelper.IsResist(type))
{
// resistance shared cap
if (player.AugmentationResistanceFamily >= MaxAugs[type])
{
player.SendWeenieError(WeenieError.AugmentationTypeUsedTooManyTimes);
return false;
}
}
// common checks
var augProp = player.GetProperty(AugProps[type]) ?? 0;
if (augProp >= MaxAugs[type])
{
player.SendWeenieError(WeenieError.AugmentationUsedTooManyTimes);
return false;
}
return true;
}
public static Dictionary<AugmentationType, int> MaxAugs = new Dictionary<AugmentationType, int>()
{
{ AugmentationType.Strength, 10 }, // attributes in shared group
{ AugmentationType.Endurance, 10 },
{ AugmentationType.Coordination, 10 },
{ AugmentationType.Quickness, 10 },
{ AugmentationType.Focus, 10 },
{ AugmentationType.Self, 10 },
{ AugmentationType.Salvage, 1 },
{ AugmentationType.ItemTinkering, 1 },
{ AugmentationType.ArmorTinkering, 1 },
{ AugmentationType.MagicItemTinkering, 1 },
{ AugmentationType.WeaponTinkering, 1 },
{ AugmentationType.PackSlot, 1 },
{ AugmentationType.BurdenLimit, 5 },
{ AugmentationType.DeathItemLoss, 3 },
{ AugmentationType.DeathSpellLoss, 1 },
{ AugmentationType.CritProtect, 1 },
{ AugmentationType.BonusXP, 1 },
{ AugmentationType.BonusSalvage, 4 },
{ AugmentationType.ImbueChance, 1 },
{ AugmentationType.RegenBonus, 2 },
{ AugmentationType.SpellDuration, 5 },
{ AugmentationType.ResistSlash, 2 }, // resistances in shared group
{ AugmentationType.ResistPierce, 2 },
{ AugmentationType.ResistBludgeon, 2 },
{ AugmentationType.ResistAcid, 2 },
{ AugmentationType.ResistFire, 2 },
{ AugmentationType.ResistCold, 2 },
{ AugmentationType.ResistElectric, 2 },
{ AugmentationType.FociCreature, 1 },
{ AugmentationType.FociItem, 1 },
{ AugmentationType.FociLife, 1 },
{ AugmentationType.FociWar, 1 },
{ AugmentationType.CritChance, 1 },
{ AugmentationType.CritDamage, 1 },
{ AugmentationType.Melee, 1 },
{ AugmentationType.Missile, 1 },
{ AugmentationType.Magic, 1 },
{ AugmentationType.Damage, 1 },
{ AugmentationType.DamageResist, 1 },
{ AugmentationType.AllStats, 1 },
{ AugmentationType.FociVoid, 1 },
};
public static Dictionary<AugmentationType, PropertyInt> AugProps = new Dictionary<AugmentationType, PropertyInt>()
{
{ AugmentationType.Strength, PropertyInt.AugmentationInnateStrength },
{ AugmentationType.Endurance, PropertyInt.AugmentationInnateEndurance },
{ AugmentationType.Coordination, PropertyInt.AugmentationInnateCoordination },
{ AugmentationType.Quickness, PropertyInt.AugmentationInnateQuickness },
{ AugmentationType.Focus, PropertyInt.AugmentationInnateFocus },
{ AugmentationType.Self, PropertyInt.AugmentationInnateSelf },
{ AugmentationType.Salvage, PropertyInt.AugmentationSpecializeSalvaging },
{ AugmentationType.ItemTinkering, PropertyInt.AugmentationSpecializeItemTinkering },
{ AugmentationType.ArmorTinkering, PropertyInt.AugmentationSpecializeArmorTinkering },
{ AugmentationType.MagicItemTinkering, PropertyInt.AugmentationSpecializeMagicItemTinkering },
{ AugmentationType.WeaponTinkering, PropertyInt.AugmentationSpecializeWeaponTinkering },
{ AugmentationType.PackSlot, PropertyInt.AugmentationExtraPackSlot },
{ AugmentationType.BurdenLimit, PropertyInt.AugmentationIncreasedCarryingCapacity },
{ AugmentationType.DeathItemLoss, PropertyInt.AugmentationLessDeathItemLoss },
{ AugmentationType.DeathSpellLoss, PropertyInt.AugmentationSpellsRemainPastDeath },
{ AugmentationType.CritProtect, PropertyInt.AugmentationCriticalDefense },
{ AugmentationType.BonusXP, PropertyInt.AugmentationBonusXp },
{ AugmentationType.BonusSalvage, PropertyInt.AugmentationBonusSalvage },
{ AugmentationType.ImbueChance, PropertyInt.AugmentationBonusImbueChance },
{ AugmentationType.RegenBonus, PropertyInt.AugmentationFasterRegen },
{ AugmentationType.SpellDuration, PropertyInt.AugmentationIncreasedSpellDuration },
{ AugmentationType.ResistSlash, PropertyInt.AugmentationResistanceSlash },
{ AugmentationType.ResistPierce, PropertyInt.AugmentationResistancePierce },
{ AugmentationType.ResistBludgeon, PropertyInt.AugmentationResistanceBlunt },
{ AugmentationType.ResistAcid, PropertyInt.AugmentationResistanceAcid },
{ AugmentationType.ResistFire, PropertyInt.AugmentationResistanceFire },
{ AugmentationType.ResistCold, PropertyInt.AugmentationResistanceFrost },
{ AugmentationType.ResistElectric, PropertyInt.AugmentationResistanceLightning },
{ AugmentationType.FociCreature, PropertyInt.AugmentationInfusedCreatureMagic },
{ AugmentationType.FociItem, PropertyInt.AugmentationInfusedItemMagic },
{ AugmentationType.FociLife, PropertyInt.AugmentationInfusedLifeMagic },
{ AugmentationType.FociWar, PropertyInt.AugmentationInfusedWarMagic },
{ AugmentationType.CritChance, PropertyInt.AugmentationCriticalExpertise },
{ AugmentationType.CritDamage, PropertyInt.AugmentationCriticalPower },
{ AugmentationType.Melee, PropertyInt.AugmentationSkilledMelee },
{ AugmentationType.Missile, PropertyInt.AugmentationSkilledMissile },
{ AugmentationType.Magic, PropertyInt.AugmentationSkilledMagic },
{ AugmentationType.Damage, PropertyInt.AugmentationDamageBonus },
{ AugmentationType.DamageResist, PropertyInt.AugmentationDamageReduction },
{ AugmentationType.AllStats, PropertyInt.AugmentationJackOfAllTrades },
{ AugmentationType.FociVoid, PropertyInt.AugmentationInfusedVoidMagic },
};
}
}