Files
OpenRA/OpenRA.Mods.Common/ServerTraits/SkirmishLogic.cs
RoosterDragon e6914f707a Introduce FirstOrDefault extensions method for Array.Find and List.Find.
This allows the LINQ spelling to be used, but benefits from the performance improvement of the specific methods for these classes that provide the same result.
2023-11-19 19:28:57 +02:00

174 lines
5.5 KiB
C#

#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* 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.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, INotifySyncLobbyInfo
{
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<SkirmishSlot>(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<ILobbyOptions>()
.Concat(server.Map.WorldActorInfo.TraitInfos<ILobbyOptions>())
.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 && option.Values.ContainsKey(optionNode.Value.Value))
{
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<IBotInfo>()
.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<MiniYamlNode>
{
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<IBotInfo>().Select(t => t.Type).FirstOrDefault();
var botController = server.LobbyInfo.Clients.FirstOrDefault(c => c.IsAdmin);
if (slot != null && bot != null)
server.InterpretCommand($"slot_bot {slot} {botController.Index} {bot}", conn);
}
}
}