diff --git a/OpenRA.Game/GameInformation.cs b/OpenRA.Game/GameInformation.cs
index 1401ad6dec..c315948345 100644
--- a/OpenRA.Game/GameInformation.cs
+++ b/OpenRA.Game/GameInformation.cs
@@ -19,6 +19,9 @@ namespace OpenRA
{
public class GameInformation
{
+ [TranslationReference("name", "number")]
+ const string EnumeratedBotName = "enumerated-bot-name";
+
public string Mod;
public string Version;
@@ -118,6 +121,7 @@ namespace OpenRA
Name = runtimePlayer.PlayerName,
IsHuman = !runtimePlayer.IsBot,
IsBot = runtimePlayer.IsBot,
+ BotType = runtimePlayer.BotType,
FactionName = runtimePlayer.Faction.Name,
FactionId = runtimePlayer.Faction.InternalName,
DisplayFactionName = runtimePlayer.DisplayFaction.Name,
@@ -143,6 +147,20 @@ namespace OpenRA
return player;
}
+ public string ResolvedPlayerName(Player player)
+ {
+ if (player.IsBot)
+ {
+ var number = Players.Where(p => p.BotType == player.BotType).ToList().IndexOf(player) + 1;
+ return TranslationProvider.GetString(EnumeratedBotName,
+ Translation.Arguments(
+ "name", TranslationProvider.GetString(player.Name),
+ "number", number));
+ }
+
+ return player.Name;
+ }
+
public class Player
{
#region Start-up information
@@ -153,6 +171,7 @@ namespace OpenRA
public string Name;
public bool IsHuman;
public bool IsBot;
+ public string BotType;
/// The faction's display name.
public string FactionName;
diff --git a/OpenRA.Game/Network/Order.cs b/OpenRA.Game/Network/Order.cs
index 399fa515cd..5e76b9d673 100644
--- a/OpenRA.Game/Network/Order.cs
+++ b/OpenRA.Game/Network/Order.cs
@@ -465,7 +465,7 @@ namespace OpenRA
public override string ToString()
{
return $"OrderString: \"{OrderString}\" \n\t Type: \"{Type}\". \n\t Subject: \"{Subject}\". \n\t Target: \"{Target}\"." +
- $"\n\t TargetString: \"{TargetString}\".\n\t IsImmediate: {IsImmediate}.\n\t Player(PlayerName): {Player?.PlayerName}\n";
+ $"\n\t TargetString: \"{TargetString}\".\n\t IsImmediate: {IsImmediate}.\n\t Player(PlayerName): {Player?.ResolvedPlayerName}\n";
}
}
}
diff --git a/OpenRA.Game/Player.cs b/OpenRA.Game/Player.cs
index 7336c7dc6f..18b0bfeb96 100644
--- a/OpenRA.Game/Player.cs
+++ b/OpenRA.Game/Player.cs
@@ -38,6 +38,9 @@ namespace OpenRA
public class Player : IScriptBindable, IScriptNotifyBind, ILuaTableBinding, ILuaEqualityBinding, ILuaToStringBinding
{
+ [TranslationReference("name", "number")]
+ const string EnumeratedBotName = "enumerated-bot-name";
+
public readonly Actor PlayerActor;
public readonly string PlayerName;
public readonly string InternalName;
@@ -80,6 +83,9 @@ namespace OpenRA
readonly IUnlocksRenderPlayer[] unlockRenderPlayer;
readonly INotifyPlayerDisconnected[] notifyDisconnected;
+ readonly IReadOnlyCollection botInfos;
+ string resolvedPlayerName;
+
// Each player is identified with a unique bit in the set
// Cache masks for the player's index and ally/enemy player indices for performance.
public LongBitSet PlayerMask;
@@ -97,6 +103,16 @@ namespace OpenRA
}
}
+ /// The chosen player name including localized and enumerated bot names.
+ public string ResolvedPlayerName
+ {
+ get
+ {
+ resolvedPlayerName ??= ResolvePlayerName();
+ return resolvedPlayerName;
+ }
+ }
+
public static FactionInfo ResolveFaction(
string factionName, IEnumerable factionInfos, MersenneTwister playerRandom, bool requireSelectable = true)
{
@@ -133,18 +149,6 @@ namespace OpenRA
return factions.FirstOrDefault(f => f.InternalName == factionName) ?? factions.First();
}
- public static string ResolvePlayerName(Session.Client client, IEnumerable clients, IEnumerable botInfos)
- {
- if (client.Bot != null)
- {
- var botInfo = botInfos.First(b => b.Type == client.Bot);
- var botsOfSameType = clients.Where(c => c.Bot == client.Bot).ToArray();
- return botsOfSameType.Length == 1 ? botInfo.Name : $"{botInfo.Name} {botsOfSameType.IndexOf(client) + 1}";
- }
-
- return client.Name;
- }
-
public Player(World world, Session.Client client, PlayerReference pr, MersenneTwister playerRandom)
{
World = world;
@@ -152,6 +156,7 @@ namespace OpenRA
PlayerReference = pr;
inMissionMap = world.Map.Visibility.HasFlag(MapVisibility.MissionSelector);
+ botInfos = World.Map.Rules.Actors[SystemActors.Player].TraitInfos();
// Real player or host-created bot
if (client != null)
@@ -159,7 +164,7 @@ namespace OpenRA
ClientIndex = client.Index;
color = client.Color;
Color = color;
- PlayerName = ResolvePlayerName(client, world.LobbyInfo.Clients, world.Map.Rules.Actors[SystemActors.Player].TraitInfos());
+ PlayerName = client.Name;
BotType = client.Bot;
Faction = ResolveFaction(world, client.Faction, playerRandom, !pr.LockFaction);
@@ -224,7 +229,21 @@ namespace OpenRA
public override string ToString()
{
- return $"{PlayerName} ({ClientIndex})";
+ return $"{ResolvedPlayerName} ({ClientIndex})";
+ }
+
+ string ResolvePlayerName()
+ {
+ if (IsBot)
+ {
+ var botInfo = botInfos.First(b => b.Type == BotType);
+ var botsOfSameType = World.Players.Where(c => c.BotType == BotType).ToArray();
+ return TranslationProvider.GetString(EnumeratedBotName,
+ Translation.Arguments("name", TranslationProvider.GetString(botInfo.Name),
+ "number", botsOfSameType.IndexOf(this) + 1));
+ }
+
+ return PlayerName;
}
public PlayerRelationship RelationshipWith(Player other)
@@ -310,7 +329,7 @@ namespace OpenRA
public LuaValue ToString(LuaRuntime runtime)
{
- return $"Player ({PlayerName})";
+ return $"Player ({ResolvedPlayerName})";
}
#endregion
diff --git a/OpenRA.Game/Scripting/ScriptPlayerInterface.cs b/OpenRA.Game/Scripting/ScriptPlayerInterface.cs
index 305a29c7c2..065aa3bf2a 100644
--- a/OpenRA.Game/Scripting/ScriptPlayerInterface.cs
+++ b/OpenRA.Game/Scripting/ScriptPlayerInterface.cs
@@ -16,9 +16,9 @@ namespace OpenRA.Scripting
readonly Player player;
protected override string DuplicateKeyError(string memberName) =>
- $"Player '{player.PlayerName}' defines the command '{memberName}' on multiple traits";
+ $"Player '{player.ResolvedPlayerName}' defines the command '{memberName}' on multiple traits";
protected override string MemberNotFoundError(string memberName) =>
- $"Player '{player.PlayerName}' does not define a property '{memberName}'";
+ $"Player '{player.ResolvedPlayerName}' does not define a property '{memberName}'";
public ScriptPlayerInterface(ScriptContext context, Player player)
: base(context)
diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs
index 462c6ea35e..ed07e9ffb5 100644
--- a/OpenRA.Game/Server/Server.cs
+++ b/OpenRA.Game/Server/Server.cs
@@ -1323,7 +1323,7 @@ namespace OpenRA.Server
foreach (var cmpi in Map.WorldActorInfo.TraitInfos())
cmpi.CreateServerPlayers(Map, LobbyInfo, worldPlayers, playerRandom);
- gameInfo = new GameInformation
+ gameInfo = new GameInformation()
{
Mod = Game.ModData.Manifest.Id,
Version = Game.ModData.Manifest.Metadata.Version,
diff --git a/OpenRA.Mods.Common/Scripting/Properties/PlayerProperties.cs b/OpenRA.Mods.Common/Scripting/Properties/PlayerProperties.cs
index a0e53b0525..985d4ae45a 100644
--- a/OpenRA.Mods.Common/Scripting/Properties/PlayerProperties.cs
+++ b/OpenRA.Mods.Common/Scripting/Properties/PlayerProperties.cs
@@ -28,7 +28,7 @@ namespace OpenRA.Mods.Common.Scripting
public string InternalName => Player.InternalName;
[Desc("The player's name.")]
- public string Name => Player.PlayerName;
+ public string Name => Player.ResolvedPlayerName;
[Desc("The player's color.")]
public Color Color => Player.GetColor(Player);
diff --git a/OpenRA.Mods.Common/Traits/BotModules/SupportPowerBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/SupportPowerBotModule.cs
index be43996aa4..ff61177d95 100644
--- a/OpenRA.Mods.Common/Traits/BotModules/SupportPowerBotModule.cs
+++ b/OpenRA.Mods.Common/Traits/BotModules/SupportPowerBotModule.cs
@@ -83,14 +83,14 @@ namespace OpenRA.Mods.Common.Traits
{
if (powerDecision == null)
{
- AIUtils.BotDebug("{0} couldn't find powerDecision for {1}", player.PlayerName, sp.Info.OrderName);
+ AIUtils.BotDebug($"{player.ResolvedPlayerName} couldn't find powerDecision for {sp.Info.OrderName}");
continue;
}
var attackLocation = FindCoarseAttackLocationToSupportPower(sp);
if (attackLocation == null)
{
- AIUtils.BotDebug("{0} can't find suitable coarse attack location for support power {1}. Delaying rescan.", player.PlayerName, sp.Info.OrderName);
+ AIUtils.BotDebug($"{player.ResolvedPlayerName} can't find suitable coarse attack location for support power {sp.Info.OrderName}. Delaying rescan.");
waitingPowers[sp] += powerDecision.GetNextScanTime(world);
continue;
@@ -100,14 +100,14 @@ namespace OpenRA.Mods.Common.Traits
attackLocation = FindFineAttackLocationToSupportPower(sp, (CPos)attackLocation);
if (attackLocation == null)
{
- AIUtils.BotDebug("{0} can't find suitable final attack location for support power {1}. Delaying rescan.", player.PlayerName, sp.Info.OrderName);
+ AIUtils.BotDebug($"{player.ResolvedPlayerName} can't find suitable final attack location for support power {sp.Info.OrderName}. Delaying rescan.");
waitingPowers[sp] += powerDecision.GetNextScanTime(world);
continue;
}
// Valid target found, delay by a few ticks to avoid rescanning before power fires via order
- AIUtils.BotDebug("{0} found new target location {1} for support power {2}.", player.PlayerName, attackLocation, sp.Info.OrderName);
+ AIUtils.BotDebug($"{player.ResolvedPlayerName} found new target location {attackLocation} for support power {sp.Info.OrderName}.");
waitingPowers[sp] += 10;
// Note: SelectDirectionalTarget uses uint.MaxValue in ExtraData to indicate that the player did not pick a direction.
@@ -131,7 +131,7 @@ namespace OpenRA.Mods.Common.Traits
var powerDecision = powerDecisions[readyPower.Info.OrderName];
if (powerDecision == null)
{
- AIUtils.BotDebug("{0} couldn't find powerDecision for {1}", player.PlayerName, readyPower.Info.OrderName);
+ AIUtils.BotDebug($"{player.ResolvedPlayerName} couldn't find powerDecision for {readyPower.Info.OrderName}");
return null;
}
@@ -181,7 +181,7 @@ namespace OpenRA.Mods.Common.Traits
var powerDecision = powerDecisions[readyPower.Info.OrderName];
if (powerDecision == null)
{
- AIUtils.BotDebug("{0} couldn't find powerDecision for {1}", player.PlayerName, readyPower.Info.OrderName);
+ AIUtils.BotDebug($"{player.ResolvedPlayerName} couldn't find powerDecision for {readyPower.Info.OrderName}");
return null;
}
diff --git a/OpenRA.Mods.Common/Traits/Player/ConquestVictoryConditions.cs b/OpenRA.Mods.Common/Traits/Player/ConquestVictoryConditions.cs
index 0d8eae8401..644fa16032 100644
--- a/OpenRA.Mods.Common/Traits/Player/ConquestVictoryConditions.cs
+++ b/OpenRA.Mods.Common/Traits/Player/ConquestVictoryConditions.cs
@@ -105,7 +105,7 @@ namespace OpenRA.Mods.Common.Traits
if (info.SuppressNotifications)
return;
- TextNotificationsManager.AddSystemLine(PlayerIsDefeated, Translation.Arguments("player", player.PlayerName));
+ TextNotificationsManager.AddSystemLine(PlayerIsDefeated, Translation.Arguments("player", player.ResolvedPlayerName));
Game.RunAfterDelay(info.NotificationDelay, () =>
{
if (Game.IsCurrentWorld(player.World) && player == player.World.LocalPlayer)
@@ -121,7 +121,7 @@ namespace OpenRA.Mods.Common.Traits
if (info.SuppressNotifications)
return;
- TextNotificationsManager.AddSystemLine(PlayerIsVictorious, Translation.Arguments("player", player.PlayerName));
+ TextNotificationsManager.AddSystemLine(PlayerIsVictorious, Translation.Arguments("player", player.ResolvedPlayerName));
Game.RunAfterDelay(info.NotificationDelay, () =>
{
if (Game.IsCurrentWorld(player.World) && player == player.World.LocalPlayer)
diff --git a/OpenRA.Mods.Common/Traits/Player/DeveloperMode.cs b/OpenRA.Mods.Common/Traits/Player/DeveloperMode.cs
index 4b80f68b77..69d2e00522 100644
--- a/OpenRA.Mods.Common/Traits/Player/DeveloperMode.cs
+++ b/OpenRA.Mods.Common/Traits/Player/DeveloperMode.cs
@@ -275,7 +275,7 @@ namespace OpenRA.Mods.Common.Traits
return;
}
- var arguments = Translation.Arguments("cheat", order.OrderString, "player", self.Owner.PlayerName, "suffix", debugSuffix);
+ var arguments = Translation.Arguments("cheat", order.OrderString, "player", self.Owner.ResolvedPlayerName, "suffix", debugSuffix);
TextNotificationsManager.Debug(TranslationProvider.GetString(CheatUsed, arguments));
}
diff --git a/OpenRA.Mods.Common/Traits/Player/ModularBot.cs b/OpenRA.Mods.Common/Traits/Player/ModularBot.cs
index 83d857d00c..e4af985ea4 100644
--- a/OpenRA.Mods.Common/Traits/Player/ModularBot.cs
+++ b/OpenRA.Mods.Common/Traits/Player/ModularBot.cs
@@ -25,8 +25,9 @@ namespace OpenRA.Mods.Common.Traits
[Desc("Internal id for this bot.")]
public readonly string Type = null;
+ [TranslationReference]
[Desc("Human-readable name this bot uses.")]
- public readonly string Name = "Unnamed Bot";
+ public readonly string Name = null;
[Desc("Minimum portion of pending orders to issue each tick (e.g. 5 issues at least 1/5th of all pending orders). " +
"Excess orders remain queued for subsequent ticks.")]
diff --git a/OpenRA.Mods.Common/Traits/Player/StrategicVictoryConditions.cs b/OpenRA.Mods.Common/Traits/Player/StrategicVictoryConditions.cs
index 9e6dc647c9..06004f517b 100644
--- a/OpenRA.Mods.Common/Traits/Player/StrategicVictoryConditions.cs
+++ b/OpenRA.Mods.Common/Traits/Player/StrategicVictoryConditions.cs
@@ -151,7 +151,7 @@ namespace OpenRA.Mods.Common.Traits
if (info.SuppressNotifications)
return;
- TextNotificationsManager.AddSystemLine(PlayerIsDefeated, Translation.Arguments("player", player.PlayerName));
+ TextNotificationsManager.AddSystemLine(PlayerIsDefeated, Translation.Arguments("player", player.ResolvedPlayerName));
Game.RunAfterDelay(info.NotificationDelay, () =>
{
if (Game.IsCurrentWorld(player.World) && player == player.World.LocalPlayer)
@@ -167,7 +167,7 @@ namespace OpenRA.Mods.Common.Traits
if (info.SuppressNotifications)
return;
- TextNotificationsManager.AddSystemLine(PlayerIsVictorious, Translation.Arguments("player", player.PlayerName));
+ TextNotificationsManager.AddSystemLine(PlayerIsVictorious, Translation.Arguments("player", player.ResolvedPlayerName));
Game.RunAfterDelay(info.NotificationDelay, () =>
{
if (Game.IsCurrentWorld(player.World) && player == player.World.LocalPlayer)
diff --git a/OpenRA.Mods.Common/Traits/Render/WithNameTagDecoration.cs b/OpenRA.Mods.Common/Traits/Render/WithNameTagDecoration.cs
index 6574d82121..e97e0d1953 100644
--- a/OpenRA.Mods.Common/Traits/Render/WithNameTagDecoration.cs
+++ b/OpenRA.Mods.Common/Traits/Render/WithNameTagDecoration.cs
@@ -54,7 +54,7 @@ namespace OpenRA.Mods.Common.Traits.Render
font = Game.Renderer.Fonts[info.Font];
this.info = info;
- name = self.Owner.PlayerName;
+ name = self.Owner.ResolvedPlayerName;
if (name.Length > info.MaxLength)
name = name[..info.MaxLength];
}
@@ -73,7 +73,7 @@ namespace OpenRA.Mods.Common.Traits.Render
void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
{
- name = self.Owner.PlayerName;
+ name = self.Owner.ResolvedPlayerName;
if (name.Length > Info.MaxLength)
name = name[..Info.MaxLength];
}
diff --git a/OpenRA.Mods.Common/Traits/World/CreateMapPlayers.cs b/OpenRA.Mods.Common/Traits/World/CreateMapPlayers.cs
index 95f390aded..e268a56f44 100644
--- a/OpenRA.Mods.Common/Traits/World/CreateMapPlayers.cs
+++ b/OpenRA.Mods.Common/Traits/World/CreateMapPlayers.cs
@@ -55,7 +55,7 @@ namespace OpenRA.Mods.Common.Traits
var player = new GameInformation.Player
{
ClientIndex = client.Index,
- Name = Player.ResolvePlayerName(client, lobbyInfo.Clients, bots),
+ Name = client.Name,
IsHuman = client.Bot == null,
IsBot = client.Bot != null,
FactionName = resolvedFaction.Name,
diff --git a/OpenRA.Mods.Common/Traits/World/ValidateOrder.cs b/OpenRA.Mods.Common/Traits/World/ValidateOrder.cs
index bb1757f240..9011a5f72b 100644
--- a/OpenRA.Mods.Common/Traits/World/ValidateOrder.cs
+++ b/OpenRA.Mods.Common/Traits/World/ValidateOrder.cs
@@ -30,7 +30,9 @@ namespace OpenRA.Mods.Common.Traits
if (subjectClient == null)
{
- Log.Write("debug", $"Tick {world.WorldTick}: Order sent to {order.Subject.Owner.PlayerName}: resolved ClientIndex `{subjectClientId}` doesn't exist");
+ Log.Write("debug", $"Tick {world.WorldTick}: " +
+ $"Order sent to {order.Subject.Owner.ResolvedPlayerName}: " +
+ $"resolved ClientIndex `{subjectClientId}` doesn't exist");
return false;
}
diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameChatLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameChatLogic.cs
index d714ee4b9a..f8d4ecce5a 100644
--- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameChatLogic.cs
+++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameChatLogic.cs
@@ -81,7 +81,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
TextNotificationsManager.MutedPlayers.Add(c.Index, false);
tabCompletion.Commands = chatTraits.OfType().ToArray().SelectMany(x => x.Commands.Keys);
- tabCompletion.Names = orderManager.LobbyInfo.Clients.Select(c => c.Name).Distinct().ToList();
+ tabCompletion.Names = orderManager.LobbyInfo.Clients.Where(c => !c.IsBot).Select(c => c.Name).Distinct().ToList();
if (logicArgs.TryGetValue("Templates", out var templateIds))
{
diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/ObserverShroudSelectorLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/ObserverShroudSelectorLogic.cs
index 48542417ed..d5517ab410 100644
--- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/ObserverShroudSelectorLogic.cs
+++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/ObserverShroudSelectorLogic.cs
@@ -65,7 +65,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
public CameraOption(ObserverShroudSelectorLogic logic, Player p)
{
Player = p;
- Label = p.PlayerName;
+ Label = p.ResolvedPlayerName;
Color = p.Color;
Faction = p.Faction.InternalName;
IsSelected = () => p.World.RenderPlayer == p;
diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/ObserverStatsLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/ObserverStatsLogic.cs
index 57bf407a32..87d8273f9d 100644
--- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/ObserverStatsLogic.cs
+++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/ObserverStatsLogic.cs
@@ -259,7 +259,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
incomeGraph.GetSeries = () =>
players.Select(p => new LineGraphSeries(
- p.PlayerName,
+ p.ResolvedPlayerName,
p.Color,
(p.PlayerActor.TraitOrDefault() ?? new PlayerStatistics(p.PlayerActor)).IncomeSamples.Select(s => (float)s)));
}
@@ -271,7 +271,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
armyValueGraph.GetSeries = () =>
players.Select(p => new LineGraphSeries(
- p.PlayerName,
+ p.ResolvedPlayerName,
p.Color,
(p.PlayerActor.TraitOrDefault() ?? new PlayerStatistics(p.PlayerActor)).ArmySamples.Select(s => (float)s)));
}
diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/WorldTooltipLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/WorldTooltipLogic.cs
index 6cde3ad671..bd6644955d 100644
--- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/WorldTooltipLogic.cs
+++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/WorldTooltipLogic.cs
@@ -112,7 +112,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (showOwner)
{
flagFaction = o.Faction.InternalName;
- ownerName = o.PlayerName;
+ ownerName = o.ResolvedPlayerName;
widget.Bounds.Height = doubleHeight;
widget.Bounds.Width = Math.Max(widget.Bounds.Width,
owner.Bounds.X + ownerFont.Measure(ownerName).X + label.Bounds.X);
diff --git a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs
index e4d174ab4e..353ad82e6e 100644
--- a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs
+++ b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs
@@ -853,7 +853,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
while (players.Children.Count > idx)
players.RemoveChild(players.Children[idx]);
- tabCompletion.Names = orderManager.LobbyInfo.Clients.Select(c => c.Name).Distinct().ToList();
+ tabCompletion.Names = orderManager.LobbyInfo.Clients.Where(c => !c.IsBot).Select(c => c.Name).Distinct().ToList();
}
void UpdateDiscordStatus()
diff --git a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyUtils.cs b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyUtils.cs
index 8ddca8d643..0f59fdaec8 100644
--- a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyUtils.cs
+++ b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyUtils.cs
@@ -75,7 +75,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
foreach (var b in map.PlayerActorInfo.TraitInfos())
{
var botController = orderManager.LobbyInfo.Clients.FirstOrDefault(c => c.IsAdmin);
- bots.Add(new SlotDropDownOption(b.Name,
+ bots.Add(new SlotDropDownOption(TranslationProvider.GetString(b.Name),
$"slot_bot {slot.PlayerReference} {botController.Index} {b.Type}",
() => client != null && client.Bot == b.Type));
}
@@ -422,11 +422,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic
public static void SetupNameWidget(Widget parent, Session.Client c, OrderManager orderManager, WorldRenderer worldRenderer)
{
- var name = parent.Get("NAME");
- name.IsVisible = () => true;
- var font = Game.Renderer.Fonts[name.Font];
- var label = WidgetUtils.TruncateText(c.Name, name.Bounds.Width, font);
- name.GetText = () => label;
+ var label = parent.Get("NAME");
+ label.IsVisible = () => true;
+ var font = Game.Renderer.Fonts[label.Font];
+ var name = c.IsBot ? TranslationProvider.GetString(c.Name) : c.Name;
+ var text = WidgetUtils.TruncateText(name, label.Bounds.Width, font);
+ label.GetText = () => text;
SetupProfileWidget(parent, c, orderManager, worldRenderer);
}
@@ -444,7 +445,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var closed = TranslationProvider.GetString(Closed);
var open = TranslationProvider.GetString(Open);
- slot.GetText = () => truncated.Update(c != null ? c.Name : s.Closed ? closed : open);
+ slot.GetText = () => truncated.Update(c != null ?
+ c.IsBot ? TranslationProvider.GetString(c.Name) : c.Name
+ : s.Closed ? closed : open);
+
slot.OnMouseDown = _ => ShowSlotDropDown(slot, s, c, orderManager, map, modData);
// Ensure Name selector (if present) is hidden
diff --git a/OpenRA.Mods.Common/Widgets/Logic/ReplayBrowserLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/ReplayBrowserLogic.cs
index 763199512c..66344c3500 100644
--- a/OpenRA.Mods.Common/Widgets/Logic/ReplayBrowserLogic.cs
+++ b/OpenRA.Mods.Common/Widgets/Logic/ReplayBrowserLogic.cs
@@ -407,7 +407,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var ddb = panel.GetOrNull("FLT_PLAYER_DROPDOWNBUTTON");
if (ddb != null)
{
- var options = replays.SelectMany(r => r.GameInfo.Players.Select(p => p.Name)).Distinct(StringComparer.OrdinalIgnoreCase).ToList();
+ var options = replays.SelectMany(r => r.GameInfo.Players.Select(p => r.GameInfo.ResolvedPlayerName(p)))
+ .Distinct(StringComparer.OrdinalIgnoreCase).ToList();
+
options.Sort(StringComparer.OrdinalIgnoreCase);
options.Insert(0, null); // no filter
@@ -666,7 +668,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (!string.IsNullOrEmpty(filter.PlayerName))
{
var player = replay.GameInfo.Players.FirstOrDefault(
- p => string.Equals(filter.PlayerName, p.Name, StringComparison.CurrentCultureIgnoreCase));
+ p => string.Equals(filter.PlayerName, replay.GameInfo.ResolvedPlayerName(p), StringComparison.CurrentCultureIgnoreCase));
if (player == null)
return false;
@@ -751,7 +753,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var label = item.Get("LABEL");
var font = Game.Renderer.Fonts[label.Font];
- var name = WidgetUtils.TruncateText(o.Name, label.Bounds.Width, font);
+ var name = WidgetUtils.TruncateText(replay.GameInfo.ResolvedPlayerName(o), label.Bounds.Width, font);
label.GetText = () => name;
label.GetColor = () => color;
diff --git a/OpenRA.Mods.Common/Widgets/MapPreviewWidget.cs b/OpenRA.Mods.Common/Widgets/MapPreviewWidget.cs
index a8156027d8..8d96a0c9e3 100644
--- a/OpenRA.Mods.Common/Widgets/MapPreviewWidget.cs
+++ b/OpenRA.Mods.Common/Widgets/MapPreviewWidget.cs
@@ -30,7 +30,7 @@ namespace OpenRA.Mods.Common.Widgets
public SpawnOccupant(Session.Client client)
{
Color = client.Color;
- PlayerName = client.Name;
+ PlayerName = client.IsBot ? TranslationProvider.GetString(client.Name) : client.Name;;
Team = client.Team;
Faction = client.Faction;
SpawnPoint = client.SpawnPoint;
@@ -39,7 +39,7 @@ namespace OpenRA.Mods.Common.Widgets
public SpawnOccupant(GameInformation.Player player)
{
Color = player.Color;
- PlayerName = player.Name;
+ PlayerName = player.IsBot ? TranslationProvider.GetString(player.Name) : player.Name;
Team = player.Team;
Faction = player.FactionId;
SpawnPoint = player.SpawnPoint;
@@ -48,7 +48,7 @@ namespace OpenRA.Mods.Common.Widgets
public SpawnOccupant(GameClient player, bool suppressFaction)
{
Color = player.Color;
- PlayerName = player.Name;
+ PlayerName = player.IsBot ? TranslationProvider.GetString(player.Name) : player.Name;
Team = player.Team;
Faction = !suppressFaction ? player.Faction : null;
SpawnPoint = player.SpawnPoint;
diff --git a/OpenRA.Mods.Common/Widgets/SupportPowerTimerWidget.cs b/OpenRA.Mods.Common/Widgets/SupportPowerTimerWidget.cs
index 1b92511efc..9541eeb563 100644
--- a/OpenRA.Mods.Common/Widgets/SupportPowerTimerWidget.cs
+++ b/OpenRA.Mods.Common/Widgets/SupportPowerTimerWidget.cs
@@ -59,7 +59,7 @@ namespace OpenRA.Mods.Common.Widgets
{
var self = p.Instances[0].Self;
var time = WidgetUtils.FormatTime(p.RemainingTicks, false, self.World.Timestep);
- var text = TranslationProvider.GetString(Format, Translation.Arguments("player", self.Owner.PlayerName, "support-power", p.Name, "time", time));
+ var text = TranslationProvider.GetString(Format, Translation.Arguments("player", self.Owner.ResolvedPlayerName, "support-power", p.Name, "time", time));
var color = !p.Ready || Game.LocalTick % 50 < 25 ? self.OwnerColor() : Color.White;
diff --git a/OpenRA.Mods.Common/Widgets/WidgetUtils.cs b/OpenRA.Mods.Common/Widgets/WidgetUtils.cs
index a599d2fbf4..589b8e3352 100644
--- a/OpenRA.Mods.Common/Widgets/WidgetUtils.cs
+++ b/OpenRA.Mods.Common/Widgets/WidgetUtils.cs
@@ -20,6 +20,15 @@ namespace OpenRA.Mods.Common.Widgets
{
public static class WidgetUtils
{
+ [TranslationReference]
+ const string Gone = "label-client-state-disconnected";
+
+ [TranslationReference]
+ const string Won = "label-win-state-won";
+
+ [TranslationReference]
+ const string Lost = "label-win-state-lost";
+
public static string GetStatefulImageName(
string baseName, bool disabled = false, bool pressed = false, bool hover = false, bool focused = false)
{
@@ -324,19 +333,29 @@ namespace OpenRA.Mods.Common.Widgets
{
var client = p.World.LobbyInfo.ClientWithIndex(p.ClientIndex);
var nameFont = Game.Renderer.Fonts[label.Font];
- var name = new CachedTransform<(string Name, WinState WinState, Session.ClientState ClientState), string>(c =>
+ var name = new CachedTransform<(WinState WinState, Session.ClientState ClientState), string>(c =>
{
- var suffix = c.WinState == WinState.Undefined ? "" : " (" + c.WinState + ")";
- if (c.ClientState == Session.ClientState.Disconnected)
- suffix = " (Gone)";
+ var text = p.ResolvedPlayerName;
- return TruncateText(c.Name, label.Bounds.Width - nameFont.Measure(suffix).X, nameFont) + suffix;
+ var suffix = "";
+ if (c.WinState == WinState.Won)
+ suffix = $" ({TranslationProvider.GetString(Won)})";
+ else if (c.WinState == WinState.Lost)
+ suffix = $" ({TranslationProvider.GetString(Lost)})";
+
+ if (client.State == Session.ClientState.Disconnected)
+ suffix = $" ({TranslationProvider.GetString(Gone)})";
+
+ text += suffix;
+
+ var size = nameFont.Measure(text) - nameFont.Measure(p.ResolvedPlayerName);
+ return TruncateText(text, label.Bounds.Width - size.X, nameFont);
});
label.GetText = () =>
{
var clientState = client != null ? client.State : Session.ClientState.Ready;
- return name.Update((p.PlayerName, p.WinState, clientState));
+ return name.Update((p.WinState, clientState));
};
}
diff --git a/mods/cnc/languages/campaign/en.ftl b/mods/cnc/languages/campaign/en.ftl
index 5735ca32a7..37fd87ec2a 100644
--- a/mods/cnc/languages/campaign/en.ftl
+++ b/mods/cnc/languages/campaign/en.ftl
@@ -8,6 +8,10 @@ options-difficulty =
.normal = Normal
.hard = Hard
+## player
+bot-campaign-ai =
+ .name = Campaign Player AI
+
## campaign-maprules.yaml
actor-moneycrate-name = Money Crate
diff --git a/mods/cnc/languages/rules/en.ftl b/mods/cnc/languages/rules/en.ftl
index 00f22bd720..63c5a290e2 100644
--- a/mods/cnc/languages/rules/en.ftl
+++ b/mods/cnc/languages/rules/en.ftl
@@ -559,3 +559,13 @@ actor-truck =
Builds fast
Unarmed
.name = Supply Truck
+
+## ai.yaml
+bot-cabal =
+ .name = Cabal
+
+bot-watson =
+ .name = Watson
+
+bot-hal9001 =
+ .name = HAL 9001
diff --git a/mods/cnc/rules/ai.yaml b/mods/cnc/rules/ai.yaml
index 3d97c08375..a2aa944617 100644
--- a/mods/cnc/rules/ai.yaml
+++ b/mods/cnc/rules/ai.yaml
@@ -1,12 +1,12 @@
Player:
ModularBot@Cabal:
- Name: Cabal
+ Name: bot-cabal.name
Type: cabal
ModularBot@Watson:
- Name: Watson
+ Name: bot-watson.name
Type: watson
ModularBot@HAL9001:
- Name: HAL 9001
+ Name: bot-hal9001.name
Type: hal9001
GrantConditionOnBotOwner@cabal:
Condition: enable-cabal-ai
diff --git a/mods/cnc/rules/campaign-maprules.yaml b/mods/cnc/rules/campaign-maprules.yaml
index ed475b42d8..91bdc90cb4 100644
--- a/mods/cnc/rules/campaign-maprules.yaml
+++ b/mods/cnc/rules/campaign-maprules.yaml
@@ -51,7 +51,7 @@ Player:
DeveloperMode:
CheckboxVisible: False
ModularBot@CampaignAI:
- Name: Campaign Player AI
+ Name: bot-campaign-ai.name
Type: campaign
airstrike.proxy:
diff --git a/mods/common/languages/en.ftl b/mods/common/languages/en.ftl
index 98c99c2c2b..de537b506a 100644
--- a/mods/common/languages/en.ftl
+++ b/mods/common/languages/en.ftl
@@ -860,3 +860,14 @@ notification-desync-compare-logs = Out of sync in frame { $frame }.
## SupportPowerTimerWidget
support-power-timer = { $player }'s { $support-power }: { $time }
+
+## WidgetUtils
+label-win-state-won = Won
+label-win-state-lost = Lost
+
+## Player
+enumerated-bot-name =
+ { $name } { $number ->
+ *[zero] {""}
+ [other] { $number }
+ }
diff --git a/mods/d2k/languages/campaign/en.ftl b/mods/d2k/languages/campaign/en.ftl
index 0b5e7b03e9..f25a5dbe9f 100644
--- a/mods/d2k/languages/campaign/en.ftl
+++ b/mods/d2k/languages/campaign/en.ftl
@@ -8,6 +8,10 @@ options-difficulty =
.normal = Normal
.hard = Hard
+## player
+bot-campaign-ai =
+ .name = Campaign Player AI
+
## campaign-tooltips.yaml
neutral-prefix = Neutral
diff --git a/mods/d2k/languages/rules/en.ftl b/mods/d2k/languages/rules/en.ftl
index 9cbbda72ee..a5f95da060 100644
--- a/mods/d2k/languages/rules/en.ftl
+++ b/mods/d2k/languages/rules/en.ftl
@@ -569,3 +569,13 @@ meta-destroyabletile =
meta-destroyedtile =
.generic-name = Passage (repairable)
.name = Passage (repairable)
+
+## ai.yaml
+bot-omnius =
+ .name = Omnius
+
+bot-vidius =
+ .name = Vidious
+
+bot-gladius =
+ .name = Gladius
diff --git a/mods/d2k/rules/ai.yaml b/mods/d2k/rules/ai.yaml
index a0a1ab54d8..82a7fb290a 100644
--- a/mods/d2k/rules/ai.yaml
+++ b/mods/d2k/rules/ai.yaml
@@ -1,12 +1,12 @@
Player:
ModularBot@Omnius:
- Name: Omnius
+ Name: bot-omnius.name
Type: omnius
ModularBot@Vidius:
- Name: Vidious
+ Name: bot-vidius.name
Type: vidious
ModularBot@Gladius:
- Name: Gladius
+ Name: bot-gladius.name
Type: gladius
GrantConditionOnBotOwner@omnius:
Condition: enable-omnius-ai
diff --git a/mods/d2k/rules/campaign-rules.yaml b/mods/d2k/rules/campaign-rules.yaml
index a695bcf994..24531d8435 100644
--- a/mods/d2k/rules/campaign-rules.yaml
+++ b/mods/d2k/rules/campaign-rules.yaml
@@ -20,7 +20,7 @@ Player:
DeveloperMode:
CheckboxVisible: False
ModularBot@CampaignAI:
- Name: Campaign Player AI
+ Name: bot-campaign-ai.name
Type: campaign
World:
diff --git a/mods/ra/languages/campaign/en.ftl b/mods/ra/languages/campaign/en.ftl
index 1dee249463..47b778cc07 100644
--- a/mods/ra/languages/campaign/en.ftl
+++ b/mods/ra/languages/campaign/en.ftl
@@ -8,6 +8,10 @@ options-difficulty =
.normal = Normal
.hard = Hard
+## player
+bot-campaign-ai =
+ .name = Campaign Player AI
+
## campaign-rules.yaml
actor-crate-name = Crate
diff --git a/mods/ra/languages/rules/en.ftl b/mods/ra/languages/rules/en.ftl
index e696aad80f..1fba7d784c 100644
--- a/mods/ra/languages/rules/en.ftl
+++ b/mods/ra/languages/rules/en.ftl
@@ -849,3 +849,16 @@ actor-powerproxy-paratroopers =
.name = Paratroopers
.description = A Badger drops a squad of infantry
anywhere on the map.
+
+## ai.yaml
+bot-rush-ai =
+ .name = Rush AI
+
+bot-normal-ai =
+ .name = Normal AI
+
+bot-turtle-ai =
+ .name = Turtle AI
+
+bot-naval-ai =
+ .name = Naval AI
diff --git a/mods/ra/rules/ai.yaml b/mods/ra/rules/ai.yaml
index 89364491a2..938e793923 100644
--- a/mods/ra/rules/ai.yaml
+++ b/mods/ra/rules/ai.yaml
@@ -1,15 +1,15 @@
Player:
ModularBot@RushAI:
- Name: Rush AI
+ Name: bot-rush-ai.name
Type: rush
ModularBot@NormalAI:
- Name: Normal AI
+ Name: bot-normal-ai.name
Type: normal
ModularBot@TurtleAI:
- Name: Turtle AI
+ Name: bot-turtle-ai.name
Type: turtle
ModularBot@NavalAI:
- Name: Naval AI
+ Name: bot-naval-ai.name
Type: naval
GrantConditionOnBotOwner@rush:
Condition: enable-rush-ai
diff --git a/mods/ra/rules/campaign-rules.yaml b/mods/ra/rules/campaign-rules.yaml
index 553e862801..c415a2cbde 100644
--- a/mods/ra/rules/campaign-rules.yaml
+++ b/mods/ra/rules/campaign-rules.yaml
@@ -29,7 +29,7 @@ Player:
DeveloperMode:
CheckboxVisible: False
ModularBot@CampaignAI:
- Name: Campaign Player AI
+ Name: bot-campaign-ai.name
Type: campaign
World:
diff --git a/mods/ts/languages/rules/en.ftl b/mods/ts/languages/rules/en.ftl
index fee22feb2c..dfb12afe13 100644
--- a/mods/ts/languages/rules/en.ftl
+++ b/mods/ts/languages/rules/en.ftl
@@ -751,3 +751,7 @@ actor-cahosp =
.name = Civilian Hospital
.captured-desc = Provides infantry with self-healing.
.capturable-desc = Capture to enable self-healing for infantry.
+
+## ai.yaml
+bot-test-ai =
+ .name = Test AI
diff --git a/mods/ts/rules/ai.yaml b/mods/ts/rules/ai.yaml
index 951e43d797..cb4249df1c 100644
--- a/mods/ts/rules/ai.yaml
+++ b/mods/ts/rules/ai.yaml
@@ -1,6 +1,6 @@
Player:
- ModularBot@TestAI:
- Name: Test AI
+ ModularBot@bot-test-ai:
+ Name: bot-test-ai.name
Type: test
GrantConditionOnBotOwner@test:
Condition: enable-test-ai