Ah yes. The complicated part - typical dev
After a heap of fiddling, I wrote a .net (C#) winform app that communicates with MM via 2 udp servers - 1 in the C#app (I call it RomViewer), 1 in MM. Primary motivation was to allow me to play at work with the game window hidden and driven through RomViewer.
I then butchered bot.lua to create my own version that doesn't prompt for a waypoint file, but just puts the bot into Wander mode with a radius of 0 (i.e. allows me to run my character around while it still does everything else its meant to do). Its a rather ugly app, but it does what I need.
Over time I added more code to implement a protocol for conversations between MM and RomViewer. The startup process for RomViewer went something like this:
1) Start the UDP comms server on a specified Port (this matches a few profile settings that have added to my characters profile)
2) Create a tab for each default chat type (World, guild, zone, whisper etc) so that when we get the chat messages from mm (via rocks ingamefunction stuff for monitorings events) we can display them in the appropriate tab.
3) Request the characters inventory and player details (gets displayed on front tab)
4) Send a request to MM via the UDP comms server for the processId and window handle for MM and Client.exe (these are used later so I can hide the windows completely so they are not on the taskbar etc and so I can send keypresses to the game window to run ACS / DIYCE as I can get better performance for combat that way)
5) Request the execution path (execution directory for MM so I can get waypoint dir etc)
Once I had all that working, I created my own version of Waypoints (called GameNodes) so I could create a database of waypoints and join them together for pathing etc. To support that I modded my bot code to send back the players location all the time - this is then used to work out which GameNode the character is closest too. When I press Insert in RomViewer it adds a new GameNode at the current coordinates and links it to the 'last waypoint'.
Code: Select all
public class GameNode
{
Vector3 _coordinates;
int _id;
string _name;
List<GameObject> _gameObjects = new List<GameObject>();
Zone _zone;
List<GameNodeLink> _gameNodeLinks = new List<GameNodeLink>();
}
public class GameNodeLink
{
GameNode _target;
string _script;
}
public enum GameObjectType
{
None,
VendorPet,
VendorGeneral,
AuctionHouse,
Bank,
Mailbox,
Housemaid,
Transporter
}
public class GameObject
{
List<GameObjectType> _objectTypes = new List<GameObjectType>();
int _id;
string _name;
List<TransportLink> _transportLinks = new List<TransportLink>();
}
public class TransportLink
{
GameNode _destination;
string _script;
}
public class Zone
{
private int _id;
private string _name;
private List<GameNode> _waypoints = new List<GameNode>();
}
Basically each GameNode links to 1 or more GameNode(s) and has a list of known objects (NPCS / mailboxes etc) which may also link to other GameNodes via TransportLinks (snoops / transporters)
From all this, I then build a model of the world by creating another set of classes (shown here for the curious):
Code: Select all
public class Waypoint
{
public GameNode Node;
public Zone Zone = null;
public Vector3 Coordinates;
public int Id;
public string Name;
public Dictionary<int, WaypointLink> Links = new Dictionary<int, WaypointLink>();
public List<GameObjectType> SupportsObjectType = new List<GameObjectType>();
public Dictionary<GameObjectType, WaypointLink> NearestTypeLinks = new Dictionary<GameObjectType, WaypointLink>();
public Waypoint(Zone zone, Vector3 coordinates, int id, string name)
{
Zone = zone;
Coordinates = coordinates;
Id = id;
Name = name;
}
public Waypoint(GameNode node)
{
Id = node.Id;
Name = node.Name;
Coordinates = node.Coordinates;
Node = node;
Zone = node.Zone;
}
public WaypointLink AddLink(Waypoint destination, LinkStyle stlye)
{
WaypointLink result = new WaypointLink(this, destination, stlye);
Links.Add(destination.Id, result);
return result;
}
public WaypointLink AddLink(Waypoint destination, LinkStyle stlye, string script)
{
WaypointLink result = new WaypointLink(this, destination, stlye, script);
Links.Add(destination.Id, result);
return result;
}
public void SetNearestTypeWaypoint(GameObjectType gameType, WaypointLink link)
{
if (NearestTypeLinks.ContainsKey(gameType)) NearestTypeLinks.Remove(gameType);
NearestTypeLinks.Add(gameType, link);
}
public override string ToString()
{
return Node.ToString();
}
public string ToRomBotXML(int num, string script)
{
string s = "";
if ((script!=null) && (script.Length > 0))
{
s = Environment.NewLine + script + Environment.NewLine;
}
string result = string.Format(" <!-- # {0:00} --><waypoint x=\"{1}\" z=\"{2}\" type=\"RUN\">{3}</waypoint>", num, Coordinates.X, Coordinates.Z, s);
return result;
}
}
public class WaypointLink
{
public Waypoint Source { get; set; }
public Waypoint Destination { get; set; }
public LinkStyle Style { get; set; }
public string Script { get; set; }
public double Distance { get; set; }
public WaypointLink(Waypoint source, Waypoint destination, LinkStyle style)
{
Source = source;
Destination = destination;
Style = style;
if (style == LinkStyle.Walk)
Distance = source.Coordinates.Distance(destination.Coordinates);
else
Distance = 1200;
}
public WaypointLink(Waypoint source, Waypoint destination, LinkStyle style, string script): this(source, destination, style)
{
Script = script;
}
public override string ToString()
{
string source = (Source != null) ? Source.Name : "unknown";
string dest = (Destination != null) ? Destination.Name : "unknown";
return source + " -> " + dest;
}
}
public enum LinkStyle
{
Walk,
Transport
}
public class InteractiveObject
{
public GameObject GameObject;
public List<GameObjectType> ImplementsTypes = new List<GameObjectType>();
public Dictionary<GameObjectType, string> TypeScripts = new Dictionary<GameObjectType, string>();
public Dictionary<Waypoint, WaypointLink> TransportLinks = new Dictionary<Waypoint, WaypointLink>();
public InteractiveObject(GameObject gameObject)
{
GameObject = gameObject;
ImplementsTypes.AddRange(gameObject.ObjectTypes);
}
}
This stuff is generated when RomViewer starts and it then calculates the shortest route from each waypoint to the various types of objects so that when I want the character to go to a mailbox, it can build a path real fast. This path is then saved to a normal waypoint file and a message is sent to MM to tell it to load that waypoint file and the bot takes over from there
Once I had all that going I created a control to draw the map for me too so I can see whats going on etc and some code to send me back the current object list every now and again.
Then some more code to
* enable editing of the GameNodes
* rebuild the instance nodes from any changes
* enable and disable flyhack,
* use teleport,
* mount up
* send custom rom commands via a textbox. e.g. speed(70) etc.
* some stuff to keep a list of all players whenever they chat in zone/guild etc (does a sendMacro("AskPlayerInfo('playername')").
* display a list of RomObjects (from my db) that are at the current location with an 'interact' button
* display a list of all objects that are nearby (from the CObjectList stuff)
* run a thread in MM to pull back all chats etc and send on to RomViewer and vice versa
* hide/show the MM and Client.exe instance from view and on taskbar.
* send keypresses to Client.exe main window.
* store a set of commonly used custom commands for things as: chatting to housekeepers, summoning pet, feeding pet, turning in dailies, Configuring lootfilter, picking up eggs and cake from egg farmers