#region Copyright & License Information /* * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. For more * information, see COPYING. */ #endregion using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using OpenRA.Primitives; namespace OpenRA.Network { public class Session { public List Clients = new List(); public List ClientPings = new List(); // Keyed by the PlayerReference id that the slot corresponds to public Dictionary Slots = new Dictionary(); public Global GlobalSettings = new Global(); public static string AnonymizeIP(IPAddress ip) { if (ip.AddressFamily == AddressFamily.InterNetwork) { // Follow convention used by Google Analytics: remove last octet var b = ip.GetAddressBytes(); return "{0}.{1}.{2}.*".F(b[0], b[1], b[2]); } return null; } public static Session Deserialize(string data) { try { var session = new Session(); var nodes = MiniYaml.FromString(data); foreach (var node in nodes) { var strings = node.Key.Split('@'); switch (strings[0]) { case "Client": session.Clients.Add(Client.Deserialize(node.Value)); break; case "ClientPing": session.ClientPings.Add(ClientPing.Deserialize(node.Value)); break; case "GlobalSettings": session.GlobalSettings = Global.Deserialize(node.Value); break; case "Slot": var s = Slot.Deserialize(node.Value); session.Slots.Add(s.PlayerReference, s); break; } } return session; } catch (YamlException) { throw new YamlException("Session deserialized invalid MiniYaml:\n{0}".F(data)); } catch (InvalidOperationException) { throw new YamlException("Session deserialized invalid MiniYaml:\n{0}".F(data)); } } public Client ClientWithIndex(int clientID) { return Clients.SingleOrDefault(c => c.Index == clientID); } public Client ClientInSlot(string slot) { return Clients.SingleOrDefault(c => c.Slot == slot); } public string FirstEmptySlot() { return Slots.FirstOrDefault(s => !s.Value.Closed && ClientInSlot(s.Key) == null).Key; } public string FirstEmptyBotSlot() { return Slots.FirstOrDefault(s => !s.Value.Closed && ClientInSlot(s.Key) == null && s.Value.AllowBots).Key; } public IEnumerable NonBotClients { get { return Clients.Where(c => c.Bot == null); } } public IEnumerable NonBotPlayers { get { return Clients.Where(c => c.Bot == null && c.Slot != null); } } public enum ClientState { NotReady, Invalid, Ready, Disconnected = 1000 } public class Client { public static Client Deserialize(MiniYaml data) { return FieldLoader.Load(data); } public int Index; public Color PreferredColor; // Color that the client normally uses from settings.yaml. public Color Color; // Actual color that the client is using. Usually the same as PreferredColor but can be different on maps with locked colors. public string Faction; public int SpawnPoint; public string Name; // The full IP address is required for the IP banning moderation feature // but we must not share the un-anonymized address with other players. [FieldLoader.Ignore] public string IPAddress; public string AnonymizedIPAddress; public string Location; public ClientState State = ClientState.Invalid; public int Team; public string Slot; // Slot ID, or null for observer public string Bot; // Bot type, null for real clients public int BotControllerClientIndex; // who added the bot to the slot public bool IsAdmin; public bool IsReady { get { return State == ClientState.Ready; } } public bool IsInvalid { get { return State == ClientState.Invalid; } } public bool IsObserver { get { return Slot == null; } } // Linked to the online player database public string Fingerprint; public MiniYamlNode Serialize() { return new MiniYamlNode("Client@{0}".F(Index), FieldSaver.Save(this)); } } public ClientPing PingFromClient(Client client) { return ClientPings.SingleOrDefault(p => p.Index == client.Index); } public class ClientPing { public int Index; public long Latency = -1; public long LatencyJitter = -1; public long[] LatencyHistory = { }; public static ClientPing Deserialize(MiniYaml data) { return FieldLoader.Load(data); } public MiniYamlNode Serialize() { return new MiniYamlNode("ClientPing@{0}".F(Index), FieldSaver.Save(this)); } } public class Slot { public string PlayerReference; // PlayerReference to bind against. public bool Closed; // Host has explicitly closed this slot. public bool AllowBots; public bool LockFaction; public bool LockColor; public bool LockTeam; public bool LockSpawn; public bool Required; public static Slot Deserialize(MiniYaml data) { return FieldLoader.Load(data); } public MiniYamlNode Serialize() { return new MiniYamlNode("Slot@{0}".F(PlayerReference), FieldSaver.Save(this)); } } public class LobbyOptionState { public string Value; public string PreferredValue; public bool IsLocked; public bool IsEnabled { get { return Value == "True"; } } } public class Global { public string ServerName; public string Map; public int Timestep = 40; public int OrderLatency = 3; // net tick frames (x 120 = ms) public int RandomSeed = 0; public bool AllowSpectators = true; public string GameUid; public bool EnableSingleplayer; public bool EnableSyncReports; public bool Dedicated; public bool GameSavesEnabled; [FieldLoader.Ignore] public Dictionary LobbyOptions = new Dictionary(); public static Global Deserialize(MiniYaml data) { var gs = FieldLoader.Load(data); var optionsNode = data.Nodes.FirstOrDefault(n => n.Key == "Options"); if (optionsNode != null) foreach (var n in optionsNode.Value.Nodes) gs.LobbyOptions[n.Key] = FieldLoader.Load(n.Value); return gs; } public MiniYamlNode Serialize() { var data = new MiniYamlNode("GlobalSettings", FieldSaver.Save(this)); var options = LobbyOptions.Select(kv => new MiniYamlNode(kv.Key, FieldSaver.Save(kv.Value))).ToList(); data.Value.Nodes.Add(new MiniYamlNode("Options", new MiniYaml(null, options))); return data; } public bool OptionOrDefault(string id, bool def) { LobbyOptionState option; if (LobbyOptions.TryGetValue(id, out option)) return option.IsEnabled; return def; } public string OptionOrDefault(string id, string def) { LobbyOptionState option; if (LobbyOptions.TryGetValue(id, out option)) return option.Value; return def; } } public string Serialize() { var sessionData = new List(); foreach (var client in Clients) sessionData.Add(client.Serialize()); foreach (var clientPing in ClientPings) sessionData.Add(clientPing.Serialize()); foreach (var slot in Slots) sessionData.Add(slot.Value.Serialize()); sessionData.Add(GlobalSettings.Serialize()); return sessionData.WriteToString(); } } }