ModernUO/ToAdd/AutoBandCommand.cs

180 lines
No EOL
5.5 KiB
C#

/**********************************
* Author: Expa (refactored) *
* Script Name: AutoBandCommand.cs *
* Target: ModernUO (.NET 10/C#14) *
**********************************/
using System;
using ModernUO.Serialization;
using Server;
using Server.Commands;
using Server.Items;
using Server.Mobiles;
using Server.Targeting;
namespace Server.Scripts.Commands;
public static class AutoBandCommand
{
public static void Initialize()
{
CommandSystem.Register("bas", AccessLevel.Player, OnBandSelf);
CommandSystem.Register("bat", AccessLevel.Player, OnBandTarget);
}
public static void OnBandSelf(CommandEventArgs e)
{
if (e.Mobile is not PlayerMobile pm) return;
Start(pm, pm);
}
public static void OnBandTarget(CommandEventArgs e)
=> e.Mobile.BeginTarget(-1, false, TargetFlags.None, OnTarget);
private static void OnTarget(Mobile from, object targeted)
{
if (from is not PlayerMobile pm || targeted is not Mobile target || target.Deleted || !target.Alive)
{
from.SendMessage("Invalid target.");
return;
}
Start(pm, target);
}
private static void Start(PlayerMobile pm, Mobile target)
{
if (pm.AutoBandageTimer is AutoBandageTimer existing && existing.Targets(target))
{
StopTimer(pm);
SendHealthMessage(pm, $"Bandage auto-heal DISABLED{(pm == target ? "" : $" on {target.Name}")}.");
return;
}
StopTimer(pm);
var band = pm.Backpack?.FindItemByType<Bandage>();
if (band == null || band.Amount <= 0)
{
pm.SendMessage("You have no bandages!");
return;
}
var timer = new AutoBandageTimer(pm, target);
pm.AutoBandageTimer = timer;
timer.Start();
SendHealthMessage(pm, $"Bandage auto-heal ENABLED{(pm == target ? "" : $" on {target.Name}")}. Health: {target.Hits}/{target.HitsMax}");
}
internal static void SendHealthMessage(Mobile m, string msg)
{
var denom = Math.Max(1, m.HitsMax);
double p = (double)m.Hits / denom;
var hue = p < 0.3 ? 33 : p < 0.7 ? 53 : 63;
m.SendMessage(hue, msg);
}
private static void StopTimer(PlayerMobile pm)
{
pm.AutoBandageTimer?.Stop();
pm.AutoBandageTimer = null;
}
}
[SerializationGenerator(0)]
public partial class AutoBandageTimer : Timer
{
private const double ResumeAt = 90.0;
private const double PauseAt = 100.0;
private static readonly TimeSpan TickInterval = TimeSpan.FromMilliseconds(100);
private readonly WeakReference<Mobile> _casterRef;
private readonly WeakReference<Mobile> _targetRef;
private bool _paused;
private DateTime _next;
public AutoBandageTimer(Mobile caster, Mobile target)
: base(TimeSpan.Zero, TickInterval)
{
_casterRef = new(caster);
_targetRef = new(target);
_next = Core.Now;
}
public bool Targets(Mobile m)
=> m is not null && _targetRef.TryGetTarget(out var t) && t == m;
protected override void OnTick()
{
if (!_casterRef.TryGetTarget(out var caster) || caster.Deleted || !caster.Alive || caster.Map == Map.Internal)
{
Stop();
Cleanup(caster);
return;
}
if (!_targetRef.TryGetTarget(out var target) || target.Deleted || !target.Alive || target.Map == Map.Internal)
{
caster.SendMessage("Target is no longer valid. Automatic bandaging stopped.");
Stop();
Cleanup(caster);
return;
}
if (BandageContext.GetContext(caster) != null)
return;
var denom = Math.Max(1, target.HitsMax);
double hpPercent = (double)target.Hits / denom * 100.0;
if (!_paused && hpPercent >= PauseAt)
{
_paused = true;
caster.SendMessage("Bandaging paused. Target's health is full.");
return;
}
if (_paused && hpPercent < ResumeAt)
{
_paused = false;
caster.SendMessage($"Bandaging resumed. Target's health dropped below {ResumeAt}%.");
_next = Core.Now;
}
if (_paused || Core.Now < _next) return;
var band = caster.Backpack?.FindItemByType<Bandage>();
if (band == null || band.Amount <= 0)
{
caster.SendMessage("Out of bandages. Automatic bandaging stopped.");
Stop();
Cleanup(caster);
return;
}
int preHits = target.Hits;
double delaySec = 5.0 + 0.05 * Math.Max(0, 120 - caster.Dex);
_next = Core.Now + TimeSpan.FromSeconds(delaySec);
Bandage.BandageTargetRequest(caster, band, target);
Timer.DelayCall(TimeSpan.FromSeconds(delaySec * 0.95), () => ShowResult(caster, target, preHits));
}
private static void ShowResult(Mobile? caster, Mobile? target, int preHits)
{
if (caster == null || target == null) return;
if (caster.Deleted || !caster.Alive || target.Deleted || !target.Alive) return;
int healed = target.Hits - preHits;
string msg = healed > 0
? $"Healing {target.Name}: {target.Hits}/{target.HitsMax} HP (+{healed})"
: $"Healing {target.Name}: {target.Hits}/{target.HitsMax} HP";
AutoBandCommand.SendHealthMessage(caster, msg);
}
private static void Cleanup(Mobile? caster)
{
if (caster is PlayerMobile pm) pm.AutoBandageTimer = null;
}
}