ModernUO/Projects/UOContent/Items/Weapons/Focus.cs
2026-07-10 22:02:14 +02:00

229 lines
6.4 KiB
C#

using System;
using System.Collections.Generic;
using ModernUO.CodeGeneratedEvents;
using Server.Collections;
using Server.Mobiles;
namespace Server.Items;
public static class FocusContext
{
// UO.com defines the -50% starting point and +20% cap but not the intermediate table.
// A deterministic 10-point step preserves those endpoints without copying ServUO's older sequence.
internal const int InitialDamageModifier = -50;
internal const int MaximumDamageModifier = 20;
internal const int DamageModifierStep = 10;
private static readonly Dictionary<BaseWeapon, Context> _contexts = [];
private static readonly Dictionary<Mobile, HashSet<BaseWeapon>> _attackerIndex = [];
private static readonly Dictionary<Mobile, HashSet<BaseWeapon>> _targetIndex = [];
private static bool _configured;
static FocusContext() => Configure();
public static void Configure()
{
if (_configured)
{
return;
}
_configured = true;
EventSink.Logout += Clear;
}
internal static int ActiveContextCount => _contexts.Count;
internal static int GetCurrentModifierForTests(BaseWeapon weapon) =>
weapon != null && _contexts.TryGetValue(weapon, out var context) ? context.DamageModifier : 0;
internal static int GetDamageBonus(BaseWeapon weapon, Mobile attacker, Mobile defender)
{
if (!CanUse(weapon, attacker, defender))
{
Clear(weapon);
return 0;
}
if (!_contexts.TryGetValue(weapon, out var context) || context.Attacker != attacker)
{
Clear(weapon);
context = new Context(attacker, defender);
_contexts[weapon] = context;
AddToIndex(_attackerIndex, attacker, weapon);
AddToIndex(_targetIndex, defender, weapon);
}
else if (context.Target != defender)
{
SetTarget(weapon, context, defender);
context.DamageModifier = InitialDamageModifier;
}
return context.DamageModifier;
}
internal static void OnSuccessfulHit(BaseWeapon weapon, Mobile attacker, Mobile defender)
{
if (!CanUse(weapon, attacker, defender))
{
Clear(weapon);
return;
}
if (!_contexts.TryGetValue(weapon, out var context) || context.Attacker != attacker ||
context.Target != defender)
{
return;
}
context.DamageModifier = Math.Min(
MaximumDamageModifier,
context.DamageModifier + DamageModifierStep
);
}
internal static void OnMiss(BaseWeapon weapon, Mobile attacker, Mobile defender)
{
if (!CanUse(weapon, attacker, defender))
{
Clear(weapon);
return;
}
if (_contexts.TryGetValue(weapon, out var context) && context.Attacker == attacker &&
context.Target != defender)
{
SetTarget(weapon, context, defender);
context.DamageModifier = InitialDamageModifier;
}
}
internal static void Clear(BaseWeapon weapon)
{
if (weapon == null || !_contexts.Remove(weapon, out var context))
{
return;
}
RemoveFromIndex(_attackerIndex, context.Attacker, weapon);
RemoveFromIndex(_targetIndex, context.Target, weapon);
}
[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)
{
return;
}
using var weapons = PooledRefList<BaseWeapon>.Create();
if (_attackerIndex.TryGetValue(mobile, out var attackers))
{
foreach (var weapon in attackers)
{
weapons.Add(weapon);
}
}
if (_targetIndex.TryGetValue(mobile, out var targets))
{
foreach (var weapon in targets)
{
weapons.Add(weapon);
}
}
for (var i = 0; i < weapons.Count; i++)
{
Clear(weapons[i]);
}
}
internal static void ClearAll()
{
using var weapons = PooledRefList<BaseWeapon>.Create();
foreach (var weapon in _contexts.Keys)
{
weapons.Add(weapon);
}
for (var i = 0; i < weapons.Count; i++)
{
Clear(weapons[i]);
}
}
private static bool CanUse(BaseWeapon weapon, Mobile attacker, Mobile defender) =>
Core.HS &&
weapon?.Deleted == false &&
weapon.ExtendedWeaponAttributes.Focus != 0 &&
attacker?.Deleted == false &&
attacker.Alive &&
attacker.Map != null &&
attacker.Map != Map.Internal &&
attacker.Weapon == weapon &&
weapon.Parent == attacker &&
defender?.Deleted == false &&
defender.Alive &&
defender.Map != null &&
defender.Map != Map.Internal;
private static void SetTarget(BaseWeapon weapon, Context context, Mobile target)
{
RemoveFromIndex(_targetIndex, context.Target, weapon);
context.Target = target;
AddToIndex(_targetIndex, target, weapon);
}
private static void AddToIndex(
Dictionary<Mobile, HashSet<BaseWeapon>> index,
Mobile mobile,
BaseWeapon weapon
)
{
if (!index.TryGetValue(mobile, out var weapons))
{
weapons = [];
index[mobile] = weapons;
}
weapons.Add(weapon);
}
private static void RemoveFromIndex(
Dictionary<Mobile, HashSet<BaseWeapon>> index,
Mobile mobile,
BaseWeapon weapon
)
{
if (mobile == null || !index.TryGetValue(mobile, out var weapons) || !weapons.Remove(weapon))
{
return;
}
if (weapons.Count == 0)
{
index.Remove(mobile);
}
}
private sealed class Context
{
public Context(Mobile attacker, Mobile target)
{
Attacker = attacker;
Target = target;
DamageModifier = InitialDamageModifier;
}
public Mobile Attacker { get; }
public Mobile Target { get; set; }
public int DamageModifier { get; set; }
}
}