Liberi
An exergame built for kids with CP!
GameServer.cs
1 using UnityEngine;
2 #if UNITY_EDITOR
3 using UnityEditor;
4 #endif
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.IO;
9 using System.Linq;
10 using System.Net;
11 using System.Threading;
12 using Lidgren.Network;
13 using LiberiNet;
14 using Janus;
15 
19 public class GameServer : MonoBehaviour
20 {
21  private class WorldServerInfo
22  {
23  public NetConnection Connection;
24  }
25 
26  private class ClientInfo
27  {
28  public NetConnection Connection;
29  public string PlayerID;
31  public int PeerIndex;
32  public ClientSyncJob SyncJob;
33  }
34 
35  private class ClientSyncJob
36  {
37  public ClientInfo Client;
38  public List<int> ExistingObjectIndices = new List<int>();
39  }
40 
41  public delegate void ConnectedToWorldServerHandler ();
42  public delegate void PlayerJoinedServerHandler (int peerIndex, string playerId, PlayerProfile playerProfile);
43  public delegate void PlayerSyncedHandler (int peerIndex, string playerId, PlayerProfile playerProfile);
44  public delegate void PlayerLeftServerHandler (int peerIndex, string playerId, PlayerProfile playerProfile);
45  public delegate void PlayerJoinedWorldServerHandler (string playerId, string playerNickname);
46  public delegate void PlayerLeftWorldServerHandler (string playerId, string playerNickname);
47  public delegate void WorldDataReceivedHandler (UJeli worldDataPage);
48  public delegate void ServerSpawnedHandler (GameServerType serverType, string mapId, IPEndPoint endPoint);
49  public delegate void ServerSpawnFailedHandler (GameServerType serverType, string mapId);
50  public delegate void ServerSyncFinishedHandler ();
51 
52 #pragma warning disable 0067
53  public event ConnectedToWorldServerHandler ConnectedToWorldServer = delegate { };
60  public event PlayerJoinedServerHandler PlayerJoinedServer = delegate { };
64  public event PlayerSyncedHandler PlayerSynced = delegate { };
68  public event PlayerLeftServerHandler PlayerLeftServer = delegate { };
72  public event PlayerJoinedWorldServerHandler PlayerJoinedWorldServer = delegate { };
76  public event PlayerLeftWorldServerHandler PlayerLeftWorldServer = delegate { };
80  public event ServerSpawnedHandler ServerSpawned = delegate { };
84  public event ServerSpawnFailedHandler ServerSpawnFailed = delegate { };
88  public event ServerSyncFinishedHandler ServerSyncFinished = delegate { };
89 #pragma warning restore 0067
90 
94  public static GameServer Instance
95  {
96  get { return _instance; }
97  }
98 
102  public int ServerPoolIndex
103  {
104  get { return _serverPoolIndex; }
105  }
106 
110  public bool IsPooled
111  {
112  get { return _serverPoolIndex != -1; }
113  }
114 
118  public UJeli MapSettings
119  {
120  get { return _mapSettings; }
121  }
122 
126  public int NumPlayers
127  {
128  get { return _clientsById.Count; }
129  }
130 
134  public string[] PlayerIDs
135  {
136  get { return _clientsById.Keys.ToArray(); }
137  }
138 
142  public int[] PlayerPeerIndices
143  {
144  get { return _clientsByPeerIndex.Keys.ToArray(); }
145  }
146 
151  {
152  get
153  {
154  return (
155  from client in _clientsById.Values
156  where client.PlayerProfile != null
157  select client.PlayerProfile).ToArray();
158  }
159  }
160 
164  public string[] PlayerNicknames
165  {
166  get
167  {
168  return (
169  from client in _clientsById.Values
170  where client.PlayerProfile != null
171  select client.PlayerProfile.Basic.Nickname).ToArray();
172  }
173  }
174 
178  public bool IsConnectedToWorldServer
179  {
180  get { return _isConnectedToWorldServer; }
181  }
182 
183  static GameServer _instance;
184 
185  static Dictionary<NetDeliveryMethod, DeliveryMode> _netToTimelineDeliveryModes
186  = new Dictionary<NetDeliveryMethod, DeliveryMode>()
187  {
188  { NetDeliveryMethod.ReliableOrdered, DeliveryMode.ReliableOrdered },
189  { NetDeliveryMethod.ReliableUnordered, DeliveryMode.ReliableUnordered },
190  { NetDeliveryMethod.Unreliable, DeliveryMode.Unreliable },
191  };
192 
193  static Dictionary<DeliveryMode, NetDeliveryMethod> _timelineToNetDeliveryModes
194  = new Dictionary<DeliveryMode, NetDeliveryMethod>()
195  {
196  { DeliveryMode.ReliableOrdered, NetDeliveryMethod.ReliableOrdered },
197  { DeliveryMode.ReliableUnordered, NetDeliveryMethod.ReliableUnordered },
198  { DeliveryMode.Unreliable, NetDeliveryMethod.Unreliable },
199  };
200 
201  public int Width;
202  public int Height;
203  public string WorldID;
204  public GameServerType ServerType;
205  public string MapID;
206 
207  string _logsBase;
208  StreamWriter _logWriter;
209  int _serverPoolIndex;
210  int _requestIndex;
211  NetPeer _netPeer;
212  WorldServerInfo _worldServer;
213  bool _isConnectedToWorldServer;
214  UJeli _mapSettings;
215  Dictionary<int, ClientInfo> _clientsByPeerIndex;
216  Dictionary<string, ClientInfo> _clientsById;
217  int _nextClientPeerIndex;
218  Dictionary<string, List<WorldDataReceivedHandler>> _worldDataRequests;
219  Dictionary<string, UJeli> _worldDataCache;
220 #if UNITY_EDITOR
221  List<string> _availableZoneMaps;
222  List<string> _availableMinigameMaps;
223 #endif
224 
225  void Awake ()
226  {
227  _instance = this;
228 
229  DontDestroyOnLoad(this);
230 
231  if (Application.platform == RuntimePlatform.OSXPlayer)
232  {
233  _logsBase = Path.GetFullPath(Application.dataPath + "/Logs/");
234  }
235  else
236  {
237  _logsBase = Path.GetFullPath("Logs/");
238  }
239 
240  var flags = CommandLineUtils.GetCommandLineFlags();
241 
242  if (flags.ContainsKey("zone"))
243  {
244  ServerType = GameServerType.Zone;
245  MapID = flags["zone"];
246  }
247  else if (flags.ContainsKey("minigame"))
248  {
249  ServerType = GameServerType.Minigame;
250  MapID = flags["minigame"];
251  }
252 
253  if (flags.ContainsKey("world"))
254  WorldID = flags["world"];
255 
256  _serverPoolIndex = -1;
257  _requestIndex = -1;
258 
259  if (flags.ContainsKey("pool"))
260  {
261  try { _serverPoolIndex = int.Parse(flags["pool"]); }
262  catch { }
263 
264  if (flags.ContainsKey("request"))
265  {
266  try { _requestIndex = int.Parse(flags["request"]); }
267  catch { }
268  }
269  }
270 
271  var netPeerConfig = new NetPeerConfiguration("Liberi");
272  netPeerConfig.AcceptIncomingConnections = true;
273  netPeerConfig.MaximumConnections = 32;
274  netPeerConfig.EnableMessageType(NetIncomingMessageType.ConnectionApproval);
275  netPeerConfig.EnableMessageType(NetIncomingMessageType.UnconnectedData);
276 
277  if (flags.ContainsKey("port"))
278  {
279  try { netPeerConfig.Port = int.Parse(flags["port"]); }
280  catch { netPeerConfig.Port = WorldNetUtils.GetFirstFreePort(34444); }
281  }
282  else netPeerConfig.Port = WorldNetUtils.GetFirstFreePort(34444);
283 
284  if (flags.ContainsKey("maxplayers"))
285  {
286  try { netPeerConfig.MaximumConnections = int.Parse(flags["maxplayers"]); }
287  catch { }
288  }
289 
290  _netPeer = new NetPeer(netPeerConfig);
291 
292  _clientsByPeerIndex = new Dictionary<int, ClientInfo>();
293  _clientsById = new Dictionary<string, ClientInfo>();
294 
295  _worldDataRequests = new Dictionary<string, List<WorldDataReceivedHandler>>();
296  _worldDataCache = new Dictionary<string,UJeli>();
297 
298  TimelineUtils.SetDefaultTimelineFunctions();
299  UTimelineUtils.SetDefautTimelineFunctions();
300 
301  Sync.PropagateSpawnDown += OnPropagateSpawnDown;
302  Sync.PropagateDespawnDown += OnPropagateDespawnDown;
303 
304  Sync.Initialize();
305 
306 #if UNITY_EDITOR
307 
308  _availableZoneMaps = new List<string>();
309  _availableMinigameMaps = new List<string>();
310 
311  foreach (var scene in EditorBuildSettings.scenes)
312  {
313  if (scene.enabled)
314  {
315  if (scene.path.Contains("/Zones/"))
316  _availableZoneMaps.Add(Path.GetFileNameWithoutExtension(scene.path));
317  else if (scene.path.Contains("/Minigames/"))
318  _availableMinigameMaps.Add(Path.GetFileNameWithoutExtension(scene.path));
319  }
320  }
321 #endif
322  }
323 
324  void OnGUI ()
325  {
326 #if UNITY_EDITOR
327  if (_isConnectedToWorldServer)
328  return;
329 
330  GUILayout.BeginArea(new Rect(5, 5, 200, Screen.height - 10), "Zones:", GUI.skin.box);
331  {
332  GUILayout.Space(20);
333 
334  foreach (var map in _availableZoneMaps)
335  {
336  if (GUILayout.Button(map))
337  {
338  ServerType = GameServerType.Zone;
339  MapID = map;
340  Initialize();
341  return;
342  }
343  }
344  }
345  GUILayout.EndArea();
346 
347  GUILayout.BeginArea(new Rect(210, 5, 200, Screen.height - 10), "Minigames:", GUI.skin.box);
348  {
349  GUILayout.Space(20);
350 
351  foreach (var map in _availableMinigameMaps)
352  {
353  if (GUILayout.Button(map))
354  {
355  ServerType = GameServerType.Minigame;
356  MapID = map;
357  Initialize();
358  return;
359  }
360  }
361  }
362  GUILayout.EndArea();
363 #endif
364  }
365 
366  void Start ()
367  {
368  Screen.SetResolution(Width, Height, false);
369  Screen.showCursor = true;
370 
371  Directory.CreateDirectory(_logsBase);
372 
373  _logWriter = File.CreateText(Path.GetFullPath(
374  string.Format("{0}LiberiServer_{1:yyyy-MM-dd_HH-mm-ss}.log", _logsBase, DateTime.Now)));
375  _logWriter.AutoFlush = true;
376 
377 #if !UNITY_EDITOR
378  Initialize();
379 #endif
380  }
381 
382  void Initialize ()
383  {
384  Log(string.Format("Server initializing... Type: {0}, Map: {1}, Pooled: {2}",
385  ServerType, MapID, IsPooled ? "Yes" : "No"));
386 
387  ConnectToWorldServer();
388  }
389 
394  public void Log (string line)
395  {
396  if (_logWriter != null)
397  _logWriter.WriteLine(string.Format("[{0:HH:mm:ss}] {1}", DateTime.Now, line));
398  }
399 
400  private ClientInfo GetClient (int peerIndex)
401  {
402  ClientInfo client = null;
403  _clientsByPeerIndex.TryGetValue(peerIndex, out client);
404  return client;
405  }
406 
407  private ClientInfo GetClient (string playerId)
408  {
409  ClientInfo client = null;
410  _clientsById.TryGetValue(playerId, out client);
411  return client;
412  }
413 
417  public int GetPlayerPeerIndex (string playerId)
418  {
419  var client = GetClient(playerId);
420 
421  if (client != null)
422  return client.PeerIndex;
423  else return -1;
424  }
425 
429  public string GetPlayerID (int peerIndex)
430  {
431  var client = GetClient(peerIndex);
432 
433  if (client != null)
434  return client.PlayerID;
435  else return null;
436  }
437 
441  public PlayerProfile GetPlayerProfile (string playerId)
442  {
443  var client = GetClient(playerId);
444 
445  if (client != null)
446  return client.PlayerProfile;
447  else return null;
448  }
449 
453  public PlayerProfile GetPlayerProfile (int peerIndex)
454  {
455  var client = GetClient(peerIndex);
456 
457  if (client != null)
458  return client.PlayerProfile;
459  else return null;
460  }
461 
467  public void RequestServerSpawn (GameServerType serverType, string mapId)
468  {
469  var requestMsg = _netPeer.CreateMessage();
470  requestMsg.Write((byte)WorldServerMessageType.SpawnServer);
471  requestMsg.Write(serverType.ToString().ToLower());
472  requestMsg.Write(mapId);
473 
474  _worldServer.Connection.SendMessage(requestMsg, NetDeliveryMethod.ReliableOrdered, 0);
475  }
476 
477  private void ConnectToWorldServer ()
478  {
479  Log("Starting network...");
480 
481  try { _netPeer.Start(); }
482  catch { }
483 
484  if (_netPeer.Status != NetPeerStatus.Running)
485  {
486  Log("Failed to start network.");
487  Application.Quit();
488  return;
489  }
490 
491  Log("Network started. Port: " + _netPeer.Configuration.Port);
492 
493  IPEndPoint worldEndPoint = null;
494 
495  if (string.IsNullOrEmpty(WorldID))
496  WorldID = "localhost";
497 
498  IPAddress ipAddress = null;
499  IPAddress.TryParse(WorldID, out ipAddress);
500 
501  Log("Searching for world server: " + WorldID);
502 
503  if (WorldID == "localhost")
504  worldEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 34443);
505  else if (ipAddress != null)
506  worldEndPoint = new IPEndPoint(ipAddress, 34443);
507  else worldEndPoint = WorldNetUtils.GetWorldEndPoint(WorldID);
508 
509  if (worldEndPoint == null)
510  {
511  Log("Failed to find world server.");
512  Application.Quit();
513  return;
514  }
515 
516  Log("World server found.");
517 
518  var localHail = _netPeer.CreateMessage();
519  localHail.Write((byte)WorldServerMessageType.RegisterServer);
520  localHail.Write(ServerType.ToString().ToLower());
521  localHail.Write(MapID);
522  localHail.Write(_netPeer.Configuration.MaximumConnections);
523  localHail.Write(_serverPoolIndex);
524 
525  Log("Connecting to world server at: " + worldEndPoint);
526 
527  _worldServer = new WorldServerInfo();
528  _worldServer.Connection = _netPeer.Connect(worldEndPoint, localHail);
529  _worldServer.Connection.Tag = _worldServer;
530  }
531 
532  private void NotifyWorldServerReady ()
533  {
534  var readyMsg = _netPeer.CreateMessage();
535  readyMsg.Write((byte)WorldServerMessageType.ServerReady);
536  readyMsg.Write(_serverPoolIndex);
537 
538  if (IsPooled)
539  readyMsg.Write(_requestIndex);
540 
541  _worldServer.Connection.SendMessage(readyMsg, NetDeliveryMethod.ReliableOrdered, 0);
542  }
543 
544  private IEnumerator LoadAndSyncMapAsync ()
545  {
546  Log("Pulling map settings...");
547 
548  PullWorldData(MapID + "_settings", OnMapSettingsReceived);
549 
550  float settingsWaitTime = 0f;
551 
552  while (_mapSettings == null)
553  {
554  settingsWaitTime += Time.deltaTime;
555 
556  if (settingsWaitTime >= 10f)
557  {
558  Abort("Failed to load map settings.");
559  yield break;
560  }
561 
562  yield return new WaitForSeconds(.1f);
563  }
564 
565  Log("Map settings received.");
566  Log("Loading map: " + MapID);
567 
568  yield return Application.LoadLevelAsync(MapID);
569 
570  Log("Map loaded.");
571  Log("Syncing...");
572 
573  Sync.SyncServer();
574 
575  Log("Synced.");
576 
578 
579  _nextClientPeerIndex = 1;
580 
581  NotifyWorldServerReady();
582 
583  yield break;
584  }
585 
592  public void PullWorldData (string pageName, WorldDataReceivedHandler callback, bool useCache = false)
593  {
594  if (useCache)
595  {
596  var cachedDataPage = GetCachedWorldData(pageName);
597 
598  if (cachedDataPage != null && callback != null)
599  {
600  callback(cachedDataPage);
601  return;
602  }
603  }
604 
605  Log("Pulling world data page: " + pageName);
606 
607  List<WorldDataReceivedHandler> callbacksByPage = null;
608 
609  if (!_worldDataRequests.TryGetValue(pageName, out callbacksByPage))
610  {
611  callbacksByPage = new List<WorldDataReceivedHandler>();
612  _worldDataRequests.Add(pageName, callbacksByPage);
613  }
614 
615  callbacksByPage.Add(callback);
616 
617  var worldDataPullMsg = _netPeer.CreateMessage();
618  worldDataPullMsg.Write((byte)WorldServerMessageType.PullWorldData);
619  worldDataPullMsg.Write(pageName);
620 
621  _worldServer.Connection.SendMessage(worldDataPullMsg, NetDeliveryMethod.ReliableOrdered, 0);
622  }
623 
628  public UJeli GetCachedWorldData (string pageName)
629  {
630  UJeli dataPage = null;
631  _worldDataCache.TryGetValue(pageName, out dataPage);
632  return dataPage;
633  }
634 
640  public void PushWorldData (string pageName, UJeli pageContents)
641  {
642  Log("Pushing world data page: " + pageName);
643 
644  _worldDataCache[pageName] = pageContents;
645 
646  var pushMsg = _netPeer.CreateMessage();
647  pushMsg.Write((byte)WorldServerMessageType.PushWorldData);
648  pushMsg.Write(pageName);
649  pushMsg.Write(pageContents.ToString());
650 
651  _worldServer.Connection.SendMessage(pushMsg, NetDeliveryMethod.ReliableOrdered, 0);
652  }
653 
661  public void PushPlayerData (string playerid, string tablePath, string propertyName, string propertyValue)
662  {
664  new PlayerDataChange() {
665  PlayerID = playerid,
666  TablePath = tablePath,
667  PropertyName = propertyName,
668  PropertyValue = propertyValue
669  });
670  }
671 
676  public void PushPlayerData (params PlayerDataChange[] changes)
677  {
678  var pushMsg = _netPeer.CreateMessage();
679  pushMsg.Write((byte)WorldServerMessageType.PushPlayerData);
680  pushMsg.Write(changes.Length);
681 
682  foreach (var change in changes)
683  {
684  pushMsg.Write(change.PlayerID);
685  pushMsg.Write(change.TablePath);
686  pushMsg.Write(change.PropertyName);
687  pushMsg.Write(change.PropertyValue);
688  }
689 
690  _worldServer.Connection.SendMessage(pushMsg, NetDeliveryMethod.ReliableOrdered, 0);
691  }
692 
693  internal void SendPlayerProfileOperation (PlayerProfile profile, GameMessage op, bool sendToAllClients = false)
694  {
695  ClientInfo client = null;
696 
697  if (!_clientsById.TryGetValue(profile.PlayerID, out client))
698  return;
699 
700  var opMsg = _netPeer.CreateMessage();
701  opMsg.Write((byte)ClientMessageType.Custom);
702  opMsg.Write((byte)CustomClientMessageType.PlayerProfileOperation);
703  opMsg.Write(client.PeerIndex);
704  opMsg.Write(op.Data);
705 
706  if (sendToAllClients)
707  {
708  var recipients = _clientsByPeerIndex.Values.Select(c => c.Connection).ToList();
709 
710  if (recipients.Count > 0)
711  _netPeer.SendMessage(opMsg, recipients, NetDeliveryMethod.ReliableOrdered, 0);
712  }
713  else client.Connection.SendMessage(opMsg, NetDeliveryMethod.ReliableOrdered, 0);
714  }
715 
716  private void AddClient (ClientInfo client)
717  {
718  _clientsByPeerIndex[client.PeerIndex] = client;
719  _clientsById[client.PlayerID] = client;
720 
721  Log(string.Format("Client added. Index: {0}, ID: {1}", client.PeerIndex, client.PlayerID));
722  }
723 
724  private void RemoveClient (ClientInfo client)
725  {
726  _clientsByPeerIndex.Remove(client.PeerIndex);
727  _clientsById.Remove(client.PlayerID);
728 
729  Log("Client " + client.PeerIndex + " removed.");
730  }
731 
732  void Update ()
733  {
734  UpdateNetwork();
735 
736 #if UNITY_EDITOR
737  if (Input.GetKey(KeyCode.LeftControl) && Input.GetKeyDown(KeyCode.Q))
738  {
740  UnityEditor.EditorApplication.isPlaying = false;
741  }
742 #endif
743  }
744 
745  private void UpdateNetwork ()
746  {
747  if (_netPeer.Status != NetPeerStatus.Running)
748  return;
749 
750  if (Sync.IsMapSynced)
751  {
752  Sync.Step();
753 
754  foreach (var client in _clientsByPeerIndex.Values)
755  {
756  foreach (var timelineMsg in Sync.TimelineSynchronizer.GetOutgoingMessages((ushort)client.PeerIndex))
757  {
758  var timelineNetMsg = _netPeer.CreateMessage();
759  timelineNetMsg.Write((byte)timelineMsg.MessageType);
760  timelineNetMsg.Write(timelineMsg.Data);
761 
762  client.Connection.SendMessage(
763  timelineNetMsg,
764  _timelineToNetDeliveryModes[timelineMsg.DeliveryMode],
765  0);
766  }
767  }
768  }
769 
770  var messages = new List<NetIncomingMessage>();
771  _netPeer.ReadMessages(messages);
772 
773  foreach (var msg in messages)
774  {
775 #if !UNITY_EDITOR
776  try
777  {
778 #endif
779  ProcessNetMessage(msg);
780 #if !UNITY_EDITOR
781  }
782  catch (Exception e)
783  {
784  Log(string.Format("Network message exception. Message Type: {0}, Exception Type: {1}",
785  msg.MessageType, e.GetType()));
786  }
787 #endif
788  }
789 
790  _netPeer.Recycle(messages);
791  }
792 
793  private void ProcessNetMessage (NetIncomingMessage msg)
794  {
795  if (msg.MessageType == NetIncomingMessageType.ConnectionApproval)
796  {
797  Log("Client connecting from: " + msg.SenderConnection.RemoteEndPoint);
798 
799  ClientInfo client = new ClientInfo()
800  {
801  Connection = msg.SenderConnection,
802  PlayerID = msg.ReadString(),
803  PeerIndex = _nextClientPeerIndex
804  };
805 
806  if (_clientsById.ContainsKey(client.PlayerID))
807  {
808  Log("Duplicate client kicked. ID: " + client.PlayerID);
809  msg.SenderConnection.Deny("error.duplicate_player");
810  return;
811  }
812 
813  AddClient(client);
814 
815  var localHail = _netPeer.CreateMessage();
816  localHail.Write(client.PeerIndex);
817 
818  msg.SenderConnection.Tag = client;
819  msg.SenderConnection.Approve(localHail);
820 
821  var worldNotification = _netPeer.CreateMessage();
822  worldNotification.Write((byte)WorldServerMessageType.PlayerJoinedServer);
823  worldNotification.Write(client.PlayerID);
824 
825  _worldServer.Connection.SendMessage(worldNotification, NetDeliveryMethod.ReliableOrdered, 0);
826 
827  _nextClientPeerIndex++;
828  }
829  else if (msg.MessageType == NetIncomingMessageType.StatusChanged)
830  {
831  var newStatus = (NetConnectionStatus)msg.ReadByte();
832 
833  if (newStatus == NetConnectionStatus.Connected)
834  {
835  var remoteHail = msg.SenderConnection.RemoteHailMessage;
836 
837  var serverMessageType = (ServerMessageType)remoteHail.ReadByte();
838 
839  if (serverMessageType == ServerMessageType.ServerInitialization)
840  {
841  if (msg.SenderConnection.Tag == _worldServer && _worldServer != null)
842  {
843  _isConnectedToWorldServer = true;
844 
845  if (ConnectedToWorldServer != null)
847 
848  Log("Connected to world server.");
849 
850  StartCoroutine(LoadAndSyncMapAsync());
851  }
852  }
853  }
854  else if (newStatus == NetConnectionStatus.Disconnected)
855  {
856  if (msg.SenderConnection.Tag == _worldServer && _worldServer != null)
857  {
858  if (_isConnectedToWorldServer)
859  Abort("Disconnected from world server.");
860  else Abort("Failed to connect to world server.");
861  }
862  else if (msg.SenderConnection.Tag is ClientInfo)
863  {
864  var client = (ClientInfo)msg.SenderConnection.Tag;
865 
866  Sync.UnregisterClient(client.PeerIndex);
867 
868  RemoveClient(client);
869 
870  var worldNotification = _netPeer.CreateMessage();
871  worldNotification.Write((byte)WorldServerMessageType.PlayerLeftServer);
872  worldNotification.Write(client.PlayerID);
873 
874  _worldServer.Connection.SendMessage(worldNotification, NetDeliveryMethod.ReliableOrdered, 0);
875 
876  if (PlayerLeftServer != null)
877  PlayerLeftServer(client.PeerIndex, client.PlayerID, client.PlayerProfile);
878 
879  var otherClientConnections = new List<NetConnection>();
880 
881  foreach (var otherClient in _clientsByPeerIndex.Values)
882  {
883  if (otherClient != client)
884  otherClientConnections.Add(otherClient.Connection);
885  }
886 
887  if (otherClientConnections.Count > 0)
888  {
889  var clientNotification = _netPeer.CreateMessage();
890  clientNotification.Write((byte)ClientMessageType.Custom);
891  clientNotification.Write((byte)CustomClientMessageType.PlayerLeftServer);
892  clientNotification.Write(client.PeerIndex);
893 
894  _netPeer.SendMessage(clientNotification, otherClientConnections, NetDeliveryMethod.ReliableOrdered, 0);
895  }
896 
897  if (ServerType == GameServerType.Minigame && _clientsById.Count == 0)
898  {
899 #if UNITY_EDITOR
900  UnityEditor.EditorApplication.isPlaying = false;
901 #else
902  Application.Quit();
903 #endif
904  }
905  }
906  }
907  }
908  else if (msg.MessageType == NetIncomingMessageType.Data)
909  {
910  byte messageTypeByte = msg.ReadByte();
911 
912  if (messageTypeByte >= (byte)TimelineMessageType.SyncerMessageBase)
913  {
914  var timelineMessageType = (TimelineMessageType)messageTypeByte;
915 
916  int peerIndex = ((ClientInfo)msg.SenderConnection.Tag).PeerIndex;
917 
918  var timelineMsg = new TimelineMessage(
919  timelineMessageType,
920  msg.ReadBytes(msg.LengthBytes - msg.PositionInBytes),
921  _netToTimelineDeliveryModes[msg.DeliveryMethod]);
922 
923  Sync.TimelineSynchronizer.ProcessIncomingMessage((ushort)peerIndex, timelineMsg);
924  }
925  else
926  {
927  var serverMessageType = (ServerMessageType)messageTypeByte;
928 
929  if (serverMessageType == ServerMessageType.PlayerData)
930  {
931  string playerId = msg.ReadString();
932  var playerProfile = new PlayerProfile(playerId, UJeli.Parse(msg.ReadString()));
933  var client = _clientsById[playerId];
934  client.PlayerProfile = playerProfile;
935 
936  Log(string.Format("Received player profile. ID: {0}, Nickname: {1}",
937  playerId, playerProfile.Basic.Nickname));
938 
939  if (PlayerJoinedServer != null)
940  PlayerJoinedServer(client.PeerIndex, client.PlayerID, playerProfile);
941 
942  var otherClientConnections = new List<NetConnection>();
943 
944  foreach (var otherClient in _clientsByPeerIndex.Values)
945  {
946  if (otherClient != client)
947  otherClientConnections.Add(otherClient.Connection);
948  }
949 
950  if (otherClientConnections.Count > 0)
951  {
952  var clientNotification = _netPeer.CreateMessage();
953  clientNotification.Write((byte)ClientMessageType.Custom);
954  clientNotification.Write((byte)CustomClientMessageType.PlayerJoinedServer);
955  clientNotification.Write(client.PeerIndex);
956  clientNotification.Write(client.PlayerID);
957 
958  clientNotification.Write(playerProfile.ToString());
959 
960  _netPeer.SendMessage(clientNotification, otherClientConnections, NetDeliveryMethod.ReliableOrdered, 0);
961  }
962  }
963  else if (serverMessageType == ServerMessageType.PlayerJoinedWorldServer)
964  {
965  // TODO: Handle player joined world server event.
966  }
967  else if (serverMessageType == ServerMessageType.PlayerLeftWorldServer)
968  {
969  // TODO: Handle player left world server event.
970  }
971  else if (serverMessageType == ServerMessageType.ServerSpawned)
972  {
973  GameServerType serverType = (GameServerType)Enum.Parse(typeof(GameServerType), msg.ReadString(), true);
974  string mapId = msg.ReadString();
975  IPEndPoint endPoint = msg.ReadIPEndPoint();
976 
977  if (ServerSpawned != null)
978  ServerSpawned(serverType, mapId, endPoint);
979  }
980  else if (serverMessageType == ServerMessageType.ServerSpawnFailed)
981  {
982  GameServerType serverType = (GameServerType)msg.ReadByte();
983  string mapId = msg.ReadString();
984 
985  if (ServerSpawnFailed != null)
986  ServerSpawnFailed(serverType, mapId);
987  }
988  else if (serverMessageType == ServerMessageType.WorldData)
989  {
990  string pageName = msg.ReadString();
991  var worldDataPage = UJeli.Parse(msg.ReadString());
992  worldDataPage.Name = pageName;
993 
994  Log("Received world data page: " + pageName);
995 
996  _worldDataCache[pageName] = worldDataPage;
997 
998  List<WorldDataReceivedHandler> callbacksByPage = null;
999 
1000  if (_worldDataRequests.TryGetValue(pageName, out callbacksByPage))
1001  {
1002  while (callbacksByPage.Count > 0)
1003  {
1004  callbacksByPage[0](worldDataPage);
1005  callbacksByPage.RemoveAt(0);
1006  }
1007  }
1008  }
1009  else if (serverMessageType == ServerMessageType.Custom)
1010  {
1011  var customServerMessageType = (CustomServerMessageType)msg.ReadByte();
1012 
1013  if (customServerMessageType == CustomServerMessageType.SyncWithClient)
1014  {
1015  var client = (ClientInfo)msg.SenderConnection.Tag;
1016 
1017  var syncJob = new ClientSyncJob()
1018  {
1019  Client = client
1020  };
1021 
1022  client.SyncJob = syncJob;
1023 
1024  Sync.RegisterClient(client.PeerIndex, msg.SenderConnection.AverageRoundtripTime);
1025 
1026  StartCoroutine(SyncWithClientAsync(syncJob));
1027  }
1028  else if (customServerMessageType == CustomServerMessageType.PlayerProfileOperation)
1029  {
1030  int peerIndex = msg.ReadInt32();
1031 
1032  var profile = GetPlayerProfile(peerIndex);
1033 
1034  if (profile == null)
1035  return;
1036 
1037  var profileOperation = new GameMessage();
1038  profileOperation.Write(msg.ReadBytes(msg.LengthBytes - msg.PositionInBytes));
1039 
1040  profile.ProcessIncomingOperation(profileOperation);
1041  }
1042  else if (customServerMessageType == CustomServerMessageType.SpawnObject)
1043  {
1044  int requesterIndex = ((ClientInfo)msg.SenderConnection.Tag).PeerIndex;
1045  int requestIndex = msg.ReadInt32();
1046  var spawnInfo = new SpawnInfo();
1047  spawnInfo.ReadFrom(msg);
1048 
1049  Sync.ProcessPropagatedUpSpawn(requesterIndex, requestIndex, spawnInfo);
1050  }
1051  else if (customServerMessageType == CustomServerMessageType.DespawnObject)
1052  {
1053  int requesterIndex = ((ClientInfo)msg.SenderConnection.Tag).PeerIndex;
1054  int objectIndex = msg.ReadInt32();
1055 
1056  Sync.ProcessPropagatedUpDespawn(requesterIndex, objectIndex);
1057  }
1058  else if (customServerMessageType == CustomServerMessageType.ClientFinishedSyncing)
1059  {
1060  var client = (ClientInfo)msg.SenderConnection.Tag;
1061 
1062  Log("Client " + client.PeerIndex + " synced.");
1063 
1064  if (PlayerSynced != null)
1065  PlayerSynced(client.PeerIndex, client.PlayerID, client.PlayerProfile);
1066  }
1067  }
1068  }
1069  }
1070  else if (msg.MessageType == NetIncomingMessageType.UnconnectedData)
1071  {
1072  var serverMessageType = (ServerMessageType)msg.ReadByte();
1073 
1074  if (serverMessageType == ServerMessageType.Custom)
1075  {
1076  var customServerMessageType = (CustomServerMessageType)msg.ReadByte();
1077 
1078  if (customServerMessageType == CustomServerMessageType.SummaryRequest)
1079  {
1080  int requestIndex = msg.ReadInt32();
1081 
1082  var summaryMsg = _netPeer.CreateMessage();
1083 
1084  summaryMsg.Write((byte)ClientMessageType.Custom);
1085  summaryMsg.Write((byte)CustomClientMessageType.ServerSummary);
1086  summaryMsg.Write(requestIndex);
1087  summaryMsg.Write((byte)ServerType);
1088  summaryMsg.Write(MapID);
1089 
1090  if (ServerType == GameServerType.Minigame)
1091  summaryMsg.Write(Minigame.Instance.MaxPlayers);
1092  else summaryMsg.Write(_netPeer.Configuration.MaximumConnections);
1093 
1094  var playerClients = _clientsById.Values.Where(
1095  c => c != null &&
1096  c.PlayerProfile != null &&
1097  !c.PlayerProfile.Basic.IsMonitor)
1098  .ToArray();
1099 
1100  summaryMsg.Write(playerClients.Length);
1101 
1102  foreach (var client in playerClients)
1103  {
1104  summaryMsg.Write(client.PlayerID);
1105  summaryMsg.Write(client.PlayerProfile.Basic.ToString());
1106  }
1107 
1108  _netPeer.SendUnconnectedMessage(summaryMsg, msg.SenderEndPoint);
1109  }
1110  }
1111  }
1112  }
1113 
1114  private IEnumerator SyncWithClientAsync (ClientSyncJob syncJob)
1115  {
1116  ClientInfo client = syncJob.Client;
1117 
1118  Log("Syncing client " + client.PeerIndex + "...");
1119 
1120  if (_clientsByPeerIndex.Count > 1)
1121  {
1122  var existingPlayersMsg = _netPeer.CreateMessage();
1123  existingPlayersMsg.Write((byte)ClientMessageType.Custom);
1124  existingPlayersMsg.Write((byte)CustomClientMessageType.ExistingPlayers);
1125  existingPlayersMsg.Write(_clientsByPeerIndex.Count - 1);
1126 
1127  foreach (var otherClient in _clientsByPeerIndex.Values)
1128  {
1129  if (otherClient == client)
1130  continue;
1131 
1132  existingPlayersMsg.Write(otherClient.PeerIndex);
1133  existingPlayersMsg.Write(otherClient.PlayerID);
1134  existingPlayersMsg.Write(otherClient.PlayerProfile.ToString());
1135  }
1136 
1137  client.Connection.SendMessage(existingPlayersMsg, NetDeliveryMethod.ReliableOrdered, 0);
1138  }
1139 
1140  yield return null;
1141 
1142  syncJob.ExistingObjectIndices = new List<int>(Sync.ObjectIndices);
1143 
1144  while (syncJob.ExistingObjectIndices.Count > 0)
1145  {
1146  if (client.Connection.Status != NetConnectionStatus.Connected)
1147  yield break;
1148 
1149  int objectIndex = syncJob.ExistingObjectIndices[0];
1150  syncJob.ExistingObjectIndices.RemoveAt(0);
1151 
1152  GameObject existingObject = Sync.GetObject(objectIndex);
1153 
1154  if (existingObject == null)
1155  continue;
1156 
1157  var spawnInfo = Sync.GetSpawnInfo(existingObject);
1158 
1159  var spawnMsg = _netPeer.CreateMessage();
1160  spawnMsg.Write((byte)ClientMessageType.Custom);
1161  spawnMsg.Write((byte)CustomClientMessageType.SpawnExistingObject);
1162  spawnMsg.Write(spawnInfo != null);
1163 
1164  if (spawnInfo != null)
1165  spawnInfo.WriteTo(spawnMsg);
1166 
1167  spawnMsg.Write(objectIndex);
1168 
1169  client.Connection.SendMessage(spawnMsg, NetDeliveryMethod.ReliableOrdered, 0);
1170 
1171  yield return null;
1172  }
1173 
1174  client.SyncJob = null;
1175 
1176  if (client.Connection.Status == NetConnectionStatus.Connected)
1177  {
1178  var syncFinishedMsg = _netPeer.CreateMessage();
1179  syncFinishedMsg.Write((byte)ClientMessageType.Custom);
1180  syncFinishedMsg.Write((byte)CustomClientMessageType.SyncedToServer);
1181 
1182  client.Connection.SendMessage(syncFinishedMsg, NetDeliveryMethod.ReliableOrdered, 0);
1183  }
1184 
1185  yield break;
1186  }
1187 
1192  public void Abort (string error)
1193  {
1194  Log(error);
1195 
1196 #if UNITY_EDITOR
1197  UnityEditor.EditorApplication.isPlaying = false;
1198 #else
1199  Application.Quit();
1200 #endif
1201  }
1202 
1203  private void OnMapSettingsReceived (UJeli mapSettings)
1204  {
1205  _mapSettings = mapSettings;
1206 
1207  if (_mapSettings.HasChild("SpawnDetailsTemplates"))
1208  {
1209  var spawnDetailsTemplatesJeli = _mapSettings["SpawnDetailsTemplates"];
1210 
1211  foreach (var templateJeli in spawnDetailsTemplatesJeli.Children)
1212  {
1213  Sync.SetSpawnDetailsTemplate(templateJeli.Name, templateJeli);
1214  }
1215  }
1216  }
1217 
1218  void OnPropagateSpawnDown (int requesterIndex, int requestIndex, int objectIndex, SpawnInfo spawnInfo)
1219  {
1220  var responseMsg = _netPeer.CreateMessage();
1221  responseMsg.Write((byte)ClientMessageType.Custom);
1222  responseMsg.Write((byte)CustomClientMessageType.ObjectSpawnRequestResponse);
1223  responseMsg.Write(requestIndex);
1224  responseMsg.Write(objectIndex);
1225 
1226  var spawnMsg = _netPeer.CreateMessage();
1227  spawnMsg.Write((byte)ClientMessageType.Custom);
1228  spawnMsg.Write((byte)CustomClientMessageType.SpawnObject);
1229  spawnInfo.WriteTo(spawnMsg);
1230  spawnMsg.Write(objectIndex);
1231 
1232  NetConnection requesterConnection = null;
1233 
1234  if (requesterIndex > 0)
1235  requesterConnection = _clientsByPeerIndex[requesterIndex].Connection;
1236 
1237  List<NetConnection> otherConnections = new List<NetConnection>();
1238 
1239  foreach (var client in _clientsByPeerIndex.Values)
1240  {
1241  if (client.Connection != requesterConnection)
1242  otherConnections.Add(client.Connection);
1243  }
1244 
1245  if (requesterConnection != null)
1246  _netPeer.SendMessage(responseMsg, requesterConnection, NetDeliveryMethod.ReliableOrdered, 0);
1247 
1248  if (otherConnections.Count > 0)
1249  _netPeer.SendMessage(spawnMsg, otherConnections, NetDeliveryMethod.ReliableOrdered, 0);
1250  }
1251 
1252  private void OnPropagateDespawnDown (int requesterIndex, int objectIndex)
1253  {
1254  StartCoroutine(PropagateDespawnDownAsync(requesterIndex, objectIndex));
1255  }
1256 
1257  private IEnumerator PropagateDespawnDownAsync (int requesterIndex, int objectIndex)
1258  {
1259  yield return null;
1260  yield return null;
1261 
1262  var despawnMsg = _netPeer.CreateMessage();
1263  despawnMsg.Write((byte)ClientMessageType.Custom);
1264  despawnMsg.Write((byte)CustomClientMessageType.DespawnObject);
1265  despawnMsg.Write(objectIndex);
1266 
1267  NetConnection requesterConnection = null;
1268 
1269  if (requesterIndex > 0)
1270  requesterConnection = _clientsByPeerIndex[requesterIndex].Connection;
1271 
1272  List<NetConnection> otherConnections = new List<NetConnection>();
1273 
1274  foreach (var client in _clientsByPeerIndex.Values)
1275  {
1276  if (client.Connection != requesterConnection)
1277  otherConnections.Add(client.Connection);
1278  }
1279 
1280  if (otherConnections.Count > 0)
1281  _netPeer.SendMessage(despawnMsg, otherConnections, NetDeliveryMethod.ReliableOrdered, 0);
1282  }
1283 
1288  {
1289  _isConnectedToWorldServer = false;
1290 
1291  if (_netPeer.Status != NetPeerStatus.NotRunning && _netPeer.Status != NetPeerStatus.ShutdownRequested)
1292  {
1293  Log("Stopping network...");
1294 
1295  _netPeer.Shutdown("");
1296 
1297  Log("Network stopped.");
1298  }
1299  }
1300 
1301  void OnDestroy ()
1302  {
1304 
1305  Log("Server shutting down...");
1306 
1307  _logWriter.Dispose();
1308 
1309  _instance = null;
1310  }
1311 }
static int[] ObjectIndices
Gets an array containing the object indices of every synchronized object.
Definition: Sync.Static.cs:78
UJeli GetCachedWorldData(string pageName)
Returns a cached copy of a previously fetched world data page.
Definition: GameServer.cs:628
static GameServer Instance
Gets the sole instance of this game server.
Definition: GameServer.cs:95
void PullWorldData(string pageName, WorldDataReceivedHandler callback, bool useCache=false)
Requests a specific page of world data from the world server.
Definition: GameServer.cs:592
PlayerLeftWorldServerHandler PlayerLeftWorldServer
Event fired when a player leaves the world server.
Definition: GameServer.cs:76
void PushPlayerData(params PlayerDataChange[] changes)
Pushes an arbitrary number of changes to a player's persistent data.
Definition: GameServer.cs:676
static Minigame Instance
Gets the sole instance of this minigame.
Definition: Minigame.cs:30
Main game server class.
Definition: GameServer.cs:19
Describes a single change to a player's persistent data.
ServerSpawnedHandler ServerSpawned
Event fired when a requested server spawn is successful.
Definition: GameServer.cs:80
void Log(string line)
Write one line to the server log. Log entries are automatically timestamped.
Definition: GameServer.cs:394
bool IsConnectedToWorldServer
Whether or not this server is connected to the world server.
Definition: GameServer.cs:179
static SpawnInfo GetSpawnInfo(GameObject go)
Gets the spawn-time information of a given object.
Definition: Sync.Static.cs:215
void RequestServerSpawn(GameServerType serverType, string mapId)
Sends a server spawn request to the world server.
Definition: GameServer.cs:467
ServerSpawnFailedHandler ServerSpawnFailed
Event fired when a requested server spawn fails.
Definition: GameServer.cs:84
int ServerPoolIndex
Gets the index of the server pool which created this server. -1 if this server is not pooled...
Definition: GameServer.cs:103
static bool IsMapSynced
Gets whether or not the map on this peer is synced to the canonical version on the network...
Definition: Sync.Static.cs:70
string GetPlayerID(int peerIndex)
Get the ID of the player with the given peer index.
Definition: GameServer.cs:429
ServerSyncFinishedHandler ServerSyncFinished
Event fired when server finsihes syncing (all map objects have had their OnSpawn called).
Definition: GameServer.cs:88
PlayerProfile GetPlayerProfile(int peerIndex)
Get the profile of the player with the given peer index.
Definition: GameServer.cs:453
PlayerJoinedWorldServerHandler PlayerJoinedWorldServer
Event fired when a player joins the world server.
Definition: GameServer.cs:72
void PushWorldData(string pageName, UJeli pageContents)
Pushes a world data page to the world server.
Definition: GameServer.cs:640
string[] PlayerNicknames
Gets an array containing the nicknames of all connected players.
Definition: GameServer.cs:165
void PushPlayerData(string playerid, string tablePath, string propertyName, string propertyValue)
Pushes a single change to a player's persistent data.
Definition: GameServer.cs:661
static TimelineSynchronizer TimelineSynchronizer
Gets the Janus timeline synchronizer if this is the server.
Definition: Sync.Static.cs:29
void DisconnectFromWorldServer()
Disconnect from the world server.
Definition: GameServer.cs:1287
int[] PlayerPeerIndices
Gets an array containing the peer indices of all connected players.
Definition: GameServer.cs:143
PlayerJoinedServerHandler PlayerJoinedServer
Event fired when a player joins this server.
Definition: GameServer.cs:60
int MaxPlayers
Gets the maximum player capacity of this minigame.
Definition: Minigame.cs:159
PlayerProfile[] PlayerProfiles
Gets an array containing the profiles of all connected players.
Definition: GameServer.cs:151
bool IsPooled
Gets whether or not this server was created by a server pool.
Definition: GameServer.cs:111
UJeli MapSettings
Settings for this map, pulled from the world server.
Definition: GameServer.cs:119
PlayerProfile GetPlayerProfile(string playerId)
Get the profile of the player with the given ID.
Definition: GameServer.cs:441
PlayerSyncedHandler PlayerSynced
Event fired when a player is fully synchronized with this server.
Definition: GameServer.cs:64
Stores data related to the player profile. Loads from a World Data file and provides runtime operatio...
A class for serializing game data into a stream for propagation between objects and peers...
Definition: GameMessage.cs:14
Class for general minigame management.
Definition: Minigame.cs:12
static GameObject GetObject(int objectIndex)
Get an object by its object index.
Definition: Sync.Static.cs:143
PlayerLeftServerHandler PlayerLeftServer
Event fired when a player leaves this server.
Definition: GameServer.cs:68
Unity version of Jeli markup class.
Definition: UJeli.cs:10
int GetPlayerPeerIndex(string playerId)
Get the peer index of the player with the given ID.
Definition: GameServer.cs:417
int NumPlayers
Gets the number of connected players.
Definition: GameServer.cs:127
ConnectedToWorldServerHandler ConnectedToWorldServer
Event fired when this server connects to the world server.
Definition: GameServer.cs:56
This class server two main functions: 1) As a MonoBehaviour, it allows for network synchronization of...
Definition: Sync.cs:13
The spawn-time information of a spawned object.
Definition: SpawnInfo.cs:13
string[] PlayerIDs
Gets an array containing the IDs of all connected players.
Definition: GameServer.cs:135
void Abort(string error)
Abort the server process with a given error.
Definition: GameServer.cs:1192