/* ShiftOS Online Hacker Battles - Matchmaker * * These classes deal with keeping things in line on the client-side of things. * They deal with CSP (Client-Side Prediction), sending and receiving messages to * and from the ShiftOS server, as well as making sure that when you join or leave a * lobby, the server and other clients actually KNOW you did. * * I wouldn't mess with this unless you really, really understand what you're doing, * as in most cases, modification to the server is required as well (in the case of * adding new commands). I'd leave modification to the system creator (Michael VanOverbeek) who * actually wrote this. He's the guy who knows all about how the server works. Wait... why am * I talking in third person? */ using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace ShiftOS.Online.Hacking { public class Matchmaker { //All available lobbies. public static List Servers = null; //All players in the lobby. public static List Players = null; //Some useful info about the lobby in which the player is. //Also contains server IP to be sent to Package_Grabber.SendMessage(). public static ServerInfo SelectedServer = null; //Enemy network information (name, codepoints, etc) public static Network SelectedNetwork = null; //There's only one transmitter because generally the player //won't be interacting with the enemy playfield enough to //warrent a request to the server. public static NetListener SelectedNetworkListener = null; //Listen for updates from the opponent. public static NetTransmitter SelectedNetworkTransmitter = null; //Send messages to the server for enemy updates on opponent clients. public static NetListener PlayerListener = null; //For receiving non-CSP updates from the server. public static NetTransmitter SecondaryTransmitter = null; //For sending CSP-created update requests to the opponent (enemy health damage, etc) //Timer that'll run during matchmaking. public static Timer MakerTimer = null; /// /// This either starts matchmaking or grabs server info. Try it out I guess. /// /// Fires: Matchmaker.Initiated /// public static void Initiate() { MakerTimer = new Timer(); MakerTimer.Interval = 100; Servers = new List(); foreach(var c in Package_Grabber.clients) { c.Value.OnReceived += (o, e) => { try { var om = (e.Data.Object as ObjectModel); if (om.Command == "server_info") { var si = JsonConvert.DeserializeObject(om.OptionalObject as string); si.IPAddress = c.Value.RemoteHost; Servers.Add(si); invoke(() => { Initiated?.Invoke(null, new EventArgs()); }); } } catch { } }; Package_Grabber.SendMessage(c.Value.RemoteHost, "get_info"); } } /// /// Matchmake in the supplied lobby. /// /// Fires: MorePlayersFound upon player leave/join. /// /// The server to matchmake in. public static void Matchmake(ServerInfo si) { SelectedServer = si; var rnd = new Random(); Players = new List(); var server = Package_Grabber.clients[si.IPAddress]; server.OnReceived += (o, e) => { try { var om = e.Data.Object as ObjectModel; if (om.Command == "matchmaking") { Players = JsonConvert.DeserializeObject>(om.OptionalObject as string); invoke(() => { MorePlayersFound?.Invoke(null, new EventArgs()); }); } } catch { } }; Package_Grabber.SendMessage(si.IPAddress, "get_matchmaking"); int index = 0; MakerTimer.Tick += (o, e) => { try { if (Players[index].Name != API.CurrentSave.MyOnlineNetwork.Name && Players[index].Name != null) { SelectedNetwork = Players[index]; MakerTimer.Stop(); PlayerListener = new NetListener(si, SelectedNetwork); SecondaryTransmitter = new NetTransmitter(si, API.CurrentSave.MyOnlineNetwork); SelectedNetworkListener = new NetListener(si, API.CurrentSave.MyOnlineNetwork); SelectedNetworkTransmitter = new NetTransmitter(si, SelectedNetwork); var h = new HackUI(SelectedNetworkTransmitter, SelectedNetworkListener, PlayerListener, SecondaryTransmitter); h.Show(); Package_Grabber.SendMessage(SelectedServer.IPAddress, $"leave_lobby {JsonConvert.SerializeObject(API.CurrentSave.MyOnlineNetwork)}"); } else { index += 1; } } catch { } }; MakerTimer.Interval = 50; MakerTimer.Start(); } /// /// Fired when Initiate() finishes. /// public static event EventHandler Initiated; /// /// Fired when someone enters/exits a lobby that we are in. /// public static event EventHandler MorePlayersFound; /// /// Helper method to allow me to invoke some code on the ShiftOS desktop thread (for UI access) /// /// The code to invoke (use a lambda expression or just pump a void through.) public static void invoke(Action method) { API.CurrentSession.Invoke(method); } internal static void DestroySession() { Servers.Clear(); Players.Clear(); ClearEvents(); SelectedNetwork = null; SelectedNetworkTransmitter = null; SelectedNetworkListener = null; PlayerListener = null; //Good to go, I guess. } public static void ClearEvents() { Initiated = null; MorePlayersFound = null; } } public class NetListener { public NetListener(ServerInfo si, Network net) { register_events(si, net); } public List MyModules = null; private void register_events(ServerInfo si, Network net) { MyModules = new List(); var server = Package_Grabber.clients[si.IPAddress]; server.OnReceived += (o, e) => { if (e.Data.Object is ObjectModel) { var data = (e.Data.Object as ObjectModel); if (data.Command != null) { string[] args = data.Command.Split(' '); if ((data.OptionalObject as Network)?.Name == net.Name) { switch (args[0].ToLower()) { case "set_health": string hn = args[1]; int hp = Convert.ToInt32(args[2]); invoke(() => { ModuleHealthSet?.Invoke(this, new Events.Health { host_name = hn, health = hp }); }); break; case "place_module": string hostname = args[1]; int grade = Convert.ToInt32(args[2]); int newhp = Convert.ToInt32(args[3]); int x = Convert.ToInt32(args[4]); int y = Convert.ToInt32(args[5]); int type = Convert.ToInt32(args[6]); var moduleToPlace = new Module { Grade = grade, Hostname = hostname, HP = newhp, Type = type, X = x, Y = y }; MyModules.Add(moduleToPlace); invoke(() => { ModulePlaced?.Invoke(this, new Events.ModulePlaced { new_module = moduleToPlace }); }); break; case "remove_module": string hostnametoremove = args[1]; var m = new Module(); foreach (var mod in MyModules) { if (mod.Hostname == hostnametoremove) { m = mod; } } MyModules.Remove(m); invoke(() => { ModuleRemoved?.Invoke(this, new Events.ModuleRemoved { new_module = hostnametoremove }); }); break; case "upgrade": invoke(() => { string hostnametoupgrade = args[1]; int newgrade = Convert.ToInt32(args[2]); ModuleUpgraded?.Invoke(this, new Events.ModuleUpgraded { hostname = hostnametoupgrade, grade = newgrade }); }); break; case "finish": string json = data.Command.Remove(0, 7); var winner = JsonConvert.DeserializeObject(json); Won?.Invoke(this, new Events.Won(winner)); break; case "disable": invoke(() => { string name = args[1]; ModuleDisabled?.Invoke(this, new Events.Disabled { hostName = name }); }); break; } } } } }; } public void invoke(Action method) { API.CurrentSession.Invoke(method); } public event EventHandler ModuleHealthSet; public event EventHandler ModulePlaced; public event EventHandler ModuleRemoved; public event EventHandler ModuleUpgraded; public event EventHandler ModuleDisabled; public event EventHandler Won; } public class NetTransmitter { public ServerInfo serverInfo = null; public Network EnemyIdent = null; public NetTransmitter(ServerInfo si, Network enemy) { EnemyIdent = enemy; serverInfo = si; //HackUI will handle everything else to do with our network. } public void send_message(Messages msg, object value) { switch(msg) { case Messages.PlaceModule: var m = value as Module; Package_Grabber.SendMessage(serverInfo.IPAddress, $"place_module {m.Hostname} {m.Grade} {m.HP} {m.X} {m.Y} {m.Type}", EnemyIdent); break; case Messages.Upgrade: string upgradestr = value as string; Package_Grabber.SendMessage(serverInfo.IPAddress, $"upgrade {upgradestr}", EnemyIdent); break; case Messages.RemoveModule: string hostnametoremove = value as string; Package_Grabber.SendMessage(serverInfo.IPAddress, $"remove_module {hostnametoremove}", EnemyIdent); break; case Messages.SetHealth: string healthsetstr = value as string; Package_Grabber.SendMessage(serverInfo.IPAddress, $"set_health {healthsetstr}", EnemyIdent); break; case Messages.FinishBattle: string json = JsonConvert.SerializeObject(value as Network); Package_Grabber.SendMessage(serverInfo.IPAddress, $"finish {json}"); break; case Messages.Disabled: string hnamestr = value as string; Package_Grabber.SendMessage(serverInfo.IPAddress, $"disable {hnamestr}", EnemyIdent); break; } } public enum Messages { PlaceModule, Upgrade, RemoveModule, SetHealth, Disabled, FinishBattle, } } namespace Events { public class Health : EventArgs { public string host_name { get; set; } public int health { get; set; } } public class Disabled : EventArgs { public string hostName { get; set; } } public class Won : EventArgs { public Network Winner { get; private set; } public Won(Network winner) { Winner = winner; } } public class ModulePlaced : EventArgs { public Module new_module { get; set; } } public class ModuleRemoved : EventArgs { public string new_module { get; set; } } public class ModuleUpgraded : EventArgs { public string hostname { get; set; } public int grade { get; set; } } } }