diff --git a/HappyFunTimes.sln b/HappyFunTimes.sln index 31b7a22..6c8a1bb 100644 --- a/HappyFunTimes.sln +++ b/HappyFunTimes.sln @@ -3,6 +3,8 @@ Microsoft Visual Studio Solution File, Format Version 10.00 # Visual Studio 2008 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HappyFunTimes", "HappyFunTimes\HappyFunTimes.csproj", "{554A0027-C03D-4D04-946D-2A34E43CC586}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HappyFunTimesEditor", "HappyFunTimesEditor\HappyFunTimesEditor.csproj", "{97F1A61A-1B41-4878-BA21-9A029AA49791}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -13,8 +15,13 @@ Global {554A0027-C03D-4D04-946D-2A34E43CC586}.Debug|Any CPU.Build.0 = Debug|Any CPU {554A0027-C03D-4D04-946D-2A34E43CC586}.Release|Any CPU.ActiveCfg = Release|Any CPU {554A0027-C03D-4D04-946D-2A34E43CC586}.Release|Any CPU.Build.0 = Release|Any CPU + {97F1A61A-1B41-4878-BA21-9A029AA49791}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {97F1A61A-1B41-4878-BA21-9A029AA49791}.Debug|Any CPU.Build.0 = Debug|Any CPU + {97F1A61A-1B41-4878-BA21-9A029AA49791}.Release|Any CPU.ActiveCfg = Release|Any CPU + {97F1A61A-1B41-4878-BA21-9A029AA49791}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution StartupItem = HappyFunTimes\HappyFunTimes.csproj + version = 0.2 EndGlobalSection EndGlobal diff --git a/HappyFunTimes/ArgParser.cs b/HappyFunTimes/ArgParser.cs new file mode 100644 index 0000000..9727e3d --- /dev/null +++ b/HappyFunTimes/ArgParser.cs @@ -0,0 +1,133 @@ +/* + * Copyright 2014, Gregg Tavares. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Gregg Tavares. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +using System.Collections.Generic; + +namespace HappyFunTimes { + +/// +/// A very simple commandline argument parser. +/// +/// It just looks arguments that start with "--". For any +/// argument that starts with "--" it will then look for the +/// first "=" sign. If found it will set remember the value +/// for that option. If no equals is found it uses the value +/// "true" +/// +/// +/// Example: Assume your command line is +/// +/// myprogram --name=gregg --count=123 --debug +/// +/// You can then read those options with +/// +/// using HappyFunTimes; +/// +/// ... +/// ArgParser p = new ArgParser(); +/// +/// // Set Defaults +/// string name = "Someone"; +/// int count = 1; +/// bool debug = false; +/// +/// p.TryGet("name", name); +/// p.TryGet("count", count); +/// if (p.Contains("debug")) { +/// debug = true; +/// } +/// +/// +/// +public class ArgParser { + + /// + /// Constructor for ArgParser that parses command line arguments + /// + public ArgParser() + { + Init(System.Environment.GetCommandLineArgs()); + } + + /// + /// Constructor for ArgParser which you can pass your own array of strings. + /// + /// Array of command line like argument strings + public ArgParser(string[] args) + { + Init(args); + } + + /// + /// Check if a switch exists. + /// + /// name of switch + /// true if switch was contained in arguments + public bool Contains(string id) + { + return m_switches.ContainsKey(id); + } + + /// + /// Gets the value of a switch if it exists + /// + /// id of switch + /// variable to receive the value + /// true if the value exists. False if it does not. If false value has not been affected. + public bool TryGet(string id, ref T value) { + string v; + bool found = m_switches.TryGetValue(id, out v); + if (found) { + value = (T)System.Convert.ChangeType(v, typeof(T)); + } + return found; + } + + private void Init(string[] arguments) + { + m_switches = new Dictionary(); + foreach (string arg in arguments) { + if (arg.StartsWith("--")) { + int equalsNdx = arg.IndexOf('='); + if (equalsNdx >= 0) { + m_switches[arg.Substring(2, equalsNdx - 2)] = arg.Substring(equalsNdx + 1); + } else { + m_switches[arg.Substring(2)] = "true"; + } + } + } + } + + private Dictionary m_switches; +}; + + +} // namespace HappyFunTimes + diff --git a/HappyFunTimes/AssemblyInfo.cs b/HappyFunTimes/AssemblyInfo.cs index 43853d9..e720bed 100644 --- a/HappyFunTimes/AssemblyInfo.cs +++ b/HappyFunTimes/AssemblyInfo.cs @@ -5,10 +5,10 @@ // Change them to the values specific to your project. [assembly: AssemblyTitle("HappyFunTimes")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyDescription(".NET Support for HappyFunTimes")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("")] +[assembly: AssemblyCompany("Greggman")] +[assembly: AssemblyProduct("HappyFunTimes")] [assembly: AssemblyCopyright("Gregg Tavares")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/HappyFunTimes/GameServer.cs b/HappyFunTimes/GameServer.cs index 789b15a..25d46f1 100644 --- a/HappyFunTimes/GameServer.cs +++ b/HappyFunTimes/GameServer.cs @@ -39,34 +39,153 @@ namespace HappyFunTimes { public class GameServer { - public class Options { - public Options() { + public delegate void UntypedCmdEventHandler(Dictionary data, string id); + public delegate void TypedCmdEventHandler(T eventArgs, string id) where T : MessageCmdData; + + private class CmdConverter where T : MessageCmdData + { + public CmdConverter(TypedCmdEventHandler handler) { + m_handler = handler; + } + + public void Callback(GameServer server, MessageCmdData data, Dictionary dict, string id) { + server.QueueEvent(delegate() { + m_handler((T)data, id); + }); + } + + TypedCmdEventHandler m_handler; + } + + private class UntypedCmdConverter { + public UntypedCmdConverter(UntypedCmdEventHandler handler) { + m_handler = handler; + } + + public void Callback(GameServer server, MessageCmdData data, Dictionary dict, string id) { + server.QueueEvent(delegate() { + object mcd = null; + // dict is the MessageCmd. We want dict for the MessageCmdData inside the MessageCmd + // It might not exist + dict.TryGetValue("data", out mcd); + m_handler((Dictionary)mcd, id); + }); + } + + UntypedCmdEventHandler m_handler; + } + + /// + /// Options for the GameServer + /// + public class Options + { + public Options() + { cwd = Application.dataPath; disconnectPlayersIfGameDisconnects = true; + url = "ws://localhost:18679"; + + // Prefix all HFT arguments with "hft-" so user can filter them out + ArgParser p = new ArgParser(); + p.TryGet("hft-id", ref id); + p.TryGet("hft-url", ref url); + master = p.Contains("hft-master"); } + + /// + /// there's generally no need to set this. + /// public string gameId; - public string controllerUrl; // not used! left over so things don't break - public bool showMessages; + + /// + /// id used for multi-player games. Can be set from command line with --hft=id=someid + /// + public string id; + + /// + ///Deprecated and not used. + /// + public string controllerUrl; + + /// + ///true allows multiple games to run as the same id. Default: false + /// + ///normally when a second game connects the first game will be disconnected + ///as it's assumed the first game probably crashed or for whatever reason did + ///not disconnect and this game is taking over. Setting this to true doesn't + ///disconnect the old game. This is needed for multi-machine games. + /// + public bool allowMultipleGames; // allow multiple games + + /// + ///For a multiple machine game designates this game as the game where players start. + ///Default: false + ///Can be set from command line with --hft-master + /// + public bool master; + + /// + ///The URL of HappyFunTimes + /// + ///Normally you don't need to set this as HappyFunTimes is running on the same machine + ///as the game. But, for multi-machine games you'd need to tell each machine the address + ///of the machine running HappyFunTimes. Example: "ws://192.168.2.9:18679". + /// + ///Can be set from the command line with --hft-url=someurl + /// + public string url; + + /// + ///Normally if a game disconnets all players are also disconnected. This means + ///they'll auto join the next game running. + ///Default: true + /// public bool disconnectPlayersIfGameDisconnects; - public string cwd; // don't set this. it will be set automatically + + /// + ///Prints all the messages in and out to the console. + /// + public bool showMessages; + + /// + /// don't set this. it will be set automatically + /// + public string cwd; }; + /// + /// Constructor for GameServer + /// + /// The objects + /// gameObject that will process messages from HappyFunTimes public GameServer(Options options, GameObject gameObject) { m_options = options; m_gameObject = gameObject; - m_players = new Dictionary(); + m_players = new Dictionary(); m_sendQueue = new List(); m_deserializer = new Deserializer(); + m_mcdc = new MessageCmdDataCreator(); + m_deserializer.RegisterCreator(m_mcdc); + m_handlers = new Dictionary(); + m_gameSystem = new GameSystem(this); m_eventProcessor = m_gameObject.AddComponent(); m_eventProcessor.Init(this); } + /// + /// Starts the connection to HappyFunTimes. + /// public void Init() { - Init("ws://localhost:18679"); + Init(m_options.url); } + /// + /// Deperacated + /// + /// public void Init(string url/* = "ws://localhost:18679" */) { if (m_socket == null) { @@ -93,38 +212,177 @@ public void QueueEvent(Action action) { m_eventProcessor.QueueEvent(action); } + /// + /// Lets you register a command to be called when the game is directly sent a message. + /// + /// The callback you register must have a CmdName attribute. That attibute will determine + /// which event the callback gets dispatched for. + /// + /// + /// + /// [CmdName("color")] + /// private class MessageColor : MessageCmdData { + /// public string color = ""; // in CSS format rgb(r,g,b) + /// }; + /// + /// ... + /// gameServer.RegisterCmdHandler(OnColor); + /// ... + /// void OnColor(MessageColor) { + /// Debug.Log("received msg with color: " + color); + /// } + /// + /// + /// Typed callback + public void RegisterCmdHandler(TypedCmdEventHandler callback) where T : MessageCmdData { + string name = MessageCmdDataNameDB.GetCmdName(typeof(T)); + if (name == null) { + throw new System.InvalidOperationException("no CmdNameAttribute on " + typeof(T).Name); + } + RegisterCmdHandler(name, callback); + } + + /// + /// Lets you register a command to be called when the game is directly sent a message. + /// + /// + /// + /// private class MessageColor : MessageCmdData { + /// public string color = ""; // in CSS format rgb(r,g,b) + /// }; + /// + /// ... + /// gameServer.RegisterCmdHandler("color", OnColor); + /// ... + /// void OnColor(MessageColor) { + /// Debug.Log("received msg with color: " + color); + /// } + /// + /// + /// command to call callback for + /// Typed callback + public void RegisterCmdHandler(string name, TypedCmdEventHandler callback) where T : MessageCmdData { + CmdConverter converter = new CmdConverter(callback); + m_handlers[name] = converter.Callback; + m_mcdc.RegisterCreator(typeof(T)); + } + + + /// + /// Lets you register a command to be called when the game is directly sent a message. + /// + /// This the most low-level basic version of RegisterCmdHandler. The function registered + /// will be called with a Dictionary with whatever data is passed in. + /// You can either pull the data out directly OR you can call Deserializer.Deserilize + /// with a type and have the data extracted for you. + /// + /// + /// + /// ... + /// gameServer.RegisterCmdHandler("color", OnColor); + /// ... + /// void OnColor(Dictionary data) { + /// Debug.Log("received msg with color: " + data["color"]); + /// } + /// + /// or + /// + /// private class MessageColor : MessageCmdData { + /// public string color = ""; // in CSS format rgb(r,g,b) + /// }; + /// ... + /// gameServer.RegisterCmdHandler("color", OnColor); + /// ... + /// void OnColor(Dictionary data) { + /// Deserializer d = new Deserializer(); + /// MessageColor msg = d.Deserialize(data); + /// Debug.Log("received msg with color: " + msg.color); + /// } + /// + /// + /// command to call callback for + /// Typed callback + public void RegisterCmdHandler(string name, UntypedCmdEventHandler callback) { + UntypedCmdConverter converter = new UntypedCmdConverter(callback); + m_handlers[name] = converter.Callback; + m_mcdc.RegisterCreator(name, typeof(Dictionary)); + } + + /// This needs the server because messages need to be queued as they need to be delivered on anther thread. + private delegate void CmdEventHandler(GameServer server, MessageCmdData cmdData, Dictionary dict, string id); + public event EventHandler OnPlayerConnect; public event EventHandler OnConnect; public event EventHandler OnDisconnect; + /// + /// Id of the machine assigned by HappyFunTimes. + /// + /// If you're running a multi-machine you can pass an id in the GameServer construtor options. + /// If you don't pass an id you'll be assigned one. This is the assigned one. + /// + /// Note: It is invalid to read this property before the game has connected. + /// + /// + /// + /// ... + /// gameServer.OnConnect += OnConnect; + /// ... + /// void OnConnect(EventArgs e) { + /// Debug.Log(gameServer.Id); + /// } + /// + /// + /// id of machine assigned by HappyFunTimes + public string Id { + get { + if (m_id == null) { + Debug.LogError("you can NOT read id before the game has connected"); + } + return m_id; + } + private set { + } + } + private Options m_options; private bool m_connected = false; private int m_totalPlayerCount = 0; + private int m_recvCount = 0; + private int m_sendCount = 0; + private int m_queueCount = 0; private bool m_gotMessages = false; private WebSocket m_socket; - private Dictionary m_players; + private Dictionary m_players; private List m_sendQueue; private Deserializer m_deserializer; private GameObject m_gameObject; private EventProcessor m_eventProcessor; private GameSystem m_gameSystem; + private Dictionary m_handlers; // handlers by command name + private MessageCmdDataCreator m_mcdc; + private string m_id = null; public class MessageToClient { public string cmd; // command 'server', 'update' - public int id; // id of client + public string id; // id of client public Dictionary data; }; + private class MessageGameStart { + public string id = ""; + }; + private class RelayServerCmd { - public RelayServerCmd(string _cmd, int _id, System.Object _data) { + public RelayServerCmd(string _cmd, string _id, object _data) { cmd = _cmd; id = _id; data = _data; } public string cmd; - public int id; - public System.Object data; + public string id; + public object data; } private void SocketOpened(object sender, System.EventArgs e) { @@ -139,11 +397,11 @@ private void SocketOpened(object sender, System.EventArgs e) { m_sendQueue.Clear(); // Inform the relayserver we're a server - SendCmd("server", -1, m_options); - - QueueEvent(delegate() { - OnConnect.Emit(this, new EventArgs()); - }); + try { + SendSysCmd("server", "-1", m_options); + } catch (Exception ex) { + Debug.LogException(ex); + } } private void SocketClosed(object sender, CloseEventArgs e) { @@ -162,14 +420,21 @@ private void SocketMessage(object sender, MessageEventArgs e) { Send("P"); return; } + if (m_options.showMessages) { + Debug.Log("r[" + (m_recvCount++) + "] " + e.Data); + } MessageToClient m = m_deserializer.Deserialize(e.Data); // TODO: make this a dict to callback - if (m.cmd.Equals("start")) { - StartPlayer(m.id, ""); + if (m.cmd.Equals("update")) { + UpdatePlayer(m.id, m.data); + } else if (m.cmd.Equals("upgame")) { + UpdateGame(m.id, m.data); + } else if (m.cmd.Equals("start")) { + StartPlayer(m.id, "", m.data); + } else if (m.cmd.Equals("gamestart")) { + StartGame(m.id, m.data); } else if (m.cmd.Equals("remove")) { RemovePlayer(m.id); - } else if (m.cmd.Equals("update")) { - UpdatePlayer(m.id, m.data); } else if (m.cmd.Equals("system")) { DoSysCommand(m.data); } else { @@ -201,13 +466,13 @@ private void Cleanup() }); while (m_players.Count > 0) { - Dictionary.Enumerator i = m_players.GetEnumerator(); + Dictionary.Enumerator i = m_players.GetEnumerator(); i.MoveNext(); RemovePlayer(i.Current.Key); } } - private void StartPlayer(int id, string name) { + private void StartPlayer(string id, string name, Dictionary data) { if (m_players.ContainsKey(id)) { return; } @@ -220,8 +485,8 @@ private void StartPlayer(int id, string name) { m_players[id] = player; QueueEvent(delegate() { // UGH! This is not thread safe because someone might add handler to OnPlayerConnect - // Odds or low though. - OnPlayerConnect.Emit(this, new PlayerConnectMessageArgs(player)); + // Odds are low though? + OnPlayerConnect.Emit(this, new PlayerConnectMessageArgs(player, name, data)); }); } @@ -229,7 +494,7 @@ private void DoSysCommand(Dictionary cmd) { m_gameSystem.HandleUnparsedCommand(cmd); } - private void UpdatePlayer(int id, Dictionary cmd) { + private void UpdatePlayer(string id, Dictionary cmd) { NetPlayer player; if (!m_players.TryGetValue(id, out player)) { return; @@ -237,7 +502,30 @@ private void UpdatePlayer(int id, Dictionary cmd) { player.SendUnparsedEvent(cmd); } - private void RemovePlayer(int id) { + private void StartGame(string id, Dictionary cmd) { + MessageGameStart data = m_deserializer.Deserialize(cmd); + m_id = data.id; + + QueueEvent(delegate() { + OnConnect.Emit(this, new EventArgs()); + }); + } + + private void UpdateGame(string id, Dictionary data) { + try { + MessageCmd cmd = m_deserializer.Deserialize(data); + CmdEventHandler handler; + if (!m_handlers.TryGetValue(cmd.cmd, out handler)) { + Debug.LogError("unhandled GameServer cmd: " + cmd.cmd); + return; + } + handler(this, cmd.data, data, id); + } catch (Exception ex) { + Debug.LogException(ex); + } + } + + private void RemovePlayer(string id) { NetPlayer player; if (!m_players.TryGetValue(id, out player)) { return; @@ -250,18 +538,59 @@ private void RemovePlayer(int id) { private void Send(string msg) { if (m_connected) { + if (m_options.showMessages) { + Debug.Log("q[" + (m_queueCount++) + "] " + msg); + } m_socket.Send(msg); } else { + if (m_options.showMessages) { + Debug.Log("s[" + (m_sendCount++) + "] " + msg); + } m_sendQueue.Add(msg); } } - // Only NetPlayer should call this. - public void SendCmd(string cmd, int id, System.Object data) { + public void SendSysCmd(string cmd, string id, object data) { var msg = new RelayServerCmd(cmd, id, data); string json = Serializer.Serialize(msg); Send(json); } + + // Only NetPlayer should call this. + public void SendCmd(string cmd, string name, MessageCmdData data, string id = "-1") { + MessageCmd msgCmd = new MessageCmd(name, data); + SendSysCmd(cmd, id, msgCmd); + } + + // Only NetPlayer should call this. + public void SendCmd(string cmd, MessageCmdData data, string id = "-1") { + string name = MessageCmdDataNameDB.GetCmdName(data.GetType()); + SendCmd(cmd, name, data, id); + } + + public void BroadcastCmd(string cmd, MessageCmdData data) { + SendCmd("broadcast", cmd, data); + } + + public void BroadcastCmd(MessageCmdData data) { + SendCmd("broadcast", data); + } + + public void SendCmdToGame(string id, string cmd, MessageCmdData data) { + SendCmd("peer", cmd, data, id); + } + + public void SendCmdToGame(string id, MessageCmdData data) { + SendCmd("peer", data, id); + } + + public void BroadcastCmdToGames(string cmd, MessageCmdData data) { + SendCmd("bcastToGames", cmd, data); + } + + public void BroadcastCmdToGames(MessageCmdData data) { + SendCmd("bcastToGames", data); + } }; } diff --git a/HappyFunTimes/GameSystem.cs b/HappyFunTimes/GameSystem.cs index 590d8bc..476a92d 100644 --- a/HappyFunTimes/GameSystem.cs +++ b/HappyFunTimes/GameSystem.cs @@ -42,7 +42,7 @@ private class MessageExit : MessageCmdData { private NetPlayer m_netPlayer; public GameSystem(GameServer server) { - m_netPlayer = new NetPlayer(server, -1); + m_netPlayer = new NetPlayer(server, "-1"); m_netPlayer.RegisterCmdHandler(OnExit); } diff --git a/HappyFunTimes/HappyFunTimes.csproj b/HappyFunTimes/HappyFunTimes.csproj index e470725..0265e2e 100644 --- a/HappyFunTimes/HappyFunTimes.csproj +++ b/HappyFunTimes/HappyFunTimes.csproj @@ -10,6 +10,7 @@ HappyFunTimes HappyFunTimes v3.5 + 0.2 True @@ -52,6 +53,7 @@ + \ No newline at end of file diff --git a/HappyFunTimes/MessageCmd.cs b/HappyFunTimes/MessageCmd.cs index 6259f6a..a348683 100644 --- a/HappyFunTimes/MessageCmd.cs +++ b/HappyFunTimes/MessageCmd.cs @@ -159,6 +159,10 @@ public void RegisterCreator(System.Type type) { m_creators[name] = new TypeBasedCreator(type); } + public void RegisterCreator(string name, System.Type type) { + m_creators[name] = new TypeBasedCreator(type); + } + public override object Create(Dictionary src, Dictionary parentSrc) { string typeName = (string)parentSrc["cmd"]; Creator creator; diff --git a/HappyFunTimes/NetPlayer.cs b/HappyFunTimes/NetPlayer.cs index b26b148..c50c3bc 100644 --- a/HappyFunTimes/NetPlayer.cs +++ b/HappyFunTimes/NetPlayer.cs @@ -36,8 +36,12 @@ namespace HappyFunTimes { +/// +/// This object represent the connections between a player's phone and this game. +/// public class NetPlayer { + public delegate void UntypedCmdEventHandler(Dictionary data); public delegate void TypedCmdEventHandler(T eventArgs) where T : MessageCmdData; private class CmdConverter where T : MessageCmdData @@ -46,7 +50,7 @@ public CmdConverter(TypedCmdEventHandler handler) { m_handler = handler; } - public void Callback(GameServer server, MessageCmdData data) { + public void Callback(GameServer server, MessageCmdData data, Dictionary dict) { server.QueueEvent(delegate() { m_handler((T)data); }); @@ -55,8 +59,27 @@ public void Callback(GameServer server, MessageCmdData data) { TypedCmdEventHandler m_handler; } - public NetPlayer(GameServer server, int id) { + private class UntypedCmdConverter { + public UntypedCmdConverter(UntypedCmdEventHandler handler) { + m_handler = handler; + } + + public void Callback(GameServer server, MessageCmdData data, Dictionary dict) { + server.QueueEvent(delegate() { + object mcd = null; + // dict is the MessageCmd. We want dict for the MessageCmdData inside the MessageCmd + // It might not exist + dict.TryGetValue("data", out mcd); + m_handler((Dictionary)mcd); + }); + } + + UntypedCmdEventHandler m_handler; + } + + public NetPlayer(GameServer server, string id) { m_server = server; + m_connected = true; m_id = id; m_handlers = new Dictionary(); m_deserializer = new Deserializer(); @@ -64,27 +87,132 @@ public NetPlayer(GameServer server, int id) { m_deserializer.RegisterCreator(m_mcdc); } + /// + /// Lets you register a command to be called when message comes in from this player. + /// + /// The callback you register must have a CmdName attribute. That attibute will determine + /// which event the callback gets dispatched for. + /// + /// + /// + /// [CmdName("color")] + /// private class MessageColor : MessageCmdData { + /// public string color = ""; // in CSS format rgb(r,g,b) + /// }; + /// + /// ... + /// netPlayer.RegisterCmdHandler(OnColor); + /// ... + /// void OnColor(MessageColor) { + /// Debug.Log("received msg with color: " + color); + /// } + /// + /// + /// Typed callback public void RegisterCmdHandler(TypedCmdEventHandler callback) where T : MessageCmdData { string name = MessageCmdDataNameDB.GetCmdName(typeof(T)); if (name == null) { throw new System.InvalidOperationException("no CmdNameAttribute on " + typeof(T).Name); } + RegisterCmdHandler(name, callback); + } + + /// + /// Lets you register a command to be called when a message comes in from this player. + /// + /// + /// + /// private class MessageColor : MessageCmdData { + /// public string color = ""; // in CSS format rgb(r,g,b) + /// }; + /// + /// ... + /// newPlayer.RegisterCmdHandler("color", OnColor); + /// ... + /// void OnColor(MessageColor) { + /// Debug.Log("received msg with color: " + color); + /// } + /// + /// + /// command to call callback for + /// Typed callback + public void RegisterCmdHandler(string name, TypedCmdEventHandler callback) where T : MessageCmdData { CmdConverter converter = new CmdConverter(callback); m_handlers[name] = converter.Callback; - m_mcdc.RegisterCreator(typeof(T)); + m_mcdc.RegisterCreator(name, typeof(T)); } + /// + /// Lets you register a command to be called when a message is sent from this player. + /// + /// This the most low-level basic version of RegisterCmdHandler. The function registered + /// will be called with a Dictionary with whatever data is passed in. + /// You can either pull the data out directly OR you can call Deserializer.Deserilize + /// with a type and have the data extracted for you. + /// + /// + /// + /// ... + /// netPlayer.RegisterCmdHandler("color", OnColor); + /// ... + /// void OnColor(Dictionary data) { + /// Debug.Log("received msg with color: " + data["color"]); + /// } + /// + /// or + /// + /// private class MessageColor : MessageCmdData { + /// public string color = ""; // in CSS format rgb(r,g,b) + /// }; + /// ... + /// gameServer.RegisterCmdHandler("color", OnColor); + /// ... + /// void OnColor(Dictionary data) { + /// Deserializer d = new Deserializer(); + /// MessageColor msg = d.Deserialize(data); + /// Debug.Log("received msg with color: " + msg.color); + /// } + /// + /// + /// command to call callback for + /// Typed callback + public void RegisterCmdHandler(string name, UntypedCmdEventHandler callback) { + UntypedCmdConverter converter = new UntypedCmdConverter(callback); + m_handlers[name] = converter.Callback; + m_mcdc.RegisterCreator(name, typeof(Dictionary)); + } /// This needs the server because messages need to be queued as they need to be delivered on anther thread. - private delegate void CmdEventHandler(GameServer server, MessageCmdData cmdData); + private delegate void CmdEventHandler(GameServer server, MessageCmdData cmdData, Dictionary dict); - public void SendCmd(MessageCmdData data) { // Make it Ob's name is the message. - string name = MessageCmdDataNameDB.GetCmdName(data.GetType()); - MessageCmd msgCmd = new MessageCmd(name, data); - m_server.SendCmd("client", m_id, msgCmd); + private void SendCmd(string cmd, MessageCmdData data) + { + if (m_connected) + { + m_server.SendCmd(cmd, data, m_id); + } } - public void SendUnparsedEvent(Dictionary data) { + /// + /// Sends a message to this player's phone + /// + /// The message. It must be derived from MessageCmdData and must have a + /// CmdName attribute + /// + /// + /// + /// + public void SendCmd(MessageCmdData data) { + SendCmd("client", data); + } + + public void SendUnparsedEvent(Dictionary data) + { + if (!m_connected) + { + return; + } + // If there are no handlers registered then the object using this NetPlayer // has not been instantiated yet. The issue is the GameSever makes a NetPlayer. // It then has to queue an event to start that player so that it can be started @@ -106,7 +234,7 @@ public void SendUnparsedEvent(Dictionary data) { Debug.LogError("unhandled NetPlayer cmd: " + cmd.cmd); return; } - handler(m_server, cmd.data); + handler(m_server, cmd.data, data); } catch (Exception ex) { Debug.LogException(ex); } @@ -117,10 +245,30 @@ public void Disconnect() { OnDisconnect(this, new EventArgs()); } + private class MessageSwitchGame : MessageCmdData { + public string gameId; + public MessageCmdData data; + + public MessageSwitchGame(string id, MessageCmdData d) { + gameId = id; + data = d; + } + } + + public void SwitchGame(string gameId, MessageCmdData data) + { + if (m_connected) + { + m_server.SendSysCmd("switchGame", m_id, new MessageSwitchGame(gameId, data)); + m_connected = false; + } + } + public event EventHandler OnDisconnect; private GameServer m_server; - private int m_id; + private string m_id; + private bool m_connected; private Dictionary m_handlers; // handlers by command name private Deserializer m_deserializer; private MessageCmdDataCreator m_mcdc; diff --git a/HappyFunTimes/PlayerConnectMessageArgs.cs b/HappyFunTimes/PlayerConnectMessageArgs.cs index 7c7bf6f..80e5e5f 100644 --- a/HappyFunTimes/PlayerConnectMessageArgs.cs +++ b/HappyFunTimes/PlayerConnectMessageArgs.cs @@ -30,15 +30,20 @@ */ using System; +using System.Collections.Generic; namespace HappyFunTimes { public class PlayerConnectMessageArgs : EventArgs { - public PlayerConnectMessageArgs(NetPlayer _netPlayer) { + public PlayerConnectMessageArgs(NetPlayer _netPlayer, string _name, Dictionary _data) { netPlayer = _netPlayer; + name = _name; + data = _data; } public NetPlayer netPlayer; + public string name; + public Dictionary data; }; } diff --git a/HappyFunTimes/PlayerSpawner.cs b/HappyFunTimes/PlayerSpawner.cs index 2aba94f..e2d80cd 100644 --- a/HappyFunTimes/PlayerSpawner.cs +++ b/HappyFunTimes/PlayerSpawner.cs @@ -9,6 +9,7 @@ namespace HappyFunTimes { public class SpawnInfo { public NetPlayer netPlayer; public string name; + public Dictionary data; }; [AddComponentMenu("HappyFunTimes/PlayerSpawner")] @@ -17,10 +18,21 @@ public class PlayerSpawner : MonoBehaviour public GameObject prefabToSpawnForPlayer; public string gameId = ""; public bool showMessages = false; + public bool allowMultipleGames; + + public GameServer server + { + get + { + return m_server; + } + } void StartConnection() { GameServer.Options options = new GameServer.Options(); options.gameId = gameId; + options.allowMultipleGames = allowMultipleGames; + options.showMessages = showMessages; m_server = new GameServer(options, gameObject); @@ -37,6 +49,7 @@ void StartNewPlayer(object sender, PlayerConnectMessageArgs e) SpawnInfo spawnInfo = new SpawnInfo(); spawnInfo.netPlayer = e.netPlayer; spawnInfo.name = "Player" + (++m_count); + spawnInfo.data = e.data; gameObject.SendMessage("InitializeNetPlayer", spawnInfo); } @@ -72,6 +85,14 @@ void OnApplicationExit() Cleanup(); } + public GameServer GameServer { + get { + return m_server; + } + private set { + } + } + private GameServer m_server; private int m_count; }; diff --git a/HappyFunTimes/libs/DeJson.dll b/HappyFunTimes/libs/DeJson.dll index 9950e33..f5d5c37 100755 Binary files a/HappyFunTimes/libs/DeJson.dll and b/HappyFunTimes/libs/DeJson.dll differ diff --git a/HappyFunTimesEditor/AssemblyInfo.cs b/HappyFunTimesEditor/AssemblyInfo.cs new file mode 100644 index 0000000..cc4aa9a --- /dev/null +++ b/HappyFunTimesEditor/AssemblyInfo.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("HappyFunTimesEditor")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Gregg Tavares")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] + diff --git a/HappyFunTimesEditor/HFTWindow.cs b/HappyFunTimesEditor/HFTWindow.cs new file mode 100644 index 0000000..42a4674 --- /dev/null +++ b/HappyFunTimesEditor/HFTWindow.cs @@ -0,0 +1,43 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Mono Runtime Version: 4.0.30319.1 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +using System; +using UnityEditor; +using UnityEngine; + +namespace HappyFunTimesEditor +{ + + public class HFTWindow : EditorWindow + { + string myString = "Hello World"; + bool groupEnabled; + bool myBool = true; + float myFloat = 1.23f; + + [MenuItem("Window/HappyFunTimes")] + public static void ShowWindow() + { + //Show existing window instance. If one doesn't exist, make one. + EditorWindow.GetWindow(typeof(HFTWindow)); + } + + void OnGUI() + { + GUILayout.Label ("Base Settings", EditorStyles.boldLabel); + myString = EditorGUILayout.TextField ("Text Field", myString); + + groupEnabled = EditorGUILayout.BeginToggleGroup ("Optional Settings", groupEnabled); + myBool = EditorGUILayout.Toggle ("Toggle", myBool); + myFloat = EditorGUILayout.Slider ("Slider", myFloat, -3, 3); + EditorGUILayout.EndToggleGroup (); + } + } +} + diff --git a/HappyFunTimesEditor/HappyFunTimesEditor.csproj b/HappyFunTimesEditor/HappyFunTimesEditor.csproj new file mode 100644 index 0000000..a7c4724 --- /dev/null +++ b/HappyFunTimesEditor/HappyFunTimesEditor.csproj @@ -0,0 +1,50 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {97F1A61A-1B41-4878-BA21-9A029AA49791} + Library + HappyFunTimesEditor + HappyFunTimesEditor + v3.5 + 0.2 + + + True + full + False + bin\Debug + DEBUG; + prompt + 4 + False + + + none + True + bin\Release + prompt + 4 + False + + + + + ..\..\..\..\..\Applications\Unity\Unity.app\Contents\Frameworks\Managed\UnityEngine.dll + + + ..\HappyFunTimes\libs\DeJson.dll + + + ..\..\..\..\..\Applications\Unity\Unity.app\Contents\Frameworks\Managed\UnityEditor.dll + + + + + + + + \ No newline at end of file