Added translation support for server orders.

This commit is contained in:
Matthias Mailänder
2021-12-18 20:12:28 +01:00
committed by teinarss
parent ee95d2591f
commit 0260884369
19 changed files with 834 additions and 149 deletions

View File

@@ -0,0 +1,121 @@
#region Copyright & License Information
/*
* Copyright 2007-2021 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.IO;
using System.Linq;
using System.Reflection;
using Fluent.Net;
using Fluent.Net.RuntimeAst;
namespace OpenRA.Mods.Common.Lint
{
class CheckTranslationReference : ILintPass
{
const BindingFlags Binding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
readonly List<string> referencedKeys = new List<string>();
readonly Dictionary<string, string[]> referencedVariablesPerKey = new Dictionary<string, string[]>();
readonly List<string> variableReferences = new List<string>();
public void Run(Action<string> emitError, Action<string> emitWarning, ModData modData)
{
var language = "en";
var translation = new Translation(language, modData.Manifest.Translations, modData.DefaultFileSystem);
foreach (var modType in modData.ObjectCreator.GetTypes())
{
foreach (var fieldInfo in modType.GetFields(Binding).Where(m => m.HasAttribute<TranslationReferenceAttribute>()))
{
if (fieldInfo.FieldType != typeof(string))
emitError($"Translation attribute on non string field {fieldInfo.Name}.");
var key = (string)fieldInfo.GetValue(string.Empty);
if (!translation.HasAttribute(key))
emitError($"{key} not present in {language} translation.");
var translationReference = fieldInfo.GetCustomAttributes<TranslationReferenceAttribute>(true)[0];
if (translationReference.RequiredVariableNames != null && translationReference.RequiredVariableNames.Length > 0)
referencedVariablesPerKey.GetOrAdd(key, translationReference.RequiredVariableNames);
referencedKeys.Add(key);
}
}
foreach (var file in modData.Manifest.Translations)
{
var stream = modData.DefaultFileSystem.Open(file);
using (var reader = new StreamReader(stream))
{
var runtimeParser = new RuntimeParser();
var result = runtimeParser.GetResource(reader);
foreach (var entry in result.Entries)
{
if (!referencedKeys.Contains(entry.Key))
emitWarning($"Unused key `{entry.Key}` in {file}.");
var message = entry.Value;
var node = message.Value;
variableReferences.Clear();
if (node is Pattern pattern)
{
foreach (var element in pattern.Elements)
{
if (element is SelectExpression selectExpression)
{
foreach (var variant in selectExpression.Variants)
{
if (variant.Value is Pattern variantPattern)
{
foreach (var variantElement in variantPattern.Elements)
CheckVariableReference(variantElement, entry, emitWarning, file);
}
}
}
CheckVariableReference(element, entry, emitWarning, file);
}
if (referencedVariablesPerKey.ContainsKey(entry.Key))
{
var referencedVariables = referencedVariablesPerKey[entry.Key];
foreach (var referencedVariable in referencedVariables)
{
if (!variableReferences.Contains(referencedVariable))
emitError($"Missing variable `{referencedVariable}` for key `{entry.Key}` in {file}.");
}
}
}
}
}
}
}
void CheckVariableReference(Node element, KeyValuePair<string, Message> entry, Action<string> emitWarning, string file)
{
if (element is VariableReference variableReference)
{
variableReferences.Add(variableReference.Name);
if (referencedVariablesPerKey.ContainsKey(entry.Key))
{
var referencedVariables = referencedVariablesPerKey[entry.Key];
if (!referencedVariables.Contains(variableReference.Name))
emitWarning($"Unused variable `{variableReference.Name}` for key `{entry.Key}` in {file}.");
}
else
emitWarning($"Unused variable `{variableReference.Name}` for key `{entry.Key}` in {file}.");
}
}
}
}

View File

@@ -0,0 +1,50 @@
#region Copyright & License Information
/*
* Copyright 2007-2021 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.IO;
using Fluent.Net;
using Fluent.Net.Ast;
namespace OpenRA.Mods.Common.Lint
{
class CheckTranslationSyntax : ILintPass
{
public void Run(Action<string> emitError, Action<string> emitWarning, ModData modData)
{
foreach (var file in modData.Manifest.Translations)
{
var stream = modData.DefaultFileSystem.Open(file);
using (var reader = new StreamReader(stream))
{
var ids = new List<string>();
var parser = new Parser();
var resource = parser.Parse(reader);
foreach (var entry in resource.Body)
{
if (entry is Junk junk)
foreach (var annotation in junk.Annotations)
emitError($"{annotation.Code}: {annotation.Message} in {file} line {annotation.Span.Start.Line}");
if (entry is MessageTermBase message)
{
if (ids.Contains(message.Id.Name))
emitWarning($"Duplicate ID `{message.Id.Name}` in {file} line {message.Span.Start.Line}");
ids.Add(message.Id.Name);
}
}
}
}
}
}
}

View File

@@ -24,6 +24,132 @@ namespace OpenRA.Mods.Common.Server
{
public class LobbyCommands : ServerTrait, IInterpretCommand, INotifyServerStart, INotifyServerEmpty, IClientJoined
{
[TranslationReference]
static readonly string CustomRules = "custom-rules";
[TranslationReference]
static readonly string OnlyHostStartGame = "only-only-host-start-game";
[TranslationReference]
static readonly string NoStartUntilRequiredSlotsFull = "no-start-until-required-slots-full";
[TranslationReference]
static readonly string TwoHumansRequired = "two-humans-required";
[TranslationReference]
static readonly string InsufficientEnabledSpawnPoints = "insufficient-enabled-spawnPoints";
[TranslationReference("command")]
static readonly string MalformedCommand = "malformed-command";
[TranslationReference]
static readonly string KickNone = "kick-none";
[TranslationReference]
static readonly string NoKickGameStarted = "no-kick-game-started";
[TranslationReference("admin", "player")]
static readonly string Kicked = "kicked";
[TranslationReference("admin", "player")]
static readonly string TempBan = "temp-ban";
[TranslationReference]
static readonly string NoTransferAdmin = "only-host-transfer-admin";
[TranslationReference]
static readonly string EmptySlot = "empty-slot";
[TranslationReference("admin", "player")]
static readonly string MoveSpectators = "move-spectators";
[TranslationReference("player", "name")]
static readonly string Nick = "nick";
[TranslationReference]
static readonly string StateUnchangedReady = "state-unchanged-ready";
[TranslationReference("command")]
static readonly string StateUnchangedGameStarted = "state-unchanged-game-started";
[TranslationReference("faction")]
static readonly string InvalidFactionSelected = "invalid-faction-selected";
[TranslationReference("factions")]
static readonly string SupportedFactions = "supported-factions";
[TranslationReference]
static readonly string RequiresHost = "requires-host";
[TranslationReference]
static readonly string InvalidBotSlot = "invalid-bot-slot";
[TranslationReference]
static readonly string InvalidBotType = "invalid-bot-type";
[TranslationReference]
static readonly string HostChangeMap = "only-host-change-map";
[TranslationReference]
static readonly string UnknownMap = "unknown-map";
[TranslationReference]
static readonly string SearchingMap = "searching-map";
[TranslationReference]
static readonly string NotAdmin = "only-host-change-configuration";
[TranslationReference]
static readonly string InvalidConfigurationCommand = "invalid-configuration-command";
[TranslationReference("option")]
static readonly string OptionLocked = "option-locked";
[TranslationReference("player", "map")]
static readonly string ChangedMap = "changed-map";
[TranslationReference]
static readonly string BotsDisabled = "bots-disabled";
[TranslationReference("player", "name", "value")]
static readonly string ValueChanged = "value-changed";
[TranslationReference]
static readonly string NoMoveSpectators = "only-host-move-spectators";
[TranslationReference]
static readonly string AdminOption = "admin-option";
[TranslationReference("raw")]
static readonly string NumberTeams = "number-teams";
[TranslationReference]
static readonly string AdminClearSpawn = "admin-clear-spawn";
[TranslationReference]
static readonly string SpawnOccupied = "spawn-occupied";
[TranslationReference]
static readonly string SpawnLocked = "spawn-locked";
[TranslationReference]
static readonly string AdminLobbyInfo = "admin-lobby-info";
[TranslationReference]
static readonly string InvalidLobbyInfo = "invalid-lobby-info";
[TranslationReference]
static readonly string AdminKick = "admin-kick";
[TranslationReference]
static readonly string SlotClosed = "slot-closed";
[TranslationReference("player")]
public static readonly string NewAdmin = "new-admin";
[TranslationReference]
static readonly string YouWereKicked = "you-were-kicked";
readonly IDictionary<string, Func<S, Connection, Session.Client, string, bool>> commandHandlers = new Dictionary<string, Func<S, Connection, Session.Client, string, bool>>
{
{ "state", State },
@@ -56,13 +182,13 @@ namespace OpenRA.Mods.Common.Server
{
if (!server.LobbyInfo.Slots.ContainsKey(arg))
{
Log.Write("server", "Invalid slot: {0}", arg);
Log.Write("server", $"Invalid slot: {arg}");
return false;
}
if (requiresHost && !client.IsAdmin)
{
server.SendOrderTo(conn, "Message", "Only the host can do that.");
server.SendLocalizedMessageTo(conn, RequiresHost);
return false;
}
@@ -70,22 +196,22 @@ namespace OpenRA.Mods.Common.Server
}
}
public static bool ValidateCommand(S server, Connection conn, Session.Client client, string cmd)
public static bool ValidateCommand(S server, Connection conn, Session.Client client, string command)
{
lock (server.LobbyInfo)
{
// Kick command is always valid for the host
if (cmd.StartsWith("kick "))
if (command.StartsWith("kick "))
return true;
if (server.State == ServerState.GameStarted)
{
server.SendOrderTo(conn, "Message", $"Cannot change state when game started. ({cmd})");
server.SendLocalizedMessageTo(conn, StateUnchangedGameStarted, Translation.Arguments("command", command));
return false;
}
else if (client.State == Session.ClientState.Ready && !(cmd.StartsWith("state") || cmd == "startgame"))
else if (client.State == Session.ClientState.Ready && !(command.StartsWith("state") || command == "startgame"))
{
server.SendOrderTo(conn, "Message", "Cannot change state when marked as ready.");
server.SendLocalizedMessageTo(conn, StateUnchangedReady);
return false;
}
@@ -139,12 +265,13 @@ namespace OpenRA.Mods.Common.Server
{
if (!Enum<Session.ClientState>.TryParse(s, false, out var state))
{
server.SendOrderTo(conn, "Message", "Malformed state command");
server.SendLocalizedMessageTo(conn, MalformedCommand, Translation.Arguments("command", "state"));
return true;
}
client.State = state;
Log.Write("server", "Player @{0} is {1}", conn.EndPoint, client.State);
Log.Write("server", $"Player @{conn.EndPoint} is {client.State}");
server.SyncLobbyClients();
CheckAutoStart(server);
@@ -159,26 +286,26 @@ namespace OpenRA.Mods.Common.Server
{
if (!client.IsAdmin)
{
server.SendOrderTo(conn, "Message", "Only the host can start the game.");
server.SendOrderTo(conn, "Message", OnlyHostStartGame);
return true;
}
if (server.LobbyInfo.Slots.Any(sl => sl.Value.Required &&
server.LobbyInfo.ClientInSlot(sl.Key) == null))
{
server.SendOrderTo(conn, "Message", "Unable to start the game until required slots are full.");
server.SendOrderTo(conn, "Message", NoStartUntilRequiredSlotsFull);
return true;
}
if (!server.LobbyInfo.GlobalSettings.EnableSingleplayer && server.LobbyInfo.NonBotPlayers.Count() < 2)
{
server.SendOrderTo(conn, "Message", server.TwoHumansRequiredText);
server.SendOrderTo(conn, "Message", TwoHumansRequired);
return true;
}
if (LobbyUtils.InsufficientEnabledSpawnPoints(server.Map, server.LobbyInfo))
{
server.SendOrderTo(conn, "Message", "Unable to start the game until more spawn points are enabled.");
server.SendOrderTo(conn, "Message", InsufficientEnabledSpawnPoints);
return true;
}
@@ -194,7 +321,7 @@ namespace OpenRA.Mods.Common.Server
{
if (!server.LobbyInfo.Slots.ContainsKey(s))
{
Log.Write("server", "Invalid slot: {0}", s);
Log.Write("server", $"Invalid slot: {s}");
return false;
}
@@ -231,7 +358,7 @@ namespace OpenRA.Mods.Common.Server
return true;
}
server.SendOrderTo(conn, "Message", "Malformed allow_spectate command");
server.SendLocalizedMessageTo(conn, MalformedCommand, Translation.Arguments("command", "allow_spectate"));
return true;
}
@@ -278,7 +405,7 @@ namespace OpenRA.Mods.Common.Server
var occupantConn = server.Conns.FirstOrDefault(c => c.PlayerIndex == occupant.Index);
if (occupantConn != null)
{
server.SendOrderTo(occupantConn, "ServerError", "Your slot was closed by the host.");
server.SendOrderTo(conn, "ServerError", SlotClosed);
server.DropClient(occupantConn);
}
}
@@ -320,7 +447,7 @@ namespace OpenRA.Mods.Common.Server
var parts = s.Split(' ');
if (parts.Length < 3)
{
server.SendOrderTo(conn, "Message", "Malformed slot_bot command");
server.SendLocalizedMessageTo(conn, MalformedCommand, Translation.Arguments("command", "slot_bot"));
return true;
}
@@ -331,14 +458,14 @@ namespace OpenRA.Mods.Common.Server
var bot = server.LobbyInfo.ClientInSlot(parts[0]);
if (!Exts.TryParseIntegerInvariant(parts[1], out var controllerClientIndex))
{
Log.Write("server", "Invalid bot controller client index: {0}", parts[1]);
Log.Write("server", $"Invalid bot controller client index: {parts[1]}");
return false;
}
// Invalid slot
if (bot != null && bot.Bot == null)
{
server.SendOrderTo(conn, "Message", "Can't add bots to a slot with another client.");
server.SendLocalizedMessageTo(conn, InvalidBotSlot);
return true;
}
@@ -348,7 +475,7 @@ namespace OpenRA.Mods.Common.Server
if (botInfo == null)
{
server.SendOrderTo(conn, "Message", "Invalid bot type.");
server.SendLocalizedMessageTo(conn, InvalidBotType);
return true;
}
@@ -400,7 +527,7 @@ namespace OpenRA.Mods.Common.Server
{
if (!client.IsAdmin)
{
server.SendOrderTo(conn, "Message", "Only the host can change the map.");
server.SendLocalizedMessageTo(conn, HostChangeMap);
return true;
}
@@ -477,15 +604,15 @@ namespace OpenRA.Mods.Common.Server
server.SyncLobbyInfo();
server.SendMessage($"{client.Name} changed the map to {server.Map.Title}.");
server.SendLocalizedMessage(ChangedMap, Translation.Arguments("player", client.Name, "map", server.Map.Title));
if ((server.LobbyInfo.GlobalSettings.MapStatus & Session.MapStatus.UnsafeCustomRules) != 0)
server.SendMessage("This map contains custom rules. Game experience may change.");
server.SendLocalizedMessage(CustomRules);
if (!server.LobbyInfo.GlobalSettings.EnableSingleplayer)
server.SendMessage(server.TwoHumansRequiredText);
server.SendLocalizedMessage(TwoHumansRequired);
else if (server.Map.Players.Players.Where(p => p.Value.Playable).All(p => !p.Value.AllowBots))
server.SendMessage("Bots have been disabled on this map.");
server.SendLocalizedMessage(BotsDisabled);
var briefing = MissionBriefingOrDefault(server);
if (briefing != null)
@@ -493,14 +620,14 @@ namespace OpenRA.Mods.Common.Server
}
};
Action queryFailed = () => server.SendOrderTo(conn, "Message", "Map was not found on server.");
Action queryFailed = () => server.SendLocalizedMessageTo(conn, UnknownMap);
var m = server.ModData.MapCache[s];
if (m.Status == MapStatus.Available || m.Status == MapStatus.DownloadAvailable)
selectMap(m);
else if (server.Settings.QueryMapRepository)
{
server.SendOrderTo(conn, "Message", "Searching for map on the Resource Center...");
server.SendLocalizedMessageTo(conn, SearchingMap);
var mapRepository = server.ModData.Manifest.Get<WebServices>().MapRepository;
var reported = false;
server.ModData.MapCache.QueryRemoteMapDetails(mapRepository, new[] { s }, selectMap, _ =>
@@ -524,7 +651,7 @@ namespace OpenRA.Mods.Common.Server
{
if (!client.IsAdmin)
{
server.SendOrderTo(conn, "Message", "Only the host can change the configuration.");
server.SendLocalizedMessageTo(conn, NotAdmin);
return true;
}
@@ -541,13 +668,13 @@ namespace OpenRA.Mods.Common.Server
if (split.Length < 2 || !options.TryGetValue(split[0], out var option) ||
!option.Values.ContainsKey(split[1]))
{
server.SendOrderTo(conn, "Message", "Invalid configuration command.");
server.SendLocalizedMessageTo(conn, InvalidConfigurationCommand);
return true;
}
if (option.IsLocked)
{
server.SendOrderTo(conn, "Message", $"{option.Name} cannot be changed.");
server.SendLocalizedMessageTo(conn, OptionLocked, Translation.Arguments("option", option.Name));
return true;
}
@@ -558,7 +685,7 @@ namespace OpenRA.Mods.Common.Server
oo.Value = oo.PreferredValue = split[1];
server.SyncLobbyGlobalSettings();
server.SendMessage(option.ValueChangedMessage(client.Name, split[1]));
server.SendLocalizedMessage(ValueChanged, Translation.Arguments("player", client.Name, "name", option.Name, "value", option.Label(split[1])));
foreach (var c in server.LobbyInfo.Clients)
c.State = Session.ClientState.NotReady;
@@ -569,19 +696,19 @@ namespace OpenRA.Mods.Common.Server
}
}
static bool AssignTeams(S server, Connection conn, Session.Client client, string s)
static bool AssignTeams(S server, Connection conn, Session.Client client, string raw)
{
lock (server.LobbyInfo)
{
if (!client.IsAdmin)
{
server.SendOrderTo(conn, "Message", "Only the host can set that option.");
server.SendLocalizedMessageTo(conn, AdminOption);
return true;
}
if (!Exts.TryParseIntegerInvariant(s, out var teamCount))
if (!Exts.TryParseIntegerInvariant(raw, out var teamCount))
{
server.SendOrderTo(conn, "Message", $"Number of teams could not be parsed: {s}");
server.SendLocalizedMessageTo(conn, NumberTeams, Translation.Arguments("raw", raw));
return true;
}
@@ -618,14 +745,14 @@ namespace OpenRA.Mods.Common.Server
{
if (!client.IsAdmin)
{
server.SendOrderTo(conn, "Message", "Only the host can kick players.");
server.SendLocalizedMessageTo(conn, AdminKick);
return true;
}
var split = s.Split(' ');
if (split.Length < 2)
{
server.SendOrderTo(conn, "Message", "Malformed kick command");
server.SendLocalizedMessageTo(conn, MalformedCommand, Translation.Arguments("command", "kick"));
return true;
}
@@ -634,26 +761,26 @@ namespace OpenRA.Mods.Common.Server
var kickConn = server.Conns.SingleOrDefault(c => server.GetClient(c)?.Index == kickClientID);
if (kickConn == null)
{
server.SendOrderTo(conn, "Message", "No-one in that slot.");
server.SendLocalizedMessageTo(conn, KickNone);
return true;
}
var kickClient = server.GetClient(kickConn);
if (server.State == ServerState.GameStarted && !kickClient.IsObserver)
{
server.SendOrderTo(conn, "Message", "Only spectators can be kicked after the game has started.");
server.SendLocalizedMessageTo(conn, NoKickGameStarted);
return true;
}
Log.Write("server", "Kicking client {0}.", kickClientID);
server.SendMessage($"{client.Name} kicked {kickClient.Name} from the server.");
server.SendOrderTo(kickConn, "ServerError", "You have been kicked from the server.");
Log.Write("server", $"Kicking client {kickClientID}.");
server.SendLocalizedMessage(Kicked, Translation.Arguments("admin", client.Name, "client", kickClient.Name));
server.SendOrderTo(kickConn, "ServerError", YouWereKicked);
server.DropClient(kickConn);
if (bool.TryParse(split[1], out var tempBan) && tempBan)
{
Log.Write("server", "Temporarily banning client {0} ({1}).", kickClientID, kickClient.IPAddress);
server.SendMessage($"{client.Name} temporarily banned {kickClient.Name} from the server.");
Log.Write("server", $"Temporarily banning client {kickClientID} ({kickClient.IPAddress}).");
server.SendLocalizedMessage(TempBan, Translation.Arguments("admin", client.Name, "client", kickClient.Name));
server.TempBans.Add(kickClient.IPAddress);
}
@@ -670,7 +797,7 @@ namespace OpenRA.Mods.Common.Server
{
if (!client.IsAdmin)
{
server.SendOrderTo(conn, "Message", "Only admins can transfer admin to another player.");
server.SendLocalizedMessageTo(conn, NoTransferAdmin);
return true;
}
@@ -679,7 +806,7 @@ namespace OpenRA.Mods.Common.Server
if (newAdminConn == null)
{
server.SendOrderTo(conn, "Message", "No-one in that slot.");
server.SendLocalizedMessageTo(conn, EmptySlot);
return true;
}
@@ -693,7 +820,7 @@ namespace OpenRA.Mods.Common.Server
foreach (var b in bots)
b.BotControllerClientIndex = newAdminId;
server.SendMessage($"{newAdminClient.Name} is now the admin.");
server.SendLocalizedMessage(NewAdmin, Translation.Arguments("player", newAdminClient.Name));
Log.Write("server", $"{newAdminClient.Name} is now the admin.");
server.SyncLobbyClients();
@@ -707,16 +834,15 @@ namespace OpenRA.Mods.Common.Server
{
if (!client.IsAdmin)
{
server.SendOrderTo(conn, "Message", "Only the host can move players to spectators.");
server.SendLocalizedMessageTo(conn, NoMoveSpectators);
return true;
}
Exts.TryParseIntegerInvariant(s, out var targetId);
var targetConn = server.Conns.SingleOrDefault(c => server.GetClient(c)?.Index == targetId);
if (targetConn == null)
{
server.SendOrderTo(conn, "Message", "No-one in that slot.");
server.SendLocalizedMessageTo(conn, EmptySlot);
return true;
}
@@ -727,7 +853,7 @@ namespace OpenRA.Mods.Common.Server
targetClient.Handicap = 0;
targetClient.Color = Color.White;
targetClient.State = Session.ClientState.NotReady;
server.SendMessage($"{client.Name} moved {targetClient.Name} to spectators.");
server.SendLocalizedMessage(MoveSpectators, Translation.Arguments("admin", client.Name, "player", targetClient.Name));
Log.Write("server", $"{client.Name} moved {targetClient.Name} to spectators.");
server.SyncLobbyClients();
CheckAutoStart(server);
@@ -744,8 +870,8 @@ namespace OpenRA.Mods.Common.Server
if (sanitizedName == client.Name)
return true;
Log.Write("server", "Player@{0} is now known as {1}.", conn.EndPoint, sanitizedName);
server.SendMessage($"{client.Name} is now known as {sanitizedName}.");
Log.Write("server", $"Player@{conn.EndPoint} is now known as {sanitizedName}.");
server.SendLocalizedMessage(Nick, Translation.Arguments("player", client.Name, "name", sanitizedName));
client.Name = sanitizedName;
server.SyncLobbyClients();
@@ -771,14 +897,15 @@ namespace OpenRA.Mods.Common.Server
var factions = server.Map.WorldActorInfo.TraitInfos<FactionInfo>()
.Where(f => f.Selectable).Select(f => f.InternalName);
if (!factions.Contains(parts[1]))
var faction = parts[1];
if (!factions.Contains(faction))
{
server.SendOrderTo(conn, "Message", $"Invalid faction selected: {parts[1]}");
server.SendOrderTo(conn, "Message", $"Supported values: {factions.JoinWith(", ")}");
server.SendLocalizedMessageTo(conn, InvalidFactionSelected, Translation.Arguments("faction", faction));
server.SendLocalizedMessageTo(conn, SupportedFactions, Translation.Arguments("factions", factions.JoinWith(", ")));
return true;
}
targetClient.Faction = parts[1];
targetClient.Faction = faction;
server.SyncLobbyClients();
return true;
@@ -858,7 +985,7 @@ namespace OpenRA.Mods.Common.Server
var existingClient = server.LobbyInfo.Clients.FirstOrDefault(cc => cc.SpawnPoint == spawnPoint);
if (client != existingClient && !client.IsAdmin)
{
server.SendOrderTo(conn, "Message", "Only admins can clear spawn points.");
server.SendLocalizedMessageTo(conn, AdminClearSpawn);
return true;
}
@@ -910,13 +1037,13 @@ namespace OpenRA.Mods.Common.Server
if (!Exts.TryParseIntegerInvariant(parts[1], out var spawnPoint)
|| spawnPoint < 0 || spawnPoint > server.Map.SpawnPoints.Length)
{
Log.Write("server", "Invalid spawn point: {0}", parts[1]);
Log.Write("server", $"Invalid spawn point: {parts[1]}");
return true;
}
if (server.LobbyInfo.Clients.Where(cc => cc != client).Any(cc => (cc.SpawnPoint == spawnPoint) && (cc.SpawnPoint != 0)))
{
server.SendOrderTo(conn, "Message", "You cannot occupy the same spawn point as another player.");
server.SendLocalizedMessageTo(conn, SpawnOccupied);
return true;
}
@@ -931,7 +1058,7 @@ namespace OpenRA.Mods.Common.Server
if (spawnLockedByAnotherSlot)
{
server.SendOrderTo(conn, "Message", "The spawn point is locked to another player slot.");
server.SendLocalizedMessageTo(conn, SpawnLocked);
return true;
}
}
@@ -978,7 +1105,7 @@ namespace OpenRA.Mods.Common.Server
{
if (!client.IsAdmin)
{
server.SendOrderTo(conn, "Message", "Only the host can set lobby info");
server.SendLocalizedMessageTo(conn, AdminLobbyInfo);
return true;
}
@@ -989,7 +1116,7 @@ namespace OpenRA.Mods.Common.Server
}
catch (Exception)
{
server.SendOrderTo(conn, "Message", "Invalid Lobby Info Sent");
server.SendLocalizedMessageTo(conn, InvalidLobbyInfo);
}
return true;
@@ -1079,8 +1206,8 @@ namespace OpenRA.Mods.Common.Server
Action<string> onError = message =>
{
if (connectionToEcho != null)
server.SendOrderTo(connectionToEcho, "Message", message);
if (connectionToEcho != null && message != null)
server.SendLocalizedMessageTo(connectionToEcho, message);
};
var terrainColors = server.ModData.DefaultTerrainInfo[server.Map.TileSet].RestrictedPlayerColors;

View File

@@ -30,11 +30,24 @@ namespace OpenRA.Mods.Common.Server
// 1 second (in milliseconds) minimum delay between pings
const int RateLimitInterval = 1000;
[TranslationReference]
static readonly string NoPortForward = "no-port-forward";
[TranslationReference]
static readonly string BlacklistedTitle = "blacklisted-title";
[TranslationReference]
static readonly string InvalidErrorCode = "invalid-error-code";
[TranslationReference]
static readonly string Connected = "master-server-connected";
[TranslationReference]
static readonly string Error = "master-server-error";
[TranslationReference]
static readonly string GameOffline = "game-offline";
static readonly Beacon LanGameBeacon;
static readonly Dictionary<int, string> MasterServerErrors = new Dictionary<int, string>()
{
{ 1, "Server port is not accessible from the internet." },
{ 2, "Server name contains a blacklisted word." }
{ 1, NoPortForward },
{ 2, BlacklistedTitle }
};
long lastPing = 0;
@@ -143,25 +156,25 @@ namespace OpenRA.Mods.Common.Server
var regex = new Regex(@"^\[(?<code>-?\d+)\](?<message>.*)");
var match = regex.Match(masterResponseText);
errorMessage = match.Success && int.TryParse(match.Groups["code"].Value, out errorCode) ?
match.Groups["message"].Value.Trim() : "Failed to parse error message";
match.Groups["message"].Value.Trim() : InvalidErrorCode;
}
isInitialPing = false;
lock (masterServerMessages)
{
masterServerMessages.Enqueue("Master server communication established.");
masterServerMessages.Enqueue(Connected);
if (errorCode != 0)
{
// Hardcoded error messages take precedence over the server-provided messages
if (!MasterServerErrors.TryGetValue(errorCode, out var message))
message = errorMessage;
masterServerMessages.Enqueue("Warning: " + message);
masterServerMessages.Enqueue(message);
// Positive error codes indicate errors that prevent advertisement
// Negative error codes are non-fatal warnings
if (errorCode > 0)
masterServerMessages.Enqueue("Game has not been advertised online.");
masterServerMessages.Enqueue(GameOffline);
}
}
}
@@ -170,7 +183,7 @@ namespace OpenRA.Mods.Common.Server
{
Log.Write("server", ex.ToString());
lock (masterServerMessages)
masterServerMessages.Enqueue("Master server communication failed.");
masterServerMessages.Enqueue(Error);
}
isBusy = false;

View File

@@ -9,6 +9,7 @@
*/
#endregion
using System.Collections.Generic;
using System.Linq;
using OpenRA.Server;
using S = OpenRA.Server.Server;
@@ -21,6 +22,18 @@ namespace OpenRA.Mods.Common.Server
static readonly int ConnReportInterval = 20000; // Report every 20 seconds
static readonly int ConnTimeout = 60000; // Drop unresponsive clients after 60 seconds
[TranslationReference]
static readonly string PlayerDropped = "player-dropped";
[TranslationReference("player")]
static readonly string ConnectionProblems = "connection-problems";
[TranslationReference("player")]
static readonly string Timeout = "timeout";
[TranslationReference("player", "timeout")]
static readonly string TimeoutIn = "timeout-in";
long lastPing = 0;
long lastConnReport = 0;
bool isInitialPing = true;
@@ -48,7 +61,7 @@ namespace OpenRA.Mods.Common.Server
if (client == null)
{
server.DropClient(c);
server.SendMessage("A player has been dropped after timing out.");
server.SendLocalizedMessage(PlayerDropped);
continue;
}
@@ -56,13 +69,13 @@ namespace OpenRA.Mods.Common.Server
{
if (!c.TimeoutMessageShown && c.TimeSinceLastResponse > PingInterval * 2)
{
server.SendMessage(client.Name + " is experiencing connection problems.");
server.SendLocalizedMessage(ConnectionProblems, Translation.Arguments("player", client.Name));
c.TimeoutMessageShown = true;
}
}
else
{
server.SendMessage(client.Name + " has been dropped after timing out.");
server.SendLocalizedMessage(Timeout, Translation.Arguments("player", client.Name));
server.DropClient(c);
}
}
@@ -79,7 +92,14 @@ namespace OpenRA.Mods.Common.Server
{
var client = server.GetClient(c);
if (client != null)
server.SendMessage($"{client.Name} will be dropped in {(ConnTimeout - c.TimeSinceLastResponse) / 1000} seconds.");
{
var timeout = (ConnTimeout - c.TimeSinceLastResponse) / 1000;
server.SendLocalizedMessage(TimeoutIn, new Dictionary<string, object>()
{
{ "player", client.Name },
{ "timeout", timeout }
});
}
}
}
}

