using System; using System.Collections.Generic; using System.IO; using Server.Commands; using Server.ContextMenus; using Server.Gumps; using Server.Items; using Server.Network; using Vertex = Server.Mobiles.GuideHelper.GuideVertex; namespace Server.Mobiles { public static class GuideHelper { private static readonly Dictionary> m_GraphDefinitions = new Dictionary>(); private static readonly List m_ShopDefinitions = new List(); private static readonly char[] m_Separators = new char[] { '\t', ' ' }; private static readonly string m_Delimiter = "--------------------------------------------------------------------------"; public static void LogMessage(string line) { try { using (FileStream stream = new FileStream("Guide.log", FileMode.Append)) using (StreamWriter writer = new StreamWriter(stream)) { writer.WriteLine(line); } } catch (Exception ex) { Console.WriteLine(ex.Message); Console.WriteLine(ex.StackTrace); } } public static GuideVertex FindVertex(List list, int id) { foreach (GuideVertex v in list) { if (v.ID == id) return v; } return null; } public static int FindShopName(int id) { if (id < 0 || id > m_ShopDefinitions.Count) return -1; return m_ShopDefinitions[id]; } public static void Initialize() { CommandSystem.Register("GuideEdit", AccessLevel.GameMaster, new CommandEventHandler(VertexEdit_OnCommand)); try { using (FileStream stream = File.OpenRead(Path.Combine("Data", "Guide", "Definitions.cfg"))) using (StreamReader reader = new StreamReader(stream)) { while (!reader.EndOfStream) { string line = reader.ReadLine(); if (!String.IsNullOrEmpty(line) && !line.StartsWith("#")) { string[] split = line.Split(m_Separators, StringSplitOptions.RemoveEmptyEntries); if (split != null && split.Length > 1) m_ShopDefinitions.Add(Int32.Parse(split[1])); } } } foreach (string file in Directory.GetFiles(Path.Combine("Data", "Guide"), "*.graph")) { using (FileStream stream = File.OpenRead(file)) using (StreamReader reader = new StreamReader(stream)) { List list = new List(); GuideVertex current = null; GuideVertex neighbour = null; while (!reader.EndOfStream) { string line = reader.ReadLine(); if (!String.IsNullOrEmpty(line)) { string[] split = line.Split(m_Separators, StringSplitOptions.RemoveEmptyEntries); int num; if (line.StartsWith("N:")) { if (current != null) { for (int i = 1; i < split.Length; i++) { num = Int32.Parse(split[i]); neighbour = FindVertex(list, num); if (neighbour == null) { neighbour = new GuideVertex(num); list.Add(neighbour); } current.Vertices.Add(neighbour); } } } else if (line.StartsWith("S:")) { if (current != null) { for (int i = 1; i < split.Length; i++) { num = Int32.Parse(split[i]); if (num >= 0 && num < m_ShopDefinitions.Count) current.Shops.Add(num); else throw new Exception(String.Format("Invalid shop ID: {0}", num)); } } } else if (line.StartsWith("V:")) { if (split.Length > 5) { num = Int32.Parse(split[1]); neighbour = FindVertex(list, num); if (neighbour != null) current = neighbour; else { current = new GuideVertex(num); list.Add(current); } Point3D location = new Point3D(); location.X = Int32.Parse(split[2]); location.Y = Int32.Parse(split[3]); location.Z = Int32.Parse(split[4]); current.Location = location; current.Teleporter = Boolean.Parse(split[5]); } else throw new Exception(String.Format("Incomplete vertex definition!")); } } } m_GraphDefinitions.Add(Path.GetFileNameWithoutExtension(file), list); } } } catch (Exception ex) { LogMessage(ex.Message); LogMessage(ex.StackTrace); LogMessage(m_Delimiter); } } public static void VertexEdit_OnCommand(CommandEventArgs e) { Mobile m = e.Mobile; if (m.Region != null) { GuideVertex closest = ClosestVetrex(m.Region.Name, m.Location); if (closest != null) m.SendGump(new GuideVertexEditGump(closest, m.Map, m.Region.Name)); else m.SendLocalizedMessage(1076113); // There are no shops nearby. Please try again when you get to a town or city. } else m.SendLocalizedMessage(1076113); // There are no shops nearby. Please try again when you get to a town or city. } public static GuideVertex ClosestVetrex(string town, Point3D location) { if (town == null || !m_GraphDefinitions.ContainsKey(town)) return null; List vertices = m_GraphDefinitions[town]; GuideVertex closest = null; double min = Int32.MaxValue; double distance; foreach (GuideVertex v in vertices) { distance = Math.Sqrt(Math.Pow(location.X - v.Location.X, 2) + Math.Pow(location.Y - v.Location.Y, 2)); if (distance < min) { closest = v; min = distance; } } return closest; } public static Dictionary FindShops(string town, Point3D location) { if (town == null || !m_GraphDefinitions.ContainsKey(town)) return null; List vertices = m_GraphDefinitions[town]; Dictionary shops = new Dictionary(); foreach (GuideVertex v in vertices) { foreach (int shop in v.Shops) { if (shops.ContainsKey(shop)) { GuideVertex d = shops[shop]; if (v.DistanceTo(location) < d.DistanceTo(location)) shops[shop] = v; } else shops.Add(shop, v); } } if (shops.Count > 0) return shops; return null; } public static List Dijkstra(string town, GuideVertex source, GuideVertex destination) { if (town == null || !m_GraphDefinitions.ContainsKey(town)) return null; Heap heap = new Heap(); List path = new List(); heap.Push(source); foreach (GuideVertex v in m_GraphDefinitions[town]) { v.Distance = Int32.MaxValue; v.Previous = null; v.Visited = false; v.Removed = false; } source.Distance = 0; GuideVertex from ; int dist = 0; while (heap.Count > 0) { from = heap.Pop(); from.Removed = true; if (from == destination) { while (from != source) { path.Add(from); from = from.Previous; } path.Add(source); return path; } foreach (GuideVertex v in from.Vertices) { if (!v.Removed) { dist = from.Distance + (from.Teleporter ? 1 : from.DistanceTo(v)); if (dist < v.Distance) { v.Distance = dist; v.Previous = from; if (!v.Visited) heap.Push(v); else heap.Fix(v); } } } } return null; } public class GuideVertexEditGump : Gump { private readonly GuideVertex m_Vertex; private readonly Map m_Map; private readonly string m_Town; private readonly Item m_Item; public GuideVertexEditGump(GuideVertex vertex, Map map, string town) : base(50, 50) { this.m_Vertex = vertex; this.m_Map = map; this.m_Town = town; this.Closable = true; this.Disposable = true; this.Dragable = true; this.Resizable = false; int size = m_ShopDefinitions.Count; bool on = false; this.AddPage(0); this.AddBackground(0, 0, 540, 35 + size * 30 / 2, 9200); this.AddAlphaRegion(15, 10, 510, 15 + size * 30 / 2); for (int i = 0; i < size; i += 2) { on = this.m_Vertex.Shops.Contains(i); this.AddButton(25, 25 + i * 30 / 2, on ? 2361 : 2360, on ? 2360 : 2361, i + 1, GumpButtonType.Reply, 0); this.AddHtmlLocalized(50, 20 + i * 30 / 2, 200, 20, m_ShopDefinitions[i], 0x7773, false, false); if (i + 1 < size) { on = this.m_Vertex.Shops.Contains(i + 1); this.AddButton(280, 25 + i * 30 / 2, on ? 2361 : 2360, on ? 2360 : 2361, i + 2, GumpButtonType.Reply, 0); this.AddHtmlLocalized(305, 20 + i * 30 / 2, 200, 20, m_ShopDefinitions[i + 1], 0x7773, false, false); } } this.m_Item = new Item(0x1183); this.m_Item.Visible = false; this.m_Item.MoveToWorld(this.m_Vertex.Location, map); } public override void OnResponse(NetState sender, RelayInfo info) { if (info.ButtonID > 0) { if (this.m_Vertex.Shops.Contains(info.ButtonID - 1)) this.m_Vertex.Shops.Remove(info.ButtonID - 1); else this.m_Vertex.Shops.Add(info.ButtonID - 1); sender.Mobile.SendGump(new GuideVertexEditGump(this.m_Vertex, this.m_Map, this.m_Town)); } else if (this.m_Item != null && !this.m_Item.Deleted) { this.m_Item.Delete(); this.Save(this.m_Town); } } private void Save(string town) { if (!m_GraphDefinitions.ContainsKey(town)) return; List list = m_GraphDefinitions[town]; string path = Core.BaseDirectory + String.Format("\\Data\\Guide\\{0}.graph", town); using (FileStream stream = new FileStream(path, FileMode.Create)) using (StreamWriter writer = new StreamWriter(stream)) { writer.WriteLine("# Graph vertices"); writer.WriteLine("# {V:}VertexID{tab, }X{tab, }Y{tab, }Z{tab, }IsTeleporter"); writer.WriteLine("# {S:}ShopID{tab, }ShopID{tab, }..."); writer.WriteLine("# {N:}VertexID{tab, }VertexID{tab, }..."); foreach (GuideVertex v in list) { writer.WriteLine(String.Format("V:\t{0}\t{1}\t{2}\t{3}\t{4}", v.ID, v.Location.X, v.Location.Y, v.Location.Z, v.Teleporter.ToString())); if (v.Shops.Count > 0) { writer.Write("S:"); foreach (int i in v.Shops) writer.Write(String.Format("\t{0}", i)); writer.WriteLine(); } if (v.Vertices.Count > 0) { writer.Write("N:"); foreach (GuideVertex n in v.Vertices) writer.Write(String.Format("\t{0}", n.ID)); writer.WriteLine(); } } } } } public class GuideVertex : IComparable { public bool m_Visited; public bool m_Removed; private readonly int m_ID; private readonly List m_Vertices = new List(); private readonly List m_Shops = new List(); private Point3D m_Location; private bool m_Teleporter; private GuideVertex m_Previous; private int m_Distance; public GuideVertex(int id) : this(id, Point3D.Zero) { } public GuideVertex(int id, Point3D location) { this.m_ID = id; this.m_Location = location; this.m_Previous = null; this.m_Distance = Int32.MaxValue; this.m_Visited = false; this.m_Removed = false; } public int ID { get { return this.m_ID; } } public Point3D Location { get { return this.m_Location; } set { this.m_Location = value; } } public List Vertices { get { return this.m_Vertices; } } public List Shops { get { return this.m_Shops; } } public bool Teleporter { get { return this.m_Teleporter; } set { this.m_Teleporter = value; } } public GuideVertex Previous { get { return this.m_Previous; } set { this.m_Previous = value; } } public int Distance { get { return this.m_Distance; } set { this.m_Distance = value; } } public bool Visited { get { return this.m_Visited; } set { this.m_Visited = value; } } public bool Removed { get { return this.m_Removed; } set { this.m_Removed = value; } } public int DistanceTo(GuideVertex to) { return Math.Abs(this.m_Location.X - to.Location.X) + Math.Abs(this.m_Location.Y - to.Location.Y); } public int DistanceTo(Point3D to) { return Math.Abs(this.m_Location.X - to.X) + Math.Abs(this.m_Location.Y - to.Y); } public int CompareTo(GuideVertex o) { if (o != null) return this.m_Distance - o.Distance; return 0; } } public class Heap where T : IComparable { private readonly List m_List; public Heap() { this.m_List = new List(); } public int Count { get { return this.m_List.Count; } } public T Top { get { return this.m_List[0]; } } public bool Contains(T item) { return this.m_List.Contains(item); } public void Push(T item) { this.m_List.Add(item); int child = this.m_List.Count - 1; int parent = (child - 1) / 2; T temp; while (item.CompareTo(this.m_List[parent]) < 0 && child > 0) { temp = this.m_List[child]; this.m_List[child] = this.m_List[parent]; this.m_List[parent] = temp; child = parent; parent = (child - 1) / 2; } } public void Fix(T item) { int child = this.m_List.IndexOf(item); int parent = (child - 1) / 2; T temp; while (item.CompareTo(this.m_List[parent]) < 0 && child > 0) { temp = this.m_List[child]; this.m_List[child] = this.m_List[parent]; this.m_List[parent] = temp; child = parent; parent = (child - 1) / 2; } } public T Pop() { if (this.m_List.Count == 0) return default( T ); T top = this.m_List[0]; T temp; this.m_List[0] = this.m_List[this.m_List.Count - 1]; this.m_List.RemoveAt(this.m_List.Count - 1); int parent = 0; int lchild; int rchild; bool ltl, ltr, cltc; do { lchild = parent * 2 + 1; rchild = parent * 2 + 2; ltl = ltr = cltc = false; if (this.m_List.Count > lchild) ltl = (this.m_List[parent].CompareTo(this.m_List[lchild]) > 0); if (this.m_List.Count > rchild) ltr = (this.m_List[parent].CompareTo(this.m_List[rchild]) > 0); if (ltl && ltr) cltc = (this.m_List[lchild].CompareTo(this.m_List[rchild]) > 0); if (ltl && !ltr) { temp = this.m_List[parent]; this.m_List[parent] = this.m_List[lchild]; this.m_List[lchild] = temp; parent = lchild; } else if (!ltl && ltr) { temp = this.m_List[parent]; this.m_List[parent] = this.m_List[rchild]; this.m_List[rchild] = temp; parent = rchild; } else if (ltl && ltr && cltc) { temp = this.m_List[parent]; this.m_List[parent] = this.m_List[rchild]; this.m_List[rchild] = temp; parent = rchild; } else if (ltl && ltr) { temp = this.m_List[parent]; this.m_List[parent] = this.m_List[lchild]; this.m_List[lchild] = temp; parent = lchild; } } while (ltl || ltr); return top; } } } public class AttendantGuide : PersonalAttendant { private List m_Path; public AttendantGuide() : base("the Guide") { this.m_Path = null; } public AttendantGuide(Serial serial) : base(serial) { } public List Path { get { return this.m_Path; } } public override void OnDoubleClick(Mobile from) { if (from.Alive && this.IsOwner(from)) { Dictionary m_Shops = GuideHelper.FindShops(this.Region != null ? this.Region.Name : null, this.Location); if (m_Shops != null) { from.CloseGump(typeof(InternalGump)); from.SendGump(new InternalGump(this, m_Shops)); } else from.SendLocalizedMessage(1076113); // There are no shops nearby. Please try again when you get to a town or city. } } public override void AddCustomContextEntries(Mobile from, List list) { if (from.Alive && this.IsOwner(from)) list.Add(new AttendantUseEntry(this, 6249)); base.AddCustomContextEntries(from, list); } public override void OnThink() { base.OnThink(); if (this.ControlMaster != null && this.m_Path != null && this.m_Path.Count > 0) { Vertex v = this.m_Path[this.m_Path.Count - 1]; Mobile m = this.ControlMaster; if (m.NetState != null) { if (Math.Abs(v.DistanceTo(m.Location) - v.DistanceTo(this.Location)) > 10) this.Frozen = true; else this.Frozen = false; if (this.CurrentWayPoint == null) { this.CurrentWayPoint = new WayPoint(); this.CurrentWayPoint.MoveToWorld(v.Location, this.Map); } int dist = v.DistanceTo(this.Location); if (dist < (v.Teleporter ? 1 : 3) || dist > 100) { this.m_Path.RemoveAt(this.m_Path.Count - 1); if (this.m_Path.Count > 0) { if (this.CurrentWayPoint == null) this.CurrentWayPoint = new WayPoint(); this.CurrentWayPoint.MoveToWorld(this.m_Path[this.m_Path.Count - 1].Location, this.Map); } else { Timer.DelayCall(TimeSpan.FromSeconds(3), new TimerStateCallback(CommandFollow), m); this.Say(1076051); // We have reached our destination this.CommandStop(m); } } } } } public override void CommandFollow(Mobile by) { this.StopGuiding(); base.CommandFollow(by); } public override void CommandStop(Mobile by) { this.StopGuiding(); base.CommandStop(by); } public override void Serialize(GenericWriter writer) { base.Serialize(writer); writer.WriteEncodedInt(0); // version } public override void Deserialize(GenericReader reader) { base.Deserialize(reader); int version = reader.ReadEncodedInt(); } public void StopGuiding() { this.CurrentSpeed = this.PassiveSpeed; if (this.CurrentWayPoint != null) this.CurrentWayPoint.Delete(); if (this.m_Path != null) this.m_Path.Clear(); this.Controlled = true; this.m_Path = null; } public void StartGuiding(List path) { this.m_Path = path; if (this.ControlMaster != null && this.m_Path != null && this.m_Path.Count > 0) { if (this.m_Path.Count > 1) this.m_Path.RemoveAt(this.m_Path.Count - 1); if (this.CurrentWayPoint == null) this.CurrentWayPoint = new WayPoint(); this.CurrentWayPoint.MoveToWorld(this.m_Path[this.m_Path.Count - 1].Location, this.Map); this.AIObject.Action = ActionType.Wander; this.CurrentSpeed = this.ActiveSpeed; this.Controlled = false; this.Say(1076114); // Please follow me. } } private class InternalGump : Gump { private const int ShopsPerPage = 10; private const int ShopHeight = 24; private readonly AttendantGuide m_Guide; private readonly Dictionary m_Shops; public InternalGump(AttendantGuide guide, Dictionary shops) : base(60, 36) { this.m_Guide = guide; this.m_Shops = shops; this.AddPage(0); this.AddBackground(0, 0, 273, 84 + ShopsPerPage * ShopHeight, 0x13BE); this.AddImageTiled(10, 10, 253, 20, 0xA40); this.AddImageTiled(10, 40, 253, 4 + ShopsPerPage * ShopHeight, 0xA40); this.AddImageTiled(10, 54 + ShopsPerPage * ShopHeight, 253, 20, 0xA40); this.AddAlphaRegion(10, 10, 253, 64 + ShopsPerPage * ShopHeight); this.AddButton(10, 54 + ShopsPerPage * ShopHeight, 0xFB1, 0xFB2, 0, GumpButtonType.Reply, 0); this.AddHtmlLocalized(45, 54 + ShopsPerPage * ShopHeight, 450, 20, 1060051, 0x7FFF, false, false); // CANCEL this.AddHtmlLocalized(14, 12, 273, 20, 1076029, 0x7FFF, false, false); // What sort of shop do you seek? int i = 0; int page = 0; int iPage = 0; foreach (KeyValuePair kvp in shops) { if (i % ShopsPerPage == 0) { if (page > 0) { this.AddButton(188, 54 + ShopsPerPage * ShopHeight, 0xFA5, 0xFA7, 0, GumpButtonType.Page, page + 1); this.AddHtmlLocalized(228, 56 + ShopsPerPage * ShopHeight, 60, 20, 1043353, 0x7FFF, false, false); // Next } this.AddPage(page + 1); if (page > 0) { this.AddButton(113, 54 + ShopsPerPage * ShopHeight, 0xFAE, 0xFB0, 0, GumpButtonType.Page, page); this.AddHtmlLocalized(153, 56 + ShopsPerPage * ShopHeight, 60, 20, 1011393, 0x7FFF, false, false); // Back } iPage = 0; page++; } this.AddButton(19, 49 + iPage * ShopHeight, 0x845, 0x846, 100 + kvp.Key, GumpButtonType.Reply, 0); this.AddHtmlLocalized(44, 47 + iPage * ShopHeight, 213, 20, GuideHelper.FindShopName(kvp.Key), 0x7FFF, false, false); i++; iPage++; } } public override void OnResponse(NetState sender, RelayInfo info) { int shop = info.ButtonID - 100; if (this.m_Guide == null || this.m_Guide.Deleted || this.m_Guide.Region == null || info.ButtonID == 0) return; Vertex source = GuideHelper.ClosestVetrex(this.m_Guide.Region.Name, this.m_Guide.Location); if (this.m_Shops.ContainsKey(shop)) { Vertex destination = this.m_Shops[shop]; List path = GuideHelper.Dijkstra(this.m_Guide.Region.Name, source, destination); this.m_Guide.StartGuiding(path); } } } } public class AttendantMaleGuide : AttendantGuide { [Constructable] public AttendantMaleGuide() : base() { } public AttendantMaleGuide(Serial serial) : base(serial) { } public override void InitBody() { this.SetStr(50, 60); this.SetDex(20, 30); this.SetInt(100, 110); this.Name = NameList.RandomName("male"); this.Female = false; this.Race = Race.Human; this.Hue = this.Race.RandomSkinHue(); this.HairItemID = this.Race.RandomHair(this.Female); this.HairHue = this.Race.RandomHairHue(); } public override void InitOutfit() { this.AddItem(new SamuraiTabi()); this.AddItem(new Kasa()); this.AddItem(new MaleKimono(Utility.RandomGreenHue())); this.AddItem(new ShepherdsCrook()); } public override void Serialize(GenericWriter writer) { base.Serialize(writer); writer.WriteEncodedInt(0); // version } public override void Deserialize(GenericReader reader) { base.Deserialize(reader); int version = reader.ReadEncodedInt(); } } public class AttendantFemaleGuide : AttendantGuide { [Constructable] public AttendantFemaleGuide() : base() { } public AttendantFemaleGuide(Serial serial) : base(serial) { } public override void InitBody() { this.SetStr(50, 60); this.SetDex(20, 30); this.SetInt(100, 110); this.Name = NameList.RandomName("female"); this.Female = true; this.Race = Race.Human; this.Hue = this.Race.RandomSkinHue(); this.HairItemID = this.Race.RandomHair(this.Female); this.HairHue = this.Race.RandomHairHue(); } public override void InitOutfit() { this.AddItem(new Shoes(Utility.RandomBlueHue())); this.AddItem(new Shirt(0x8FD)); this.AddItem(new FeatheredHat(Utility.RandomBlueHue())); this.AddItem(new Kilt(Utility.RandomBlueHue())); Item item = new Spellbook(); item.Hue = Utility.RandomBlueHue(); this.AddItem(item); } public override void Serialize(GenericWriter writer) { base.Serialize(writer); writer.WriteEncodedInt(0); // version } public override void Deserialize(GenericReader reader) { base.Deserialize(reader); int version = reader.ReadEncodedInt(); } } }