ModernUO/Projects/UOContent/Spells/Mysticism/SpellTriggerSpell.cs
2026-07-10 16:07:36 +02:00

335 lines
9.4 KiB
C#

using System;
using System.Collections.Generic;
using ModernUO.CodeGeneratedEvents;
using Server.Gumps;
using Server.Items;
using Server.Mobiles;
namespace Server.Spells.Mysticism;
public class SpellTriggerSpell : MysticSpell
{
public const int SpellId = 685;
public const int MaximumStorableCircle = 6;
public const int CooldownSeconds = 300;
private static readonly SpellInfo _info = new(
"Spell Trigger",
"In Vas Ort Ex",
230,
9022,
Reagent.DragonsBlood,
Reagent.Garlic,
Reagent.MandrakeRoot,
Reagent.SpidersSilk
);
private static readonly SpellTriggerDefinition[] _definitions =
[
new(677, "Nether Bolt", SpellCircle.First, 0x2D9E),
new(678, "Healing Stone", SpellCircle.First, 0x2D9F),
new(679, "Purge Magic", SpellCircle.Second, 0x2DA0),
new(680, "Enchant", SpellCircle.Second, 0x2DA1),
new(682, "Eagle Strike", SpellCircle.Third, 0x2DA3),
new(683, "Animated Weapon", SpellCircle.Fourth, 0x2DA4),
new(684, "Stone Form", SpellCircle.Fourth, 0x2DA5),
new(687, "Cleansing Winds", SpellCircle.Sixth, 0x2DA8),
new(688, "Bombard", SpellCircle.Sixth, 0x2DA9)
];
private static readonly Dictionary<Mobile, DateTime> _cooldowns = new();
private TimerExecutionToken _selectionTimer;
static SpellTriggerSpell()
{
EventSink.Logout += OnLogout;
}
public SpellTriggerSpell(Mobile caster, Item scroll = null) : base(caster, scroll, _info)
{
}
public override SpellCircle Circle => SpellCircle.Fifth;
public static IReadOnlyList<SpellTriggerDefinition> Definitions => _definitions;
public override bool CheckCast()
{
if (!Core.SA || !base.CheckCast())
{
return false;
}
if (Caster.Backpack == null)
{
Caster.SendLocalizedMessage(1042001); // That must be in your pack for you to use it.
return false;
}
if (GetEligibleDefinitions(Caster).Count == 0)
{
Caster.SendMessage("You do not know any Mysticism spells that can be stored.");
return false;
}
return true;
}
public override void OnCast()
{
var definitions = GetEligibleDefinitions(Caster);
if (definitions.Count == 0 || !SpellTriggerGump.DisplayTo(Caster, this, definitions))
{
FinishSequence();
return;
}
Timer.StartTimer(TimeSpan.FromSeconds(30), CancelSelection, out _selectionTimer);
}
public override void OnDisturb(DisturbType type, bool message)
{
Caster.CloseGump<SpellTriggerGump>();
_selectionTimer.Cancel();
base.OnDisturb(type, message);
}
public override void FinishSequence()
{
_selectionTimer.Cancel();
Caster.CloseGump<SpellTriggerGump>();
base.FinishSequence();
}
internal void FinishSelection(int spellId)
{
if (Caster.Spell != this || State != SpellState.Sequencing)
{
return;
}
try
{
var definition = GetDefinition(spellId);
if (definition == null || !IsEligible(Caster, definition))
{
return;
}
var newStone = new SpellStone(definition.SpellId);
if (!Caster.PlaceInBackpack(newStone))
{
newStone.Delete();
Caster.SendMessage("There is not enough room in your backpack for a Spell Stone.");
return;
}
if (!CheckSequence())
{
newStone.Delete();
return;
}
using (var stones = Caster.Backpack.EnumerateItemsByType<SpellStone>())
{
foreach (var stone in stones)
{
if (stone != newStone)
{
stone.Delete();
}
}
}
Caster.PlaySound(0x659);
Caster.SendLocalizedMessage(1080165); // A Spell Stone appears in your backpack.
}
finally
{
FinishSequence();
}
}
internal void CancelSelection()
{
if (Caster.Spell == this && State == SpellState.Sequencing)
{
Caster.SendMessage("You decide not to create a Spell Stone.");
FinishSequence();
}
else
{
_selectionTimer.Cancel();
Caster.CloseGump<SpellTriggerGump>();
}
}
internal static int GetMaximumStorableCircle(Mobile caster)
{
if (caster == null)
{
return 0;
}
var skillTotal = GetBaseSkill(caster) + Math.Max(caster.Skills.Focus.Value, caster.Skills.Imbuing.Value);
return Math.Min(MaximumStorableCircle, (int)(skillTotal / 40.0));
}
internal static List<SpellTriggerDefinition> GetEligibleDefinitions(Mobile caster)
{
var definitions = new List<SpellTriggerDefinition>();
var maximumCircle = GetMaximumStorableCircle(caster);
if (caster == null || caster.Deleted || caster.Backpack == null || maximumCircle == 0)
{
return definitions;
}
for (var i = 0; i < _definitions.Length; i++)
{
var definition = _definitions[i];
if (definition.Rank > maximumCircle || !IsRegistered(definition.SpellId) || !IsKnown(caster, definition.SpellId))
{
continue;
}
definitions.Add(definition);
}
return definitions;
}
internal static SpellTriggerDefinition GetDefinition(int spellId)
{
for (var i = 0; i < _definitions.Length; i++)
{
if (_definitions[i].SpellId == spellId)
{
return _definitions[i];
}
}
return null;
}
internal static bool TryUseStone(SpellStone stone, Mobile caster)
{
if (stone == null || stone.Deleted || caster == null || caster.Deleted || !caster.Alive)
{
return false;
}
if (caster.Backpack == null || !stone.IsChildOf(caster.Backpack))
{
caster.SendLocalizedMessage(1042001); // That must be in your pack for you to use it.
return false;
}
if (IsOnCooldown(caster, out var remaining))
{
var seconds = Math.Max(1, (int)Math.Ceiling(remaining.TotalSeconds));
caster.SendLocalizedMessage(1079263, seconds.ToString()); // You must wait ~1_seconds~ seconds before you can use this item.
return false;
}
var definition = GetDefinition(stone.SpellId);
if (definition == null || !IsRegistered(stone.SpellId))
{
caster.SendLocalizedMessage(502345); // This spell has been temporarily disabled.
return false;
}
if (!IsKnown(caster, stone.SpellId))
{
caster.SendLocalizedMessage(500015); // You do not have that spell!
return false;
}
var spell = SpellRegistry.NewSpell(stone.SpellId, caster, stone);
if (spell == null || !spell.Cast())
{
return false;
}
_cooldowns[caster] = Core.Now.AddSeconds(CooldownSeconds);
stone.Delete();
return true;
}
internal static bool IsOnCooldown(Mobile caster, out TimeSpan remaining)
{
if (caster == null || !_cooldowns.TryGetValue(caster, out var cooldownUntil))
{
remaining = TimeSpan.Zero;
return false;
}
remaining = cooldownUntil - Core.Now;
if (remaining <= TimeSpan.Zero)
{
_cooldowns.Remove(caster);
remaining = TimeSpan.Zero;
return false;
}
return true;
}
internal static void ClearCooldownForTests(Mobile caster) => ClearCooldown(caster);
[OnEvent(nameof(PlayerMobile.PlayerDeathEvent))]
[OnEvent(nameof(PlayerMobile.PlayerDeletedEvent))]
[OnEvent(nameof(BaseCreature.CreatureDeathEvent))]
[OnEvent(nameof(BaseCreature.CreatureDeletedEvent))]
public static void OnCasterRemoved(Mobile caster) => ClearCooldown(caster);
private static void OnLogout(Mobile caster) => ClearCooldown(caster);
private static void ClearCooldown(Mobile caster)
{
if (caster != null)
{
_cooldowns.Remove(caster);
}
}
private static bool IsEligible(Mobile caster, SpellTriggerDefinition definition) =>
definition != null &&
definition.Rank <= GetMaximumStorableCircle(caster) &&
IsRegistered(definition.SpellId) &&
IsKnown(caster, definition.SpellId);
private static bool IsKnown(Mobile caster, int spellId) => Spellbook.Find(caster, spellId)?.HasSpell(spellId) == true;
private static bool IsRegistered(int spellId) =>
spellId >= 0 && spellId < SpellRegistry.Types.Length && SpellRegistry.Types[spellId] != null;
}
public sealed class SpellTriggerDefinition
{
public SpellTriggerDefinition(int spellId, string name, SpellCircle circle, int itemId)
{
SpellId = spellId;
Name = name;
Circle = circle;
ItemId = itemId;
}
public int SpellId { get; }
public string Name { get; }
public SpellCircle Circle { get; }
public int ItemId { get; }
public int Rank => (int)Circle + 1;
}