Files
OpenRA/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs
RoosterDragon 2fde98a0d1 Fix uses of LabelWidget.Text and ButtonWidget.Text to use GetText instead.
The Text element of these widgets was changed from display text to a translation key as part of adding translation support. Functions interested in the display text need to invoke GetText instead. Lots of functions have not been updated, resulting in symptoms such as measuring the font size of the translation key rather than the display text and resizing a widget to the wrong size.

Update all callers to use GetText when getting or setting display text. This ensure their existing functionality that was intended to work in terms of the display text and not the translation key works as expected.
2024-01-15 15:16:58 +02:00

954 lines
33 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;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Traits;
using OpenRA.Network;
using OpenRA.Traits;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class LobbyLogic : ChromeLogic, INotificationHandler<TextNotification>
{
[TranslationReference]
const string Add = "options-slot-admin.add-bots";
[TranslationReference]
const string Remove = "options-slot-admin.remove-bots";
[TranslationReference]
const string ConfigureBots = "options-slot-admin.configure-bots";
[TranslationReference("count")]
const string NumberTeams = "options-slot-admin.teams-count";
[TranslationReference]
const string HumanVsBots = "options-slot-admin.humans-vs-bots";
[TranslationReference]
const string FreeForAll = "options-slot-admin.free-for-all";
[TranslationReference]
const string ConfigureTeams = "options-slot-admin.configure-teams";
[TranslationReference]
const string Back = "button-back";
[TranslationReference]
const string TeamChat = "button-team-chat";
[TranslationReference]
const string GeneralChat = "button-general-chat";
[TranslationReference("seconds")]
const string ChatAvailability = "label-chat-availability";
[TranslationReference]
const string ChatDisabled = "label-chat-disabled";
static readonly Action DoNothing = () => { };
readonly ModData modData;
readonly Action onStart;
readonly Action onExit;
readonly OrderManager orderManager;
readonly WorldRenderer worldRenderer;
readonly bool skirmishMode;
readonly Ruleset modRules;
readonly WebServices services;
enum PanelType { Players, Options, Music, Servers, Kick, ForceStart }
PanelType panel = PanelType.Players;
readonly Widget lobby;
readonly Widget editablePlayerTemplate;
readonly Widget nonEditablePlayerTemplate;
readonly Widget emptySlotTemplate;
readonly Widget editableSpectatorTemplate;
readonly Widget nonEditableSpectatorTemplate;
readonly Widget newSpectatorTemplate;
readonly ScrollPanelWidget lobbyChatPanel;
readonly Dictionary<TextNotificationPool, Widget> chatTemplates = new();
readonly TextFieldWidget chatTextField;
readonly CachedTransform<int, string> chatAvailableIn;
readonly string chatDisabled;
readonly ScrollPanelWidget players;
readonly Dictionary<string, LobbyFaction> factions = new();
readonly IColorPickerManagerInfo colorManager;
readonly TabCompletionLogic tabCompletion = new();
MapPreview map;
Session.MapStatus mapStatus;
bool chatEnabled;
bool disableTeamChat;
bool insufficientPlayerSpawns;
bool teamChat;
bool updateDiscordStatus = true;
bool resetOptionsButtonEnabled;
Dictionary<int, SpawnOccupant> spawnOccupants = new();
readonly string chatLineSound;
readonly string playerJoinedSound;
readonly string playerLeftSound;
readonly string lobbyOptionChangedSound;
bool MapIsPlayable => (mapStatus & Session.MapStatus.Playable) == Session.MapStatus.Playable;
// Listen for connection failures
void ConnectionStateChanged(OrderManager om, string password, NetworkConnection connection)
{
if (connection.ConnectionState == ConnectionState.NotConnected)
{
// Show connection failed dialog
Ui.CloseWindow();
void OnConnect()
{
Game.OpenWindow("SERVER_LOBBY", new WidgetArgs()
{
{ "onExit", onExit },
{ "onStart", onStart },
{ "skirmishMode", false }
});
}
Action<string> onRetry = pass => ConnectionLogic.Connect(connection.Target, pass, OnConnect, onExit);
var switchPanel = CurrentServerSettings.ServerExternalMod != null ? "CONNECTION_SWITCHMOD_PANEL" : "CONNECTIONFAILED_PANEL";
Ui.OpenWindow(switchPanel, new WidgetArgs()
{
{ "orderManager", om },
{ "connection", connection },
{ "password", password },
{ "onAbort", onExit },
{ "onQuit", null },
{ "onRetry", onRetry }
});
}
}
[ObjectCreator.UseCtor]
internal LobbyLogic(Widget widget, ModData modData, WorldRenderer worldRenderer, OrderManager orderManager,
Action onExit, Action onStart, bool skirmishMode, Dictionary<string, MiniYaml> logicArgs)
{
map = MapCache.UnknownMap;
lobby = widget;
this.modData = modData;
this.orderManager = orderManager;
this.worldRenderer = worldRenderer;
this.onStart = onStart;
this.onExit = onExit;
this.skirmishMode = skirmishMode;
// TODO: This needs to be reworked to support per-map tech levels, bots, etc.
modRules = modData.DefaultRules;
services = modData.Manifest.Get<WebServices>();
Game.LobbyInfoChanged += UpdateCurrentMap;
Game.LobbyInfoChanged += UpdatePlayerList;
Game.LobbyInfoChanged += UpdateDiscordStatus;
Game.LobbyInfoChanged += UpdateSpawnOccupants;
Game.LobbyInfoChanged += UpdateOptions;
Game.BeforeGameStart += OnGameStart;
Game.ConnectionStateChanged += ConnectionStateChanged;
ChromeMetrics.TryGet("ChatLineSound", out chatLineSound);
ChromeMetrics.TryGet("PlayerJoinedSound", out playerJoinedSound);
ChromeMetrics.TryGet("PlayerLeftSound", out playerLeftSound);
ChromeMetrics.TryGet("LobbyOptionChangedSound", out lobbyOptionChangedSound);
var name = lobby.GetOrNull<LabelWidget>("SERVER_NAME");
if (name != null)
name.GetText = () => orderManager.LobbyInfo.GlobalSettings.ServerName;
var mapContainer = Ui.LoadWidget("MAP_PREVIEW", lobby.Get("MAP_PREVIEW_ROOT"), new WidgetArgs
{
{ "orderManager", orderManager },
{ "getMap", (Func<(MapPreview, Session.MapStatus)>)(() => (map, mapStatus)) },
{
"onMouseDown", (Action<MapPreviewWidget, MapPreview, MouseInput>)((preview, mapPreview, mi) =>
LobbyUtils.SelectSpawnPoint(orderManager, preview, mapPreview, mi))
},
{ "getSpawnOccupants", (Func<Dictionary<int, SpawnOccupant>>)(() => spawnOccupants) },
{ "getDisabledSpawnPoints", (Func<HashSet<int>>)(() => orderManager.LobbyInfo.DisabledSpawnPoints) },
{ "showUnoccupiedSpawnpoints", true },
{ "mapUpdatesEnabled", true },
{
"onMapUpdate", (Action<string>)(uid =>
{
orderManager.IssueOrder(Order.Command("map " + uid));
Game.Settings.Server.Map = uid;
Game.Settings.Save();
})
},
});
mapContainer.IsVisible = () => panel != PanelType.Servers;
UpdateCurrentMap();
var playerBin = Ui.LoadWidget("LOBBY_PLAYER_BIN", lobby.Get("TOP_PANELS_ROOT"), new WidgetArgs());
playerBin.IsVisible = () => panel == PanelType.Players;
players = playerBin.Get<ScrollPanelWidget>("LOBBY_PLAYERS");
editablePlayerTemplate = players.Get("TEMPLATE_EDITABLE_PLAYER");
nonEditablePlayerTemplate = players.Get("TEMPLATE_NONEDITABLE_PLAYER");
emptySlotTemplate = players.Get("TEMPLATE_EMPTY");
editableSpectatorTemplate = players.Get("TEMPLATE_EDITABLE_SPECTATOR");
nonEditableSpectatorTemplate = players.Get("TEMPLATE_NONEDITABLE_SPECTATOR");
newSpectatorTemplate = players.Get("TEMPLATE_NEW_SPECTATOR");
colorManager = modRules.Actors[SystemActors.World].TraitInfo<IColorPickerManagerInfo>();
foreach (var f in modRules.Actors[SystemActors.World].TraitInfos<FactionInfo>())
factions.Add(f.InternalName, new LobbyFaction { Selectable = f.Selectable, Name = f.Name, Side = f.Side, Description = f.Description?.Replace(@"\n", "\n") });
var gameStarting = false;
Func<bool> configurationDisabled = () => !Game.IsHost || gameStarting ||
panel == PanelType.Kick || panel == PanelType.ForceStart || !MapIsPlayable ||
orderManager.LocalClient == null || orderManager.LocalClient.IsReady;
var mapButton = lobby.GetOrNull<ButtonWidget>("CHANGEMAP_BUTTON");
if (mapButton != null)
{
mapButton.IsVisible = () => panel != PanelType.Servers;
mapButton.IsDisabled = () => gameStarting || panel == PanelType.Kick || panel == PanelType.ForceStart ||
orderManager.LocalClient == null || orderManager.LocalClient.IsReady;
mapButton.OnClick = () =>
{
var onSelect = new Action<string>(uid =>
{
// Don't select the same map again, and handle map becoming unavailable
var status = modData.MapCache[uid].Status;
if (uid == map.Uid || (status != MapStatus.Available && status != MapStatus.DownloadAvailable))
return;
orderManager.IssueOrder(Order.Command("map " + uid));
Game.Settings.Server.Map = uid;
Game.Settings.Save();
});
// Check for updated maps, if the user has edited a map we'll preselect it for them
modData.MapCache.UpdateMaps();
Ui.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs()
{
{ "initialMap", modData.MapCache.PickLastModifiedMap(MapVisibility.Lobby) ?? map.Uid },
{ "remoteMapPool", orderManager.ServerMapPool },
{ "initialTab", MapClassification.System },
{ "onExit", modData.MapCache.UpdateMaps },
{ "onSelect", Game.IsHost ? onSelect : null },
{ "filter", MapVisibility.Lobby },
});
};
}
var slotsButton = lobby.GetOrNull<DropDownButtonWidget>("SLOTS_DROPDOWNBUTTON");
if (slotsButton != null)
{
slotsButton.IsVisible = () => panel != PanelType.Servers && panel != PanelType.Options;
slotsButton.IsDisabled = () => configurationDisabled() || panel != PanelType.Players ||
(orderManager.LobbyInfo.Slots.Values.All(s => !s.AllowBots) &&
!orderManager.LobbyInfo.Slots.Any(s => !s.Value.LockTeam && orderManager.LobbyInfo.ClientInSlot(s.Key) != null));
slotsButton.OnMouseDown = _ =>
{
var botTypes = map.PlayerActorInfo.TraitInfos<IBotInfo>().Select(t => t.Type);
var options = new Dictionary<string, IEnumerable<DropDownOption>>();
var botController = orderManager.LobbyInfo.Clients.FirstOrDefault(c => c.IsAdmin);
if (orderManager.LobbyInfo.Slots.Values.Any(s => s.AllowBots))
{
var botOptions = new List<DropDownOption>()
{
new()
{
Title = TranslationProvider.GetString(Add),
IsSelected = () => false,
OnClick = () =>
{
foreach (var slot in orderManager.LobbyInfo.Slots)
{
var bot = botTypes.Random(Game.CosmeticRandom);
var c = orderManager.LobbyInfo.ClientInSlot(slot.Key);
if (slot.Value.AllowBots && (c == null || c.Bot != null))
orderManager.IssueOrder(Order.Command($"slot_bot {slot.Key} {botController.Index} {bot}"));
}
}
}
};
if (orderManager.LobbyInfo.Clients.Any(c => c.Bot != null))
{
botOptions.Add(new DropDownOption()
{
Title = TranslationProvider.GetString(Remove),
IsSelected = () => false,
OnClick = () =>
{
foreach (var slot in orderManager.LobbyInfo.Slots)
{
var c = orderManager.LobbyInfo.ClientInSlot(slot.Key);
if (c != null && c.Bot != null)
orderManager.IssueOrder(Order.Command("slot_open " + slot.Value.PlayerReference));
}
}
});
}
options.Add(TranslationProvider.GetString(ConfigureBots), botOptions);
}
var teamCount = (orderManager.LobbyInfo.Slots.Count(s => !s.Value.LockTeam && orderManager.LobbyInfo.ClientInSlot(s.Key) != null) + 1) / 2;
if (teamCount >= 1)
{
var teamOptions = Enumerable.Range(2, teamCount - 1).Reverse().Select(d => new DropDownOption
{
Title = TranslationProvider.GetString(NumberTeams, Translation.Arguments("count", d)),
IsSelected = () => false,
OnClick = () => orderManager.IssueOrder(Order.Command($"assignteams {d}"))
}).ToList();
if (orderManager.LobbyInfo.Slots.Any(s => s.Value.AllowBots))
{
teamOptions.Add(new DropDownOption
{
Title = TranslationProvider.GetString(HumanVsBots),
IsSelected = () => false,
OnClick = () => orderManager.IssueOrder(Order.Command("assignteams 1"))
});
}
teamOptions.Add(new DropDownOption
{
Title = TranslationProvider.GetString(FreeForAll),
IsSelected = () => false,
OnClick = () => orderManager.IssueOrder(Order.Command("assignteams 0"))
});
options.Add(TranslationProvider.GetString(ConfigureTeams), teamOptions);
}
ScrollItemWidget SetupItem(DropDownOption option, ScrollItemWidget template)
{
var item = ScrollItemWidget.Setup(template, option.IsSelected, option.OnClick);
item.Get<LabelWidget>("LABEL").GetText = () => option.Title;
return item;
}
slotsButton.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 175, options, SetupItem);
};
}
var resetOptionsButton = lobby.GetOrNull<ButtonWidget>("RESET_OPTIONS_BUTTON");
if (resetOptionsButton != null)
{
resetOptionsButton.IsVisible = () => panel == PanelType.Options;
resetOptionsButton.IsDisabled = () => configurationDisabled() || !resetOptionsButtonEnabled;
resetOptionsButton.OnMouseDown = _ => orderManager.IssueOrder(Order.Command("reset_options"));
}
var optionsBin = Ui.LoadWidget("LOBBY_OPTIONS_BIN", lobby.Get("TOP_PANELS_ROOT"), new WidgetArgs()
{
{ "orderManager", orderManager },
{ "getMap", (Func<MapPreview>)(() => map) },
{ "configurationDisabled", configurationDisabled }
});
optionsBin.IsVisible = () => panel == PanelType.Options;
var musicBin = Ui.LoadWidget("LOBBY_MUSIC_BIN", lobby.Get("TOP_PANELS_ROOT"), new WidgetArgs
{
{ "onExit", DoNothing },
{ "world", worldRenderer.World }
});
musicBin.IsVisible = () => panel == PanelType.Music;
ServerListLogic serverListLogic = null;
if (!skirmishMode)
{
Action<GameServer> doNothingWithServer = _ => { };
var serversBin = Ui.LoadWidget("LOBBY_SERVERS_BIN", lobby.Get("TOP_PANELS_ROOT"), new WidgetArgs
{
{ "onJoin", doNothingWithServer },
});
serverListLogic = serversBin.LogicObjects.Select(l => l as ServerListLogic).FirstOrDefault(l => l != null);
serversBin.IsVisible = () => panel == PanelType.Servers;
}
var tabContainer = skirmishMode ? lobby.Get("SKIRMISH_TABS") : lobby.Get("MULTIPLAYER_TABS");
tabContainer.IsVisible = () => true;
var optionsTab = tabContainer.Get<ButtonWidget>("OPTIONS_TAB");
optionsTab.IsHighlighted = () => panel == PanelType.Options;
optionsTab.IsDisabled = OptionsTabDisabled;
optionsTab.OnClick = () => panel = PanelType.Options;
var playersTab = tabContainer.Get<ButtonWidget>("PLAYERS_TAB");
playersTab.IsHighlighted = () => panel == PanelType.Players;
playersTab.IsDisabled = () => panel == PanelType.Kick || panel == PanelType.ForceStart;
playersTab.OnClick = () => panel = PanelType.Players;
var musicTab = tabContainer.Get<ButtonWidget>("MUSIC_TAB");
musicTab.IsHighlighted = () => panel == PanelType.Music;
musicTab.IsDisabled = () => panel == PanelType.Kick || panel == PanelType.ForceStart;
musicTab.OnClick = () => panel = PanelType.Music;
var serversTab = tabContainer.GetOrNull<ButtonWidget>("SERVERS_TAB");
if (serversTab != null)
{
serversTab.IsHighlighted = () => panel == PanelType.Servers;
serversTab.IsDisabled = () => panel == PanelType.Kick || panel == PanelType.ForceStart;
serversTab.OnClick = () =>
{
// Refresh the list when switching to the servers tab
if (serverListLogic != null && panel != PanelType.Servers)
serverListLogic.RefreshServerList();
panel = PanelType.Servers;
};
}
// Force start panel
void StartGame()
{
// Refresh MapCache and check if the selected map is available before attempting to start the game
if (modData.MapCache[map.Uid].Status == MapStatus.Available)
{
gameStarting = true;
orderManager.IssueOrder(Order.Command("startgame"));
}
else
modData.MapCache.UpdateMaps();
}
bool StartDisabled() => map.Status != MapStatus.Available ||
orderManager.LobbyInfo.Slots.Any(sl => sl.Value.Required && orderManager.LobbyInfo.ClientInSlot(sl.Key) == null) ||
orderManager.LobbyInfo.Slots.All(sl => orderManager.LobbyInfo.ClientInSlot(sl.Key) == null) ||
(!orderManager.LobbyInfo.GlobalSettings.EnableSingleplayer && orderManager.LobbyInfo.NonBotPlayers.Count() < 2) ||
insufficientPlayerSpawns;
var startGameButton = lobby.GetOrNull<ButtonWidget>("START_GAME_BUTTON");
if (startGameButton != null)
{
startGameButton.IsDisabled = () => configurationDisabled() || StartDisabled();
startGameButton.OnClick = () =>
{
// Bots and admins don't count
if (orderManager.LobbyInfo.Clients.Any(c => c.Slot != null && !c.IsAdmin && c.Bot == null && !c.IsReady))
panel = PanelType.ForceStart;
else
StartGame();
};
}
var forceStartBin = Ui.LoadWidget("FORCE_START_DIALOG", lobby.Get("TOP_PANELS_ROOT"), new WidgetArgs());
forceStartBin.IsVisible = () => panel == PanelType.ForceStart;
forceStartBin.Get("KICK_WARNING").IsVisible = () => orderManager.LobbyInfo.Clients.Any(c => c.IsInvalid);
var forceStartButton = forceStartBin.Get<ButtonWidget>("OK_BUTTON");
forceStartButton.OnClick = StartGame;
forceStartButton.IsDisabled = StartDisabled;
forceStartBin.Get<ButtonWidget>("CANCEL_BUTTON").OnClick = () => panel = PanelType.Players;
var disconnectButton = lobby.Get<ButtonWidget>("DISCONNECT_BUTTON");
disconnectButton.OnClick = () =>
{
Ui.CloseWindow();
onExit();
Game.Sound.PlayNotification(modRules, null, "Sounds", playerLeftSound, null);
};
if (skirmishMode)
{
var disconnectButtonText = TranslationProvider.GetString(Back);
disconnectButton.GetText = () => disconnectButtonText;
}
if (logicArgs.TryGetValue("ChatTemplates", out var templateIds))
{
foreach (var item in templateIds.Nodes)
{
var key = FieldLoader.GetValue<TextNotificationPool>("key", item.Key);
chatTemplates[key] = Ui.LoadWidget(item.Value.Value, null, new WidgetArgs());
}
}
var chatMode = lobby.Get<ButtonWidget>("CHAT_MODE");
var team = TranslationProvider.GetString(TeamChat);
var all = TranslationProvider.GetString(GeneralChat);
chatMode.GetText = () => teamChat ? team : all;
chatMode.OnClick = () => teamChat ^= true;
chatMode.IsDisabled = () => disableTeamChat || !chatEnabled;
chatTextField = lobby.Get<TextFieldWidget>("CHAT_TEXTFIELD");
chatTextField.IsDisabled = () => !chatEnabled;
chatTextField.MaxLength = UnitOrders.ChatMessageMaxLength;
chatTextField.OnEnterKey = _ =>
{
if (chatTextField.Text.Length == 0)
return true;
// Always scroll to bottom when we've typed something
lobbyChatPanel.ScrollToBottom();
var teamNumber = 0U;
if (teamChat && orderManager.LocalClient != null)
teamNumber = orderManager.LocalClient.IsObserver ? uint.MaxValue : (uint)orderManager.LocalClient.Team;
orderManager.IssueOrder(Order.Chat(chatTextField.Text, teamNumber));
chatTextField.Text = "";
return true;
};
chatTextField.OnTabKey = e =>
{
if (!chatMode.Key.IsActivatedBy(e) || chatMode.IsDisabled())
{
chatTextField.Text = tabCompletion.Complete(chatTextField.Text);
chatTextField.CursorPosition = chatTextField.Text.Length;
}
else
chatMode.OnKeyPress(e);
return true;
};
chatTextField.OnEscKey = _ => chatTextField.YieldKeyboardFocus();
chatAvailableIn = new CachedTransform<int, string>(x => TranslationProvider.GetString(ChatAvailability, Translation.Arguments("seconds", x)));
chatDisabled = TranslationProvider.GetString(ChatDisabled);
lobbyChatPanel = lobby.Get<ScrollPanelWidget>("CHAT_DISPLAY");
lobbyChatPanel.RemoveChildren();
var settingsButton = lobby.GetOrNull<ButtonWidget>("SETTINGS_BUTTON");
if (settingsButton != null)
{
settingsButton.OnClick = () => Ui.OpenWindow("SETTINGS_PANEL", new WidgetArgs
{
{ "onExit", DoNothing },
{ "worldRenderer", worldRenderer }
});
}
if (logicArgs.TryGetValue("ChatLineSound", out var yaml))
chatLineSound = yaml.Value;
if (logicArgs.TryGetValue("PlayerJoinedSound", out yaml))
playerJoinedSound = yaml.Value;
if (logicArgs.TryGetValue("PlayerLeftSound", out yaml))
playerLeftSound = yaml.Value;
if (logicArgs.TryGetValue("LobbyOptionChangedSound", out yaml))
lobbyOptionChangedSound = yaml.Value;
}
bool disposed;
protected override void Dispose(bool disposing)
{
if (disposing && !disposed)
{
disposed = true;
Game.LobbyInfoChanged -= UpdateCurrentMap;
Game.LobbyInfoChanged -= UpdatePlayerList;
Game.LobbyInfoChanged -= UpdateDiscordStatus;
Game.LobbyInfoChanged -= UpdateSpawnOccupants;
Game.BeforeGameStart -= OnGameStart;
Game.ConnectionStateChanged -= ConnectionStateChanged;
}
base.Dispose(disposing);
}
bool OptionsTabDisabled()
{
return !MapIsPlayable || panel == PanelType.Kick || panel == PanelType.ForceStart;
}
public override void Tick()
{
if (panel == PanelType.Options && OptionsTabDisabled())
panel = PanelType.Players;
var chatWasEnabled = chatEnabled;
chatEnabled = worldRenderer.World.IsReplay || (Game.RunTime >= TextNotificationsManager.ChatDisabledUntil && TextNotificationsManager.ChatDisabledUntil != uint.MaxValue);
if (chatEnabled && !chatWasEnabled)
{
chatTextField.Text = "";
if (Ui.KeyboardFocusWidget == null)
chatTextField.TakeKeyboardFocus();
}
else if (!chatEnabled)
{
var remaining = 0;
if (TextNotificationsManager.ChatDisabledUntil != uint.MaxValue)
remaining = (int)(TextNotificationsManager.ChatDisabledUntil - Game.RunTime + 999) / 1000;
chatTextField.Text = remaining == 0 ? chatDisabled : chatAvailableIn.Update(remaining);
}
}
void INotificationHandler<TextNotification>.Handle(TextNotification notification)
{
var chatLine = chatTemplates[notification.Pool].Clone();
WidgetUtils.SetupTextNotification(chatLine, notification, lobbyChatPanel.Bounds.Width - lobbyChatPanel.ScrollbarWidth, true);
var scrolledToBottom = lobbyChatPanel.ScrolledToBottom;
lobbyChatPanel.AddChild(chatLine);
if (scrolledToBottom)
lobbyChatPanel.ScrollToBottom(smooth: true);
switch (notification.Pool)
{
case TextNotificationPool.Chat:
Game.Sound.PlayNotification(modRules, null, "Sounds", chatLineSound, null);
break;
case TextNotificationPool.System:
Game.Sound.PlayNotification(modRules, null, "Sounds", lobbyOptionChangedSound, null);
break;
case TextNotificationPool.Join:
Game.Sound.PlayNotification(modRules, null, "Sounds", playerJoinedSound, null);
break;
case TextNotificationPool.Leave:
Game.Sound.PlayNotification(modRules, null, "Sounds", playerLeftSound, null);
break;
}
}
void UpdateCurrentMap()
{
mapStatus = orderManager.LobbyInfo.GlobalSettings.MapStatus;
var uid = orderManager.LobbyInfo.GlobalSettings.Map;
if (map.Uid == uid)
return;
map = modData.MapCache[uid];
// Tell the server that we have the map
if (map.Status == MapStatus.Available)
orderManager.IssueOrder(Order.Command($"state {Session.ClientState.NotReady}"));
// We don't have the map
else if (map.Status != MapStatus.DownloadAvailable && Game.Settings.Game.AllowDownloading)
modData.MapCache.QueryRemoteMapDetails(services.MapRepository, new[] { uid });
}
void UpdatePlayerList()
{
if (orderManager.LocalClient == null)
return;
// Check if we are not assigned to any team, and are no spectator
// If we are a spectator, check if there are more and enable spectator chat
// Otherwise check if our assigned team has more players
if (orderManager.LocalClient.Team == 0 && !orderManager.LocalClient.IsObserver)
disableTeamChat = true;
else if (orderManager.LocalClient.IsObserver)
disableTeamChat = !orderManager.LobbyInfo.Clients.Any(c => c != orderManager.LocalClient && c.IsObserver);
else
disableTeamChat = !orderManager.LobbyInfo.Clients.Any(c =>
c != orderManager.LocalClient &&
c.Bot == null &&
c.Team == orderManager.LocalClient.Team);
insufficientPlayerSpawns = LobbyUtils.InsufficientEnabledSpawnPoints(map, orderManager.LobbyInfo);
if (disableTeamChat)
teamChat = false;
var isHost = Game.IsHost;
var idx = 0;
foreach (var kv in orderManager.LobbyInfo.Slots)
{
var key = kv.Key;
var slot = kv.Value;
var client = orderManager.LobbyInfo.ClientInSlot(key);
Widget template = null;
// get template for possible reuse
if (idx < players.Children.Count)
template = players.Children[idx];
if (client == null)
{
// Empty slot
if (template == null || template.Id != emptySlotTemplate.Id)
template = emptySlotTemplate.Clone();
if (isHost)
LobbyUtils.SetupEditableSlotWidget(template, slot, client, orderManager, map, modData);
else
LobbyUtils.SetupSlotWidget(template, modData, slot, client);
var join = template.Get<ButtonWidget>("JOIN");
join.IsVisible = () => !slot.Closed;
join.IsDisabled = () => orderManager.LocalClient.IsReady;
join.OnClick = () => orderManager.IssueOrder(Order.Command("slot " + key));
}
else if ((client.Index == orderManager.LocalClient.Index) ||
(client.Bot != null && isHost))
{
// Editable player in slot
if (template == null || template.Id != editablePlayerTemplate.Id)
template = editablePlayerTemplate.Clone();
LobbyUtils.SetupLatencyWidget(template, client, orderManager);
if (client.Bot != null)
LobbyUtils.SetupEditableSlotWidget(template, slot, client, orderManager, map, modData);
else
LobbyUtils.SetupEditableNameWidget(template, client, orderManager, worldRenderer);
LobbyUtils.SetupEditableColorWidget(template, slot, client, orderManager, worldRenderer, colorManager);
LobbyUtils.SetupEditableFactionWidget(template, slot, client, orderManager, factions);
LobbyUtils.SetupEditableTeamWidget(template, slot, client, orderManager, map);
LobbyUtils.SetupEditableHandicapWidget(template, slot, client, orderManager);
LobbyUtils.SetupEditableSpawnWidget(template, slot, client, orderManager, map);
LobbyUtils.SetupEditableReadyWidget(template, client, orderManager, map, MapIsPlayable);
}
else
{
// Non-editable player in slot
if (template == null || template.Id != nonEditablePlayerTemplate.Id)
template = nonEditablePlayerTemplate.Clone();
LobbyUtils.SetupLatencyWidget(template, client, orderManager);
LobbyUtils.SetupColorWidget(template, client);
LobbyUtils.SetupFactionWidget(template, client, factions);
if (isHost)
{
LobbyUtils.SetupEditableTeamWidget(template, slot, client, orderManager, map);
LobbyUtils.SetupEditableHandicapWidget(template, slot, client, orderManager);
LobbyUtils.SetupEditableSpawnWidget(template, slot, client, orderManager, map);
LobbyUtils.SetupPlayerActionWidget(template, client, orderManager, worldRenderer,
lobby, () => panel = PanelType.Kick, () => panel = PanelType.Players);
}
else
{
LobbyUtils.SetupNameWidget(template, client, orderManager, worldRenderer);
LobbyUtils.SetupTeamWidget(template, client);
LobbyUtils.SetupHandicapWidget(template, client);
LobbyUtils.SetupSpawnWidget(template, client);
}
LobbyUtils.SetupReadyWidget(template, client);
}
template.IsVisible = () => true;
if (idx >= players.Children.Count)
players.AddChild(template);
else if (players.Children[idx].Id != template.Id)
players.ReplaceChild(players.Children[idx], template);
idx++;
}
// Add spectators
foreach (var client in orderManager.LobbyInfo.Clients.Where(client => client.Slot == null))
{
Widget template = null;
var c = client;
// get template for possible reuse
if (idx < players.Children.Count)
template = players.Children[idx];
// Editable spectator
if (c.Index == orderManager.LocalClient.Index)
{
if (template == null || template.Id != editableSpectatorTemplate.Id)
template = editableSpectatorTemplate.Clone();
LobbyUtils.SetupEditableNameWidget(template, c, orderManager, worldRenderer);
if (client.IsAdmin)
LobbyUtils.SetupEditableReadyWidget(template, client, orderManager, map, MapIsPlayable);
else
LobbyUtils.HideReadyWidgets(template);
}
else
{
// Non-editable spectator
if (template == null || template.Id != nonEditableSpectatorTemplate.Id)
template = nonEditableSpectatorTemplate.Clone();
if (isHost)
LobbyUtils.SetupPlayerActionWidget(template, client, orderManager, worldRenderer,
lobby, () => panel = PanelType.Kick, () => panel = PanelType.Players);
else
LobbyUtils.SetupNameWidget(template, client, orderManager, worldRenderer);
if (client.IsAdmin)
LobbyUtils.SetupReadyWidget(template, client);
else
LobbyUtils.HideReadyWidgets(template);
}
LobbyUtils.SetupLatencyWidget(template, c, orderManager);
template.IsVisible = () => true;
if (idx >= players.Children.Count)
players.AddChild(template);
else if (players.Children[idx].Id != template.Id)
players.ReplaceChild(players.Children[idx], template);
idx++;
}
// Spectate button
if (orderManager.LocalClient.Slot != null)
{
Widget spec = null;
if (idx < players.Children.Count)
spec = players.Children[idx];
if (spec == null || spec.Id != newSpectatorTemplate.Id)
spec = newSpectatorTemplate.Clone();
LobbyUtils.SetupKickSpectatorsWidget(spec, orderManager, lobby,
() => panel = PanelType.Kick, () => panel = PanelType.Players, skirmishMode);
var btn = spec.Get<ButtonWidget>("SPECTATE");
btn.OnClick = () => orderManager.IssueOrder(Order.Command("spectate"));
btn.IsDisabled = () => orderManager.LocalClient.IsReady;
btn.IsVisible = () => orderManager.LobbyInfo.GlobalSettings.AllowSpectators
|| orderManager.LocalClient.IsAdmin;
spec.IsVisible = () => true;
if (idx >= players.Children.Count)
players.AddChild(spec);
else if (players.Children[idx].Id != spec.Id)
players.ReplaceChild(players.Children[idx], spec);
idx++;
}
while (players.Children.Count > idx)
players.RemoveChild(players.Children[idx]);
tabCompletion.Names = orderManager.LobbyInfo.Clients.Select(c => c.Name).Distinct().ToList();
}
void UpdateDiscordStatus()
{
var numberOfPlayers = 0;
var slots = 0;
if (!skirmishMode)
{
foreach (var kv in orderManager.LobbyInfo.Slots)
{
if (kv.Value.Closed)
continue;
slots++;
var client = orderManager.LobbyInfo.ClientInSlot(kv.Key);
if (client != null)
numberOfPlayers++;
}
}
// Add extra slots to keep the join button active for spectators
if (numberOfPlayers == slots && orderManager.LobbyInfo.GlobalSettings.AllowSpectators)
slots = numberOfPlayers + 1;
var details = map.Title + " - " + orderManager.LobbyInfo.GlobalSettings.ServerName;
if (updateDiscordStatus)
{
string secret = null;
if (orderManager.LobbyInfo.GlobalSettings.Dedicated)
{
var endpoint = CurrentServerSettings.Target.GetConnectEndPoints().First();
secret = string.Concat(endpoint.Address, "|", endpoint.Port);
}
var state = skirmishMode ? DiscordState.InSkirmishLobby : DiscordState.InMultiplayerLobby;
DiscordService.UpdateStatus(state, details, secret, numberOfPlayers, slots);
updateDiscordStatus = false;
}
else
{
if (!skirmishMode)
DiscordService.UpdatePlayers(numberOfPlayers, slots);
DiscordService.UpdateDetails(details);
}
}
void UpdateSpawnOccupants()
{
spawnOccupants = orderManager.LobbyInfo.Clients
.Where(c => c.SpawnPoint != 0)
.ToDictionary(c => c.SpawnPoint, c => new SpawnOccupant(c));
}
void UpdateOptions()
{
if (map == null || map.WorldActorInfo == null)
return;
var serverOptions = orderManager.LobbyInfo.GlobalSettings.LobbyOptions;
var mapOptions = map.PlayerActorInfo.TraitInfos<ILobbyOptions>()
.Concat(map.WorldActorInfo.TraitInfos<ILobbyOptions>())
.SelectMany(t => t.LobbyOptions(map))
.Where(o => o.IsVisible)
.OrderBy(o => o.DisplayOrder)
.ToArray();
resetOptionsButtonEnabled = mapOptions.Any(o => o.DefaultValue != serverOptions[o.Id].Value);
}
void OnGameStart()
{
Ui.CloseWindow();
var state = skirmishMode ? DiscordState.PlayingSkirmish : DiscordState.PlayingMultiplayer;
var details = map.Title + " - " + orderManager.LobbyInfo.GlobalSettings.ServerName;
DiscordService.UpdateStatus(state, details);
onStart();
}
}
public class LobbyFaction
{
public bool Selectable;
public string Name;
public string Description;
public string Side;
}
sealed class DropDownOption
{
public string Title;
public Func<bool> IsSelected = () => false;
public Action OnClick;
}
}