Files
abysmal-isle/Scripts/Mobiles/NPCs/AttendantGuide.cs
Unstable Kitsune b918192e4e Overwrite
Complete Overwrite of the Folder with the free shard. ServUO 57.3 has been added.
2023-11-28 23:20:26 -05:00

1012 lines
34 KiB
C#

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<string, List<GuideVertex>> m_GraphDefinitions = new Dictionary<string, List<GuideVertex>>();
private static readonly List<int> m_ShopDefinitions = new List<int>();
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<GuideVertex> 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<GuideVertex> list = new List<GuideVertex>();
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<GuideVertex> 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<int, GuideVertex> FindShops(string town, Point3D location)
{
if (town == null || !m_GraphDefinitions.ContainsKey(town))
return null;
List<GuideVertex> vertices = m_GraphDefinitions[town];
Dictionary<int, GuideVertex> shops = new Dictionary<int, GuideVertex>();
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<GuideVertex> Dijkstra(string town, GuideVertex source, GuideVertex destination)
{
if (town == null || !m_GraphDefinitions.ContainsKey(town))
return null;
Heap<GuideVertex> heap = new Heap<GuideVertex>();
List<GuideVertex> path = new List<GuideVertex>();
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<GuideVertex> 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<GuideVertex>
{
public bool m_Visited;
public bool m_Removed;
private readonly int m_ID;
private readonly List<GuideVertex> m_Vertices = new List<GuideVertex>();
private readonly List<int> m_Shops = new List<int>();
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<GuideVertex> Vertices
{
get
{
return this.m_Vertices;
}
}
public List<int> 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<T> where T : IComparable<T>
{
private readonly List<T> m_List;
public Heap()
{
this.m_List = new List<T>();
}
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<Vertex> m_Path;
public AttendantGuide()
: base("the Guide")
{
this.m_Path = null;
}
public AttendantGuide(Serial serial)
: base(serial)
{
}
public List<Vertex> Path
{
get
{
return this.m_Path;
}
}
public override void OnDoubleClick(Mobile from)
{
if (from.Alive && this.IsOwner(from))
{
Dictionary<int, Vertex> 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<ContextMenuEntry> 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<Mobile>(TimeSpan.FromSeconds(3), new TimerStateCallback<Mobile>(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<Vertex> 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<int, Vertex> m_Shops;
public InternalGump(AttendantGuide guide, Dictionary<int, Vertex> 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<int, Vertex> 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<Vertex> 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();
}
}
}