ModernUO/Projects/UOContent/Spells/Base/SpecialMove.cs
2025-12-27 16:47:28 -08:00

320 lines
8.7 KiB
C#

using System;
using System.Collections.Generic;
using Server.Engines.ConPVP;
using Server.Items;
using Server.Spells.Bushido;
using Server.Spells.Necromancy;
using Server.Spells.Ninjitsu;
namespace Server.Spells
{
public abstract class SpecialMove
{
private static readonly Dictionary<Mobile, SpecialMoveContext> _playersTable = new();
public virtual int BaseMana => 0;
public virtual SkillName MoveSkill => SkillName.Bushido;
public virtual double RequiredSkill => 0.0;
public virtual TextDefinition AbilityMessage => TextDefinition.Empty;
public virtual bool BlockedByAnimalForm => true;
public virtual bool DelayedContext => false;
public static Dictionary<Mobile, SpecialMove> Table { get; } = new();
public virtual bool ValidatesDuringHit => true;
public virtual int GetAccuracyBonus(Mobile attacker) => 0;
public virtual double GetDamageScalar(Mobile attacker, Mobile defender) => 1.0;
// Called before swinging, to make sure the accuracy scalar is to be computed.
public virtual bool OnBeforeSwing(Mobile attacker, Mobile defender) => true;
// Called when a hit connects, but before damage is calculated.
public virtual bool OnBeforeDamage(Mobile attacker, Mobile defender) => true;
// Called as soon as the ability is used.
public virtual void OnUse(Mobile from)
{
}
// Called when a hit connects, at the end of the weapon.OnHit() method.
public virtual void OnHit(Mobile attacker, Mobile defender, int damage)
{
}
// Called when a hit misses.
public virtual void OnMiss(Mobile attacker, Mobile defender)
{
}
// Called when the move is cleared.
public virtual void OnClearMove(Mobile from)
{
}
public virtual bool IgnoreArmor(Mobile attacker) => false;
public virtual double GetPropertyBonus(Mobile attacker) => 1.0;
public virtual bool CheckSkills(Mobile m)
{
if (m.Skills[MoveSkill].Value < RequiredSkill)
{
// You need at least ~1_SKILL_REQUIREMENT~ ~2_SKILL_NAME~ skill to use that ability.
m.SendLocalizedMessage(1063013, $"{RequiredSkill:F1}\t{MoveSkill}\t ");
return false;
}
return true;
}
public virtual int ScaleMana(Mobile m, int mana)
{
var scalar = 1.0;
if (!MindRotSpell.GetMindRotScalar(m, ref scalar))
{
scalar = 1.0;
}
// Lower Mana Cost = 40%
var lmc = Math.Min(AosAttributes.GetValue(m, AosAttribute.LowerManaCost), 40);
scalar -= (double)lmc / 100;
var total = (int)(mana * scalar);
if (m.Skills[MoveSkill].Value < 50.0 && GetContext(m) != null)
{
total *= 2;
}
return total;
}
public virtual bool CheckMana(Mobile from, bool consume)
{
var mana = ScaleMana(from, BaseMana);
if (from.Mana < mana)
{
// You need ~1_MANA_REQUIREMENT~ mana to perform that attack
from.SendLocalizedMessage(1060181, mana.ToString());
return false;
}
if (consume)
{
if (!DelayedContext)
{
SetContext(from);
}
from.Mana -= mana;
}
return true;
}
public virtual void SetContext(Mobile from)
{
if (GetContext(from) == null)
{
if (DelayedContext || from.Skills[MoveSkill].Value < 50.0)
{
Timer timer = new SpecialMoveTimer(from);
timer.Start();
AddContext(from, new SpecialMoveContext(timer, GetType()));
}
}
}
public virtual bool Validate(Mobile from)
{
if (!from.Player)
{
return true;
}
if (HonorableExecution.IsUnderPenalty(from))
{
from.SendLocalizedMessage(1063024); // You cannot perform this special move right now.
return false;
}
if (AnimalForm.UnderTransformation(from))
{
from.SendLocalizedMessage(1063024); // You cannot perform this special move right now.
return false;
}
var option = this switch
{
Backstab => "Backstab",
DeathStrike => "Death Strike",
FocusAttack => "Focus Attack",
KiAttack => "Ki Attack",
SurpriseAttack => "Surprise Attack",
HonorableExecution => "Honorable Execution",
LightningStrike => "Lightning Strike",
MomentumStrike => "Momentum Strike",
_ => null
};
if (option != null && !DuelContext.AllowSpecialMove(from, option, this))
{
return false;
}
return CheckSkills(from) && CheckMana(from, false);
}
public virtual void CheckGain(Mobile m)
{
m.CheckSkill(MoveSkill, RequiredSkill, RequiredSkill + 37.5);
}
public static void ClearAllMoves(Mobile m)
{
foreach (var kvp in SpellRegistry.SpecialMoves)
{
var moveID = kvp.Key;
if (moveID != -1)
{
m.NetState.SendToggleSpecialAbility(moveID + 1, false);
}
}
}
public static SpecialMove GetCurrentMove(Mobile m)
{
if (m == null)
{
return null;
}
if (!Core.SE)
{
ClearCurrentMove(m);
return null;
}
if (Table.TryGetValue(m, out var move) && move.ValidatesDuringHit && !move.Validate(m))
{
ClearCurrentMove(m);
return null;
}
return move;
}
public static bool SetCurrentMove(Mobile m, SpecialMove move)
{
if (!Core.SE)
{
ClearCurrentMove(m);
return false;
}
if (move?.Validate(m) == false)
{
ClearCurrentMove(m);
return false;
}
var sameMove = move == GetCurrentMove(m);
ClearCurrentMove(m);
if (sameMove)
{
return true;
}
if (move != null)
{
WeaponAbility.ClearCurrentAbility(m);
Table[m] = move;
move.OnUse(m);
var moveID = SpellRegistry.GetRegistryNumber(move);
if (moveID > 0)
{
m.NetState.SendToggleSpecialAbility(moveID + 1, true);
}
move.AbilityMessage.SendMessageTo(m);
}
return true;
}
public static void ClearCurrentMove(Mobile m)
{
if (Table.Remove(m, out var move))
{
move.OnClearMove(m);
var moveID = SpellRegistry.GetRegistryNumber(move);
if (moveID > 0)
{
m.NetState.SendToggleSpecialAbility(moveID + 1, false);
}
}
}
private static void AddContext(Mobile m, SpecialMoveContext context)
{
_playersTable[m] = context;
}
private static void RemoveContext(Mobile m)
{
var context = GetContext(m);
if (context != null)
{
_playersTable.Remove(m);
context.Timer.Stop();
}
}
private static SpecialMoveContext GetContext(Mobile m) => _playersTable.GetValueOrDefault(m);
private class SpecialMoveTimer : Timer
{
private readonly Mobile m_Mobile;
public SpecialMoveTimer(Mobile from) : base(TimeSpan.FromSeconds(3.0)) => m_Mobile = from;
protected override void OnTick()
{
RemoveContext(m_Mobile);
}
}
public class SpecialMoveContext
{
public SpecialMoveContext(Timer timer, Type type)
{
Timer = timer;
Type = type;
}
public Timer Timer { get; }
public Type Type { get; }
}
}
}