CI reported success despite two failing tests because the Test step ran `dotnet test --no-restore`, but the Build step (BuildTool) only restores and builds Application - never the test projects. Without a restore, the test projects have no project.assets.json, so the Microsoft.NET.Test.Sdk build targets aren't imported, the projects aren't recognized as test projects, and `dotnet test` invokes the VSTest target against zero projects: no output, exit 0. Failures were silently masked. - Replace `--no-restore` with `dotnet test --logger trx --results-directory` on both the macOS and Linux jobs so the test projects restore and run. - Add a guard that fails the job if no .trx is produced, as a permanent backstop against silent zero-test passes. Also removes the two AosWeapon/AosArmor `EmitsLowerStatReqWhenPassed` tests. PR #2501 deliberately emits LowerStatReq (1060435) inline in each item, not in AosWeaponAttributes/AosArmorAttributes.GetProperties, which no longer take a lowerStatReq argument. A prior "fix" dropped the argument to make them compile but left the assertions expecting 1060435, so they threw KeyNotFoundException at runtime. The "not emitted by GetProperties" behavior stays covered by the sibling tests; the stale comments that claimed lowerStatReq is passed in are removed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1796 lines
52 KiB
C#
1796 lines
52 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using ModernUO.Serialization;
|
|
using Server.Items;
|
|
using Server.Mobiles;
|
|
using Server.Spells;
|
|
using Server.Spells.Fifth;
|
|
using Server.Spells.Ninjitsu;
|
|
using Server.Spells.Seventh;
|
|
|
|
namespace Server
|
|
{
|
|
public static class AOS
|
|
{
|
|
public static void DisableStatInfluences()
|
|
{
|
|
for (var i = 0; i < SkillInfo.Table.Length; ++i)
|
|
{
|
|
var info = SkillInfo.Table[i];
|
|
|
|
info.StrScale = 0.0;
|
|
info.DexScale = 0.0;
|
|
info.IntScale = 0.0;
|
|
info.StatTotal = 0.0;
|
|
}
|
|
}
|
|
|
|
public static int Damage(Mobile m, int damage, bool ignoreArmor, int phys, int fire, int cold, int pois, int nrgy) =>
|
|
Damage(m, null, damage, ignoreArmor, phys, fire, cold, pois, nrgy);
|
|
|
|
public static int Damage(Mobile m, int damage, int phys, int fire, int cold, int pois, int nrgy) =>
|
|
Damage(m, null, damage, phys, fire, cold, pois, nrgy);
|
|
|
|
public static int Damage(Mobile m, Mobile from, int damage, int phys, int fire, int cold, int pois, int nrgy) =>
|
|
Damage(m, from, damage, false, phys, fire, cold, pois, nrgy);
|
|
|
|
public static int Damage(
|
|
Mobile m, Mobile from, int damage, int phys, int fire, int cold, int pois, int nrgy,
|
|
int chaos
|
|
) => Damage(m, from, damage, false, phys, fire, cold, pois, nrgy, chaos);
|
|
|
|
public static int Damage(
|
|
Mobile m, Mobile from, int damage, int phys, int fire, int cold, int pois, int nrgy,
|
|
bool keepAlive
|
|
) => Damage(m, from, damage, false, phys, fire, cold, pois, nrgy, 0, 0, keepAlive);
|
|
|
|
public static int Damage(
|
|
Mobile m, Mobile from, int damage, bool ignoreArmor, int phys, int fire, int cold, int pois,
|
|
int nrgy, int chaos = 0, int direct = 0, bool keepAlive = false, bool archer = false, bool deathStrike = false
|
|
)
|
|
{
|
|
if (m?.Deleted != false || !m.Alive || damage <= 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (phys == 0 && fire == 100 && cold == 0 && pois == 0 && nrgy == 0)
|
|
{
|
|
MeerMage.StopEffect(m, true);
|
|
}
|
|
|
|
if (!Core.AOS)
|
|
{
|
|
m.Damage(damage, from);
|
|
return damage;
|
|
}
|
|
|
|
Fix(ref phys);
|
|
Fix(ref fire);
|
|
Fix(ref cold);
|
|
Fix(ref pois);
|
|
Fix(ref nrgy);
|
|
Fix(ref chaos);
|
|
Fix(ref direct);
|
|
|
|
if (Core.ML && chaos > 0)
|
|
{
|
|
switch (Utility.Random(5))
|
|
{
|
|
case 0:
|
|
{
|
|
phys += chaos;
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
fire += chaos;
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
cold += chaos;
|
|
break;
|
|
}
|
|
case 3:
|
|
{
|
|
pois += chaos;
|
|
break;
|
|
}
|
|
case 4:
|
|
{
|
|
nrgy += chaos;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
BaseQuiver quiver = null;
|
|
|
|
if (archer && from != null)
|
|
{
|
|
quiver = from.FindItemOnLayer<BaseQuiver>(Layer.Cloak);
|
|
}
|
|
|
|
int totalDamage;
|
|
|
|
if (!ignoreArmor)
|
|
{
|
|
// Armor Ignore on OSI ignores all defenses, not just physical.
|
|
var resPhys = m.PhysicalResistance;
|
|
var resFire = m.FireResistance;
|
|
var resCold = m.ColdResistance;
|
|
var resPois = m.PoisonResistance;
|
|
var resNrgy = m.EnergyResistance;
|
|
|
|
totalDamage = damage * phys * (100 - resPhys);
|
|
totalDamage += damage * fire * (100 - resFire);
|
|
totalDamage += damage * cold * (100 - resCold);
|
|
totalDamage += damage * pois * (100 - resPois);
|
|
totalDamage += damage * nrgy * (100 - resNrgy);
|
|
|
|
totalDamage /= 10000;
|
|
|
|
if (Core.ML)
|
|
{
|
|
totalDamage += damage * direct / 100;
|
|
|
|
if (quiver != null)
|
|
{
|
|
totalDamage += totalDamage * quiver.DamageIncrease / 100;
|
|
}
|
|
}
|
|
|
|
if (totalDamage < 1)
|
|
{
|
|
totalDamage = 1;
|
|
}
|
|
}
|
|
else if (Core.ML && m is PlayerMobile && from is PlayerMobile)
|
|
{
|
|
if (quiver != null)
|
|
{
|
|
damage += damage * quiver.DamageIncrease / 100;
|
|
}
|
|
|
|
if (!deathStrike)
|
|
{
|
|
totalDamage = Math.Min(damage, 35); // Direct Damage cap of 35
|
|
}
|
|
else
|
|
{
|
|
totalDamage = Math.Min(damage, 70); // Direct Damage cap of 70
|
|
}
|
|
}
|
|
else
|
|
{
|
|
totalDamage = damage;
|
|
|
|
if (Core.ML && quiver != null)
|
|
{
|
|
totalDamage += totalDamage * quiver.DamageIncrease / 100;
|
|
}
|
|
}
|
|
|
|
if (from?.Player != true && m.Player && m.Mount is SwampDragon { HasBarding: true } pet)
|
|
{
|
|
var percent = pet.BardingExceptional ? 20 : 10;
|
|
var absorbed = Scale(totalDamage, percent);
|
|
|
|
totalDamage -= absorbed;
|
|
pet.BardingHP -= absorbed;
|
|
|
|
if (pet.BardingHP < 0)
|
|
{
|
|
pet.HasBarding = false;
|
|
pet.BardingHP = 0;
|
|
|
|
m.SendLocalizedMessage(1053031); // Your dragon's barding has been destroyed!
|
|
}
|
|
}
|
|
|
|
if (keepAlive && totalDamage > m.Hits)
|
|
{
|
|
totalDamage = m.Hits;
|
|
}
|
|
|
|
var bcFrom = from as BaseCreature;
|
|
|
|
if (from is { Deleted: false, Alive: true })
|
|
{
|
|
var reflectPhys = AosAttributes.GetValue(m, AosAttribute.ReflectPhysical);
|
|
var reflectPhysAbility = bcFrom
|
|
?.GetAbility(MonsterAbilityType.ReflectPhysicalDamage) as ReflectPhysicalDamage;
|
|
|
|
if (reflectPhysAbility?.CanTrigger(bcFrom, MonsterAbilityTrigger.CombatAction) == true)
|
|
{
|
|
reflectPhys += reflectPhysAbility.PercentReflected;
|
|
m.SendLocalizedMessage(1070844); // The creature repels the attack back at you.
|
|
}
|
|
|
|
if (reflectPhys > 0)
|
|
{
|
|
var reflectDamage = Scale(
|
|
damage * phys * (100 - (ignoreArmor ? 0 : m.PhysicalResistance)) / 10000,
|
|
reflectPhys
|
|
);
|
|
|
|
bcFrom
|
|
?.GetAbility(MonsterAbilityType.MagicalBarrier)
|
|
?.AlterMeleeDamageFrom(bcFrom, m, ref reflectDamage);
|
|
|
|
if (reflectDamage > 0)
|
|
{
|
|
from.Damage(reflectDamage, m);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (totalDamage <= 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (from != null) // sanity check
|
|
{
|
|
SpellHelper.DoLeech(totalDamage, from, m);
|
|
}
|
|
|
|
m.Damage(totalDamage, from);
|
|
return totalDamage;
|
|
}
|
|
|
|
public static void Fix(ref int val)
|
|
{
|
|
if (val < 0)
|
|
{
|
|
val = 0;
|
|
}
|
|
}
|
|
|
|
public static int Scale(int input, int percent) => input * percent / 100;
|
|
|
|
public static int GetStatus(Mobile from, int index)
|
|
{
|
|
return index switch
|
|
{
|
|
// TODO: Account for buffs/debuffs
|
|
0 => from.GetMaxResistance(ResistanceType.Physical),
|
|
1 => from.GetMaxResistance(ResistanceType.Fire),
|
|
2 => from.GetMaxResistance(ResistanceType.Cold),
|
|
3 => from.GetMaxResistance(ResistanceType.Poison),
|
|
4 => from.GetMaxResistance(ResistanceType.Energy),
|
|
5 => AosAttributes.GetValue(from, AosAttribute.DefendChance),
|
|
6 => 45,
|
|
7 => AosAttributes.GetValue(from, AosAttribute.AttackChance),
|
|
8 => AosAttributes.GetValue(from, AosAttribute.WeaponSpeed),
|
|
9 => AosAttributes.GetValue(from, AosAttribute.WeaponDamage),
|
|
10 => AosAttributes.GetValue(from, AosAttribute.LowerRegCost),
|
|
11 => AosAttributes.GetValue(from, AosAttribute.SpellDamage),
|
|
12 => AosAttributes.GetValue(from, AosAttribute.CastRecovery),
|
|
13 => AosAttributes.GetValue(from, AosAttribute.CastSpeed),
|
|
14 => AosAttributes.GetValue(from, AosAttribute.LowerManaCost),
|
|
_ => 0
|
|
};
|
|
}
|
|
}
|
|
|
|
[Flags]
|
|
public enum AosAttribute
|
|
{
|
|
RegenHits = 0x00000001,
|
|
RegenStam = 0x00000002,
|
|
RegenMana = 0x00000004,
|
|
DefendChance = 0x00000008,
|
|
AttackChance = 0x00000010,
|
|
BonusStr = 0x00000020,
|
|
BonusDex = 0x00000040,
|
|
BonusInt = 0x00000080,
|
|
BonusHits = 0x00000100,
|
|
BonusStam = 0x00000200,
|
|
BonusMana = 0x00000400,
|
|
WeaponDamage = 0x00000800,
|
|
WeaponSpeed = 0x00001000,
|
|
SpellDamage = 0x00002000,
|
|
CastRecovery = 0x00004000,
|
|
CastSpeed = 0x00008000,
|
|
LowerManaCost = 0x00010000,
|
|
LowerRegCost = 0x00020000,
|
|
ReflectPhysical = 0x00040000,
|
|
EnhancePotions = 0x00080000,
|
|
Luck = 0x00100000,
|
|
SpellChanneling = 0x00200000,
|
|
NightSight = 0x00400000,
|
|
IncreasedKarmaLoss = 0x00800000
|
|
}
|
|
|
|
public interface IAosItem
|
|
{
|
|
public AosAttributes Attributes { get; }
|
|
}
|
|
|
|
public sealed class AosAttributes : BaseAttributes
|
|
{
|
|
public AosAttributes(Item owner) : base(owner)
|
|
{
|
|
}
|
|
|
|
public AosAttributes(Item owner, AosAttributes other) : base(owner, other)
|
|
{
|
|
}
|
|
|
|
public int this[AosAttribute attribute]
|
|
{
|
|
get => GetValue((int)attribute);
|
|
set => SetValue((int)attribute, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int RegenHits
|
|
{
|
|
get => this[AosAttribute.RegenHits];
|
|
set => this[AosAttribute.RegenHits] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int RegenStam
|
|
{
|
|
get => this[AosAttribute.RegenStam];
|
|
set => this[AosAttribute.RegenStam] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int RegenMana
|
|
{
|
|
get => this[AosAttribute.RegenMana];
|
|
set => this[AosAttribute.RegenMana] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int DefendChance
|
|
{
|
|
get => this[AosAttribute.DefendChance];
|
|
set => this[AosAttribute.DefendChance] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int AttackChance
|
|
{
|
|
get => this[AosAttribute.AttackChance];
|
|
set => this[AosAttribute.AttackChance] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int BonusStr
|
|
{
|
|
get => this[AosAttribute.BonusStr];
|
|
set => this[AosAttribute.BonusStr] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int BonusDex
|
|
{
|
|
get => this[AosAttribute.BonusDex];
|
|
set => this[AosAttribute.BonusDex] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int BonusInt
|
|
{
|
|
get => this[AosAttribute.BonusInt];
|
|
set => this[AosAttribute.BonusInt] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int BonusHits
|
|
{
|
|
get => this[AosAttribute.BonusHits];
|
|
set => this[AosAttribute.BonusHits] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int BonusStam
|
|
{
|
|
get => this[AosAttribute.BonusStam];
|
|
set => this[AosAttribute.BonusStam] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int BonusMana
|
|
{
|
|
get => this[AosAttribute.BonusMana];
|
|
set => this[AosAttribute.BonusMana] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int WeaponDamage
|
|
{
|
|
get => this[AosAttribute.WeaponDamage];
|
|
set => this[AosAttribute.WeaponDamage] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int WeaponSpeed
|
|
{
|
|
get => this[AosAttribute.WeaponSpeed];
|
|
set => this[AosAttribute.WeaponSpeed] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int SpellDamage
|
|
{
|
|
get => this[AosAttribute.SpellDamage];
|
|
set => this[AosAttribute.SpellDamage] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int CastRecovery
|
|
{
|
|
get => this[AosAttribute.CastRecovery];
|
|
set => this[AosAttribute.CastRecovery] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int CastSpeed
|
|
{
|
|
get => this[AosAttribute.CastSpeed];
|
|
set => this[AosAttribute.CastSpeed] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int LowerManaCost
|
|
{
|
|
get => this[AosAttribute.LowerManaCost];
|
|
set => this[AosAttribute.LowerManaCost] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int LowerRegCost
|
|
{
|
|
get => this[AosAttribute.LowerRegCost];
|
|
set => this[AosAttribute.LowerRegCost] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int ReflectPhysical
|
|
{
|
|
get => this[AosAttribute.ReflectPhysical];
|
|
set => this[AosAttribute.ReflectPhysical] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int EnhancePotions
|
|
{
|
|
get => this[AosAttribute.EnhancePotions];
|
|
set => this[AosAttribute.EnhancePotions] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int Luck
|
|
{
|
|
get => this[AosAttribute.Luck];
|
|
set => this[AosAttribute.Luck] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int SpellChanneling
|
|
{
|
|
get => this[AosAttribute.SpellChanneling];
|
|
set => this[AosAttribute.SpellChanneling] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int NightSight
|
|
{
|
|
get => this[AosAttribute.NightSight];
|
|
set => this[AosAttribute.NightSight] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int IncreasedKarmaLoss
|
|
{
|
|
get => this[AosAttribute.IncreasedKarmaLoss];
|
|
set => this[AosAttribute.IncreasedKarmaLoss] = value;
|
|
}
|
|
|
|
public void GetProperties(IPropertyList list, int damageBonus = 0, int hitChanceBonus = 0, int luckBonus = 0)
|
|
{
|
|
int prop;
|
|
|
|
if ((prop = WeaponDamage + damageBonus) != 0)
|
|
{
|
|
list.Add(1060401, prop); // damage increase ~1_val~%
|
|
}
|
|
|
|
if ((prop = DefendChance) != 0)
|
|
{
|
|
list.Add(1060408, prop); // defense chance increase ~1_val~%
|
|
}
|
|
|
|
if ((prop = BonusDex) != 0)
|
|
{
|
|
list.Add(1060409, prop); // dexterity bonus ~1_val~
|
|
}
|
|
|
|
if ((prop = EnhancePotions) != 0)
|
|
{
|
|
list.Add(1060411, prop); // enhance potions ~1_val~%
|
|
}
|
|
|
|
if ((prop = CastRecovery) != 0)
|
|
{
|
|
list.Add(1060412, prop); // faster cast recovery ~1_val~
|
|
}
|
|
|
|
if ((prop = CastSpeed) != 0)
|
|
{
|
|
list.Add(1060413, prop); // faster casting ~1_val~
|
|
}
|
|
|
|
if ((prop = AttackChance + hitChanceBonus) != 0)
|
|
{
|
|
list.Add(1060415, prop); // hit chance increase ~1_val~%
|
|
}
|
|
|
|
if ((prop = BonusHits) != 0)
|
|
{
|
|
list.Add(1060431, prop); // hit point increase ~1_val~
|
|
}
|
|
|
|
if ((prop = BonusInt) != 0)
|
|
{
|
|
list.Add(1060432, prop); // intelligence bonus ~1_val~
|
|
}
|
|
|
|
if ((prop = LowerManaCost) != 0)
|
|
{
|
|
list.Add(1060433, prop); // lower mana cost ~1_val~%
|
|
}
|
|
|
|
if ((prop = LowerRegCost) != 0)
|
|
{
|
|
list.Add(1060434, prop); // lower reagent cost ~1_val~%
|
|
}
|
|
|
|
if ((prop = Luck + luckBonus) != 0)
|
|
{
|
|
list.Add(1060436, prop); // luck ~1_val~
|
|
}
|
|
|
|
if ((prop = BonusMana) != 0)
|
|
{
|
|
list.Add(1060439, prop); // mana increase ~1_val~
|
|
}
|
|
|
|
if ((prop = RegenMana) != 0)
|
|
{
|
|
list.Add(1060440, prop); // mana regeneration ~1_val~
|
|
}
|
|
|
|
if (NightSight != 0)
|
|
{
|
|
list.Add(1060441); // night sight
|
|
}
|
|
|
|
if ((prop = ReflectPhysical) != 0)
|
|
{
|
|
list.Add(1060442, prop); // reflect physical damage ~1_val~%
|
|
}
|
|
|
|
if ((prop = RegenStam) != 0)
|
|
{
|
|
list.Add(1060443, prop); // stamina regeneration ~1_val~
|
|
}
|
|
|
|
if ((prop = RegenHits) != 0)
|
|
{
|
|
list.Add(1060444, prop); // hit point regeneration ~1_val~
|
|
}
|
|
|
|
if (SpellChanneling != 0)
|
|
{
|
|
list.Add(1060482); // spell channeling
|
|
}
|
|
|
|
if ((prop = SpellDamage) != 0)
|
|
{
|
|
list.Add(1060483, prop); // spell damage increase ~1_val~%
|
|
}
|
|
|
|
if ((prop = BonusStam) != 0)
|
|
{
|
|
list.Add(1060484, prop); // stamina increase ~1_val~
|
|
}
|
|
|
|
if ((prop = BonusStr) != 0)
|
|
{
|
|
list.Add(1060485, prop); // strength bonus ~1_val~
|
|
}
|
|
|
|
if ((prop = WeaponSpeed) != 0)
|
|
{
|
|
list.Add(1060486, prop); // swing speed increase ~1_val~%
|
|
}
|
|
|
|
if (Core.ML && (prop = IncreasedKarmaLoss) != 0)
|
|
{
|
|
list.Add(1075210, prop); // Increased Karma Loss ~1val~%
|
|
}
|
|
}
|
|
|
|
public static int GetValue(Mobile m, AosAttribute attribute)
|
|
{
|
|
if (!Core.AOS)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
var items = m.Items;
|
|
var value = 0;
|
|
|
|
for (var i = 0; i < items.Count; ++i)
|
|
{
|
|
var obj = items[i];
|
|
|
|
if (obj is BaseWeapon weapon)
|
|
{
|
|
var attrs = weapon.Attributes;
|
|
|
|
if (attrs != null)
|
|
{
|
|
value += attrs[attribute];
|
|
}
|
|
|
|
if (attribute == AosAttribute.Luck)
|
|
{
|
|
value += weapon.GetLuckBonus();
|
|
}
|
|
}
|
|
else if (obj is BaseArmor armor)
|
|
{
|
|
var attrs = armor.Attributes;
|
|
|
|
if (attrs != null)
|
|
{
|
|
value += attrs[attribute];
|
|
}
|
|
|
|
if (attribute == AosAttribute.Luck)
|
|
{
|
|
value += armor.GetLuckBonus();
|
|
}
|
|
}
|
|
else if (obj is BaseJewel jewel)
|
|
{
|
|
var attrs = jewel.Attributes;
|
|
|
|
if (attrs != null)
|
|
{
|
|
value += attrs[attribute];
|
|
}
|
|
}
|
|
else if (obj is BaseClothing clothing)
|
|
{
|
|
var attrs = clothing.Attributes;
|
|
|
|
if (attrs != null)
|
|
{
|
|
value += attrs[attribute];
|
|
}
|
|
}
|
|
else if (obj is Spellbook spellbook)
|
|
{
|
|
var attrs = spellbook.Attributes;
|
|
|
|
if (attrs != null)
|
|
{
|
|
value += attrs[attribute];
|
|
}
|
|
}
|
|
else if (obj is BaseQuiver quiver)
|
|
{
|
|
var attrs = quiver.Attributes;
|
|
|
|
if (attrs != null)
|
|
{
|
|
value += attrs[attribute];
|
|
}
|
|
}
|
|
else if (obj is BaseTalisman talisman)
|
|
{
|
|
var attrs = talisman.Attributes;
|
|
|
|
if (attrs != null)
|
|
{
|
|
value += attrs[attribute];
|
|
}
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
public override string ToString() => "...";
|
|
|
|
public void AddStatBonuses(Mobile to)
|
|
{
|
|
var strBonus = BonusStr;
|
|
var dexBonus = BonusDex;
|
|
var intBonus = BonusInt;
|
|
|
|
if (strBonus != 0 || dexBonus != 0 || intBonus != 0)
|
|
{
|
|
var hashCode = GetHashCode();
|
|
if (strBonus != 0)
|
|
{
|
|
to.AddStatMod(new StatMod(StatType.Str, $"{hashCode}Str", strBonus, TimeSpan.Zero));
|
|
}
|
|
|
|
if (dexBonus != 0)
|
|
{
|
|
to.AddStatMod(new StatMod(StatType.Dex, $"{hashCode}Dex", dexBonus, TimeSpan.Zero));
|
|
}
|
|
|
|
if (intBonus != 0)
|
|
{
|
|
to.AddStatMod(new StatMod(StatType.Int, $"{hashCode}Int", intBonus, TimeSpan.Zero));
|
|
}
|
|
}
|
|
|
|
to.CheckStatTimers();
|
|
}
|
|
|
|
public void RemoveStatBonuses(Mobile from)
|
|
{
|
|
var serial = Owner.Serial;
|
|
|
|
from.RemoveStatMod($"{serial}Str");
|
|
from.RemoveStatMod($"{serial}Dex");
|
|
from.RemoveStatMod($"{serial}Int");
|
|
|
|
from.CheckStatTimers();
|
|
}
|
|
}
|
|
|
|
[Flags]
|
|
public enum AosWeaponAttribute
|
|
{
|
|
LowerStatReq = 0x00000001,
|
|
SelfRepair = 0x00000002,
|
|
HitLeechHits = 0x00000004,
|
|
HitLeechStam = 0x00000008,
|
|
HitLeechMana = 0x00000010,
|
|
HitLowerAttack = 0x00000020,
|
|
HitLowerDefend = 0x00000040,
|
|
HitMagicArrow = 0x00000080,
|
|
HitHarm = 0x00000100,
|
|
HitFireball = 0x00000200,
|
|
HitLightning = 0x00000400,
|
|
HitDispel = 0x00000800,
|
|
HitColdArea = 0x00001000,
|
|
HitFireArea = 0x00002000,
|
|
HitPoisonArea = 0x00004000,
|
|
HitEnergyArea = 0x00008000,
|
|
HitPhysicalArea = 0x00010000,
|
|
ResistPhysicalBonus = 0x00020000,
|
|
ResistFireBonus = 0x00040000,
|
|
ResistColdBonus = 0x00080000,
|
|
ResistPoisonBonus = 0x00100000,
|
|
ResistEnergyBonus = 0x00200000,
|
|
UseBestSkill = 0x00400000,
|
|
MageWeapon = 0x00800000,
|
|
DurabilityBonus = 0x01000000
|
|
}
|
|
|
|
public sealed class AosWeaponAttributes : BaseAttributes
|
|
{
|
|
public AosWeaponAttributes(Item owner) : base(owner)
|
|
{
|
|
}
|
|
|
|
public AosWeaponAttributes(Item owner, AosWeaponAttributes other) : base(owner, other)
|
|
{
|
|
}
|
|
|
|
public int this[AosWeaponAttribute attribute]
|
|
{
|
|
get => GetValue((int)attribute);
|
|
set => SetValue((int)attribute, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int LowerStatReq
|
|
{
|
|
get => this[AosWeaponAttribute.LowerStatReq];
|
|
set => this[AosWeaponAttribute.LowerStatReq] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int SelfRepair
|
|
{
|
|
get => this[AosWeaponAttribute.SelfRepair];
|
|
set => this[AosWeaponAttribute.SelfRepair] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int HitLeechHits
|
|
{
|
|
get => this[AosWeaponAttribute.HitLeechHits];
|
|
set => this[AosWeaponAttribute.HitLeechHits] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int HitLeechStam
|
|
{
|
|
get => this[AosWeaponAttribute.HitLeechStam];
|
|
set => this[AosWeaponAttribute.HitLeechStam] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int HitLeechMana
|
|
{
|
|
get => this[AosWeaponAttribute.HitLeechMana];
|
|
set => this[AosWeaponAttribute.HitLeechMana] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int HitLowerAttack
|
|
{
|
|
get => this[AosWeaponAttribute.HitLowerAttack];
|
|
set => this[AosWeaponAttribute.HitLowerAttack] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int HitLowerDefend
|
|
{
|
|
get => this[AosWeaponAttribute.HitLowerDefend];
|
|
set => this[AosWeaponAttribute.HitLowerDefend] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int HitMagicArrow
|
|
{
|
|
get => this[AosWeaponAttribute.HitMagicArrow];
|
|
set => this[AosWeaponAttribute.HitMagicArrow] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int HitHarm
|
|
{
|
|
get => this[AosWeaponAttribute.HitHarm];
|
|
set => this[AosWeaponAttribute.HitHarm] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int HitFireball
|
|
{
|
|
get => this[AosWeaponAttribute.HitFireball];
|
|
set => this[AosWeaponAttribute.HitFireball] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int HitLightning
|
|
{
|
|
get => this[AosWeaponAttribute.HitLightning];
|
|
set => this[AosWeaponAttribute.HitLightning] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int HitDispel
|
|
{
|
|
get => this[AosWeaponAttribute.HitDispel];
|
|
set => this[AosWeaponAttribute.HitDispel] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int HitColdArea
|
|
{
|
|
get => this[AosWeaponAttribute.HitColdArea];
|
|
set => this[AosWeaponAttribute.HitColdArea] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int HitFireArea
|
|
{
|
|
get => this[AosWeaponAttribute.HitFireArea];
|
|
set => this[AosWeaponAttribute.HitFireArea] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int HitPoisonArea
|
|
{
|
|
get => this[AosWeaponAttribute.HitPoisonArea];
|
|
set => this[AosWeaponAttribute.HitPoisonArea] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int HitEnergyArea
|
|
{
|
|
get => this[AosWeaponAttribute.HitEnergyArea];
|
|
set => this[AosWeaponAttribute.HitEnergyArea] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int HitPhysicalArea
|
|
{
|
|
get => this[AosWeaponAttribute.HitPhysicalArea];
|
|
set => this[AosWeaponAttribute.HitPhysicalArea] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int ResistPhysicalBonus
|
|
{
|
|
get => this[AosWeaponAttribute.ResistPhysicalBonus];
|
|
set => this[AosWeaponAttribute.ResistPhysicalBonus] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int ResistFireBonus
|
|
{
|
|
get => this[AosWeaponAttribute.ResistFireBonus];
|
|
set => this[AosWeaponAttribute.ResistFireBonus] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int ResistColdBonus
|
|
{
|
|
get => this[AosWeaponAttribute.ResistColdBonus];
|
|
set => this[AosWeaponAttribute.ResistColdBonus] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int ResistPoisonBonus
|
|
{
|
|
get => this[AosWeaponAttribute.ResistPoisonBonus];
|
|
set => this[AosWeaponAttribute.ResistPoisonBonus] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int ResistEnergyBonus
|
|
{
|
|
get => this[AosWeaponAttribute.ResistEnergyBonus];
|
|
set => this[AosWeaponAttribute.ResistEnergyBonus] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int UseBestSkill
|
|
{
|
|
get => this[AosWeaponAttribute.UseBestSkill];
|
|
set => this[AosWeaponAttribute.UseBestSkill] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int MageWeapon
|
|
{
|
|
get => this[AosWeaponAttribute.MageWeapon];
|
|
set => this[AosWeaponAttribute.MageWeapon] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int DurabilityBonus
|
|
{
|
|
get => this[AosWeaponAttribute.DurabilityBonus];
|
|
set => this[AosWeaponAttribute.DurabilityBonus] = value;
|
|
}
|
|
|
|
public static int GetValue(Mobile m, AosWeaponAttribute attribute)
|
|
{
|
|
if (!Core.AOS)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
var items = m.Items;
|
|
var value = 0;
|
|
|
|
for (var i = 0; i < items.Count; ++i)
|
|
{
|
|
var obj = items[i];
|
|
|
|
if (obj is BaseWeapon weapon)
|
|
{
|
|
var attrs = weapon.WeaponAttributes;
|
|
|
|
if (attrs != null)
|
|
{
|
|
value += attrs[attribute];
|
|
}
|
|
}
|
|
else if (obj is ElvenGlasses glasses)
|
|
{
|
|
var attrs = glasses._weaponAttributes;
|
|
|
|
if (attrs != null)
|
|
{
|
|
value += attrs[attribute];
|
|
}
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
public void GetProperties(IPropertyList list)
|
|
{
|
|
int prop;
|
|
|
|
if (UseBestSkill != 0)
|
|
{
|
|
list.Add(1060400); // use best weapon skill
|
|
}
|
|
|
|
if ((prop = HitColdArea) != 0)
|
|
{
|
|
list.Add(1060416, prop); // hit cold area ~1_val~%
|
|
}
|
|
|
|
if ((prop = HitDispel) != 0)
|
|
{
|
|
list.Add(1060417, prop); // hit dispel ~1_val~%
|
|
}
|
|
|
|
if ((prop = HitEnergyArea) != 0)
|
|
{
|
|
list.Add(1060418, prop); // hit energy area ~1_val~%
|
|
}
|
|
|
|
if ((prop = HitFireArea) != 0)
|
|
{
|
|
list.Add(1060419, prop); // hit fire area ~1_val~%
|
|
}
|
|
|
|
if ((prop = HitFireball) != 0)
|
|
{
|
|
list.Add(1060420, prop); // hit fireball ~1_val~%
|
|
}
|
|
|
|
if ((prop = HitHarm) != 0)
|
|
{
|
|
list.Add(1060421, prop); // hit harm ~1_val~%
|
|
}
|
|
|
|
if ((prop = HitLeechHits) != 0)
|
|
{
|
|
list.Add(1060422, prop); // hit life leech ~1_val~%
|
|
}
|
|
|
|
if ((prop = HitLightning) != 0)
|
|
{
|
|
list.Add(1060423, prop); // hit lightning ~1_val~%
|
|
}
|
|
|
|
if ((prop = HitLowerAttack) != 0)
|
|
{
|
|
list.Add(1060424, prop); // hit lower attack ~1_val~%
|
|
}
|
|
|
|
if ((prop = HitLowerDefend) != 0)
|
|
{
|
|
list.Add(1060425, prop); // hit lower defense ~1_val~%
|
|
}
|
|
|
|
if ((prop = HitMagicArrow) != 0)
|
|
{
|
|
list.Add(1060426, prop); // hit magic arrow ~1_val~%
|
|
}
|
|
|
|
if ((prop = HitLeechMana) != 0)
|
|
{
|
|
list.Add(1060427, prop); // hit mana leech ~1_val~%
|
|
}
|
|
|
|
if ((prop = HitPhysicalArea) != 0)
|
|
{
|
|
list.Add(1060428, prop); // hit physical area ~1_val~%
|
|
}
|
|
|
|
if ((prop = HitPoisonArea) != 0)
|
|
{
|
|
list.Add(1060429, prop); // hit poison area ~1_val~%
|
|
}
|
|
|
|
if ((prop = HitLeechStam) != 0)
|
|
{
|
|
list.Add(1060430, prop); // hit stamina leech ~1_val~%
|
|
}
|
|
|
|
if ((prop = MageWeapon) != 0)
|
|
{
|
|
list.Add(1060438, 30 - prop); // mage weapon -~1_val~ skill
|
|
}
|
|
|
|
if ((prop = SelfRepair) != 0)
|
|
{
|
|
list.Add(1060450, prop); // self repair ~1_val~
|
|
}
|
|
}
|
|
|
|
public override string ToString() => "...";
|
|
}
|
|
|
|
[Flags]
|
|
public enum AosArmorAttribute
|
|
{
|
|
LowerStatReq = 0x00000001,
|
|
SelfRepair = 0x00000002,
|
|
MageArmor = 0x00000004,
|
|
DurabilityBonus = 0x00000008
|
|
}
|
|
|
|
public sealed class AosArmorAttributes : BaseAttributes
|
|
{
|
|
public AosArmorAttributes(Item owner) : base(owner)
|
|
{
|
|
}
|
|
|
|
public AosArmorAttributes(Item owner, AosArmorAttributes other) : base(owner, other)
|
|
{
|
|
}
|
|
|
|
public int this[AosArmorAttribute attribute]
|
|
{
|
|
get => GetValue((int)attribute);
|
|
set => SetValue((int)attribute, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int LowerStatReq
|
|
{
|
|
get => this[AosArmorAttribute.LowerStatReq];
|
|
set => this[AosArmorAttribute.LowerStatReq] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int SelfRepair
|
|
{
|
|
get => this[AosArmorAttribute.SelfRepair];
|
|
set => this[AosArmorAttribute.SelfRepair] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int MageArmor
|
|
{
|
|
get => this[AosArmorAttribute.MageArmor];
|
|
set => this[AosArmorAttribute.MageArmor] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int DurabilityBonus
|
|
{
|
|
get => this[AosArmorAttribute.DurabilityBonus];
|
|
set => this[AosArmorAttribute.DurabilityBonus] = value;
|
|
}
|
|
|
|
public static int GetValue(Mobile m, AosArmorAttribute attribute)
|
|
{
|
|
if (!Core.AOS)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
var items = m.Items;
|
|
var value = 0;
|
|
|
|
for (var i = 0; i < items.Count; ++i)
|
|
{
|
|
var obj = items[i];
|
|
|
|
if (obj is BaseArmor armor)
|
|
{
|
|
var attrs = armor.ArmorAttributes;
|
|
|
|
if (attrs != null)
|
|
{
|
|
value += attrs[attribute];
|
|
}
|
|
}
|
|
else if (obj is BaseClothing clothing)
|
|
{
|
|
var attrs = clothing.ClothingAttributes;
|
|
|
|
if (attrs != null)
|
|
{
|
|
value += attrs[attribute];
|
|
}
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
public void GetProperties(IPropertyList list)
|
|
{
|
|
int prop;
|
|
|
|
if (MageArmor != 0)
|
|
{
|
|
list.Add(1060437); // mage armor
|
|
}
|
|
|
|
if ((prop = SelfRepair) != 0)
|
|
{
|
|
list.Add(1060450, prop); // self repair ~1_val~
|
|
}
|
|
}
|
|
|
|
public override string ToString() => "...";
|
|
}
|
|
|
|
public sealed class AosSkillBonuses : BaseAttributes
|
|
{
|
|
private HashSet<SkillMod> m_Mods;
|
|
|
|
public AosSkillBonuses(Item owner) : base(owner)
|
|
{
|
|
}
|
|
|
|
public AosSkillBonuses(Item owner, AosSkillBonuses other) : base(owner, other)
|
|
{
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public double Skill_1_Value
|
|
{
|
|
get => GetBonus(0);
|
|
set => SetBonus(0, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public SkillName Skill_1_Name
|
|
{
|
|
get => GetSkill(0);
|
|
set => SetSkill(0, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public double Skill_2_Value
|
|
{
|
|
get => GetBonus(1);
|
|
set => SetBonus(1, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public SkillName Skill_2_Name
|
|
{
|
|
get => GetSkill(1);
|
|
set => SetSkill(1, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public double Skill_3_Value
|
|
{
|
|
get => GetBonus(2);
|
|
set => SetBonus(2, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public SkillName Skill_3_Name
|
|
{
|
|
get => GetSkill(2);
|
|
set => SetSkill(2, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public double Skill_4_Value
|
|
{
|
|
get => GetBonus(3);
|
|
set => SetBonus(3, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public SkillName Skill_4_Name
|
|
{
|
|
get => GetSkill(3);
|
|
set => SetSkill(3, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public double Skill_5_Value
|
|
{
|
|
get => GetBonus(4);
|
|
set => SetBonus(4, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public SkillName Skill_5_Name
|
|
{
|
|
get => GetSkill(4);
|
|
set => SetSkill(4, value);
|
|
}
|
|
|
|
public void GetProperties(IPropertyList list)
|
|
{
|
|
for (var i = 0; i < 5; ++i)
|
|
{
|
|
if (GetValues(i, out var skill, out var bonus))
|
|
{
|
|
list.Add(1060451 + i, $"{GetLabel(skill):#}\t{bonus}");
|
|
}
|
|
}
|
|
}
|
|
|
|
public static int GetLabel(SkillName skill)
|
|
{
|
|
return skill switch
|
|
{
|
|
SkillName.EvalInt => 1002070, // Evaluate Intelligence
|
|
SkillName.Forensics => 1002078, // Forensic Evaluation
|
|
SkillName.Lockpicking => 1002097, // Lockpicking
|
|
_ => 1044060 + (int)skill
|
|
};
|
|
}
|
|
|
|
public static int GetLowercaseLabel(SkillName skill)
|
|
{
|
|
return skill switch
|
|
{
|
|
SkillName.Provocation => 1049473, // provocation
|
|
SkillName.MagicResist => 1049471, // resisting spells
|
|
SkillName.AnimalTaming => 1049472, // animal taming
|
|
SkillName.Macing => 1049470, // mace fighting
|
|
SkillName.Necromancy => 1060842, // necromancy
|
|
SkillName.Focus => 1061613, // focus
|
|
SkillName.Chivalry => 1061615, // chivalry
|
|
SkillName.Bushido => 1062935, // bushido
|
|
SkillName.Ninjitsu => 1062936, // ninjitsu
|
|
SkillName.Spellweaving => 1074397, // spellweaving
|
|
SkillName.Mysticism => 1112544, // mysticism
|
|
SkillName.Imbuing => 1112545, // imbuing
|
|
SkillName.Throwing => 1112553, // throwing
|
|
_ => 1042347 + (int)skill
|
|
};
|
|
}
|
|
|
|
public void AddTo(Mobile m)
|
|
{
|
|
Remove();
|
|
|
|
for (var i = 0; i < 5; ++i)
|
|
{
|
|
if (!GetValues(i, out var skill, out var bonus))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
m_Mods ??= new HashSet<SkillMod>();
|
|
|
|
SkillMod sk = new DefaultSkillMod(skill, $"{GetHashCode()}{skill}", true, bonus);
|
|
sk.ObeyCap = true;
|
|
m.AddSkillMod(sk);
|
|
m_Mods.Add(sk);
|
|
}
|
|
}
|
|
|
|
public void Remove()
|
|
{
|
|
if (m_Mods == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var mod in m_Mods)
|
|
{
|
|
var m = mod.Owner;
|
|
mod.Remove();
|
|
|
|
if (Core.ML)
|
|
{
|
|
CheckCancelMorph(m);
|
|
}
|
|
}
|
|
|
|
m_Mods = null;
|
|
}
|
|
|
|
public bool GetValues(int index, out SkillName skill, out double bonus)
|
|
{
|
|
var v = GetValue(1 << index);
|
|
var vSkill = 0;
|
|
var vBonus = 0;
|
|
|
|
for (var i = 0; i < 16; ++i)
|
|
{
|
|
vSkill <<= 1;
|
|
vSkill |= v & 1;
|
|
v >>= 1;
|
|
|
|
vBonus <<= 1;
|
|
vBonus |= v & 1;
|
|
v >>= 1;
|
|
}
|
|
|
|
skill = (SkillName)vSkill;
|
|
bonus = (double)vBonus / 10;
|
|
|
|
return bonus != 0;
|
|
}
|
|
|
|
public void SetValues(int index, SkillName skill, double bonus)
|
|
{
|
|
var v = 0;
|
|
var vSkill = (int)skill;
|
|
var vBonus = (int)(bonus * 10);
|
|
|
|
for (var i = 0; i < 16; ++i)
|
|
{
|
|
v <<= 1;
|
|
v |= vBonus & 1;
|
|
vBonus >>= 1;
|
|
|
|
v <<= 1;
|
|
v |= vSkill & 1;
|
|
vSkill >>= 1;
|
|
}
|
|
|
|
SetValue(1 << index, v);
|
|
}
|
|
|
|
public SkillName GetSkill(int index)
|
|
{
|
|
GetValues(index, out var skill, out var _);
|
|
|
|
return skill;
|
|
}
|
|
|
|
public void SetSkill(int index, SkillName skill)
|
|
{
|
|
SetValues(index, skill, GetBonus(index));
|
|
}
|
|
|
|
public double GetBonus(int index)
|
|
{
|
|
GetValues(index, out var _, out var bonus);
|
|
|
|
return bonus;
|
|
}
|
|
|
|
public void SetBonus(int index, double bonus)
|
|
{
|
|
SetValues(index, GetSkill(index), bonus);
|
|
}
|
|
|
|
public override string ToString() => "...";
|
|
|
|
public static void CheckCancelMorph(Mobile m)
|
|
{
|
|
if (m == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var acontext = AnimalForm.GetContext(m);
|
|
var context = TransformationSpellHelper.GetContext(m);
|
|
|
|
if (context?.Spell is Spell spell)
|
|
{
|
|
spell.GetCastSkills(out var minSkill, out _);
|
|
if (m.Skills[spell.CastSkill].Value < minSkill)
|
|
{
|
|
TransformationSpellHelper.RemoveContext(m, context, true);
|
|
}
|
|
}
|
|
|
|
if (acontext != null)
|
|
{
|
|
int i;
|
|
for (i = 0; i < AnimalForm.Entries.Length; ++i)
|
|
{
|
|
if (AnimalForm.Entries[i].Type == acontext.Type)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (m.Skills.Ninjitsu.Value < AnimalForm.Entries[i].ReqSkill)
|
|
{
|
|
AnimalForm.RemoveContext(m);
|
|
}
|
|
}
|
|
|
|
if (m.Skills.Magery.Value < 66.1)
|
|
{
|
|
PolymorphSpell.EndPolymorph(m);
|
|
}
|
|
|
|
if (m.Skills.Magery.Value < 38.1)
|
|
{
|
|
IncognitoSpell.EndIncognito(m);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Flags]
|
|
public enum AosElementAttribute
|
|
{
|
|
Physical = 0x00000001,
|
|
Fire = 0x00000002,
|
|
Cold = 0x00000004,
|
|
Poison = 0x00000008,
|
|
Energy = 0x00000010,
|
|
Chaos = 0x00000020,
|
|
Direct = 0x00000040
|
|
}
|
|
|
|
public sealed class AosElementAttributes : BaseAttributes
|
|
{
|
|
public AosElementAttributes(Item owner) : base(owner)
|
|
{
|
|
}
|
|
|
|
public AosElementAttributes(Item owner, AosElementAttributes other) : base(owner, other)
|
|
{
|
|
}
|
|
|
|
public int this[AosElementAttribute attribute]
|
|
{
|
|
get => GetValue((int)attribute);
|
|
set => SetValue((int)attribute, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int Physical
|
|
{
|
|
get => this[AosElementAttribute.Physical];
|
|
set => this[AosElementAttribute.Physical] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int Fire
|
|
{
|
|
get => this[AosElementAttribute.Fire];
|
|
set => this[AosElementAttribute.Fire] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int Cold
|
|
{
|
|
get => this[AosElementAttribute.Cold];
|
|
set => this[AosElementAttribute.Cold] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int Poison
|
|
{
|
|
get => this[AosElementAttribute.Poison];
|
|
set => this[AosElementAttribute.Poison] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int Energy
|
|
{
|
|
get => this[AosElementAttribute.Energy];
|
|
set => this[AosElementAttribute.Energy] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int Chaos
|
|
{
|
|
get => this[AosElementAttribute.Chaos];
|
|
set => this[AosElementAttribute.Chaos] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int Direct
|
|
{
|
|
get => this[AosElementAttribute.Direct];
|
|
set => this[AosElementAttribute.Direct] = value;
|
|
}
|
|
|
|
public override string ToString() => "...";
|
|
}
|
|
|
|
[PropertyObject]
|
|
[SerializationGenerator(0)]
|
|
public abstract partial class BaseAttributes
|
|
{
|
|
[SerializableField(0, setter: "private")]
|
|
private uint _names;
|
|
|
|
[EncodedInt]
|
|
[SerializableField(1, setter: "private")]
|
|
private int[] _values;
|
|
|
|
public BaseAttributes(Item owner)
|
|
{
|
|
_owner = owner;
|
|
_values = Array.Empty<int>();
|
|
}
|
|
|
|
public BaseAttributes(Item owner, BaseAttributes other)
|
|
{
|
|
_owner = owner;
|
|
_values = new int[other._values.Length];
|
|
other._values.CopyTo(_values, 0);
|
|
_names = other._names;
|
|
}
|
|
|
|
public bool IsEmpty => _names == 0;
|
|
|
|
private IEntity _owner;
|
|
|
|
[DirtyTrackingEntity]
|
|
public IEntity Owner => _owner;
|
|
|
|
public int GetValue(int bitmask)
|
|
{
|
|
if (!Core.AOS)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
var mask = (uint)bitmask;
|
|
|
|
if ((_names & mask) == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
var index = GetIndex(mask);
|
|
|
|
if (index >= 0 && index < _values.Length)
|
|
{
|
|
return _values[index];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
public void SetValue(int bitmask, int value)
|
|
{
|
|
if (bitmask == (int)AosWeaponAttribute.DurabilityBonus && this is AosWeaponAttributes)
|
|
{
|
|
if (Owner is BaseWeapon weapon)
|
|
{
|
|
weapon.UnscaleDurability();
|
|
}
|
|
}
|
|
else if (bitmask == (int)AosArmorAttribute.DurabilityBonus && this is AosArmorAttributes)
|
|
{
|
|
if (Owner is BaseArmor armor)
|
|
{
|
|
armor.UnscaleDurability();
|
|
}
|
|
else if (Owner is BaseClothing clothing)
|
|
{
|
|
clothing.UnscaleDurability();
|
|
}
|
|
}
|
|
|
|
var mask = (uint)bitmask;
|
|
|
|
if (value != 0)
|
|
{
|
|
if ((_names & mask) != 0)
|
|
{
|
|
var index = GetIndex(mask);
|
|
|
|
if (index >= 0 && index < _values.Length)
|
|
{
|
|
_values[index] = value;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var index = GetIndex(mask);
|
|
|
|
if (index >= 0 && index <= _values.Length)
|
|
{
|
|
var old = _values;
|
|
_values = new int[old.Length + 1];
|
|
|
|
for (var i = 0; i < index; ++i)
|
|
{
|
|
_values[i] = old[i];
|
|
}
|
|
|
|
_values[index] = value;
|
|
|
|
for (var i = index; i < old.Length; ++i)
|
|
{
|
|
_values[i + 1] = old[i];
|
|
}
|
|
|
|
_names |= mask;
|
|
}
|
|
}
|
|
}
|
|
else if ((_names & mask) != 0)
|
|
{
|
|
var index = GetIndex(mask);
|
|
|
|
if (index >= 0 && index < _values.Length)
|
|
{
|
|
_names &= ~mask;
|
|
|
|
if (_values.Length == 1)
|
|
{
|
|
_values = Array.Empty<int>();
|
|
}
|
|
else
|
|
{
|
|
var old = _values;
|
|
_values = new int[old.Length - 1];
|
|
|
|
for (var i = 0; i < index; ++i)
|
|
{
|
|
_values[i] = old[i];
|
|
}
|
|
|
|
for (var i = index + 1; i < old.Length; ++i)
|
|
{
|
|
_values[i - 1] = old[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bitmask == (int)AosWeaponAttribute.DurabilityBonus && this is AosWeaponAttributes)
|
|
{
|
|
if (Owner is BaseWeapon weapon)
|
|
{
|
|
weapon.ScaleDurability();
|
|
}
|
|
}
|
|
else if (bitmask == (int)AosArmorAttribute.DurabilityBonus && this is AosArmorAttributes)
|
|
{
|
|
if (Owner is BaseArmor armor)
|
|
{
|
|
armor.ScaleDurability();
|
|
}
|
|
else if (Owner is BaseClothing clothing)
|
|
{
|
|
clothing.ScaleDurability();
|
|
}
|
|
}
|
|
|
|
if (Owner is Item item)
|
|
{
|
|
if (item.Parent is Mobile m)
|
|
{
|
|
m.CheckStatTimers();
|
|
m.UpdateResistances();
|
|
m.Delta(
|
|
MobileDelta.Stat | MobileDelta.WeaponDamage | MobileDelta.Hits | MobileDelta.Stam |
|
|
MobileDelta.Mana
|
|
);
|
|
|
|
if (this is AosSkillBonuses skillBonuses)
|
|
{
|
|
skillBonuses.Remove();
|
|
skillBonuses.AddTo(m);
|
|
}
|
|
}
|
|
|
|
item.InvalidateProperties();
|
|
}
|
|
else if (Owner is Mobile mob)
|
|
{
|
|
mob.InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
private int GetIndex(uint mask)
|
|
{
|
|
var index = 0;
|
|
var ourNames = _names;
|
|
uint currentBit = 1;
|
|
|
|
while (currentBit != mask)
|
|
{
|
|
if ((ourNames & currentBit) != 0)
|
|
{
|
|
++index;
|
|
}
|
|
|
|
if (currentBit == 0x80000000)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
currentBit <<= 1;
|
|
}
|
|
|
|
return index;
|
|
}
|
|
}
|
|
}
|