From 2faae285db1a6d0e8ac276f77c2b76ece5cf5f09 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 12 Nov 2023 18:26:17 +0000 Subject: [PATCH] Persist skirmish settings between sessions. --- .../ServerTraits/LobbyCommands.cs | 2 +- .../ServerTraits/SkirmishLogic.cs | 144 +++++++++++++++++- 2 files changed, 143 insertions(+), 3 deletions(-) diff --git a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs index ad2fffd7a5..e1366b8139 100644 --- a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs +++ b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs @@ -1410,7 +1410,7 @@ namespace OpenRA.Mods.Common.Server } } - static Color SanitizePlayerColor(S server, Color askedColor, int playerIndex, Connection connectionToEcho = null) + public static Color SanitizePlayerColor(S server, Color askedColor, int playerIndex, Connection connectionToEcho = null) { lock (server.LobbyInfo) { diff --git a/OpenRA.Mods.Common/ServerTraits/SkirmishLogic.cs b/OpenRA.Mods.Common/ServerTraits/SkirmishLogic.cs index 0fa7e18075..99e02d3e8a 100644 --- a/OpenRA.Mods.Common/ServerTraits/SkirmishLogic.cs +++ b/OpenRA.Mods.Common/ServerTraits/SkirmishLogic.cs @@ -9,20 +9,160 @@ */ #endregion +using System.Collections.Generic; +using System.IO; using System.Linq; +using OpenRA.Network; +using OpenRA.Primitives; using OpenRA.Server; using OpenRA.Traits; using S = OpenRA.Server.Server; namespace OpenRA.Mods.Common.Server { - public class SkirmishLogic : ServerTrait, IClientJoined + public class SkirmishLogic : ServerTrait, IClientJoined, INotifySyncLobbyInfo { - public void ClientJoined(S server, Connection conn) + class SkirmishSlot + { + [FieldLoader.Serialize(FromYamlKey = true)] + public readonly string Slot; + public readonly Color Color; + public readonly string Faction; + public readonly int SpawnPoint; + public readonly int Team; + public readonly int Handicap; + + public SkirmishSlot() { } + + public SkirmishSlot(Session.Client c) + { + Slot = c.Slot; + Color = c.Color; + Faction = c.Faction; + SpawnPoint = c.SpawnPoint; + Team = c.Team; + Handicap = c.Handicap; + } + + public static void DeserializeToClient(MiniYaml yaml, Session.Client c) + { + var s = FieldLoader.Load(yaml); + c.Slot = s.Slot; + c.Color = c.PreferredColor = s.Color; + c.Faction = s.Faction; + c.SpawnPoint = s.SpawnPoint; + c.Team = s.Team; + c.Handicap = s.Handicap; + } + } + + static bool TryInitializeFromFile(S server, string path, Connection conn) + { + if (!File.Exists(path)) + return false; + + var nodes = new MiniYaml("", MiniYaml.FromFile(path)); + var mapNode = nodes.NodeWithKeyOrDefault("Map"); + if (mapNode == null) + return false; + + // Only set players and options if the map is available + if (server.LobbyInfo.GlobalSettings.Map != mapNode.Value.Value) + { + var map = server.ModData.MapCache[mapNode.Value.Value]; + if (map.Status != MapStatus.Available || !server.InterpretCommand($"map {map.Uid}", conn)) + return false; + } + + var optionsNode = nodes.NodeWithKeyOrDefault("Options"); + if (optionsNode != null) + { + var options = server.Map.PlayerActorInfo.TraitInfos() + .Concat(server.Map.WorldActorInfo.TraitInfos()) + .SelectMany(t => t.LobbyOptions(server.Map)) + .ToDictionary(o => o.Id, o => o); + + foreach (var optionNode in optionsNode.Value.Nodes) + { + if (options.TryGetValue(optionNode.Key, out var option) && !option.IsLocked) + { + var oo = server.LobbyInfo.GlobalSettings.LobbyOptions[option.Id]; + oo.Value = oo.PreferredValue = optionNode.Value.Value; + } + } + } + + var playerNode = nodes.NodeWithKeyOrDefault("Player"); + if (playerNode != null) + { + var client = server.GetClient(conn); + SkirmishSlot.DeserializeToClient(playerNode.Value, client); + client.Color = LobbyCommands.SanitizePlayerColor(server, client.Color, client.Index); + } + + var botsNode = nodes.NodeWithKeyOrDefault("Bots"); + if (botsNode != null) + { + var botController = server.LobbyInfo.Clients.First(c => c.IsAdmin); + foreach (var botNode in botsNode.Value.Nodes) + { + var botInfo = server.Map.PlayerActorInfo.TraitInfos() + .FirstOrDefault(b => b.Type == botNode.Key); + + if (botInfo == null) + continue; + + var client = new Session.Client + { + Index = server.ChooseFreePlayerIndex(), + Name = botInfo.Name, + Bot = botInfo.Type, + Slot = botNode.Value.Value, + State = Session.ClientState.NotReady, + BotControllerClientIndex = botController.Index + }; + + SkirmishSlot.DeserializeToClient(botNode.Value, client); + + // Validate whether color is allowed and get an alternative if it isn't + if (client.Slot != null && !server.LobbyInfo.Slots[client.Slot].LockColor) + client.Color = LobbyCommands.SanitizePlayerColor(server, client.Color, client.Index); + + server.LobbyInfo.Clients.Add(client); + S.SyncClientToPlayerReference(client, server.Map.Players.Players[client.Slot]); + } + } + + return true; + } + + void INotifySyncLobbyInfo.LobbyInfoSynced(S server) { if (server.Type != ServerType.Skirmish) return; + var path = Path.Combine(Platform.SupportDir, $"skirmish.{server.ModData.Manifest.Id}.yaml"); + var playerClient = server.LobbyInfo.NonBotClients.First(); + new List + { + new("Map", server.LobbyInfo.GlobalSettings.Map), + new("Options", new MiniYaml("", server.LobbyInfo.GlobalSettings.LobbyOptions + .Select(kv => new MiniYamlNode(kv.Key, kv.Value.Value)))), + new("Player", FieldSaver.Save(new SkirmishSlot(playerClient))), + new("Bots", new MiniYaml("", server.LobbyInfo.Clients.Where(c => c.IsBot) + .Select(b => new MiniYamlNode(b.Bot, FieldSaver.Save(new SkirmishSlot(b)))))) + }.WriteToFile(path); + } + + void IClientJoined.ClientJoined(S server, Connection conn) + { + if (server.Type != ServerType.Skirmish) + return; + + var skirmishFile = Path.Combine(Platform.SupportDir, $"skirmish.{server.ModData.Manifest.Id}.yaml"); + if (TryInitializeFromFile(server, skirmishFile, conn)) + return; + var slot = server.LobbyInfo.FirstEmptyBotSlot(); var bot = server.Map.PlayerActorInfo.TraitInfos().Select(t => t.Type).FirstOrDefault(); var botController = server.LobbyInfo.Clients.FirstOrDefault(c => c.IsAdmin);