using System; using System.Collections.Generic; using ModernUO.Serialization; using Server.Mobiles; using Server.Network; using Server.Spells; using Server.Text; namespace Server.Items; [Flags] public enum TeleporterFlags { None = 0x00000000, Active = 0x00000001, Creatures = 0x00000002, CombatCheck = 0x00000004, CriminalCheck = 0x00000008, SourceEffect = 0x00000010, DestEffect = 0x00000020 } [SerializationGenerator(5, false)] public partial class Teleporter : Item { [InvalidateProperties] [SerializableField(0)] [SerializedCommandProperty(AccessLevel.GameMaster)] private TeleporterFlags _flags; [InvalidateProperties] [SerializableField(1)] [SerializedCommandProperty(AccessLevel.GameMaster)] private TimeSpan _delay; [EncodedInt] [InvalidateProperties] [SerializableField(2)] [SerializedCommandProperty(AccessLevel.GameMaster)] private int _soundID; [InvalidateProperties] [SerializableField(3)] [SerializedCommandProperty(AccessLevel.GameMaster)] private Point3D _pointDest; [InvalidateProperties] [SerializableField(4)] [SerializedCommandProperty(AccessLevel.GameMaster)] private Map _mapDest; [Constructible] public Teleporter() : this(new Point3D(0, 0, 0)) { } [Constructible] public Teleporter(Point3D pointDest, Map mapDest = null, bool creatures = false) : base(0x1BC3) { Movable = false; Visible = false; _flags = TeleporterFlags.Active; Creatures = creatures; _pointDest = pointDest; _mapDest = mapDest; } public bool GetFlag(TeleporterFlags flag) => (Flags & flag) != 0; public void SetFlag(TeleporterFlags flag, bool value) { if (value) { Flags |= flag; } else { Flags &= ~flag; } InvalidateProperties(); this.MarkDirty(); } [CommandProperty(AccessLevel.GameMaster)] public bool SourceEffect { get => GetFlag(TeleporterFlags.SourceEffect); set => SetFlag(TeleporterFlags.SourceEffect, value); } [CommandProperty(AccessLevel.GameMaster)] public bool DestEffect { get => GetFlag(TeleporterFlags.DestEffect); set => SetFlag(TeleporterFlags.DestEffect, value); } [CommandProperty(AccessLevel.GameMaster)] public bool Active { get => GetFlag(TeleporterFlags.Active); set => SetFlag(TeleporterFlags.Active, value); } [CommandProperty(AccessLevel.GameMaster)] public bool Creatures { get => GetFlag(TeleporterFlags.Creatures); set => SetFlag(TeleporterFlags.Creatures, value); } [CommandProperty(AccessLevel.GameMaster)] public bool CombatCheck { get => GetFlag(TeleporterFlags.CombatCheck); set => SetFlag(TeleporterFlags.CombatCheck, value); } [CommandProperty(AccessLevel.GameMaster)] public bool CriminalCheck { get => GetFlag(TeleporterFlags.CriminalCheck); set => SetFlag(TeleporterFlags.CriminalCheck, value); } public override int LabelNumber => 1026095; // teleporter public override void GetProperties(IPropertyList list) { base.GetProperties(list); if (Active) { list.Add(1060742); // active } else { list.Add(1060743); // inactive } if (_mapDest != null) { list.Add(1060658, $"{"Map"}\t{_mapDest}"); } if (_pointDest != Point3D.Zero) { list.Add(1060659, $"{"Coords"}\t{_pointDest}"); } list.Add(1060660, $"{"Creatures"}\t{(Creatures ? "Yes" : "No")}"); } public override void OnSingleClick(Mobile from) { base.OnSingleClick(from); if (Active) { if (_mapDest != null && _pointDest != Point3D.Zero) { LabelTo(from, $"{_pointDest} [{_mapDest}]"); } else if (_mapDest != null) { LabelTo(from, $"[{_mapDest}]"); } else if (_pointDest != Point3D.Zero) { LabelTo(from, _pointDest.ToString()); } } else { LabelTo(from, "(inactive)"); } } public virtual bool CanTeleport(Mobile m) { if (!Creatures && !m.Player) { return false; } if (CriminalCheck && m.Criminal) { m.SendLocalizedMessage(1005561, "", 0x22); // Thou'rt a criminal and cannot escape so easily. return false; } if (CombatCheck && SpellHelper.CheckCombat(m)) { m.SendLocalizedMessage(1005564, "", 0x22); // Wouldst thou flee during the heat of battle?? return false; } return true; } public virtual void StartTeleport(Mobile m) { if (_delay == TimeSpan.Zero) { DoTeleport(m); } else { Timer.StartTimer(_delay, () => DoTeleport(m)); } } public virtual void DoTeleport(Mobile m) { var map = _mapDest; if (map == null || map == Map.Internal) { map = m.Map; } var p = _pointDest; if (p == Point3D.Zero) { p = m.Location; } BaseCreature.TeleportPets(m, p, map); var sendEffect = !m.Hidden || m.AccessLevel == AccessLevel.Player; if (SourceEffect && sendEffect) { Effects.SendLocationEffect(m.Location, m.Map, 0x3728, 10); } m.MoveToWorld(p, map); if (DestEffect && sendEffect) { Effects.SendLocationEffect(m.Location, m.Map, 0x3728, 10); } if (_soundID > 0 && sendEffect) { Effects.PlaySound(m.Location, m.Map, _soundID); } } public override bool OnMoveOver(Mobile m) { if (Active && CanTeleport(m)) { StartTeleport(m); return false; } return true; } private void Deserialize(IGenericReader reader, int version) { if (reader.ReadBool()) { _flags |= TeleporterFlags.CriminalCheck; } if (reader.ReadBool()) { _flags |= TeleporterFlags.CombatCheck; } if (reader.ReadBool()) { _flags |= TeleporterFlags.SourceEffect; } if (reader.ReadBool()) { _flags |= TeleporterFlags.DestEffect; } _delay = reader.ReadTimeSpan(); _soundID = reader.ReadEncodedInt(); if (reader.ReadBool()) { _flags |= TeleporterFlags.Creatures; } if (reader.ReadBool()) { _flags |= TeleporterFlags.Active; } _pointDest = reader.ReadPoint3D(); _mapDest = reader.ReadMap(); } } [SerializationGenerator(1, false)] public partial class SkillTeleporter : Teleporter { [InvalidateProperties] [SerializableField(0)] [SerializedCommandProperty(AccessLevel.GameMaster)] private SkillName _skill; [InvalidateProperties] [SerializableField(1)] [SerializedCommandProperty(AccessLevel.GameMaster)] private double _required; [CanBeNull] [InvalidateProperties] [SerializableField(2)] [SerializedCommandProperty(AccessLevel.GameMaster)] private TextDefinition _message; [Constructible] public SkillTeleporter() { } public override bool CanTeleport(Mobile m) { if (!base.CanTeleport(m)) { return false; } var sk = m.Skills[_skill]; if (sk?.Base >= _required) { return true; } if (!m.BeginAction(this)) { return false; } if (_message?.Number > 0) { m.NetState.SendMessageLocalized( Serial, ItemID, MessageType.Regular, 0x3B2, 3, _message.Number, null ); } else if (!string.IsNullOrWhiteSpace(_message?.String)) { m.NetState.SendMessage( Serial, ItemID, MessageType.Regular, 0x3B2, 3, false, "ENU", null, _message.String ); } Timer.StartTimer(TimeSpan.FromSeconds(5.0), () => m.EndAction(this)); return false; } public override void GetProperties(IPropertyList list) { base.GetProperties(list); var skillIndex = (int)_skill; string skillName; if (skillIndex >= 0 && skillIndex < SkillInfo.Table.Length) { skillName = SkillInfo.Table[skillIndex].Name; } else { skillName = "(Invalid)"; } list.Add(1060661, $"{skillName}\t{_required:F1}"); if (_message?.Number > 0) { list.Add(1060662, $"{"Message"}\t{_message.Number:#}"); } else if (!string.IsNullOrWhiteSpace(_message?.String)) { list.Add(1060662, $"{"Message"}\t{_message.String}"); } } private void Deserialize(IGenericReader reader, int version) { _skill = (SkillName)reader.ReadInt(); _required = reader.ReadDouble(); _message = reader.ReadString(); var localNumber = reader.ReadInt(); if (localNumber > 0) { _message = localNumber; } } } [SerializationGenerator(0, false)] public partial class KeywordTeleporter : Teleporter { [SerializableField(0)] [SerializedCommandProperty(AccessLevel.GameMaster)] private string _substring; [SerializableField(1)] [SerializedCommandProperty(AccessLevel.GameMaster)] private int _keyword; [SerializableField(2)] [SerializedCommandProperty(AccessLevel.GameMaster)] private int _range; [Constructible] public KeywordTeleporter() { _keyword = -1; _substring = null; } public override bool HandlesOnSpeech => true; public override void OnSpeech(SpeechEventArgs e) { if (!e.Handled && Active) { var m = e.Mobile; if (!m.InRange(GetWorldLocation(), _range)) { return; } var isMatch = false; if (_keyword >= 0 && e.HasKeyword(_keyword)) { isMatch = true; } else if (_substring != null && e.Speech.InsensitiveContains(_substring)) { isMatch = true; } if (!isMatch || !CanTeleport(m)) { return; } e.Handled = true; StartTeleport(m); } } public override void DoTeleport(Mobile m) { if (!m.InRange(GetWorldLocation(), _range) || m.Map != Map) { return; } base.DoTeleport(m); } public override bool OnMoveOver(Mobile m) => true; public override void GetProperties(IPropertyList list) { base.GetProperties(list); list.Add(1060661, $"{"Range"}\t{_range}"); if (_keyword >= 0) { list.Add(1060662, $"{"Keyword"}\t{_keyword}"); } if (_substring != null) { list.Add(1060663, $"{"Substring"}\t{_substring}"); } } } [SerializationGenerator(1, false)] public partial class WaitTeleporter : KeywordTeleporter { private static Dictionary _table = new(); [CanBeNull] [InvalidateProperties] [SerializableField(0)] [SerializedCommandProperty(AccessLevel.GameMaster)] private TextDefinition _startMessage; [CanBeNull] [InvalidateProperties] [SerializableField(1)] [SerializedCommandProperty(AccessLevel.GameMaster)] private TextDefinition _progressMessage; [InvalidateProperties] [SerializableField(2)] [SerializedCommandProperty(AccessLevel.GameMaster)] private bool _showTimeRemaining; [Constructible] public WaitTeleporter() { } public static void Initialize() { EventSink.Logout += EventSink_Logout; } public static void EventSink_Logout(Mobile from) { if (from != null && _table.Remove(from, out var info)) { info.TimerToken.Cancel(); } } public static string FormatTime(TimeSpan ts) { if (ts.TotalHours >= 1) { var h = (int)Math.Round(ts.TotalHours); return $"{h} hour{(h == 1 ? "" : "s")}"; } if (ts.TotalMinutes >= 1) { var m = (int)Math.Round(ts.TotalMinutes); return $"{m} minute{(m == 1 ? "" : "s")}"; } var s = Math.Max((int)Math.Round(ts.TotalSeconds), 0); return $"{s} second{(s == 1 ? "" : "s")}"; } public override void StartTeleport(Mobile m) { if (_table.TryGetValue(m, out var info)) { if (info.Teleporter == this) { if (!m.BeginAction(this)) { return; } _progressMessage.SendMessageTo(m); if (_showTimeRemaining) { m.SendMessage($"Time remaining: {FormatTime(info.TimerToken.Next - Core.Now)}"); } Timer.StartTimer(TimeSpan.FromSeconds(5), () => m.EndAction(this)); return; } info.TimerToken.Cancel(); } _startMessage.SendMessageTo(m); if (Delay == TimeSpan.Zero) { DoTeleport(m); } else { Timer.StartTimer(Delay, () => DoTeleport(m), out var timerToken); _table[m] = new TeleportingInfo(this, timerToken); } } public override void DoTeleport(Mobile m) { _table.Remove(m); base.DoTeleport(m); } private void Deserialize(IGenericReader reader, int version) { var number = reader.ReadInt(); var message = reader.ReadString(); _startMessage = number > 0 ? number : message; number = reader.ReadInt(); message = reader.ReadString(); _progressMessage = number > 0 ? number : message; _showTimeRemaining = reader.ReadBool(); } private class TeleportingInfo { public TeleportingInfo(WaitTeleporter tele, TimerExecutionToken token) { Teleporter = tele; TimerToken = token; } public WaitTeleporter Teleporter { get; } public TimerExecutionToken TimerToken { get; } } } [SerializationGenerator(1, false)] public partial class TimeoutTeleporter : Teleporter { [SerializableField(0)] [SerializedCommandProperty(AccessLevel.GameMaster)] private TimeSpan _timeoutDelay; private Dictionary _teleporting; [Constructible] public TimeoutTeleporter() : this(new Point3D(0, 0, 0)) { } [Constructible] public TimeoutTeleporter(Point3D pointDest, Map mapDest = null, bool creatures = false) : base(pointDest, mapDest, creatures) => _teleporting = new Dictionary(); public void StartTimer(Mobile m) { StartTimer(m, _timeoutDelay); } private void StartTimer(Mobile m, TimeSpan delay) { StopTimer(m); Timer.StartTimer(delay, () => StartTeleport(m), out var timerToken); _teleporting[m] = timerToken; } public void StopTimer(Mobile m) { if (_teleporting.Remove(m, out var t)) { t.Cancel(); } } public override void DoTeleport(Mobile m) { _teleporting.Remove(m); base.DoTeleport(m); } public override bool OnMoveOver(Mobile m) { if (Active) { if (!CanTeleport(m)) { return false; } StartTimer(m); } return true; } private void Deserialize(IGenericReader reader, int version) { _timeoutDelay = reader.ReadTimeSpan(); _teleporting = new Dictionary(); var count = reader.ReadInt(); for (var i = 0; i < count; ++i) { reader.ReadEntity(); reader.ReadDateTime(); } } } [SerializationGenerator(0, false)] public partial class TimeoutGoal : Item { [SerializableField(0)] [SerializedCommandProperty(AccessLevel.GameMaster)] private TimeoutTeleporter _teleporter; [Constructible] public TimeoutGoal() : base(0x1822) { Movable = false; Visible = false; Hue = 1154; } public override string DefaultName => "timeout teleporter goal"; public override bool OnMoveOver(Mobile m) { Teleporter?.StopTimer(m); return true; } } [SerializationGenerator(1, false)] public partial class ConditionTeleporter : Teleporter { [SerializableField(0, getter: "protected", setter: "protected")] private ConditionFlag _flags; [Constructible] public ConditionTeleporter() { } [CommandProperty(AccessLevel.GameMaster)] public bool DenyMounted { get => GetFlag(ConditionFlag.DenyMounted); set { SetFlag(ConditionFlag.DenyMounted, value); InvalidateProperties(); } } [CommandProperty(AccessLevel.GameMaster)] public bool DenyFollowers { get => GetFlag(ConditionFlag.DenyFollowers); set { SetFlag(ConditionFlag.DenyFollowers, value); InvalidateProperties(); } } [CommandProperty(AccessLevel.GameMaster)] public bool DenyPackContents { get => GetFlag(ConditionFlag.DenyPackContents); set { SetFlag(ConditionFlag.DenyPackContents, value); InvalidateProperties(); } } [CommandProperty(AccessLevel.GameMaster)] public bool DenyHolding { get => GetFlag(ConditionFlag.DenyHolding); set { SetFlag(ConditionFlag.DenyHolding, value); InvalidateProperties(); } } [CommandProperty(AccessLevel.GameMaster)] public bool DenyEquipment { get => GetFlag(ConditionFlag.DenyEquipment); set { SetFlag(ConditionFlag.DenyEquipment, value); InvalidateProperties(); } } [CommandProperty(AccessLevel.GameMaster)] public bool DenyTransformed { get => GetFlag(ConditionFlag.DenyTransformed); set { SetFlag(ConditionFlag.DenyTransformed, value); InvalidateProperties(); } } [CommandProperty(AccessLevel.GameMaster)] public bool StaffOnly { get => GetFlag(ConditionFlag.StaffOnly); set { SetFlag(ConditionFlag.StaffOnly, value); InvalidateProperties(); } } [CommandProperty(AccessLevel.GameMaster)] public bool DenyPackEthereals { get => GetFlag(ConditionFlag.DenyPackEthereals); set { SetFlag(ConditionFlag.DenyPackEthereals, value); InvalidateProperties(); } } [CommandProperty(AccessLevel.GameMaster)] public bool DeadOnly { get => GetFlag(ConditionFlag.DeadOnly); set { SetFlag(ConditionFlag.DeadOnly, value); InvalidateProperties(); } } public override bool CanTeleport(Mobile m) { if (!base.CanTeleport(m)) { return false; } if (GetFlag(ConditionFlag.StaffOnly) && m.AccessLevel < AccessLevel.Counselor) { return false; } if (GetFlag(ConditionFlag.DenyMounted) && m.Mounted) { m.SendLocalizedMessage(1077252); // You must dismount before proceeding. return false; } if (GetFlag(ConditionFlag.DenyFollowers) && (m.Followers != 0 || m is PlayerMobile mobile && mobile.AutoStabled?.Count != 0)) { m.SendLocalizedMessage(1077250); // No pets permitted beyond this point. return false; } var pack = m.Backpack; if (pack != null) { if (GetFlag(ConditionFlag.DenyPackContents) && pack.TotalItems != 0) { m.SendMessage("You must empty your backpack before proceeding."); return false; } if (GetFlag(ConditionFlag.DenyPackEthereals) && pack.FindItemByType(new[] { typeof(EtherealMount), typeof(BaseImprisonedMobile) }) != null) { m.SendMessage("You must empty your backpack of ethereal mounts before proceeding."); return false; } } if (GetFlag(ConditionFlag.DenyHolding) && m.Holding != null) { m.SendMessage("You must let go of what you are holding before proceeding."); return false; } if (GetFlag(ConditionFlag.DenyEquipment)) { foreach (var item in m.Items) { switch (item.Layer) { case Layer.Hair: case Layer.FacialHair: case Layer.Backpack: case Layer.Mount: case Layer.Bank: { continue; // ignore } default: { m.SendMessage("You must remove all equipment before proceeding."); return false; } } } } if (GetFlag(ConditionFlag.DenyTransformed) && m.IsBodyMod) { m.SendMessage("You cannot go there in this form."); return false; } if (GetFlag(ConditionFlag.DeadOnly) && m.Alive) { m.SendLocalizedMessage(1060014); // Only the dead may pass. return false; } return true; } public override void GetProperties(IPropertyList list) { base.GetProperties(list); using var props = ValueStringBuilder.Create(128); if (GetFlag(ConditionFlag.DenyMounted)) { props.Append("
Deny Mounted"); } if (GetFlag(ConditionFlag.DenyFollowers)) { props.Append("
Deny Followers"); } if (GetFlag(ConditionFlag.DenyPackContents)) { props.Append("
Deny Pack Contents"); } if (GetFlag(ConditionFlag.DenyPackEthereals)) { props.Append("
Deny Pack Ethereals"); } if (GetFlag(ConditionFlag.DenyHolding)) { props.Append("
Deny Holding"); } if (GetFlag(ConditionFlag.DenyEquipment)) { props.Append("
Deny Equipment"); } if (GetFlag(ConditionFlag.DenyTransformed)) { props.Append("
Deny Transformed"); } if (GetFlag(ConditionFlag.StaffOnly)) { props.Append("
Staff Only"); } if (GetFlag(ConditionFlag.DeadOnly)) { props.Append("
Dead Only"); } if (props.Length != 0) { props.Remove(0, 4); list.Add(props.ToString()); } } private void Deserialize(IGenericReader reader, int version) { _flags = (ConditionFlag)reader.ReadInt(); } protected bool GetFlag(ConditionFlag flag) => (_flags & flag) != 0; protected void SetFlag(ConditionFlag flag, bool value) { if (value) { _flags |= flag; } else { _flags &= ~flag; } } [Flags] protected enum ConditionFlag { None = 0x000, DenyMounted = 0x001, DenyFollowers = 0x002, DenyPackContents = 0x004, DenyHolding = 0x008, DenyEquipment = 0x010, DenyTransformed = 0x020, StaffOnly = 0x040, DenyPackEthereals = 0x080, DeadOnly = 0x100 } }