Merge pull request #6 from RebirthUO/feat/issue-3-battle-lust
feat(items): implement Battle Lust property
This commit is contained in:
commit
0e0af2287c
4 changed files with 862 additions and 1 deletions
|
|
@ -0,0 +1,624 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Server;
|
||||
using Server.Items;
|
||||
using Server.Mobiles;
|
||||
using Server.Text;
|
||||
using Xunit;
|
||||
|
||||
namespace UOContent.Tests;
|
||||
|
||||
[Collection("Sequential UOContent Tests")]
|
||||
public class BattleLustPropertyTests
|
||||
{
|
||||
private const int BattleLustCliloc = 1113710;
|
||||
private static readonly DateTime TestNow = new(2035, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
[Fact]
|
||||
public void WeaponAttributes_StoresAndDupesBattleLust()
|
||||
{
|
||||
var weapon = new TestKatana();
|
||||
var dupe = new TestKatana();
|
||||
|
||||
try
|
||||
{
|
||||
weapon.WeaponAttributes.BattleLust = 1;
|
||||
|
||||
weapon.Dupe(dupe);
|
||||
|
||||
Assert.Equal(1, weapon.WeaponAttributes.BattleLust);
|
||||
Assert.Equal(1, dupe.WeaponAttributes.BattleLust);
|
||||
}
|
||||
finally
|
||||
{
|
||||
weapon.Delete();
|
||||
dupe.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeaponAttributes_GetProperties_GatesBattleLustTooltipToStygianAbyss()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var weapon = new TestKatana();
|
||||
|
||||
try
|
||||
{
|
||||
weapon.WeaponAttributes.BattleLust = 1;
|
||||
|
||||
Core.Expansion = Expansion.ML;
|
||||
var preStygianAbyss = new RecordingPropertyList();
|
||||
weapon.WeaponAttributes.GetProperties(preStygianAbyss);
|
||||
Assert.DoesNotContain(preStygianAbyss.Numbers, number => number == BattleLustCliloc);
|
||||
|
||||
Core.Expansion = Expansion.SA;
|
||||
var stygianAbyss = new RecordingPropertyList();
|
||||
weapon.WeaponAttributes.GetProperties(stygianAbyss);
|
||||
Assert.Contains(stygianAbyss.Numbers, number => number == BattleLustCliloc);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
weapon.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DamageTaken_PreStygianAbyss_DoesNotGainBattleLustPoints()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var previousNow = Core._now;
|
||||
var wielder = CreateMobile();
|
||||
var source = CreateMobile(location: new Point3D(6201, 500, 0));
|
||||
var weapon = EquipBattleLustWeapon(wielder);
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.ML;
|
||||
Core._now = TestNow;
|
||||
|
||||
ApplyDamage(wielder, source, BattleLust.DamageThreshold);
|
||||
|
||||
Assert.Equal(0, BattleLust.GetPoints(wielder));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
Core._now = previousNow;
|
||||
BattleLust.Clear(wielder);
|
||||
weapon.Delete();
|
||||
wielder.Delete();
|
||||
source.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DamageTaken_GainsOnlyFromQualifyingExternalLivingMobileDamage()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var previousNow = Core._now;
|
||||
var wielder = CreateMobile(hitsMax: 5000, hits: 5000);
|
||||
var source = CreateMobile(location: new Point3D(6201, 500, 0));
|
||||
var deletedSource = CreateMobile(location: new Point3D(6202, 500, 0));
|
||||
var deadSource = CreateDeadPlayerMobile(new Point3D(6203, 500, 0));
|
||||
var weapon = EquipBattleLustWeapon(wielder);
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.SA;
|
||||
Core._now = TestNow;
|
||||
|
||||
ApplyDamage(wielder, source, BattleLust.DamageThreshold - 1);
|
||||
Assert.Equal(0, BattleLust.GetPoints(wielder));
|
||||
|
||||
Core._now = Core._now.AddSeconds(2);
|
||||
ApplyDamage(wielder, null, BattleLust.DamageThreshold);
|
||||
Assert.Equal(0, BattleLust.GetPoints(wielder));
|
||||
|
||||
Core._now = Core._now.AddSeconds(2);
|
||||
ApplyDamage(wielder, wielder, BattleLust.DamageThreshold);
|
||||
Assert.Equal(0, BattleLust.GetPoints(wielder));
|
||||
|
||||
deletedSource.Delete();
|
||||
Core._now = Core._now.AddSeconds(2);
|
||||
ApplyDamage(wielder, deletedSource, BattleLust.DamageThreshold);
|
||||
Assert.Equal(0, BattleLust.GetPoints(wielder));
|
||||
|
||||
Core._now = Core._now.AddSeconds(2);
|
||||
ApplyDamage(wielder, deadSource, BattleLust.DamageThreshold);
|
||||
Assert.Equal(0, BattleLust.GetPoints(wielder));
|
||||
|
||||
Core._now = Core._now.AddSeconds(2);
|
||||
ApplyDamage(wielder, source, BattleLust.DamageThreshold);
|
||||
Assert.Equal(1, BattleLust.GetPoints(wielder));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
Core._now = previousNow;
|
||||
BattleLust.Clear(wielder);
|
||||
weapon.Delete();
|
||||
wielder.Delete();
|
||||
source.Delete();
|
||||
deletedSource.Delete();
|
||||
deadSource.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DamageTaken_DoesNotGainWhenMobileDamageAppliesNoHitPointLoss()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var previousNow = Core._now;
|
||||
var wielder = CreateMobile(hitsMax: 5000, hits: 5000);
|
||||
var source = CreateMobile(location: new Point3D(6201, 500, 0));
|
||||
var weapon = EquipBattleLustWeapon(wielder);
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.SA;
|
||||
Core._now = TestNow;
|
||||
wielder.Blessed = true;
|
||||
|
||||
ApplyDamage(wielder, source, BattleLust.DamageThreshold);
|
||||
|
||||
Assert.Equal(5000, wielder.Hits);
|
||||
Assert.Equal(0, BattleLust.GetPoints(wielder));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
Core._now = previousNow;
|
||||
BattleLust.Clear(wielder);
|
||||
weapon.Delete();
|
||||
wielder.Delete();
|
||||
source.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DamageTaken_GainCooldownAllowsOnlyOnePointEveryTwoSecondsAndCapsAtFifteen()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var previousNow = Core._now;
|
||||
var wielder = CreateMobile(hitsMax: 5000, hits: 5000);
|
||||
var source = CreateMobile(location: new Point3D(6201, 500, 0));
|
||||
var weapon = EquipBattleLustWeapon(wielder);
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.SA;
|
||||
Core._now = TestNow;
|
||||
|
||||
ApplyDamage(wielder, source, BattleLust.DamageThreshold);
|
||||
Assert.Equal(1, BattleLust.GetPoints(wielder));
|
||||
|
||||
Core._now = Core._now.AddSeconds(1);
|
||||
ApplyDamage(wielder, source, BattleLust.DamageThreshold);
|
||||
Assert.Equal(1, BattleLust.GetPoints(wielder));
|
||||
|
||||
Core._now = Core._now.AddSeconds(1);
|
||||
ApplyDamage(wielder, source, BattleLust.DamageThreshold);
|
||||
Assert.Equal(2, BattleLust.GetPoints(wielder));
|
||||
|
||||
for (var i = 0; i < 20; i++)
|
||||
{
|
||||
Core._now = Core._now.AddSeconds(2);
|
||||
ApplyDamage(wielder, source, BattleLust.DamageThreshold);
|
||||
}
|
||||
|
||||
Assert.Equal(BattleLust.MaxPoints, BattleLust.GetPoints(wielder));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
Core._now = previousNow;
|
||||
BattleLust.Clear(wielder);
|
||||
weapon.Delete();
|
||||
wielder.Delete();
|
||||
source.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DamageTaken_DecaysOnePointEverySixSecondsAndCleansUpAtZero()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var previousNow = Core._now;
|
||||
var wielder = CreateMobile();
|
||||
var source = CreateMobile(location: new Point3D(6201, 500, 0));
|
||||
var weapon = EquipBattleLustWeapon(wielder);
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.SA;
|
||||
Core._now = TestNow;
|
||||
|
||||
ApplyDamage(wielder, source, BattleLust.DamageThreshold);
|
||||
Assert.Equal(1, BattleLust.GetPoints(wielder));
|
||||
|
||||
Core._now = Core._now.AddSeconds(6);
|
||||
Core._tickCount += 6000;
|
||||
Timer.Slice(Core._tickCount);
|
||||
|
||||
Assert.Equal(0, BattleLust.GetPoints(wielder));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
Core._now = previousNow;
|
||||
BattleLust.Clear(wielder);
|
||||
weapon.Delete();
|
||||
wielder.Delete();
|
||||
source.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RuntimeContext_CleansUpWhenOwnerDiesOrLosesBattleLustWeapon()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var previousNow = Core._now;
|
||||
var deadWielder = CreateMobile();
|
||||
var disarmedWielder = CreateMobile(location: new Point3D(6202, 500, 0));
|
||||
var source = CreateMobile(location: new Point3D(6201, 500, 0));
|
||||
var deadWeapon = EquipBattleLustWeapon(deadWielder);
|
||||
var disarmedWeapon = EquipBattleLustWeapon(disarmedWielder);
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.SA;
|
||||
Core._now = TestNow;
|
||||
|
||||
ApplyDamage(deadWielder, source, BattleLust.DamageThreshold);
|
||||
Assert.Equal(1, BattleLust.GetPoints(deadWielder));
|
||||
deadWielder.Kill();
|
||||
Assert.Equal(0, BattleLust.GetPoints(deadWielder));
|
||||
|
||||
ApplyDamage(disarmedWielder, source, BattleLust.DamageThreshold);
|
||||
Assert.Equal(1, BattleLust.GetPoints(disarmedWielder));
|
||||
disarmedWeapon.WeaponAttributes.BattleLust = 0;
|
||||
Assert.Equal(0, BattleLust.GetDamageBonus(disarmedWielder, source));
|
||||
Assert.Equal(0, BattleLust.GetPoints(disarmedWielder));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
Core._now = previousNow;
|
||||
BattleLust.Clear(deadWielder);
|
||||
BattleLust.Clear(disarmedWielder);
|
||||
deadWeapon.Delete();
|
||||
disarmedWeapon.Delete();
|
||||
deadWielder.Delete();
|
||||
disarmedWielder.Delete();
|
||||
source.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RuntimeContext_ClearsOnWeaponLossEvenIfReequippedBeforeLazyCleanup()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var previousNow = Core._now;
|
||||
var wielder = CreateMobile();
|
||||
var source = CreateMobile(location: new Point3D(6201, 500, 0));
|
||||
var weapon = EquipBattleLustWeapon(wielder);
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.SA;
|
||||
Core._now = TestNow;
|
||||
|
||||
ApplyDamage(wielder, source, BattleLust.DamageThreshold);
|
||||
Assert.Equal(1, BattleLust.GetPoints(wielder));
|
||||
|
||||
wielder.RemoveItem(weapon);
|
||||
wielder.AddItem(weapon);
|
||||
|
||||
Assert.Equal(0, BattleLust.GetPoints(wielder));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
Core._now = previousNow;
|
||||
BattleLust.Clear(wielder);
|
||||
weapon.Delete();
|
||||
wielder.Delete();
|
||||
source.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RuntimeContext_ClearsWhenBattleLustPropertyIsRemovedEvenIfRestoredBeforeLazyCleanup()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var previousNow = Core._now;
|
||||
var wielder = CreateMobile();
|
||||
var source = CreateMobile(location: new Point3D(6201, 500, 0));
|
||||
var weapon = EquipBattleLustWeapon(wielder);
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.SA;
|
||||
Core._now = TestNow;
|
||||
|
||||
ApplyDamage(wielder, source, BattleLust.DamageThreshold);
|
||||
Assert.Equal(1, BattleLust.GetPoints(wielder));
|
||||
|
||||
weapon.WeaponAttributes.BattleLust = 0;
|
||||
weapon.WeaponAttributes.BattleLust = 1;
|
||||
|
||||
Assert.Equal(0, BattleLust.GetPoints(wielder));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
Core._now = previousNow;
|
||||
BattleLust.Clear(wielder);
|
||||
weapon.Delete();
|
||||
wielder.Delete();
|
||||
source.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RuntimeContext_ClearsOnInvalidMapEvenIfOwnerReturnsBeforeDecayTick()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var previousNow = Core._now;
|
||||
var wielder = CreateMobile();
|
||||
var source = CreateMobile(location: new Point3D(6201, 500, 0));
|
||||
var weapon = EquipBattleLustWeapon(wielder);
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.SA;
|
||||
Core._now = TestNow;
|
||||
|
||||
ApplyDamage(wielder, source, BattleLust.DamageThreshold);
|
||||
Assert.Equal(1, BattleLust.GetPoints(wielder));
|
||||
|
||||
wielder.MoveToWorld(Point3D.Zero, Map.Internal);
|
||||
wielder.MoveToWorld(new Point3D(6200, 500, 0), Map.Felucca);
|
||||
|
||||
Assert.Equal(0, BattleLust.GetPoints(wielder));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
Core._now = previousNow;
|
||||
BattleLust.Clear(wielder);
|
||||
weapon.Delete();
|
||||
wielder.Delete();
|
||||
source.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDamageBonus_UsesAggressedCountAndPropertySpecificPvpPvmCaps()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var previousNow = Core._now;
|
||||
var attacker = CreateMobile(hitsMax: 5000, hits: 5000);
|
||||
var source = CreateMobile(location: new Point3D(6201, 500, 0));
|
||||
var playerDefender = CreateMobile(player: true, location: new Point3D(6202, 500, 0));
|
||||
var creatureDefender = CreateMobile(location: new Point3D(6203, 500, 0));
|
||||
var weapon = EquipBattleLustWeapon(attacker);
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.SA;
|
||||
Core._now = TestNow;
|
||||
BuildBattleLustPoints(attacker, source, BattleLust.MaxPoints);
|
||||
|
||||
Assert.Equal(0, BattleLust.GetDamageBonus(attacker, creatureDefender));
|
||||
|
||||
AddAggressed(attacker, creatureDefender);
|
||||
Assert.Equal(15, BattleLust.GetDamageBonus(attacker, creatureDefender));
|
||||
|
||||
AddAggressed(attacker, CreateMobile(location: new Point3D(6204, 500, 0)));
|
||||
AddAggressed(attacker, playerDefender);
|
||||
Assert.Equal(BattleLust.PvpDamageCap, BattleLust.GetDamageBonus(attacker, playerDefender));
|
||||
|
||||
for (var i = 0; i < 6; i++)
|
||||
{
|
||||
AddAggressed(attacker, CreateMobile(location: new Point3D(6205 + i, 500, 0)));
|
||||
}
|
||||
|
||||
Assert.Equal(BattleLust.PvmDamageCap, BattleLust.GetDamageBonus(attacker, creatureDefender));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
Core._now = previousNow;
|
||||
BattleLust.Clear(attacker);
|
||||
weapon.Delete();
|
||||
DeleteAggressedDefenders(attacker);
|
||||
attacker.Delete();
|
||||
source.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnHit_AppliesBattleLustBeforeGlobalWeaponDamageCap()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var previousNow = Core._now;
|
||||
var attacker = CreateMobile(hitsMax: 5000, hits: 5000);
|
||||
var source = CreateMobile(location: new Point3D(6201, 500, 0));
|
||||
var defender = CreateMobile(location: new Point3D(6202, 500, 0));
|
||||
var weapon = EquipBattleLustWeapon(attacker);
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.SA;
|
||||
Core._now = TestNow;
|
||||
BuildBattleLustPoints(attacker, source, BattleLust.MaxPoints);
|
||||
AddAggressed(attacker, defender);
|
||||
|
||||
var before = defender.Hits;
|
||||
weapon.OnHit(attacker, defender);
|
||||
Assert.Equal(115, before - defender.Hits);
|
||||
|
||||
defender.Hits = before;
|
||||
weapon.OnHit(attacker, defender, 4.0);
|
||||
Assert.Equal(400, before - defender.Hits);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
Core._now = previousNow;
|
||||
BattleLust.Clear(attacker);
|
||||
weapon.Delete();
|
||||
DeleteAggressedDefenders(attacker);
|
||||
attacker.Delete();
|
||||
source.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RunicAttributeGeneration_DoesNotRollBattleLust()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var weapon = new TestKatana();
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.SA;
|
||||
|
||||
BaseRunicTool.ApplyAttributesTo(weapon, false, 0, 25, 100, 100);
|
||||
|
||||
Assert.Equal(0, weapon.WeaponAttributes.BattleLust);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
weapon.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
private static TestKatana EquipBattleLustWeapon(Mobile wielder)
|
||||
{
|
||||
var weapon = new TestKatana();
|
||||
weapon.WeaponAttributes.BattleLust = 1;
|
||||
wielder.AddItem(weapon);
|
||||
return weapon;
|
||||
}
|
||||
|
||||
private static void BuildBattleLustPoints(Mobile wielder, Mobile source, int points)
|
||||
{
|
||||
for (var i = 0; i < points; i++)
|
||||
{
|
||||
ApplyDamage(wielder, source, BattleLust.DamageThreshold);
|
||||
Core._now = Core._now.AddSeconds(2);
|
||||
}
|
||||
|
||||
Assert.Equal(points, BattleLust.GetPoints(wielder));
|
||||
}
|
||||
|
||||
private static void ApplyDamage(Mobile damaged, Mobile source, int damage) =>
|
||||
AOS.Damage(damaged, source, damage, false, 100, 0, 0, 0, 0);
|
||||
|
||||
private static Mobile CreateMobile(
|
||||
int hitsMax = 500,
|
||||
int hits = 500,
|
||||
bool player = false,
|
||||
Point3D location = default
|
||||
)
|
||||
{
|
||||
var mobile = new Mobile(World.NewMobile)
|
||||
{
|
||||
Player = player
|
||||
};
|
||||
|
||||
mobile.DefaultMobileInit();
|
||||
InitializeHits(mobile, hitsMax, hits);
|
||||
mobile.MoveToWorld(location == default ? new Point3D(6200, 500, 0) : location, Map.Felucca);
|
||||
return mobile;
|
||||
}
|
||||
|
||||
private static PlayerMobile CreateDeadPlayerMobile(Point3D location)
|
||||
{
|
||||
var mobile = new PlayerMobile(World.NewMobile);
|
||||
mobile.DefaultMobileInit();
|
||||
mobile.Player = true;
|
||||
mobile.Hits = mobile.HitsMax;
|
||||
mobile.MoveToWorld(location, Map.Felucca);
|
||||
mobile.Body = mobile.Race.GhostBody(mobile);
|
||||
Assert.False(mobile.Alive);
|
||||
return mobile;
|
||||
}
|
||||
|
||||
private static void InitializeHits(Mobile mobile, int hitsMax, int hits)
|
||||
{
|
||||
mobile.RawStr = Math.Max(1, (hitsMax - 50) * 2);
|
||||
Assert.Equal(hitsMax, mobile.HitsMax);
|
||||
mobile.Hits = hits;
|
||||
}
|
||||
|
||||
private static void AddAggressed(Mobile attacker, Mobile defender) =>
|
||||
attacker.Aggressed.Add(AggressorInfo.Create(attacker, defender, false));
|
||||
|
||||
private static void DeleteAggressedDefenders(Mobile attacker)
|
||||
{
|
||||
for (var i = 0; i < attacker.Aggressed.Count; i++)
|
||||
{
|
||||
var info = attacker.Aggressed[i];
|
||||
var defender = info.Defender;
|
||||
defender.Delete();
|
||||
info.Free();
|
||||
}
|
||||
|
||||
attacker.Aggressed.Clear();
|
||||
}
|
||||
|
||||
private class TestKatana : Katana
|
||||
{
|
||||
public override int ComputeDamage(Mobile attacker, Mobile defender) => 100;
|
||||
public override int AbsorbDamage(Mobile attacker, Mobile defender, int damage) => damage;
|
||||
public override void AddBlood(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RecordingPropertyList : IPropertyList
|
||||
{
|
||||
private string _interpolated = string.Empty;
|
||||
|
||||
public List<int> Numbers { get; } = [];
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{
|
||||
}
|
||||
|
||||
public void Add(int number) => Numbers.Add(number);
|
||||
public void Add(int number, string argument) => Numbers.Add(number);
|
||||
public void Add(ReadOnlySpan<char> argument) => Numbers.Add(0);
|
||||
public void Add(int number, ReadOnlySpan<char> argument) => Numbers.Add(number);
|
||||
public void AddChunked(ReadOnlySpan<char> text) => Numbers.Add(0);
|
||||
public OplTextBlock TextBlock() => new(this);
|
||||
public void Add(int number, int value) => Numbers.Add(number);
|
||||
public void AddLocalized(int value) => Numbers.Add(0);
|
||||
public void AddLocalized(int number, int value) => Numbers.Add(number);
|
||||
public void Add(ref IPropertyList.InterpolatedStringHandler handler) => Numbers.Add(0);
|
||||
public void Add(int number, ref IPropertyList.InterpolatedStringHandler handler) => Numbers.Add(number);
|
||||
public void InitializeInterpolation(int literalLength, int formattedCount) => _interpolated = string.Empty;
|
||||
public void AppendLiteral(string value) => _interpolated += value;
|
||||
public void AppendFormatted<T>(T value) => _interpolated += value;
|
||||
public void AppendFormatted<T>(T value, string format) =>
|
||||
_interpolated += value is IFormattable formattable ? formattable.ToString(format, null) : value;
|
||||
public void AppendFormatted<T>(T value, int alignment) => _interpolated += value;
|
||||
public void AppendFormatted<T>(T value, int alignment, string format) =>
|
||||
_interpolated += value is IFormattable formattable ? formattable.ToString(format, null) : value;
|
||||
public void AppendFormatted(ReadOnlySpan<char> value) => _interpolated += value.ToString();
|
||||
public void AppendFormatted(ReadOnlySpan<char> value, int alignment, string format = null) =>
|
||||
_interpolated += value.ToString();
|
||||
public void AppendFormatted(object value, int alignment = 0, string format = null) => _interpolated += value;
|
||||
public void AppendFormatted(string value) => _interpolated += value;
|
||||
public void AppendFormatted(string value, int alignment, string format = null) => _interpolated += value;
|
||||
}
|
||||
}
|
||||
|
|
@ -1140,6 +1140,11 @@ public abstract partial class BaseWeapon
|
|||
return;
|
||||
}
|
||||
|
||||
if (WeaponAttributes.BattleLust != 0)
|
||||
{
|
||||
BattleLust.Clear(m);
|
||||
}
|
||||
|
||||
var serial = Serial;
|
||||
|
||||
m.RemoveStatMod($"{serial}Str");
|
||||
|
|
@ -1176,6 +1181,16 @@ public abstract partial class BaseWeapon
|
|||
m.Delta(MobileDelta.WeaponDamage);
|
||||
}
|
||||
|
||||
public override void OnMapChange()
|
||||
{
|
||||
base.OnMapChange();
|
||||
|
||||
if ((Map == null || Map == Map.Internal) && Parent is Mobile m && WeaponAttributes.BattleLust != 0)
|
||||
{
|
||||
BattleLust.Clear(m);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual SkillName GetUsedSkill(Mobile m, bool checkSkillAttrs)
|
||||
{
|
||||
SkillName sk;
|
||||
|
|
@ -1886,6 +1901,8 @@ public abstract partial class BaseWeapon
|
|||
percentageBonus += talisman.Killer.DamageBonus(defender);
|
||||
}
|
||||
|
||||
percentageBonus += BattleLust.GetDamageBonus(attacker, defender);
|
||||
|
||||
percentageBonus = Math.Min(percentageBonus, 300);
|
||||
|
||||
damage = AOS.Scale(damage, 100 + percentageBonus);
|
||||
|
|
|
|||
195
Projects/UOContent/Items/Weapons/BattleLust.cs
Normal file
195
Projects/UOContent/Items/Weapons/BattleLust.cs
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ModernUO.CodeGeneratedEvents;
|
||||
using Server.Mobiles;
|
||||
|
||||
namespace Server.Items;
|
||||
|
||||
public static class BattleLust
|
||||
{
|
||||
internal const int DamageThreshold = 30;
|
||||
internal const int MaxPoints = 15;
|
||||
internal const int PvpDamageCap = 45;
|
||||
internal const int PvmDamageCap = 90;
|
||||
|
||||
private static readonly TimeSpan GainDelay = TimeSpan.FromSeconds(2.0);
|
||||
private static readonly TimeSpan DecayDelay = TimeSpan.FromSeconds(6.0);
|
||||
|
||||
private static readonly Dictionary<Mobile, BattleLustContext> _contexts = [];
|
||||
|
||||
public static void OnDamageTaken(Mobile damaged, Mobile source, int damage)
|
||||
{
|
||||
if (damaged == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CanGain(damaged, source, damage))
|
||||
{
|
||||
if (_contexts.TryGetValue(damaged, out var existing) && !IsValidOwner(damaged))
|
||||
{
|
||||
Remove(damaged, existing);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_contexts.TryGetValue(damaged, out var context))
|
||||
{
|
||||
context = new BattleLustContext(damaged);
|
||||
_contexts[damaged] = context;
|
||||
}
|
||||
|
||||
context.TryGain();
|
||||
}
|
||||
|
||||
internal static int GetDamageBonus(Mobile attacker, Mobile defender)
|
||||
{
|
||||
if (attacker == null || defender?.Deleted != false)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!_contexts.TryGetValue(attacker, out var context))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!IsValidOwner(attacker))
|
||||
{
|
||||
Remove(attacker, context);
|
||||
return 0;
|
||||
}
|
||||
|
||||
var opponentCount = GetActiveAggressedCount(attacker);
|
||||
|
||||
if (opponentCount <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var cap = defender.Player ? PvpDamageCap : PvmDamageCap;
|
||||
return Math.Min(context.Points * opponentCount, cap);
|
||||
}
|
||||
|
||||
internal static int GetPoints(Mobile mobile)
|
||||
{
|
||||
if (mobile == null || !_contexts.TryGetValue(mobile, out var context))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!IsValidOwner(mobile))
|
||||
{
|
||||
Remove(mobile, context);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return context.Points;
|
||||
}
|
||||
|
||||
[OnEvent(nameof(PlayerMobile.PlayerDeathEvent))]
|
||||
[OnEvent(nameof(PlayerMobile.PlayerDeletedEvent))]
|
||||
[OnEvent(nameof(BaseCreature.CreatureDeathEvent))]
|
||||
[OnEvent(nameof(BaseCreature.CreatureDeletedEvent))]
|
||||
public static void Clear(Mobile mobile)
|
||||
{
|
||||
if (mobile != null && _contexts.Remove(mobile, out var context))
|
||||
{
|
||||
context.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CanGain(Mobile damaged, Mobile source, int damage) =>
|
||||
Core.SA &&
|
||||
damage >= DamageThreshold &&
|
||||
source?.Deleted == false &&
|
||||
source.Alive &&
|
||||
source != damaged &&
|
||||
IsValidOwner(damaged);
|
||||
|
||||
private static bool IsValidOwner(Mobile mobile) =>
|
||||
Core.SA &&
|
||||
mobile?.Deleted == false &&
|
||||
mobile.Alive &&
|
||||
mobile.Map != null &&
|
||||
mobile.Map != Map.Internal &&
|
||||
mobile.Weapon is BaseWeapon weapon &&
|
||||
weapon.WeaponAttributes.BattleLust != 0;
|
||||
|
||||
private static int GetActiveAggressedCount(Mobile mobile)
|
||||
{
|
||||
var count = 0;
|
||||
var list = mobile.Aggressed;
|
||||
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
var info = list[i];
|
||||
var defender = info.Defender;
|
||||
|
||||
if (!info.Expired && defender?.Deleted == false && defender.Alive)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private static void Remove(Mobile mobile, BattleLustContext context)
|
||||
{
|
||||
_contexts.Remove(mobile);
|
||||
context.Stop();
|
||||
}
|
||||
|
||||
private sealed class BattleLustContext
|
||||
{
|
||||
private readonly Mobile _owner;
|
||||
private TimerExecutionToken _decayTimerToken;
|
||||
private DateTime _nextGain;
|
||||
|
||||
public BattleLustContext(Mobile owner)
|
||||
{
|
||||
_owner = owner;
|
||||
Timer.StartTimer(DecayDelay, DecayDelay, Decay, out _decayTimerToken);
|
||||
}
|
||||
|
||||
public int Points { get; private set; }
|
||||
|
||||
public void TryGain()
|
||||
{
|
||||
if (Core.Now < _nextGain)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Points < MaxPoints)
|
||||
{
|
||||
Points++;
|
||||
}
|
||||
|
||||
_nextGain = Core.Now + GainDelay;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_decayTimerToken.Cancel();
|
||||
}
|
||||
|
||||
private void Decay()
|
||||
{
|
||||
if (!IsValidOwner(_owner))
|
||||
{
|
||||
BattleLust.Clear(_owner);
|
||||
return;
|
||||
}
|
||||
|
||||
Points--;
|
||||
|
||||
if (Points <= 0)
|
||||
{
|
||||
BattleLust.Clear(_owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -236,7 +236,9 @@ namespace Server
|
|||
SpellHelper.DoLeech(totalDamage, from, m);
|
||||
}
|
||||
|
||||
var oldHits = m.Hits;
|
||||
m.Damage(totalDamage, from);
|
||||
BattleLust.OnDamageTaken(m, from, Math.Max(0, oldHits - m.Hits));
|
||||
return totalDamage;
|
||||
}
|
||||
|
||||
|
|
@ -780,7 +782,8 @@ namespace Server
|
|||
UseBestSkill = 0x00400000,
|
||||
MageWeapon = 0x00800000,
|
||||
DurabilityBonus = 0x01000000,
|
||||
Bane = 0x02000000
|
||||
Bane = 0x02000000,
|
||||
BattleLust = 0x04000000
|
||||
}
|
||||
|
||||
public sealed class AosWeaponAttributes : BaseAttributes
|
||||
|
|
@ -981,6 +984,23 @@ namespace Server
|
|||
set => this[AosWeaponAttribute.Bane] = value;
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int BattleLust
|
||||
{
|
||||
get => this[AosWeaponAttribute.BattleLust];
|
||||
set
|
||||
{
|
||||
var hadBattleLust = BattleLust != 0;
|
||||
|
||||
this[AosWeaponAttribute.BattleLust] = value;
|
||||
|
||||
if (hadBattleLust && value == 0 && Owner is BaseWeapon { Parent: Mobile m })
|
||||
{
|
||||
Server.Items.BattleLust.Clear(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetValue(Mobile m, AosWeaponAttribute attribute)
|
||||
{
|
||||
if (!Core.AOS)
|
||||
|
|
@ -1027,6 +1047,11 @@ namespace Server
|
|||
list.Add(1154671); // Bane
|
||||
}
|
||||
|
||||
if (Core.SA && BattleLust != 0)
|
||||
{
|
||||
list.Add(1113710); // Battle Lust
|
||||
}
|
||||
|
||||
if (UseBestSkill != 0)
|
||||
{
|
||||
list.Add(1060400); // use best weapon skill
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue