using System; using System.Collections.Generic; using System.Text; using Server.Commands; using Server.Gumps; using Server.Items; using Server.Network; using Server.Prompts; using System.IO; using Server.Engines.CityLoyalty; using Server.ContextMenus; using Server.Services.TownCryer; namespace Server.Mobiles { public interface ITownCrierEntryList { List Entries { get; } TownCrierEntry GetRandomEntry(); TownCrierEntry AddEntry(string[] lines, TimeSpan duration); void RemoveEntry(TownCrierEntry entry); } public class GlobalTownCrierEntryList : ITownCrierEntryList { private static GlobalTownCrierEntryList m_Instance; private List m_Entries; public GlobalTownCrierEntryList() { } public static GlobalTownCrierEntryList Instance { get { if (m_Instance == null) m_Instance = new GlobalTownCrierEntryList(); return m_Instance; } } public bool IsEmpty { get { return (m_Entries == null || m_Entries.Count == 0); } } public List Entries { get { return m_Entries; } } public static void Initialize() { CommandSystem.Register("TownCriers", AccessLevel.GameMaster, new CommandEventHandler(TownCriers_OnCommand)); } [Usage("TownCriers")] [Description("Manages the global town crier list.")] public static void TownCriers_OnCommand(CommandEventArgs e) { e.Mobile.SendGump(new TownCrierGump(e.Mobile, Instance)); } public TownCrierEntry GetRandomEntry() { if (m_Entries == null || m_Entries.Count == 0) return null; for (int i = m_Entries.Count - 1; m_Entries != null && i >= 0; --i) { if (i >= m_Entries.Count) continue; TownCrierEntry tce = m_Entries[i]; if (tce.Expired) RemoveEntry(tce); } if (m_Entries == null || m_Entries.Count == 0) return null; return m_Entries[Utility.Random(m_Entries.Count)]; } public TownCrierEntry AddEntry(string[] lines, TimeSpan duration) { if (m_Entries == null) m_Entries = new List(); TownCrierEntry tce = new TownCrierEntry(lines, duration); m_Entries.Add(tce); List instances = TownCrier.Instances; for (int i = 0; i < instances.Count; ++i) instances[i].ForceBeginAutoShout(); return tce; } public void AddEntry(TownCrierEntry entry) { if (m_Entries == null) m_Entries = new List(); m_Entries.Add(entry); List instances = TownCrier.Instances; for (int i = 0; i < instances.Count; ++i) instances[i].ForceBeginAutoShout(); } public void RemoveEntry(TownCrierEntry tce) { if (m_Entries == null) return; m_Entries.Remove(tce); if (m_Entries.Count == 0) m_Entries = null; } #region Serialization public static string FilePath = Path.Combine("Saves/Misc", "TownCrierGlobalEntries.bin"); public static void Configure() { EventSink.WorldSave += OnSave; EventSink.WorldLoad += OnLoad; } public static void OnSave(WorldSaveEventArgs e) { Persistence.Serialize( FilePath, writer => { writer.Write(1); TownCryerSystem.Save(writer); writer.Write(Instance.Entries == null ? 0 : Instance.Entries.Count); if (Instance.Entries != null) { Instance.Entries.ForEach(entry => entry.Serialize(writer)); } }); } public static void OnLoad() { Persistence.Deserialize( FilePath, reader => { int version = reader.ReadInt(); switch (version) { case 1: { TownCryerSystem.Load(reader); goto case 0; } case 0: { int count = reader.ReadInt(); for (int i = 0; i < count; i++) { var entry = new TownCrierEntry(reader); if (!entry.Expired) { Instance.AddEntry(entry); } } } break; } }); } #endregion } public class TownCrierEntry { private readonly string[] m_Lines; private readonly DateTime m_ExpireTime; public TownCrierEntry(string[] lines, TimeSpan duration) { m_Lines = lines; if (duration < TimeSpan.Zero) duration = TimeSpan.Zero; else if (duration > TimeSpan.FromDays(365.0)) duration = TimeSpan.FromDays(365.0); m_ExpireTime = DateTime.UtcNow + duration; } public string[] Lines { get { return m_Lines; } } public DateTime ExpireTime { get { return m_ExpireTime; } } public bool Expired { get { return (DateTime.UtcNow >= m_ExpireTime); } } public TownCrierEntry(GenericReader reader) { int version = reader.ReadInt(); int count = reader.ReadInt(); m_Lines = new string[count]; for (int i = 0; i < count; i++) { m_Lines[i] = reader.ReadString(); } m_ExpireTime = reader.ReadDateTime(); } public void Serialize(GenericWriter writer) { writer.Write(0); writer.Write(Lines.Length); foreach (var str in Lines) { writer.Write(str); } writer.Write(m_ExpireTime); } } public class TownCrierDurationPrompt : Prompt { private readonly ITownCrierEntryList m_Owner; public TownCrierDurationPrompt(ITownCrierEntryList owner) { m_Owner = owner; } public override void OnResponse(Mobile from, string text) { TimeSpan ts; if (!TimeSpan.TryParse(text, out ts)) { from.SendMessage("Value was not properly formatted. Use: "); from.SendGump(new TownCrierGump(from, m_Owner)); return; } if (ts < TimeSpan.Zero) ts = TimeSpan.Zero; from.SendMessage("Duration set to: {0}", ts); from.SendMessage("Enter the first line to shout:"); from.Prompt = new TownCrierLinesPrompt(m_Owner, null, new List(), ts); } public override void OnCancel(Mobile from) { from.SendLocalizedMessage(502980); // Message entry cancelled. from.SendGump(new TownCrierGump(from, m_Owner)); } } public class TownCrierLinesPrompt : Prompt { private readonly ITownCrierEntryList m_Owner; private readonly TownCrierEntry m_Entry; private readonly List m_Lines; private readonly TimeSpan m_Duration; public TownCrierLinesPrompt(ITownCrierEntryList owner, TownCrierEntry entry, List lines, TimeSpan duration) { m_Owner = owner; m_Entry = entry; m_Lines = lines; m_Duration = duration; } public override void OnResponse(Mobile from, string text) { m_Lines.Add(text); from.SendMessage("Enter the next line to shout, or press if the message is finished."); from.Prompt = new TownCrierLinesPrompt(m_Owner, m_Entry, m_Lines, m_Duration); } public override void OnCancel(Mobile from) { if (m_Entry != null) m_Owner.RemoveEntry(m_Entry); if (m_Lines.Count > 0) { m_Owner.AddEntry(m_Lines.ToArray(), m_Duration); from.SendMessage("Message has been set."); } else { if (m_Entry != null) from.SendMessage("Message deleted."); else from.SendLocalizedMessage(502980); // Message entry cancelled. } from.SendGump(new TownCrierGump(from, m_Owner)); } } public class TownCrierGump : Gump { private readonly Mobile m_From; private readonly ITownCrierEntryList m_Owner; public TownCrierGump(Mobile from, ITownCrierEntryList owner) : base(50, 50) { m_From = from; m_Owner = owner; from.CloseGump(typeof(TownCrierGump)); AddPage(0); List entries = owner.Entries; owner.GetRandomEntry(); // force expiration checks int count = 0; if (entries != null) count = entries.Count; AddImageTiled(0, 0, 300, 38 + (count == 0 ? 20 : (count * 85)), 0xA40); AddAlphaRegion(1, 1, 298, 36 + (count == 0 ? 20 : (count * 85))); if (owner is GlobalTownCrierEntryList) { AddHtml(8, 8, 300 - 8 - 30, 20, "
GLOBAL TOWN CRIER MESSAGES
", false, false); } else { AddHtml(8, 8, 300 - 8 - 30, 20, "
TOWN CRIER MESSAGES
", false, false); } AddButton(300 - 8 - 30, 8, 0xFAB, 0xFAD, 1, GumpButtonType.Reply, 0); AddTooltip(3000161); // New Message if (count == 0) { AddHtml(8, 30, 284, 20, "The crier has no news.", false, false); } else { for (int i = 0; i < entries.Count; ++i) { TownCrierEntry tce = (TownCrierEntry)entries[i]; TimeSpan toExpire = tce.ExpireTime - DateTime.UtcNow; if (toExpire < TimeSpan.Zero) toExpire = TimeSpan.Zero; StringBuilder sb = new StringBuilder(); sb.Append("[Expires: "); if (toExpire.TotalHours >= 1) { sb.Append((int)toExpire.TotalHours); sb.Append(':'); sb.Append(toExpire.Minutes.ToString("D2")); } else { sb.Append(toExpire.Minutes); } sb.Append(':'); sb.Append(toExpire.Seconds.ToString("D2")); sb.Append("] "); for (int j = 0; j < tce.Lines.Length; ++j) { if (j > 0) sb.Append("
"); sb.Append(tce.Lines[j]); } AddHtml(8, 35 + (i * 85), 254, 80, sb.ToString(), true, true); AddButton(300 - 8 - 26, 35 + (i * 85), 0x15E1, 0x15E5, 2 + i, GumpButtonType.Reply, 0); AddTooltip(3005101); // Edit } } } public override void OnResponse(NetState sender, RelayInfo info) { if (info.ButtonID == 1) { m_From.SendMessage("Enter the duration for the new message. Format: "); m_From.Prompt = new TownCrierDurationPrompt(m_Owner); } else if (info.ButtonID > 1) { List entries = m_Owner.Entries; int index = info.ButtonID - 2; if (entries != null && index < entries.Count) { TownCrierEntry tce = entries[index]; TimeSpan ts = tce.ExpireTime - DateTime.UtcNow; if (ts < TimeSpan.Zero) ts = TimeSpan.Zero; m_From.SendMessage("Editing entry #{0}.", index + 1); m_From.SendMessage("Push to delete this entry."); m_From.SendMessage("Enter the first line to shout:"); m_From.Prompt = new TownCrierLinesPrompt(m_Owner, tce, new List(), ts); } } } } public class TownCrier : Mobile, ITownCrierEntryList { private static readonly List m_Instances = new List(); private List m_Entries; private Timer m_NewsTimer; private Timer m_AutoShoutTimer; [Constructable] public TownCrier() { m_Instances.Add(this); InitStats(100, 100, 25); Title = "the town crier"; Hue = Utility.RandomSkinHue(); if (!Core.AOS) NameHue = 0x35; if (Female = Utility.RandomBool()) { Body = 0x191; Name = NameList.RandomName("female"); } else { Body = 0x190; Name = NameList.RandomName("male"); } AddItem(new FancyShirt(Utility.RandomBlueHue())); Item skirt; switch ( Utility.Random(2) ) { case 0: skirt = new Skirt(); break; default: case 1: skirt = new Kilt(); break; } skirt.Hue = Utility.RandomGreenHue(); AddItem(skirt); AddItem(new FeatheredHat(Utility.RandomGreenHue())); Item boots; switch ( Utility.Random(2) ) { case 0: boots = new Boots(); break; default: case 1: boots = new ThighBoots(); break; } AddItem(boots); Utility.AssignRandomHair(this); } public TownCrier(Serial serial) : base(serial) { m_Instances.Add(this); } public static List Instances { get { return m_Instances; } } public List Entries { get { return m_Entries; } } public TownCrierEntry GetRandomEntry() { if (m_Entries == null || m_Entries.Count == 0) return GlobalTownCrierEntryList.Instance.GetRandomEntry(); for (int i = m_Entries.Count - 1; m_Entries != null && i >= 0; --i) { if (i >= m_Entries.Count) continue; TownCrierEntry tce = m_Entries[i]; if (tce.Expired) RemoveEntry(tce); } if (m_Entries == null || m_Entries.Count == 0) return GlobalTownCrierEntryList.Instance.GetRandomEntry(); TownCrierEntry entry = GlobalTownCrierEntryList.Instance.GetRandomEntry(); if (entry == null || Utility.RandomBool()) entry = m_Entries[Utility.Random(m_Entries.Count)]; return entry; } public void ForceBeginAutoShout() { if (m_AutoShoutTimer == null) m_AutoShoutTimer = Timer.DelayCall(TimeSpan.FromMinutes(1.0), TimeSpan.FromMinutes(5.0), new TimerCallback(AutoShout_Callback)); } public TownCrierEntry AddEntry(string[] lines, TimeSpan duration) { if (m_Entries == null) m_Entries = new List(); TownCrierEntry tce = new TownCrierEntry(lines, duration); m_Entries.Add(tce); if (m_AutoShoutTimer == null) m_AutoShoutTimer = Timer.DelayCall(TimeSpan.FromMinutes(1.0), TimeSpan.FromMinutes(5.0), new TimerCallback(AutoShout_Callback)); return tce; } public void AddEntry(TownCrierEntry entry) { if (m_Entries == null) m_Entries = new List(); m_Entries.Add(entry); if (m_AutoShoutTimer == null) m_AutoShoutTimer = Timer.DelayCall(TimeSpan.FromMinutes(1.0), TimeSpan.FromMinutes(5.0), new TimerCallback(AutoShout_Callback)); } public void RemoveEntry(TownCrierEntry tce) { if (m_Entries == null) return; m_Entries.Remove(tce); if (m_Entries.Count == 0) m_Entries = null; if (m_Entries == null && GlobalTownCrierEntryList.Instance.IsEmpty) { if (m_AutoShoutTimer != null) m_AutoShoutTimer.Stop(); m_AutoShoutTimer = null; } } public override void OnDoubleClick(Mobile from) { if (from is PlayerMobile && TownCryerSystem.Enabled) { BaseGump.SendGump(new TownCryerGump((PlayerMobile)from, this)); } if (from.AccessLevel >= AccessLevel.GameMaster) { from.SendGump(new TownCrierGump(from, this)); } else { base.OnDoubleClick(from); } } public override void GetContextMenuEntries(Mobile from, List list) { base.GetContextMenuEntries(from, list); if (TownCryerSystem.Enabled) { TownCryerSystem.GetContextMenus(this, from, list); } } public override bool HandlesOnSpeech(Mobile from) { return (m_NewsTimer == null && from.Alive && InRange(from, 12)); } public override void OnSpeech(SpeechEventArgs e) { if (m_NewsTimer == null && e.HasKeyword(0x30) && e.Mobile.Alive && InRange(e.Mobile, 12)) // *news* { Direction = GetDirectionTo(e.Mobile); TownCrierEntry tce = GetRandomEntry(); if (tce == null) { PublicOverheadMessage(MessageType.Regular, 0x3B2, 1005643); // I have no news at this time. } else { m_NewsTimer = Timer.DelayCall(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(3.0), new TimerStateCallback(ShoutNews_Callback), new object[] { tce, 0 }); PublicOverheadMessage(MessageType.Regular, 0x3B2, 502978); // Some of the latest news! } if (e.Mobile is PlayerMobile && TownCryerSystem.Enabled) { BaseGump.SendGump(new TownCryerGump((PlayerMobile)e.Mobile, this)); } } } public override bool CanBeDamaged() { return false; } public override void OnDelete() { m_Instances.Remove(this); base.OnDelete(); } public override void Serialize(GenericWriter writer) { base.Serialize(writer); writer.Write((int)1); // version writer.Write(m_Entries == null ? 0 : m_Entries.Count); if (m_Entries != null) { m_Entries.ForEach(e => e.Serialize(writer)); } } public override void Deserialize(GenericReader reader) { base.Deserialize(reader); int version = reader.ReadInt(); switch (version) { case 1: { int count = reader.ReadInt(); for (int i = 0; i < count; i++) { var entry = new TownCrierEntry(reader); if (!entry.Expired) { AddEntry(entry); } } break; } } if (Core.AOS && NameHue == 0x35) NameHue = -1; } private void AutoShout_Callback() { TownCrierEntry tce = GetRandomEntry(); if (tce == null) { if (m_AutoShoutTimer != null) m_AutoShoutTimer.Stop(); m_AutoShoutTimer = null; } else if (m_NewsTimer == null) { m_NewsTimer = Timer.DelayCall(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(3.0), new TimerStateCallback(ShoutNews_Callback), new object[] { tce, 0 }); PublicOverheadMessage(MessageType.Regular, 0x3B2, 502976); // Hear ye! Hear ye! } } private void ShoutNews_Callback(object state) { object[] states = (object[])state; TownCrierEntry tce = (TownCrierEntry)states[0]; int index = (int)states[1]; if (index < 0 || index >= tce.Lines.Length) { if (m_NewsTimer != null) m_NewsTimer.Stop(); m_NewsTimer = null; } else { PublicOverheadMessage(MessageType.Regular, 0x3B2, false, tce.Lines[index]); states[1] = index + 1; } } } }