using System; using System.Collections.Generic; using System.Text.Json.Serialization; using Server.Mobiles; namespace Server.Regions; public class GuardedRegion : BaseRegion { private static readonly object[] m_GuardParams = new object[1]; private readonly Dictionary m_GuardCandidates = new(); [JsonConstructor] // Don't include parent, since it is special public GuardedRegion(string name, Map map, int priority, params Rectangle3D[] area) : base(name, map, priority, area) => GuardType = DefaultGuardType; public GuardedRegion(string name, Map map, int priority, params Rectangle2D[] area) : base(name, map, priority, area) => GuardType = DefaultGuardType; public GuardedRegion(string name, Map map, Region parent, params Rectangle3D[] area) : base(name, map, parent, area) => GuardType = DefaultGuardType; public GuardedRegion(string name, Map map, Region parent, int priority, params Rectangle3D[] area) : base(name, map, parent, priority, area) => GuardType = DefaultGuardType; public GuardedRegion(string name, Map map, Region parent, int priority, Type guardType, params Rectangle3D[] area) : base(name, map, parent, priority, area) => GuardType = guardType ?? DefaultGuardType; public Type GuardType { get; set; } public bool GuardsDisabled { get; set; } public virtual bool AllowReds => Core.AOS; public virtual Type DefaultGuardType { get { if (Map == Map.Ilshenar || Map == Map.Malas) { return typeof(ArcherGuard); } return typeof(WarriorGuard); } } public virtual bool IsDisabled() => GuardsDisabled; public static void Configure() { CommandSystem.Register("CheckGuarded", AccessLevel.GameMaster, CheckGuarded_OnCommand); CommandSystem.Register("SetGuarded", AccessLevel.Administrator, SetGuarded_OnCommand); CommandSystem.Register("ToggleGuarded", AccessLevel.Administrator, ToggleGuarded_OnCommand); } [Usage("CheckGuarded"), Description("Returns a value indicating if the current region is guarded or not.")] private static void CheckGuarded_OnCommand(CommandEventArgs e) { var from = e.Mobile; var reg = from.Region.GetRegion(); if (reg == null) { from.SendMessage("You are not in a guardable region."); } else if (reg.GuardsDisabled) { from.SendMessage("The guards in this region have been disabled."); } else { from.SendMessage("This region is actively guarded."); } } [Usage("SetGuarded "), Description("Enables or disables guards for the current region.")] private static void SetGuarded_OnCommand(CommandEventArgs e) { var from = e.Mobile; if (e.Length == 1) { var reg = from.Region.GetRegion(); if (reg == null) { from.SendMessage("You are not in a guardable region."); } else { reg.GuardsDisabled = !e.GetBoolean(0); from.SendMessage( reg.GuardsDisabled ? "The guards in this region have been disabled." : "The guards in this region have been enabled." ); } } else { from.SendMessage("Format: SetGuarded "); } } [Usage("ToggleGuarded"), Description("Toggles the state of guards for the current region.")] private static void ToggleGuarded_OnCommand(CommandEventArgs e) { var from = e.Mobile; var reg = from.Region.GetRegion(); if (reg == null) { from.SendMessage("You are not in a guardable region."); } else { reg.GuardsDisabled = !reg.GuardsDisabled; from.SendMessage( reg.GuardsDisabled ? "The guards in this region have been disabled." : "The guards in this region have been enabled." ); } } public static GuardedRegion Disable(GuardedRegion reg) { reg.GuardsDisabled = true; return reg; } public virtual bool CheckVendorAccess(BaseVendor vendor, Mobile from) => from.AccessLevel >= AccessLevel.GameMaster || IsDisabled() || from.Kills < 5; public override bool OnBeginSpellCast(Mobile m, ISpell s) { if (!IsDisabled() && !s.OnCastInTown(this)) { m.SendLocalizedMessage(500946); // You cannot cast this in town! return false; } return base.OnBeginSpellCast(m, s); } public override bool AllowHousing(Mobile from, Point3D p) => false; public override void MakeGuard(Mobile focus) { BaseGuard useGuard = null; foreach (var m in focus.GetMobilesInRange(8)) { if (m.Focus == null) { useGuard = m; break; } } if (useGuard == null) { m_GuardParams[0] = focus; try { GuardType.CreateInstance(m_GuardParams); } catch { // ignored } } else { useGuard.Focus = focus; } } public override void OnEnter(Mobile m) { if (IsDisabled()) { return; } if (!AllowReds && m.Kills >= 5) { CheckGuardCandidate(m); } } public override void OnExit(Mobile m) { } public override void OnSpeech(SpeechEventArgs args) { base.OnSpeech(args); if (IsDisabled()) { return; } if (args.Mobile.Alive && args.HasKeyword(0x0007)) // *guards* { CallGuards(args.Mobile.Location); } } public override void OnAggressed(Mobile aggressor, Mobile aggressed, bool criminal) { base.OnAggressed(aggressor, aggressed, criminal); if (!IsDisabled() && aggressor != aggressed && criminal) { CheckGuardCandidate(aggressor); } } public override void OnGotBeneficialAction(Mobile helper, Mobile helped) { base.OnGotBeneficialAction(helper, helped); if (IsDisabled()) { return; } var noto = Notoriety.Compute(helper, helped); if (helper != helped && noto is Notoriety.Criminal or Notoriety.Murderer) { CheckGuardCandidate(helper); } } public override void OnCriminalAction(Mobile m, bool message) { base.OnCriminalAction(m, message); if (!IsDisabled()) { CheckGuardCandidate(m); } } public void CheckGuardCandidate(Mobile m) { if (IsDisabled() || !IsGuardCandidate(m)) { return; } if (!m_GuardCandidates.TryGetValue(m, out var timer)) { timer = new GuardTimer(m, m_GuardCandidates); timer.Start(); m_GuardCandidates[m] = timer; m.SendLocalizedMessage(502275); // Guards can now be called on you! var map = m.Map; if (map == null) { return; } Mobile fakeCall = null; var prio = 0.0; foreach (var v in m.GetMobilesInRange(8)) { if (!v.Player && v != m && !IsGuardCandidate(v) && ((v as BaseCreature)?.IsHumanInTown() ?? v.Body.IsHuman && v.Region.IsPartOf(this))) { var dist = m.GetDistanceToSqrt(v); if (fakeCall == null || dist < prio) { fakeCall = v; prio = dist; } } } if (fakeCall != null) { fakeCall.Say(Utility.Random(1013037, 16)); MakeGuard(m); timer.Stop(); m_GuardCandidates.Remove(m); m.SendLocalizedMessage(502276); // Guards can no longer be called on you. } } else { timer.Stop(); timer.Start(); } } public void CallGuards(Point3D p) { if (IsDisabled()) { return; } foreach (var m in Map.GetMobilesInRange(p, 14)) { if (IsGuardCandidate(m) && (!AllowReds && m.Kills >= 5 && m.Region.IsPartOf(this) || m_GuardCandidates.ContainsKey(m))) { if (m_GuardCandidates.Remove(m, out var timer)) { timer.Stop(); } MakeGuard(m); m.SendLocalizedMessage(502276); // Guards can no longer be called on you. break; } } } public bool IsGuardCandidate(Mobile m) => m is not BaseGuard && m.Alive && m.AccessLevel <= AccessLevel.Player && !m.Blessed && (m is not BaseCreature creature || !creature.IsInvulnerable) && !IsDisabled() && (!AllowReds && m.Kills >= 5 || m.Criminal); private class GuardTimer : Timer { private readonly Mobile m_Mobile; private readonly Dictionary m_Table; public GuardTimer(Mobile m, Dictionary table) : base(TimeSpan.FromSeconds(15.0)) { m_Mobile = m; m_Table = table; } protected override void OnTick() { if (m_Table.Remove(m_Mobile)) { m_Mobile.SendLocalizedMessage(502276); // Guards can no longer be called on you. } } } }