View File

@@ -60,6 +60,13 @@ namespace OpenRA.Mods.Common.Traits
public Color Color;
[TranslationReference]
static readonly string PlayerColorTerrain = "player-color-terrain";
[TranslationReference]
static readonly string PlayerColorPlayer = "player-color-player";
[TranslationReference]
static readonly string InvalidPlayerColor = "invalid-player-color";
bool TryGetBlockingColor((float R, float G, float B) color, List<(float R, float G, float B)> candidateBlockers, out (float R, float G, float B) closestBlocker)
{
var closestDistance = SimilarityThreshold;
@@ -148,9 +155,9 @@ namespace OpenRA.Mods.Common.Traits
{
var linear = Color.FromAhsv(hue, sat, V).ToLinear();
if (TryGetBlockingColor(linear, terrainLinear, out var blocker))
errorMessage = "Color was adjusted to be less similar to the terrain.";
errorMessage = PlayerColorTerrain;
else if (TryGetBlockingColor(linear, playerLinear, out blocker))
errorMessage = "Color was adjusted to be less similar to another player.";
errorMessage = PlayerColorPlayer;
else
{
if (errorMessage != null)
@@ -169,7 +176,7 @@ namespace OpenRA.Mods.Common.Traits
}
// Failed to find a solution within a reasonable time: return a random color without any validation
onError?.Invoke("Unable to determine a valid player color. A random color has been selected.");
onError?.Invoke(InvalidPlayerColor);
return Color.FromAhsv(random.NextFloat(), float2.Lerp(HsvSaturationRange[0], HsvSaturationRange[1], random.NextFloat()), V);
}
}

View File

@@ -107,7 +107,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
widget.Get<LabelWidget>("CONNECTING_DESC").GetText = () => $"Could not connect to {connection.Target}";
var connectionError = widget.Get<LabelWidget>("CONNECTION_ERROR");
connectionError.GetText = () => orderManager.ServerError ?? connection.ErrorMessage ?? "Unknown error";
connectionError.GetText = () => Ui.Translate(orderManager.ServerError) ?? connection.ErrorMessage ?? "Unknown error";
var panelTitle = widget.Get<LabelWidget>("TITLE");
panelTitle.GetText = () => orderManager.AuthenticationFailed ? "Password Required" : "Connection Failed";