move connection UI to commons

This commit is contained in:
Matthias Mailänder
2015-01-10 09:37:23 +01:00
parent 94a3fc0186
commit 5aeb6eda06
19 changed files with 42 additions and 43 deletions

View File

@@ -0,0 +1,145 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 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. For more information,
* see COPYING.
*/
#endregion
using System;
using OpenRA.Network;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class ConnectionLogic
{
Action onConnect, onAbort;
Action<string> onRetry;
void ConnectionStateChanged(OrderManager om)
{
if (om.Connection.ConnectionState == ConnectionState.Connected)
{
CloseWindow();
onConnect();
}
else if (om.Connection.ConnectionState == ConnectionState.NotConnected)
{
CloseWindow();
Ui.OpenWindow("CONNECTIONFAILED_PANEL", new WidgetArgs()
{
{ "orderManager", om },
{ "onAbort", onAbort },
{ "onRetry", onRetry }
});
}
}
void CloseWindow()
{
Game.ConnectionStateChanged -= ConnectionStateChanged;
Ui.CloseWindow();
}
[ObjectCreator.UseCtor]
public ConnectionLogic(Widget widget, string host, int port, Action onConnect, Action onAbort, Action<string> onRetry)
{
this.onConnect = onConnect;
this.onAbort = onAbort;
this.onRetry = onRetry;
Game.ConnectionStateChanged += ConnectionStateChanged;
var panel = widget;
panel.Get<ButtonWidget>("ABORT_BUTTON").OnClick = () => { CloseWindow(); onAbort(); };
widget.Get<LabelWidget>("CONNECTING_DESC").GetText = () =>
"Connecting to {0}:{1}...".F(host, port);
}
public static void Connect(string host, int port, string password, Action onConnect, Action onAbort)
{
Game.JoinServer(host, port, password);
Action<string> onRetry = newPassword => Connect(host, port, newPassword, onConnect, onAbort);
Ui.OpenWindow("CONNECTING_PANEL", new WidgetArgs()
{
{ "host", host },
{ "port", port },
{ "onConnect", onConnect },
{ "onAbort", onAbort },
{ "onRetry", onRetry }
});
}
}
public class ConnectionFailedLogic
{
PasswordFieldWidget passwordField;
bool passwordOffsetAdjusted;
[ObjectCreator.UseCtor]
public ConnectionFailedLogic(Widget widget, OrderManager orderManager, Action onAbort, Action<string> onRetry)
{
var panel = widget;
var abortButton = panel.Get<ButtonWidget>("ABORT_BUTTON");
var retryButton = panel.Get<ButtonWidget>("RETRY_BUTTON");
abortButton.Visible = onAbort != null;
abortButton.OnClick = () => { Ui.CloseWindow(); onAbort(); };
retryButton.Visible = onRetry != null;
retryButton.OnClick = () =>
{
var password = passwordField != null && passwordField.IsVisible() ? passwordField.Text : orderManager.Password;
Ui.CloseWindow();
onRetry(password);
};
widget.Get<LabelWidget>("CONNECTING_DESC").GetText = () =>
"Could not connect to {0}:{1}".F(orderManager.Host, orderManager.Port);
var connectionError = widget.Get<LabelWidget>("CONNECTION_ERROR");
connectionError.GetText = () => orderManager.ServerError;
passwordField = panel.GetOrNull<PasswordFieldWidget>("PASSWORD");
if (passwordField != null)
{
passwordField.Text = orderManager.Password;
passwordField.IsVisible = () => orderManager.AuthenticationFailed;
var passwordLabel = widget.Get<LabelWidget>("PASSWORD_LABEL");
passwordLabel.IsVisible = passwordField.IsVisible;
passwordField.OnEnterKey = () => { retryButton.OnClick(); return true; };
passwordField.OnEscKey = () => { abortButton.OnClick(); return true; };
}
passwordOffsetAdjusted = false;
var connectionFailedTicker = panel.GetOrNull<LogicTickerWidget>("CONNECTION_FAILED_TICKER");
if (connectionFailedTicker != null)
{
connectionFailedTicker.OnTick = () =>
{
// Adjust the dialog once the AuthenticationError is parsed.
if (passwordField.IsVisible() && !passwordOffsetAdjusted)
{
var offset = passwordField.Bounds.Y - connectionError.Bounds.Y;
abortButton.Bounds.Y += offset;
retryButton.Bounds.Y += offset;
panel.Bounds.Height += offset;
panel.Bounds.Y -= offset / 2;
var background = panel.GetOrNull("CONNECTION_BACKGROUND");
if (background != null)
background.Bounds.Height += offset;
passwordOffsetAdjusted = true;
}
};
}
}
}
}

View File

@@ -0,0 +1,43 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 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. For more information,
* see COPYING.
*/
#endregion
using System;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class DirectConnectLogic
{
[ObjectCreator.UseCtor]
public DirectConnectLogic(Widget widget, Action onExit, Action openLobby)
{
var panel = widget;
var ipField = panel.Get<TextFieldWidget>("IP");
var portField = panel.Get<TextFieldWidget>("PORT");
var last = Game.Settings.Player.LastServer.Split(':');
ipField.Text = last.Length > 1 ? last[0] : "localhost";
portField.Text = last.Length == 2 ? last[1] : "1234";
panel.Get<ButtonWidget>("JOIN_BUTTON").OnClick = () =>
{
var port = Exts.WithDefault(1234, () => Exts.ParseIntegerInvariant(portField.Text));
Game.Settings.Player.LastServer = "{0}:{1}".F(ipField.Text, port);
Game.Settings.Save();
Ui.CloseWindow();
ConnectionLogic.Connect(ipField.Text, port, "", openLobby, onExit);
};
panel.Get<ButtonWidget>("BACK_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); };
}
}
}

View File

@@ -0,0 +1,219 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 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. For more information,
* see COPYING.
*/
#endregion
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA.Mods.Common.Commands;
using OpenRA.Mods.Common.Traits;
using OpenRA.Network;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class IngameChatLogic
{
readonly OrderManager orderManager;
readonly Ruleset modRules;
readonly ContainerWidget chatOverlay;
readonly ChatDisplayWidget chatOverlayDisplay;
readonly ContainerWidget chatChrome;
readonly ScrollPanelWidget chatScrollPanel;
readonly ContainerWidget chatTemplate;
readonly TextFieldWidget chatText;
readonly List<INotifyChat> chatTraits;
readonly TabCompletionLogic tabCompletion = new TabCompletionLogic();
bool disableTeamChat;
bool teamChat;
bool inDialog;
[ObjectCreator.UseCtor]
public IngameChatLogic(Widget widget, OrderManager orderManager, World world, Ruleset modRules)
{
this.orderManager = orderManager;
this.modRules = modRules;
chatTraits = world.WorldActor.TraitsImplementing<INotifyChat>().ToList();
var players = world.Players.Where(p => p != world.LocalPlayer && !p.NonCombatant && !p.IsBot);
disableTeamChat = world.LocalPlayer == null || world.LobbyInfo.IsSinglePlayer || !players.Any(p => p.IsAlliedWith(world.LocalPlayer));
teamChat = !disableTeamChat;
tabCompletion.Commands = chatTraits.OfType<ChatCommands>().SelectMany(x => x.Commands.Keys).ToList();
tabCompletion.Names = orderManager.LobbyInfo.Clients.Select(c => c.Name).Distinct().ToList();
var chatPanel = (ContainerWidget)widget;
chatOverlay = chatPanel.GetOrNull<ContainerWidget>("CHAT_OVERLAY");
if (chatOverlay != null)
{
chatOverlayDisplay = chatOverlay.Get<ChatDisplayWidget>("CHAT_DISPLAY");
chatOverlay.Visible = false;
}
else
inDialog = true;
chatChrome = chatPanel.Get<ContainerWidget>("CHAT_CHROME");
chatChrome.Visible = true;
var chatMode = chatChrome.Get<ButtonWidget>("CHAT_MODE");
chatMode.GetText = () => teamChat ? "Team" : "All";
chatMode.OnClick = () => teamChat ^= true;
chatMode.IsDisabled = () => disableTeamChat;
chatText = chatChrome.Get<TextFieldWidget>("CHAT_TEXTFIELD");
chatText.OnEnterKey = () =>
{
var team = teamChat && !disableTeamChat;
if (chatText.Text != "")
if (!chatText.Text.StartsWith("/"))
orderManager.IssueOrder(Order.Chat(team, chatText.Text.Trim()));
else
if (chatTraits != null)
{
var text = chatText.Text.Trim();
foreach (var trait in chatTraits)
trait.OnChat(orderManager.LocalClient.Name, text);
}
chatText.Text = "";
CloseChat();
return true;
};
chatText.OnTabKey = () =>
{
var previousText = chatText.Text;
chatText.Text = tabCompletion.Complete(chatText.Text);
chatText.CursorPosition = chatText.Text.Length;
if (chatText.Text == previousText)
return SwitchTeamChat();
else
return true;
};
chatText.OnEscKey = () => { CloseChat(); return true; };
if (!inDialog)
{
var chatClose = chatChrome.Get<ButtonWidget>("CHAT_CLOSE");
chatClose.OnClick += CloseChat;
chatPanel.OnKeyPress = e =>
{
if (e.Event == KeyInputEvent.Up)
return false;
if (!chatChrome.IsVisible() && (e.Key == Keycode.RETURN || e.Key == Keycode.KP_ENTER))
{
OpenChat();
return true;
}
return false;
};
}
chatScrollPanel = chatChrome.Get<ScrollPanelWidget>("CHAT_SCROLLPANEL");
chatTemplate = chatScrollPanel.Get<ContainerWidget>("CHAT_TEMPLATE");
chatScrollPanel.RemoveChildren();
chatScrollPanel.ScrollToBottom();
foreach (var chatLine in orderManager.ChatCache)
AddChatLine(chatLine.Color, chatLine.Name, chatLine.Text, true);
orderManager.AddChatLine += AddChatLineWrapper;
Game.BeforeGameStart += UnregisterEvents;
CloseChat();
}
bool SwitchTeamChat()
{
if (!disableTeamChat)
teamChat ^= true;
return true;
}
void UnregisterEvents()
{
orderManager.AddChatLine -= AddChatLineWrapper;
Game.BeforeGameStart -= UnregisterEvents;
}
public void OpenChat()
{
chatText.Text = "";
chatChrome.Visible = true;
chatScrollPanel.ScrollToBottom();
chatText.TakeKeyboardFocus();
if (!inDialog)
chatOverlay.Visible = false;
}
public void CloseChat()
{
if (inDialog)
return;
chatChrome.Visible = false;
chatText.YieldKeyboardFocus();
chatOverlay.Visible = true;
}
public void AddChatLineWrapper(Color c, string from, string text)
{
AddChatLine(c, from, text, false);
}
void AddChatLine(Color c, string from, string text, bool replayCache)
{
if (!(inDialog || replayCache))
chatOverlayDisplay.AddLine(c, from, text);
var template = chatTemplate.Clone();
var nameLabel = template.Get<LabelWidget>("NAME");
var textLabel = template.Get<LabelWidget>("TEXT");
var name = "";
if (!string.IsNullOrEmpty(from))
name = from + ":";
var font = Game.Renderer.Fonts[nameLabel.Font];
var nameSize = font.Measure(from);
nameLabel.GetColor = () => c;
nameLabel.GetText = () => name;
nameLabel.Bounds.Width = nameSize.X;
textLabel.Bounds.X += nameSize.X;
textLabel.Bounds.Width -= nameSize.X;
// Hack around our hacky wordwrap behavior: need to resize the widget to fit the text
text = WidgetUtils.WrapText(text, textLabel.Bounds.Width, font);
textLabel.GetText = () => text;
var dh = font.Measure(text).Y - textLabel.Bounds.Height;
if (dh > 0)
{
textLabel.Bounds.Height += dh;
template.Bounds.Height += dh;
}
var scrolledToBottom = chatScrollPanel.ScrolledToBottom;
chatScrollPanel.AddChild(template);
if (scrolledToBottom)
chatScrollPanel.ScrollToBottom(smooth: true);
if (!replayCache)
Sound.PlayNotification(modRules, null, "Sounds", "ChatLine", null);
}
}
}

View File

@@ -0,0 +1,88 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 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. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Net;
using OpenRA.Network;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class ClientTooltipLogic
{
[ObjectCreator.UseCtor]
public ClientTooltipLogic(Widget widget, TooltipContainerWidget tooltipContainer, OrderManager orderManager, int clientIndex)
{
var admin = widget.Get<LabelWidget>("ADMIN");
var adminFont = Game.Renderer.Fonts[admin.Font];
var latency = widget.Get<LabelWidget>("LATENCY");
var latencyFont = Game.Renderer.Fonts[latency.Font];
var latencyPrefix = widget.Get<LabelWidget>("LATENCY_PREFIX");
var latencyPrefixFont = Game.Renderer.Fonts[latencyPrefix.Font];
var ip = widget.Get<LabelWidget>("IP");
var addressFont = Game.Renderer.Fonts[ip.Font];
var location = widget.Get<LabelWidget>("LOCATION");
var locationFont = Game.Renderer.Fonts[location.Font];
var locationOffset = location.Bounds.Y;
var addressOffset = ip.Bounds.Y;
var latencyOffset = latency.Bounds.Y;
var tooltipHeight = widget.Bounds.Height;
var margin = widget.Bounds.Width;
tooltipContainer.IsVisible = () => (orderManager.LobbyInfo.ClientWithIndex(clientIndex) != null);
tooltipContainer.BeforeRender = () =>
{
var latencyPrefixSize = latencyPrefix.Bounds.X + latencyPrefixFont.Measure(latencyPrefix.GetText() + " ").X;
var width = Math.Max(locationFont.Measure(location.GetText()).X, Math.Max(adminFont.Measure(admin.GetText()).X, Math.Max(addressFont.Measure(ip.GetText()).X, latencyPrefixSize + latencyFont.Measure(latency.GetText()).X)));
widget.Bounds.Width = width + 2 * margin;
latency.Bounds.Width = widget.Bounds.Width;
ip.Bounds.Width = widget.Bounds.Width;
admin.Bounds.Width = widget.Bounds.Width;
location.Bounds.Width = widget.Bounds.Width;
ip.Bounds.Y = addressOffset;
latency.Bounds.Y = latencyOffset;
location.Bounds.Y = locationOffset;
widget.Bounds.Height = tooltipHeight;
if (admin.IsVisible())
{
ip.Bounds.Y += admin.Bounds.Height;
latency.Bounds.Y += admin.Bounds.Height;
location.Bounds.Y += admin.Bounds.Height;
widget.Bounds.Height += admin.Bounds.Height;
}
latencyPrefix.Bounds.Y = latency.Bounds.Y;
latency.Bounds.X = latencyPrefixSize;
};
admin.IsVisible = () => orderManager.LobbyInfo.ClientWithIndex(clientIndex).IsAdmin;
var client = orderManager.LobbyInfo.ClientWithIndex(clientIndex);
var ping = orderManager.LobbyInfo.PingFromClient(client);
latency.GetText = () => LobbyUtils.LatencyDescription(ping);
latency.GetColor = () => LobbyUtils.LatencyColor(ping);
var address = orderManager.LobbyInfo.ClientWithIndex(clientIndex).IpAddress;
if (clientIndex == orderManager.LocalClient.Index && UPnP.NatDevice != null
&& address == IPAddress.Loopback.ToString())
address = UPnP.NatDevice.GetExternalIP().ToString();
var cachedDescriptiveIP = LobbyUtils.DescriptiveIpAddress(address);
ip.GetText = () => cachedDescriptiveIP;
var cachedCountryLookup = LobbyUtils.LookupCountry(address);
location.GetText = () => cachedCountryLookup;
}
}
}

View File

@@ -0,0 +1,41 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 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. For more information,
* see COPYING.
*/
#endregion
using System;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
class KickClientLogic
{
[ObjectCreator.UseCtor]
public KickClientLogic(Widget widget, string clientName, Action<bool> okPressed, Action cancelPressed)
{
widget.Get<LabelWidget>("TITLE").GetText = () => "Kick {0}?".F(clientName);
var tempBan = false;
var preventRejoiningCheckbox = widget.Get<CheckboxWidget>("PREVENT_REJOINING_CHECKBOX");
preventRejoiningCheckbox.IsChecked = () => tempBan;
preventRejoiningCheckbox.OnClick = () => tempBan ^= true;
widget.Get<ButtonWidget>("OK_BUTTON").OnClick = () =>
{
widget.Parent.RemoveChild(widget);
okPressed(tempBan);
};
widget.Get<ButtonWidget>("CANCEL_BUTTON").OnClick = () =>
{
widget.Parent.RemoveChild(widget);
cancelPressed();
};
}
}
}

View File

@@ -0,0 +1,36 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 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. For more information,
* see COPYING.
*/
#endregion
using System;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
class KickSpectatorsLogic
{
[ObjectCreator.UseCtor]
public KickSpectatorsLogic(Widget widget, string clientCount, Action okPressed, Action cancelPressed)
{
widget.Get<LabelWidget>("TEXT").GetText = () => "Are you sure you want to kick {0} spectators?".F(clientCount);
widget.Get<ButtonWidget>("OK_BUTTON").OnClick = () =>
{
widget.Parent.RemoveChild(widget);
okPressed();
};
widget.Get<ButtonWidget>("CANCEL_BUTTON").OnClick = () =>
{
widget.Parent.RemoveChild(widget);
cancelPressed();
};
}
}
}

View File

@@ -0,0 +1,829 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 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. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading;
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
{
static readonly Action DoNothing = () => { };
public MapPreview Map = MapCache.UnknownMap;
readonly Action onStart;
readonly Action onExit;
readonly OrderManager orderManager;
readonly bool skirmishMode;
readonly Ruleset modRules;
enum PanelType { Players, Options, 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 chatPanel;
readonly Widget chatTemplate;
readonly ScrollPanelWidget players;
readonly Dictionary<string, LobbyCountry> countries = new Dictionary<string, LobbyCountry>();
readonly ColorPreviewManagerWidget colorPreview;
readonly TabCompletionLogic tabCompletion = new TabCompletionLogic();
readonly LabelWidget chatLabel;
bool teamChat;
// Listen for connection failures
void ConnectionStateChanged(OrderManager om)
{
if (om.Connection.ConnectionState == ConnectionState.NotConnected)
{
// Show connection failed dialog
CloseWindow();
Action onConnect = () =>
{
Game.OpenWindow("SERVER_LOBBY", new WidgetArgs()
{
{ "onExit", onExit },
{ "onStart", onStart },
{ "skirmishMode", false }
});
};
Action<string> onRetry = password => ConnectionLogic.Connect(om.Host, om.Port, password, onConnect, onExit);
Ui.OpenWindow("CONNECTIONFAILED_PANEL", new WidgetArgs()
{
{ "orderManager", om },
{ "onAbort", onExit },
{ "onRetry", onRetry }
});
}
}
void CloseWindow()
{
orderManager.AddChatLine -= AddChatLine;
Game.LobbyInfoChanged -= UpdateCurrentMap;
Game.LobbyInfoChanged -= UpdatePlayerList;
Game.BeforeGameStart -= OnGameStart;
Game.ConnectionStateChanged -= ConnectionStateChanged;
Ui.CloseWindow();
}
[ObjectCreator.UseCtor]
internal LobbyLogic(Widget widget, WorldRenderer worldRenderer, OrderManager orderManager,
Action onExit, Action onStart, bool skirmishMode, Ruleset modRules)
{
lobby = widget;
this.orderManager = orderManager;
this.onStart = onStart;
this.onExit = onExit;
this.skirmishMode = skirmishMode;
this.modRules = modRules;
orderManager.AddChatLine += AddChatLine;
Game.LobbyInfoChanged += UpdateCurrentMap;
Game.LobbyInfoChanged += UpdatePlayerList;
Game.BeforeGameStart += OnGameStart;
Game.ConnectionStateChanged += ConnectionStateChanged;
var name = lobby.GetOrNull<LabelWidget>("SERVER_NAME");
if (name != null)
name.GetText = () => orderManager.LobbyInfo.GlobalSettings.ServerName;
Ui.LoadWidget("LOBBY_MAP_PREVIEW", lobby.Get("MAP_PREVIEW_ROOT"), new WidgetArgs
{
{ "orderManager", orderManager },
{ "lobby", this }
});
UpdateCurrentMap();
players = Ui.LoadWidget<ScrollPanelWidget>("LOBBY_PLAYER_BIN", lobby.Get("PLAYER_BIN_ROOT"), new WidgetArgs());
players.IsVisible = () => panel == PanelType.Players;
var playerBinHeaders = lobby.GetOrNull<ContainerWidget>("LABEL_CONTAINER");
if (playerBinHeaders != null)
playerBinHeaders.IsVisible = () => panel == PanelType.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");
colorPreview = lobby.Get<ColorPreviewManagerWidget>("COLOR_MANAGER");
colorPreview.Color = Game.Settings.Player.Color;
countries.Add("random", new LobbyCountry { Name = "Any" });
foreach (var c in modRules.Actors["world"].Traits.WithInterface<CountryInfo>().Where(c => c.Selectable))
countries.Add(c.Race, new LobbyCountry { Name = c.Name, Side = c.Side, Description = c.Description });
var gameStarting = false;
Func<bool> configurationDisabled = () => !Game.IsHost || gameStarting ||
panel == PanelType.Kick || panel == PanelType.ForceStart ||
orderManager.LocalClient == null || orderManager.LocalClient.IsReady;
var mapButton = lobby.GetOrNull<ButtonWidget>("CHANGEMAP_BUTTON");
if (mapButton != null)
{
mapButton.IsDisabled = configurationDisabled;
mapButton.OnClick = () =>
{
var onSelect = new Action<string>(uid =>
{
// Don't select the same map again
if (uid == Map.Uid)
return;
orderManager.IssueOrder(Order.Command("map " + uid));
Game.Settings.Server.Map = uid;
Game.Settings.Save();
});
Ui.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs()
{
{ "initialMap", Map.Uid },
{ "onExit", DoNothing },
{ "onSelect", onSelect },
{ "filter", MapVisibility.Lobby },
});
};
}
var slotsButton = lobby.GetOrNull<DropDownButtonWidget>("SLOTS_DROPDOWNBUTTON");
if (slotsButton != null)
{
slotsButton.IsDisabled = () => configurationDisabled() || panel != PanelType.Players ||
Map.RuleStatus != MapRuleStatus.Cached || !orderManager.LobbyInfo.Slots.Values.Any(s => s.AllowBots || !s.LockTeam);
var botNames = modRules.Actors["player"].Traits.WithInterface<IBotInfo>().Select(t => t.Name);
slotsButton.OnMouseDown = _ =>
{
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 DropDownOption()
{
Title = "Add",
IsSelected = () => false,
OnClick = () =>
{
foreach (var slot in orderManager.LobbyInfo.Slots)
{
var bot = botNames.Random(Game.CosmeticRandom);
var c = orderManager.LobbyInfo.ClientInSlot(slot.Key);
if (slot.Value.AllowBots == true && (c == null || c.Bot != null))
orderManager.IssueOrder(Order.Command("slot_bot {0} {1} {2}".F(slot.Key, botController.Index, bot)));
}
}
}
};
if (orderManager.LobbyInfo.Clients.Any(c => c.Bot != null))
{
botOptions.Add(new DropDownOption()
{
Title = "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("Configure Bots", 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 = "{0} Teams".F(d),
IsSelected = () => false,
OnClick = () => orderManager.IssueOrder(Order.Command("assignteams {0}".F(d.ToString())))
}).ToList();
if (orderManager.LobbyInfo.Slots.Any(s => s.Value.AllowBots))
{
teamOptions.Add(new DropDownOption
{
Title = "Humans vs Bots",
IsSelected = () => false,
OnClick = () => orderManager.IssueOrder(Order.Command("assignteams 1"))
});
}
teamOptions.Add(new DropDownOption
{
Title = "Free for all",
IsSelected = () => false,
OnClick = () => orderManager.IssueOrder(Order.Command("assignteams 0"))
});
options.Add("Configure Teams", teamOptions);
}
Func<DropDownOption, ScrollItemWidget, ScrollItemWidget> setupItem = (option, 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 optionsBin = Ui.LoadWidget("LOBBY_OPTIONS_BIN", lobby, new WidgetArgs());
optionsBin.IsVisible = () => panel == PanelType.Options;
var optionsButton = lobby.Get<ButtonWidget>("OPTIONS_BUTTON");
optionsButton.IsDisabled = () => Map.RuleStatus != MapRuleStatus.Cached || panel == PanelType.Kick || panel == PanelType.ForceStart;
optionsButton.GetText = () => panel == PanelType.Options ? "Players" : "Options";
optionsButton.OnClick = () => panel = (panel == PanelType.Options) ? PanelType.Players : PanelType.Options;
// Force start panel
Action startGame = () =>
{
gameStarting = true;
orderManager.IssueOrder(Order.Command("startgame"));
};
var startGameButton = lobby.GetOrNull<ButtonWidget>("START_GAME_BUTTON");
if (startGameButton != null)
{
startGameButton.IsDisabled = () => configurationDisabled() || Map.RuleStatus != MapRuleStatus.Cached ||
orderManager.LobbyInfo.Slots.Any(sl => sl.Value.Required && orderManager.LobbyInfo.ClientInSlot(sl.Key) == null);
startGameButton.OnClick = () =>
{
Func<KeyValuePair<string, Session.Slot>, bool> notReady = sl =>
{
var cl = orderManager.LobbyInfo.ClientInSlot(sl.Key);
// Bots and admins don't count
return cl != null && !cl.IsAdmin && cl.Bot == null && !cl.IsReady;
};
if (orderManager.LobbyInfo.Slots.Any(notReady))
panel = PanelType.ForceStart;
else
startGame();
};
}
var forceStartBin = Ui.LoadWidget("FORCE_START_DIALOG", lobby, new WidgetArgs());
forceStartBin.IsVisible = () => panel == PanelType.ForceStart;
forceStartBin.Get("KICK_WARNING").IsVisible = () => orderManager.LobbyInfo.Clients.Any(c => c.IsInvalid);
forceStartBin.Get<ButtonWidget>("OK_BUTTON").OnClick = startGame;
forceStartBin.Get<ButtonWidget>("CANCEL_BUTTON").OnClick = () => panel = PanelType.Players;
// Options panel
var allowCheats = optionsBin.GetOrNull<CheckboxWidget>("ALLOWCHEATS_CHECKBOX");
if (allowCheats != null)
{
allowCheats.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.AllowCheats;
allowCheats.IsDisabled = () => Map.Status != MapStatus.Available || Map.Map.Options.Cheats.HasValue || configurationDisabled();
allowCheats.OnClick = () => orderManager.IssueOrder(Order.Command(
"allowcheats {0}".F(!orderManager.LobbyInfo.GlobalSettings.AllowCheats)));
}
var crates = optionsBin.GetOrNull<CheckboxWidget>("CRATES_CHECKBOX");
if (crates != null)
{
crates.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.Crates;
crates.IsDisabled = () => Map.Status != MapStatus.Available || Map.Map.Options.Crates.HasValue || configurationDisabled();
crates.OnClick = () => orderManager.IssueOrder(Order.Command(
"crates {0}".F(!orderManager.LobbyInfo.GlobalSettings.Crates)));
}
var creeps = optionsBin.GetOrNull<CheckboxWidget>("CREEPS_CHECKBOX");
if (creeps != null)
{
creeps.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.Creeps;
creeps.IsDisabled = () => Map.Status != MapStatus.Available || Map.Map.Options.Creeps.HasValue || configurationDisabled();
creeps.OnClick = () => orderManager.IssueOrder(Order.Command(
"creeps {0}".F(!orderManager.LobbyInfo.GlobalSettings.Creeps)));
}
var allybuildradius = optionsBin.GetOrNull<CheckboxWidget>("ALLYBUILDRADIUS_CHECKBOX");
if (allybuildradius != null)
{
allybuildradius.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.AllyBuildRadius;
allybuildradius.IsDisabled = () => Map.Status != MapStatus.Available || Map.Map.Options.AllyBuildRadius.HasValue || configurationDisabled();
allybuildradius.OnClick = () => orderManager.IssueOrder(Order.Command(
"allybuildradius {0}".F(!orderManager.LobbyInfo.GlobalSettings.AllyBuildRadius)));
}
var fragileAlliance = optionsBin.GetOrNull<CheckboxWidget>("FRAGILEALLIANCES_CHECKBOX");
if (fragileAlliance != null)
{
fragileAlliance.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.FragileAlliances;
fragileAlliance.IsDisabled = () => Map.Status != MapStatus.Available || Map.Map.Options.FragileAlliances.HasValue || configurationDisabled();
fragileAlliance.OnClick = () => orderManager.IssueOrder(Order.Command(
"fragilealliance {0}".F(!orderManager.LobbyInfo.GlobalSettings.FragileAlliances)));
}
var shortGame = optionsBin.GetOrNull<CheckboxWidget>("SHORTGAME_CHECKBOX");
if (shortGame != null)
{
shortGame.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.ShortGame;
shortGame.IsDisabled = () => Map.Status != MapStatus.Available || Map.Map.Options.ShortGame.HasValue || configurationDisabled();
shortGame.OnClick = () => orderManager.IssueOrder(Order.Command(
"shortgame {0}".F(!orderManager.LobbyInfo.GlobalSettings.ShortGame)));
}
var difficulty = optionsBin.GetOrNull<DropDownButtonWidget>("DIFFICULTY_DROPDOWNBUTTON");
if (difficulty != null)
{
difficulty.IsVisible = () => Map.Status == MapStatus.Available && Map.Map.Options.Difficulties.Any();
difficulty.IsDisabled = () => Map.Status != MapStatus.Available || configurationDisabled();
difficulty.GetText = () => orderManager.LobbyInfo.GlobalSettings.Difficulty;
difficulty.OnMouseDown = _ =>
{
var options = Map.Map.Options.Difficulties.Select(d => new DropDownOption
{
Title = d,
IsSelected = () => orderManager.LobbyInfo.GlobalSettings.Difficulty == d,
OnClick = () => orderManager.IssueOrder(Order.Command("difficulty {0}".F(d)))
});
Func<DropDownOption, ScrollItemWidget, ScrollItemWidget> setupItem = (option, template) =>
{
var item = ScrollItemWidget.Setup(template, option.IsSelected, option.OnClick);
item.Get<LabelWidget>("LABEL").GetText = () => option.Title;
return item;
};
difficulty.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", options.Count() * 30, options, setupItem);
};
optionsBin.Get<LabelWidget>("DIFFICULTY_DESC").IsVisible = difficulty.IsVisible;
}
var startingUnits = optionsBin.GetOrNull<DropDownButtonWidget>("STARTINGUNITS_DROPDOWNBUTTON");
if (startingUnits != null)
{
var startUnitsInfo = modRules.Actors["world"].Traits.WithInterface<MPStartUnitsInfo>();
var classes = startUnitsInfo.Select(a => a.Class).Distinct();
Func<string, string> className = c =>
{
var selectedClass = startUnitsInfo.Where(s => s.Class == c).Select(u => u.ClassName).FirstOrDefault();
return selectedClass != null ? selectedClass : c;
};
startingUnits.IsDisabled = () => Map.Status != MapStatus.Available || !Map.Map.Options.ConfigurableStartingUnits || configurationDisabled();
startingUnits.GetText = () => Map.Status != MapStatus.Available || !Map.Map.Options.ConfigurableStartingUnits ? "Not Available" : className(orderManager.LobbyInfo.GlobalSettings.StartingUnitsClass);
startingUnits.OnMouseDown = _ =>
{
var options = classes.Select(c => new DropDownOption
{
Title = className(c),
IsSelected = () => orderManager.LobbyInfo.GlobalSettings.StartingUnitsClass == c,
OnClick = () => orderManager.IssueOrder(Order.Command("startingunits {0}".F(c)))
});
Func<DropDownOption, ScrollItemWidget, ScrollItemWidget> setupItem = (option, template) =>
{
var item = ScrollItemWidget.Setup(template, option.IsSelected, option.OnClick);
item.Get<LabelWidget>("LABEL").GetText = () => option.Title;
return item;
};
startingUnits.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", options.Count() * 30, options, setupItem);
};
optionsBin.Get<LabelWidget>("STARTINGUNITS_DESC").IsVisible = startingUnits.IsVisible;
}
var startingCash = optionsBin.GetOrNull<DropDownButtonWidget>("STARTINGCASH_DROPDOWNBUTTON");
if (startingCash != null)
{
startingCash.IsDisabled = () => Map.Status != MapStatus.Available || Map.Map.Options.StartingCash.HasValue || configurationDisabled();
startingCash.GetText = () => Map.Status != MapStatus.Available || Map.Map.Options.StartingCash.HasValue ? "Not Available" : "${0}".F(orderManager.LobbyInfo.GlobalSettings.StartingCash);
startingCash.OnMouseDown = _ =>
{
var options = modRules.Actors["player"].Traits.Get<PlayerResourcesInfo>().SelectableCash.Select(c => new DropDownOption
{
Title = "${0}".F(c),
IsSelected = () => orderManager.LobbyInfo.GlobalSettings.StartingCash == c,
OnClick = () => orderManager.IssueOrder(Order.Command("startingcash {0}".F(c)))
});
Func<DropDownOption, ScrollItemWidget, ScrollItemWidget> setupItem = (option, template) =>
{
var item = ScrollItemWidget.Setup(template, option.IsSelected, option.OnClick);
item.Get<LabelWidget>("LABEL").GetText = () => option.Title;
return item;
};
startingCash.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", options.Count() * 30, options, setupItem);
};
}
var techLevel = optionsBin.GetOrNull<DropDownButtonWidget>("TECHLEVEL_DROPDOWNBUTTON");
if (techLevel != null)
{
var techTraits = modRules.Actors["player"].Traits.WithInterface<ProvidesTechPrerequisiteInfo>().ToArray();
techLevel.IsVisible = () => techTraits.Length > 0;
optionsBin.GetOrNull<LabelWidget>("TECHLEVEL_DESC").IsVisible = () => techTraits.Length > 0;
techLevel.IsDisabled = () => Map.Status != MapStatus.Available || Map.Map.Options.TechLevel != null || configurationDisabled() || techTraits.Length <= 1;
techLevel.GetText = () => Map.Status != MapStatus.Available || Map.Map.Options.TechLevel != null ? "Not Available" : "{0}".F(orderManager.LobbyInfo.GlobalSettings.TechLevel);
techLevel.OnMouseDown = _ =>
{
var options = techTraits.Select(c => new DropDownOption
{
Title = "{0}".F(c.Name),
IsSelected = () => orderManager.LobbyInfo.GlobalSettings.TechLevel == c.Name,
OnClick = () => orderManager.IssueOrder(Order.Command("techlevel {0}".F(c.Name)))
});
Func<DropDownOption, ScrollItemWidget, ScrollItemWidget> setupItem = (option, template) =>
{
var item = ScrollItemWidget.Setup(template, option.IsSelected, option.OnClick);
item.Get<LabelWidget>("LABEL").GetText = () => option.Title;
return item;
};
techLevel.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", options.Count() * 30, options, setupItem);
};
}
var enableShroud = optionsBin.GetOrNull<CheckboxWidget>("SHROUD_CHECKBOX");
if (enableShroud != null)
{
enableShroud.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.Shroud;
enableShroud.IsDisabled = () => Map.Status != MapStatus.Available || Map.Map.Options.Shroud.HasValue || configurationDisabled();
enableShroud.OnClick = () => orderManager.IssueOrder(Order.Command(
"shroud {0}".F(!orderManager.LobbyInfo.GlobalSettings.Shroud)));
}
var enableFog = optionsBin.GetOrNull<CheckboxWidget>("FOG_CHECKBOX");
if (enableFog != null)
{
enableFog.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.Fog;
enableFog.IsDisabled = () => Map.Status != MapStatus.Available || Map.Map.Options.Fog.HasValue || configurationDisabled();
enableFog.OnClick = () => orderManager.IssueOrder(Order.Command(
"fog {0}".F(!orderManager.LobbyInfo.GlobalSettings.Fog)));
}
var disconnectButton = lobby.Get<ButtonWidget>("DISCONNECT_BUTTON");
disconnectButton.OnClick = () => { CloseWindow(); onExit(); };
if (skirmishMode)
disconnectButton.Text = "Back";
chatLabel = lobby.Get<LabelWidget>("LABEL_CHATTYPE");
var chatTextField = lobby.Get<TextFieldWidget>("CHAT_TEXTFIELD");
chatTextField.TakeKeyboardFocus();
chatTextField.OnEnterKey = () =>
{
if (chatTextField.Text.Length == 0)
return true;
// Always scroll to bottom when we've typed something
chatPanel.ScrollToBottom();
orderManager.IssueOrder(Order.Chat(teamChat, chatTextField.Text));
chatTextField.Text = "";
return true;
};
chatTextField.OnTabKey = () =>
{
var previousText = chatTextField.Text;
chatTextField.Text = tabCompletion.Complete(chatTextField.Text);
chatTextField.CursorPosition = chatTextField.Text.Length;
if (chatTextField.Text == previousText)
return SwitchTeamChat();
else
return true;
};
chatPanel = lobby.Get<ScrollPanelWidget>("CHAT_DISPLAY");
chatTemplate = chatPanel.Get("CHAT_TEMPLATE");
chatPanel.RemoveChildren();
var musicButton = lobby.GetOrNull<ButtonWidget>("MUSIC_BUTTON");
if (musicButton != null)
musicButton.OnClick = () => Ui.OpenWindow("MUSIC_PANEL", new WidgetArgs
{
{ "onExit", DoNothing },
{ "world", orderManager.World }
});
var settingsButton = lobby.GetOrNull<ButtonWidget>("SETTINGS_BUTTON");
if (settingsButton != null)
{
settingsButton.OnClick = () => Ui.OpenWindow("SETTINGS_PANEL", new WidgetArgs
{
{ "onExit", DoNothing },
{ "worldRenderer", worldRenderer }
});
}
// Add a bot on the first lobbyinfo update
if (skirmishMode)
{
Game.LobbyInfoChanged += WidgetUtils.Once(() =>
{
var slot = orderManager.LobbyInfo.FirstEmptyBotSlot();
var bot = modRules.Actors["player"].Traits.WithInterface<IBotInfo>().Select(t => t.Name).FirstOrDefault();
var botController = orderManager.LobbyInfo.Clients.FirstOrDefault(c => c.IsAdmin);
if (slot != null && bot != null)
orderManager.IssueOrder(Order.Command("slot_bot {0} {1} {2}".F(slot, botController.Index, bot)));
});
}
}
void AddChatLine(Color c, string from, string text)
{
var template = chatTemplate.Clone();
var nameLabel = template.Get<LabelWidget>("NAME");
var timeLabel = template.Get<LabelWidget>("TIME");
var textLabel = template.Get<LabelWidget>("TEXT");
var name = from + ":";
var font = Game.Renderer.Fonts[nameLabel.Font];
var nameSize = font.Measure(from);
var time = DateTime.Now;
timeLabel.GetText = () => "{0:D2}:{1:D2}".F(time.Hour, time.Minute);
nameLabel.GetColor = () => c;
nameLabel.GetText = () => name;
nameLabel.Bounds.Width = nameSize.X;
textLabel.Bounds.X += nameSize.X;
textLabel.Bounds.Width -= nameSize.X;
// Hack around our hacky wordwrap behavior: need to resize the widget to fit the text
text = WidgetUtils.WrapText(text, textLabel.Bounds.Width, font);
textLabel.GetText = () => text;
var dh = font.Measure(text).Y - textLabel.Bounds.Height;
if (dh > 0)
{
textLabel.Bounds.Height += dh;
template.Bounds.Height += dh;
}
var scrolledToBottom = chatPanel.ScrolledToBottom;
chatPanel.AddChild(template);
if (scrolledToBottom)
chatPanel.ScrollToBottom(smooth: true);
Sound.PlayNotification(modRules, null, "Sounds", "ChatLine", null);
}
bool SwitchTeamChat()
{
teamChat ^= true;
chatLabel.Text = teamChat ? "Team:" : "Chat:";
return true;
}
void UpdateCurrentMap()
{
var uid = orderManager.LobbyInfo.GlobalSettings.Map;
if (Map.Uid == uid)
return;
Map = Game.ModData.MapCache[uid];
if (Map.Status == MapStatus.Available)
{
// Maps need to be validated and pre-loaded before they can be accessed
new Thread(_ =>
{
var map = Map;
map.CacheRules();
Game.RunAfterTick(() =>
{
// Map may have changed in the meantime
if (map != Map)
return;
if (map.RuleStatus != MapRuleStatus.Invalid)
{
// Tell the server that we have the map
orderManager.IssueOrder(Order.Command("state {0}".F(Session.ClientState.NotReady)));
// Restore default starting cash if the last map set it to something invalid
var pri = modRules.Actors["player"].Traits.Get<PlayerResourcesInfo>();
if (!Map.Map.Options.StartingCash.HasValue && !pri.SelectableCash.Contains(orderManager.LobbyInfo.GlobalSettings.StartingCash))
orderManager.IssueOrder(Order.Command("startingcash {0}".F(pri.DefaultCash)));
}
});
}).Start();
}
else if (Game.Settings.Game.AllowDownloading)
Game.ModData.MapCache.QueryRemoteMapDetails(new[] { uid });
}
void UpdatePlayerList()
{
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 (Game.IsHost)
LobbyUtils.SetupEditableSlotWidget(template, slot, client, orderManager, modRules);
else
LobbyUtils.SetupSlotWidget(template, 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 && Game.IsHost))
{
// Editable player in slot
if (template == null || template.Id != editablePlayerTemplate.Id)
template = editablePlayerTemplate.Clone();
LobbyUtils.SetupClientWidget(template, slot, client, orderManager, client.Bot == null);
if (client.Bot != null)
LobbyUtils.SetupEditableSlotWidget(template, slot, client, orderManager, modRules);
else
LobbyUtils.SetupEditableNameWidget(template, slot, client, orderManager);
LobbyUtils.SetupEditableColorWidget(template, slot, client, orderManager, colorPreview);
LobbyUtils.SetupEditableFactionWidget(template, slot, client, orderManager, countries);
LobbyUtils.SetupEditableTeamWidget(template, slot, client, orderManager, Map);
LobbyUtils.SetupEditableSpawnWidget(template, slot, client, orderManager, Map);
LobbyUtils.SetupEditableReadyWidget(template, slot, client, orderManager, Map);
}
else
{
// Non-editable player in slot
if (template == null || template.Id != nonEditablePlayerTemplate.Id)
template = nonEditablePlayerTemplate.Clone();
LobbyUtils.SetupClientWidget(template, slot, client, orderManager, client.Bot == null);
LobbyUtils.SetupNameWidget(template, slot, client);
LobbyUtils.SetupKickWidget(template, slot, client, orderManager, lobby,
() => panel = PanelType.Kick, () => panel = PanelType.Players);
LobbyUtils.SetupColorWidget(template, slot, client);
LobbyUtils.SetupFactionWidget(template, slot, client, countries);
LobbyUtils.SetupTeamWidget(template, slot, client);
LobbyUtils.SetupSpawnWidget(template, slot, client);
LobbyUtils.SetupReadyWidget(template, slot, 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, null, c, orderManager);
}
else
{
// Non-editable spectator
if (template == null || template.Id != nonEditableSpectatorTemplate.Id)
template = nonEditableSpectatorTemplate.Clone();
LobbyUtils.SetupNameWidget(template, null, client);
LobbyUtils.SetupKickWidget(template, null, client, orderManager, lobby,
() => panel = PanelType.Kick, () => panel = PanelType.Players);
}
LobbyUtils.SetupClientWidget(template, null, c, orderManager, true);
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 OnGameStart()
{
CloseWindow();
onStart();
}
class DropDownOption
{
public string Title;
public Func<bool> IsSelected;
public Action OnClick;
}
}
public class LobbyCountry
{
public string Name;
public string Description;
public string Side;
}
}

View File

@@ -0,0 +1,162 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 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. For more information,
* see COPYING.
*/
#endregion
using OpenRA.Network;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class LobbyMapPreviewLogic
{
[ObjectCreator.UseCtor]
internal LobbyMapPreviewLogic(Widget widget, OrderManager orderManager, LobbyLogic lobby)
{
var available = widget.GetOrNull("MAP_AVAILABLE");
if (available != null)
{
available.IsVisible = () => lobby.Map.Status == MapStatus.Available && lobby.Map.RuleStatus == MapRuleStatus.Cached;
var preview = available.Get<MapPreviewWidget>("MAP_PREVIEW");
preview.Preview = () => lobby.Map;
preview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, preview, lobby.Map, mi);
preview.SpawnOccupants = () => LobbyUtils.GetSpawnOccupants(orderManager.LobbyInfo, lobby.Map);
var title = available.GetOrNull<LabelWidget>("MAP_TITLE");
if (title != null)
title.GetText = () => lobby.Map.Title;
var type = available.GetOrNull<LabelWidget>("MAP_TYPE");
if (type != null)
type.GetText = () => lobby.Map.Type;
var author = available.GetOrNull<LabelWidget>("MAP_AUTHOR");
if (author != null)
author.GetText = () => "Created by {0}".F(lobby.Map.Author);
}
var invalid = widget.GetOrNull("MAP_INVALID");
if (invalid != null)
{
invalid.IsVisible = () => lobby.Map.Status == MapStatus.Available && lobby.Map.RuleStatus == MapRuleStatus.Invalid;
var preview = invalid.Get<MapPreviewWidget>("MAP_PREVIEW");
preview.Preview = () => lobby.Map;
preview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, preview, lobby.Map, mi);
preview.SpawnOccupants = () => LobbyUtils.GetSpawnOccupants(orderManager.LobbyInfo, lobby.Map);
var title = invalid.GetOrNull<LabelWidget>("MAP_TITLE");
if (title != null)
title.GetText = () => lobby.Map.Title;
var type = invalid.GetOrNull<LabelWidget>("MAP_TYPE");
if (type != null)
type.GetText = () => lobby.Map.Type;
}
var download = widget.GetOrNull("MAP_DOWNLOADABLE");
if (download != null)
{
download.IsVisible = () => lobby.Map.Status == MapStatus.DownloadAvailable;
var preview = download.Get<MapPreviewWidget>("MAP_PREVIEW");
preview.Preview = () => lobby.Map;
preview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, preview, lobby.Map, mi);
preview.SpawnOccupants = () => LobbyUtils.GetSpawnOccupants(orderManager.LobbyInfo, lobby.Map);
var title = download.GetOrNull<LabelWidget>("MAP_TITLE");
if (title != null)
title.GetText = () => lobby.Map.Title;
var type = download.GetOrNull<LabelWidget>("MAP_TYPE");
if (type != null)
type.GetText = () => lobby.Map.Type;
var author = download.GetOrNull<LabelWidget>("MAP_AUTHOR");
if (author != null)
author.GetText = () => "Created by {0}".F(lobby.Map.Author);
var install = download.GetOrNull<ButtonWidget>("MAP_INSTALL");
if (install != null)
install.OnClick = () => lobby.Map.Install();
}
var progress = widget.GetOrNull("MAP_PROGRESS");
if (progress != null)
{
progress.IsVisible = () => (lobby.Map.Status != MapStatus.Available || lobby.Map.RuleStatus == MapRuleStatus.Unknown) && lobby.Map.Status != MapStatus.DownloadAvailable;
var preview = progress.Get<MapPreviewWidget>("MAP_PREVIEW");
preview.Preview = () => lobby.Map;
preview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, preview, lobby.Map, mi);
preview.SpawnOccupants = () => LobbyUtils.GetSpawnOccupants(orderManager.LobbyInfo, lobby.Map);
var title = progress.GetOrNull<LabelWidget>("MAP_TITLE");
if (title != null)
title.GetText = () => lobby.Map.Title;
var type = progress.GetOrNull<LabelWidget>("MAP_TYPE");
if (type != null)
type.GetText = () => lobby.Map.Type;
var statusSearching = progress.GetOrNull("MAP_STATUS_SEARCHING");
if (statusSearching != null)
statusSearching.IsVisible = () => lobby.Map.Status == MapStatus.Searching;
var statusUnavailable = progress.GetOrNull("MAP_STATUS_UNAVAILABLE");
if (statusUnavailable != null)
statusUnavailable.IsVisible = () => lobby.Map.Status == MapStatus.Unavailable;
var statusError = progress.GetOrNull("MAP_STATUS_ERROR");
if (statusError != null)
statusError.IsVisible = () => lobby.Map.Status == MapStatus.DownloadError;
var statusDownloading = progress.GetOrNull<LabelWidget>("MAP_STATUS_DOWNLOADING");
if (statusDownloading != null)
{
statusDownloading.IsVisible = () => lobby.Map.Status == MapStatus.Downloading;
statusDownloading.GetText = () =>
{
if (lobby.Map.DownloadBytes == 0)
return "Connecting...";
// Server does not provide the total file length
if (lobby.Map.DownloadPercentage == 0)
return "Downloading {0} kB".F(lobby.Map.DownloadBytes / 1024);
return "Downloading {0} kB ({1}%)".F(lobby.Map.DownloadBytes / 1024, lobby.Map.DownloadPercentage);
};
}
var retry = progress.GetOrNull<ButtonWidget>("MAP_RETRY");
if (retry != null)
{
retry.IsVisible = () => (lobby.Map.Status == MapStatus.DownloadError || lobby.Map.Status == MapStatus.Unavailable) && lobby.Map != MapCache.UnknownMap;
retry.OnClick = () =>
{
if (lobby.Map.Status == MapStatus.DownloadError)
lobby.Map.Install();
else if (lobby.Map.Status == MapStatus.Unavailable)
Game.ModData.MapCache.QueryRemoteMapDetails(new[] { lobby.Map.Uid });
};
retry.GetText = () => lobby.Map.Status == MapStatus.DownloadError ? "Retry Install" : "Retry Search";
}
var progressbar = progress.GetOrNull<ProgressBarWidget>("MAP_PROGRESSBAR");
if (progressbar != null)
{
progressbar.IsIndeterminate = () => lobby.Map.DownloadPercentage == 0;
progressbar.GetPercentage = () => lobby.Map.DownloadPercentage;
progressbar.IsVisible = () => !retry.IsVisible();
}
}
}
}
}

View File

@@ -0,0 +1,475 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 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. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Net;
using OpenRA.Graphics;
using OpenRA.Network;
using OpenRA.Primitives;
using OpenRA.Traits;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public static class LobbyUtils
{
class SlotDropDownOption
{
public string Title;
public string Order;
public Func<bool> Selected;
public SlotDropDownOption(string title, string order, Func<bool> selected)
{
Title = title;
Order = order;
Selected = selected;
}
}
public static void ShowSlotDropDown(Ruleset rules, DropDownButtonWidget dropdown, Session.Slot slot,
Session.Client client, OrderManager orderManager)
{
var options = new Dictionary<string, IEnumerable<SlotDropDownOption>>() { { "Slot", new List<SlotDropDownOption>()
{
new SlotDropDownOption("Open", "slot_open " + slot.PlayerReference, () => (!slot.Closed && client == null)),
new SlotDropDownOption("Closed", "slot_close " + slot.PlayerReference, () => slot.Closed)
} } };
var bots = new List<SlotDropDownOption>();
if (slot.AllowBots)
{
foreach (var b in rules.Actors["player"].Traits.WithInterface<IBotInfo>().Select(t => t.Name))
{
var bot = b;
var botController = orderManager.LobbyInfo.Clients.FirstOrDefault(c => c.IsAdmin);
bots.Add(new SlotDropDownOption(bot,
"slot_bot {0} {1} {2}".F(slot.PlayerReference, botController.Index, bot),
() => client != null && client.Bot == bot));
}
}
options.Add(bots.Any() ? "Bots" : "Bots Disabled", bots);
Func<SlotDropDownOption, ScrollItemWidget, ScrollItemWidget> setupItem = (o, itemTemplate) =>
{
var item = ScrollItemWidget.Setup(itemTemplate,
o.Selected,
() => orderManager.IssueOrder(Order.Command(o.Order)));
item.Get<LabelWidget>("LABEL").GetText = () => o.Title;
return item;
};
dropdown.ShowDropDown<SlotDropDownOption>("LABEL_DROPDOWN_TEMPLATE", 167, options, setupItem);
}
public static void ShowTeamDropDown(DropDownButtonWidget dropdown, Session.Client client,
OrderManager orderManager, int teamCount)
{
Func<int, ScrollItemWidget, ScrollItemWidget> setupItem = (ii, itemTemplate) =>
{
var item = ScrollItemWidget.Setup(itemTemplate,
() => client.Team == ii,
() => orderManager.IssueOrder(Order.Command("team {0} {1}".F(client.Index, ii))));
item.Get<LabelWidget>("LABEL").GetText = () => ii == 0 ? "-" : ii.ToString();
return item;
};
var options = Enumerable.Range(0, teamCount + 1);
dropdown.ShowDropDown("TEAM_DROPDOWN_TEMPLATE", 150, options, setupItem);
}
public static void ShowSpawnDropDown(DropDownButtonWidget dropdown, Session.Client client,
OrderManager orderManager, IEnumerable<int> spawnPoints)
{
Func<int, ScrollItemWidget, ScrollItemWidget> setupItem = (ii, itemTemplate) =>
{
var item = ScrollItemWidget.Setup(itemTemplate,
() => client.SpawnPoint == ii,
() => SetSpawnPoint(orderManager, client, ii));
item.Get<LabelWidget>("LABEL").GetText = () => ii == 0 ? "-" : Convert.ToChar('A' - 1 + ii).ToString();
return item;
};
dropdown.ShowDropDown("SPAWN_DROPDOWN_TEMPLATE", 150, spawnPoints, setupItem);
}
public static void ShowRaceDropDown(DropDownButtonWidget dropdown, Session.Client client,
OrderManager orderManager, Dictionary<string, LobbyCountry> countries)
{
Func<string, ScrollItemWidget, ScrollItemWidget> setupItem = (race, itemTemplate) =>
{
var item = ScrollItemWidget.Setup(itemTemplate,
() => client.Country == race,
() => orderManager.IssueOrder(Order.Command("race {0} {1}".F(client.Index, race))));
var country = countries[race];
item.Get<LabelWidget>("LABEL").GetText = () => country.Name;
var flag = item.Get<ImageWidget>("FLAG");
flag.GetImageCollection = () => "flags";
flag.GetImageName = () => race;
item.GetTooltipText = () => country.Description;
return item;
};
var options = countries.GroupBy(c => c.Value.Side).ToDictionary(g => g.Key ?? "", g => g.Select(c => c.Key));
dropdown.ShowDropDown("RACE_DROPDOWN_TEMPLATE", 150, options, setupItem);
}
public static void ShowColorDropDown(DropDownButtonWidget color, Session.Client client,
OrderManager orderManager, ColorPreviewManagerWidget preview)
{
Action onExit = () =>
{
if (client.Bot == null)
{
Game.Settings.Player.Color = preview.Color;
Game.Settings.Save();
}
color.RemovePanel();
orderManager.IssueOrder(Order.Command("color {0} {1}".F(client.Index, preview.Color)));
};
Action<HSLColor> onChange = c => preview.Color = c;
var colorChooser = Game.LoadWidget(orderManager.World, "COLOR_CHOOSER", null, new WidgetArgs()
{
{ "onChange", onChange },
{ "initialColor", client.Color }
});
color.AttachPanel(colorChooser, onExit);
}
public static Dictionary<CPos, SpawnOccupant> GetSpawnOccupants(Session lobbyInfo, MapPreview preview)
{
var spawns = preview.SpawnPoints;
return lobbyInfo.Clients
.Where(c => (c.SpawnPoint - 1 >= 0) && (c.SpawnPoint - 1 < spawns.Count))
.ToDictionary(c => spawns[c.SpawnPoint - 1], c => new SpawnOccupant(c));
}
public static Dictionary<CPos, SpawnOccupant> GetSpawnOccupants(IEnumerable<GameInformation.Player> players, MapPreview preview)
{
var spawns = preview.SpawnPoints;
return players
.Where(c => (c.SpawnPoint - 1 >= 0) && (c.SpawnPoint - 1 < spawns.Count))
.ToDictionary(c => spawns[c.SpawnPoint - 1], c => new SpawnOccupant(c));
}
public static void SelectSpawnPoint(OrderManager orderManager, MapPreviewWidget mapPreview, MapPreview preview, MouseInput mi)
{
if (mi.Button != MouseButton.Left)
return;
if (!orderManager.LocalClient.IsObserver && orderManager.LocalClient.State == Session.ClientState.Ready)
return;
var spawnSize = new float2(ChromeProvider.GetImage("lobby-bits", "spawn-unclaimed").Bounds.Size);
var selectedSpawn = preview.SpawnPoints
.Select((sp, i) => Pair.New(mapPreview.ConvertToPreview(sp), i))
.Where(a => ((a.First - mi.Location).ToFloat2() / spawnSize * 2).LengthSquared <= 1)
.Select(a => a.Second + 1)
.FirstOrDefault();
var locals = orderManager.LobbyInfo.Clients.Where(c => c.Index == orderManager.LocalClient.Index || (Game.IsHost && c.Bot != null));
var playerToMove = locals.FirstOrDefault(c => ((selectedSpawn == 0) ^ (c.SpawnPoint == 0) && !c.IsObserver));
SetSpawnPoint(orderManager, playerToMove, selectedSpawn);
}
private static void SetSpawnPoint(OrderManager orderManager, Session.Client playerToMove, int selectedSpawn)
{
var owned = orderManager.LobbyInfo.Clients.Any(c => c.SpawnPoint == selectedSpawn);
if (selectedSpawn == 0 || !owned)
orderManager.IssueOrder(Order.Command("spawn {0} {1}".F((playerToMove ?? orderManager.LocalClient).Index, selectedSpawn)));
}
public static Color LatencyColor(Session.ClientPing ping)
{
if (ping == null)
return Color.Gray;
// Levels set relative to the default order lag of 3 net ticks (360ms)
// TODO: Adjust this once dynamic lag is implemented
if (ping.Latency < 0)
return Color.Gray;
if (ping.Latency < 300)
return Color.LimeGreen;
if (ping.Latency < 600)
return Color.Orange;
return Color.Red;
}
public static string LatencyDescription(Session.ClientPing ping)
{
if (ping == null)
return "Unknown";
if (ping.Latency < 0)
return "Unknown";
if (ping.Latency < 300)
return "Good";
if (ping.Latency < 600)
return "Moderate";
return "Poor";
}
public static string DescriptiveIpAddress(string ip)
{
if (ip == null)
return "Unknown Host";
if (ip == IPAddress.Loopback.ToString())
return "Local Host";
return ip;
}
public static string LookupCountry(string ip)
{
try
{
return Game.GeoIpDatabase.Country(ip).Country.Name;
}
catch (Exception e)
{
Log.Write("geoip", "LookupCountry failed: {0}", e);
return "Unknown Location";
}
}
public static void SetupClientWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager, bool visible)
{
parent.Get("ADMIN_INDICATOR").IsVisible = () => c.IsAdmin;
var block = parent.Get("LATENCY");
block.IsVisible = () => visible;
if (visible)
block.Get<ColorBlockWidget>("LATENCY_COLOR").GetColor = () => LatencyColor(
orderManager.LobbyInfo.PingFromClient(c));
var tooltip = parent.Get<ClientTooltipRegionWidget>("CLIENT_REGION");
tooltip.IsVisible = () => visible;
tooltip.Bind(orderManager, c.Index);
}
public static void SetupEditableNameWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager)
{
var name = parent.Get<TextFieldWidget>("NAME");
name.IsVisible = () => true;
name.IsDisabled = () => orderManager.LocalClient.IsReady;
name.Text = c.Name;
name.OnLoseFocus = () =>
{
name.Text = name.Text.Trim();
if (name.Text.Length == 0)
name.Text = c.Name;
if (name.Text == c.Name)
return;
orderManager.IssueOrder(Order.Command("name " + name.Text));
Game.Settings.Player.Name = name.Text;
Game.Settings.Save();
};
name.OnEnterKey = () => { name.YieldKeyboardFocus(); return true; };
}
public static void SetupNameWidget(Widget parent, Session.Slot s, Session.Client c)
{
var name = parent.Get<LabelWidget>("NAME");
name.GetText = () => c.Name;
}
public static void SetupEditableSlotWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager, Ruleset rules)
{
var slot = parent.Get<DropDownButtonWidget>("SLOT_OPTIONS");
slot.IsVisible = () => true;
slot.IsDisabled = () => orderManager.LocalClient.IsReady;
slot.GetText = () => c != null ? c.Name : s.Closed ? "Closed" : "Open";
slot.OnMouseDown = _ => ShowSlotDropDown(rules, slot, s, c, orderManager);
// Ensure Name selector (if present) is hidden
var name = parent.GetOrNull("NAME");
if (name != null)
name.IsVisible = () => false;
}
public static void SetupSlotWidget(Widget parent, Session.Slot s, Session.Client c)
{
var name = parent.Get<LabelWidget>("NAME");
name.IsVisible = () => true;
name.GetText = () => c != null ? c.Name : s.Closed ? "Closed" : "Open";
// Ensure Slot selector (if present) is hidden
var slot = parent.GetOrNull("SLOT_OPTIONS");
if (slot != null)
slot.IsVisible = () => false;
}
public static void SetupKickWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager, Widget lobby, Action before, Action after)
{
var button = parent.Get<ButtonWidget>("KICK");
button.IsVisible = () => Game.IsHost && c.Index != orderManager.LocalClient.Index;
button.IsDisabled = () => orderManager.LocalClient.IsReady;
Action<bool> okPressed = tempBan => { orderManager.IssueOrder(Order.Command("kick {0} {1}".F(c.Index, tempBan))); after(); };
button.OnClick = () =>
{
before();
Game.LoadWidget(null, "KICK_CLIENT_DIALOG", lobby, new WidgetArgs
{
{ "clientName", c.Name },
{ "okPressed", okPressed },
{ "cancelPressed", after }
});
};
}
public static void SetupKickSpectatorsWidget(Widget parent, OrderManager orderManager, Widget lobby, Action before, Action after, bool skirmishMode)
{
var checkBox = parent.Get<CheckboxWidget>("TOGGLE_SPECTATORS");
checkBox.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.AllowSpectators;
checkBox.IsVisible = () => orderManager.LocalClient.IsAdmin && !skirmishMode;
checkBox.IsDisabled = () => false;
Action okPressed = () =>
{
orderManager.IssueOrder(Order.Command("allow_spectators {0}".F(!orderManager.LobbyInfo.GlobalSettings.AllowSpectators)));
orderManager.IssueOrders(
orderManager.LobbyInfo.Clients.Where(
c => c.IsObserver && !c.IsAdmin).Select(
client => Order.Command("kick {0} {1}".F(client.Index, client.Name))).ToArray());
after();
};
checkBox.OnClick = () =>
{
before();
var spectatorCount = orderManager.LobbyInfo.Clients.Count(c => c.IsObserver);
if (spectatorCount > 0)
{
Game.LoadWidget(null, "KICK_SPECTATORS_DIALOG", lobby, new WidgetArgs
{
{ "clientCount", "{0}".F(spectatorCount) },
{ "okPressed", okPressed },
{ "cancelPressed", after }
});
}
else
{
orderManager.IssueOrder(Order.Command("allow_spectators {0}".F(!orderManager.LobbyInfo.GlobalSettings.AllowSpectators)));
after();
}
};
}
public static void SetupEditableColorWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager, ColorPreviewManagerWidget colorPreview)
{
var color = parent.Get<DropDownButtonWidget>("COLOR");
color.IsDisabled = () => (s != null && s.LockColor) || orderManager.LocalClient.IsReady;
color.OnMouseDown = _ => ShowColorDropDown(color, c, orderManager, colorPreview);
SetupColorWidget(color, s, c);
}
public static void SetupColorWidget(Widget parent, Session.Slot s, Session.Client c)
{
var color = parent.Get<ColorBlockWidget>("COLORBLOCK");
color.GetColor = () => c.Color.RGB;
}
public static void SetupEditableFactionWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager,
Dictionary<string, LobbyCountry> countries)
{
var dropdown = parent.Get<DropDownButtonWidget>("FACTION");
dropdown.IsDisabled = () => s.LockRace || orderManager.LocalClient.IsReady;
dropdown.OnMouseDown = _ => ShowRaceDropDown(dropdown, c, orderManager, countries);
var factionDescription = countries[c.Country].Description;
dropdown.GetTooltipText = () => factionDescription;
SetupFactionWidget(dropdown, s, c, countries);
}
public static void SetupFactionWidget(Widget parent, Session.Slot s, Session.Client c,
Dictionary<string, LobbyCountry> countries)
{
var factionName = parent.Get<LabelWidget>("FACTIONNAME");
factionName.GetText = () => countries[c.Country].Name;
var factionFlag = parent.Get<ImageWidget>("FACTIONFLAG");
factionFlag.GetImageName = () => c.Country;
factionFlag.GetImageCollection = () => "flags";
}
public static void SetupEditableTeamWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager, MapPreview map)
{
var dropdown = parent.Get<DropDownButtonWidget>("TEAM");
dropdown.IsDisabled = () => s.LockTeam || orderManager.LocalClient.IsReady;
dropdown.OnMouseDown = _ => ShowTeamDropDown(dropdown, c, orderManager, map.PlayerCount);
dropdown.GetText = () => (c.Team == 0) ? "-" : c.Team.ToString();
}
public static void SetupTeamWidget(Widget parent, Session.Slot s, Session.Client c)
{
parent.Get<LabelWidget>("TEAM").GetText = () => (c.Team == 0) ? "-" : c.Team.ToString();
}
public static void SetupEditableSpawnWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager, MapPreview map)
{
var dropdown = parent.Get<DropDownButtonWidget>("SPAWN");
dropdown.IsDisabled = () => s.LockSpawn || orderManager.LocalClient.IsReady;
dropdown.OnMouseDown = _ => ShowSpawnDropDown(dropdown, c, orderManager, Enumerable.Range(0, map.SpawnPoints.Count + 1).Except(orderManager.LobbyInfo.Clients.Where(client => client != c && client.SpawnPoint != 0).Select(client => client.SpawnPoint)));
dropdown.GetText = () => (c.SpawnPoint == 0) ? "-" : Convert.ToChar('A' - 1 + c.SpawnPoint).ToString();
}
public static void SetupSpawnWidget(Widget parent, Session.Slot s, Session.Client c)
{
parent.Get<LabelWidget>("SPAWN").GetText = () => (c.SpawnPoint == 0) ? "-" : Convert.ToChar('A' - 1 + c.SpawnPoint).ToString();
}
public static void SetupEditableReadyWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager, MapPreview map)
{
var status = parent.Get<CheckboxWidget>("STATUS_CHECKBOX");
status.IsChecked = () => orderManager.LocalClient.IsReady || c.Bot != null;
status.IsVisible = () => true;
status.IsDisabled = () => c.Bot != null || map.Status != MapStatus.Available || map.RuleStatus != MapRuleStatus.Cached;
var state = orderManager.LocalClient.IsReady ? Session.ClientState.NotReady : Session.ClientState.Ready;
status.OnClick = () => orderManager.IssueOrder(Order.Command("state {0}".F(state)));
}
public static void SetupReadyWidget(Widget parent, Session.Slot s, Session.Client c)
{
parent.Get<ImageWidget>("STATUS_IMAGE").IsVisible = () => c.IsReady || c.Bot != null;
}
public static void AddPlayerFlagAndName(ScrollItemWidget template, Player player)
{
var flag = template.Get<ImageWidget>("FLAG");
flag.GetImageName = () => player.Country.Race;
flag.GetImageCollection = () => "flags";
var playerName = template.Get<LabelWidget>("PLAYER");
var client = player.World.LobbyInfo.ClientWithIndex(player.ClientIndex);
playerName.GetText = () =>
{
if (client != null && client.State == Network.Session.ClientState.Disconnected)
return player.PlayerName + " (Gone)";
return player.PlayerName + (player.WinState == WinState.Undefined ? "" : " (" + player.WinState + ")");
};
playerName.GetColor = () => player.Color.RGB;
}
}
}

View File

@@ -0,0 +1,166 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 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. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Primitives;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class MapChooserLogic
{
string selectedUid;
// May be a subset of available maps if a mode filter is active
List<string> visibleMaps;
ScrollPanelWidget scrollpanel;
ScrollItemWidget itemTemplate;
string mapFilter;
string gameMode;
[ObjectCreator.UseCtor]
internal MapChooserLogic(Widget widget, string initialMap, Action onExit, Action<string> onSelect, MapVisibility filter)
{
selectedUid = WidgetUtils.ChooseInitialMap(initialMap);
var approving = new Action(() => { Ui.CloseWindow(); onSelect(selectedUid); });
var canceling = new Action(() => { Ui.CloseWindow(); onExit(); });
widget.Get<ButtonWidget>("BUTTON_OK").OnClick = approving;
widget.Get<ButtonWidget>("BUTTON_CANCEL").OnClick = canceling;
scrollpanel = widget.Get<ScrollPanelWidget>("MAP_LIST");
scrollpanel.Layout = new GridLayout(scrollpanel);
itemTemplate = scrollpanel.Get<ScrollItemWidget>("MAP_TEMPLATE");
var gameModeDropdown = widget.GetOrNull<DropDownButtonWidget>("GAMEMODE_FILTER");
if (gameModeDropdown != null)
{
var selectableMaps = Game.ModData.MapCache.Where(m => m.Status == MapStatus.Available && (m.Map.Visibility & filter) != 0);
var gameModes = selectableMaps
.GroupBy(m => m.Type)
.Select(g => Pair.New(g.Key, g.Count())).ToList();
// 'all game types' extra item
gameModes.Insert(0, Pair.New(null as string, selectableMaps.Count()));
Func<Pair<string, int>, string> showItem =
x => "{0} ({1})".F(x.First ?? "All Game Types", x.Second);
Func<Pair<string, int>, ScrollItemWidget, ScrollItemWidget> setupItem = (ii, template) =>
{
var item = ScrollItemWidget.Setup(template,
() => gameMode == ii.First,
() => { gameMode = ii.First; EnumerateMaps(onSelect, filter); });
item.Get<LabelWidget>("LABEL").GetText = () => showItem(ii);
return item;
};
gameModeDropdown.OnClick = () =>
gameModeDropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 210, gameModes, setupItem);
gameModeDropdown.GetText = () => showItem(gameModes.First(m => m.First == gameMode));
}
var mapfilterInput = widget.GetOrNull<TextFieldWidget>("MAPFILTER_INPUT");
if (mapfilterInput != null)
{
mapfilterInput.TakeKeyboardFocus();
mapfilterInput.OnEscKey = () =>
{
if (mapfilterInput.Text.Length == 0)
canceling();
else
{
mapFilter = mapfilterInput.Text = null;
EnumerateMaps(onSelect, filter);
}
return true;
};
mapfilterInput.OnEnterKey = () => { approving(); return true; };
mapfilterInput.OnTextEdited = () =>
{ mapFilter = mapfilterInput.Text; EnumerateMaps(onSelect, filter); };
}
var randomMapButton = widget.GetOrNull<ButtonWidget>("RANDOMMAP_BUTTON");
if (randomMapButton != null)
{
randomMapButton.OnClick = () =>
{
var uid = visibleMaps.Random(Game.CosmeticRandom);
selectedUid = uid;
scrollpanel.ScrollToItem(uid, smooth: true);
};
randomMapButton.IsDisabled = () => visibleMaps == null || visibleMaps.Count == 0;
}
EnumerateMaps(onSelect, filter);
}
void EnumerateMaps(Action<string> onSelect, MapVisibility filter)
{
var maps = Game.ModData.MapCache
.Where(m => m.Status == MapStatus.Available && (m.Map.Visibility & filter) != 0)
.Where(m => gameMode == null || m.Type == gameMode)
.Where(m => mapFilter == null || m.Title.IndexOf(mapFilter, StringComparison.OrdinalIgnoreCase) >= 0 || m.Author.IndexOf(mapFilter, StringComparison.OrdinalIgnoreCase) >= 0)
.OrderBy(m => m.PlayerCount)
.ThenBy(m => m.Title);
scrollpanel.RemoveChildren();
foreach (var loop in maps)
{
var preview = loop;
// Access the minimap to trigger async generation of the minimap.
preview.GetMinimap();
var item = ScrollItemWidget.Setup(preview.Uid, itemTemplate, () => selectedUid == preview.Uid, () => selectedUid = preview.Uid, () => { Ui.CloseWindow(); onSelect(preview.Uid); });
item.IsVisible = () => item.RenderBounds.IntersectsWith(scrollpanel.RenderBounds);
var titleLabel = item.Get<LabelWidget>("TITLE");
titleLabel.GetText = () => preview.Title;
var previewWidget = item.Get<MapPreviewWidget>("PREVIEW");
previewWidget.Preview = () => preview;
var detailsWidget = item.GetOrNull<LabelWidget>("DETAILS");
if (detailsWidget != null)
detailsWidget.GetText = () => "{0} ({1} players)".F(preview.Type, preview.PlayerCount);
var authorWidget = item.GetOrNull<LabelWidget>("AUTHOR");
if (authorWidget != null)
authorWidget.GetText = () => "Created by {0}".F(preview.Author);
var sizeWidget = item.GetOrNull<LabelWidget>("SIZE");
if (sizeWidget != null)
{
var size = preview.Bounds.Width + "x" + preview.Bounds.Height;
var numberPlayableCells = preview.Bounds.Width * preview.Bounds.Height;
if (numberPlayableCells >= 120 * 120) size += " (Huge)";
else if (numberPlayableCells >= 90 * 90) size += " (Large)";
else if (numberPlayableCells >= 60 * 60) size += " (Medium)";
else size += " (Small)";
sizeWidget.GetText = () => size;
}
scrollpanel.AddChild(item);
}
visibleMaps = maps.Select(m => m.Uid).ToList();
if (visibleMaps.Contains(selectedUid))
scrollpanel.ScrollToItem(selectedUid);
}
}
}

View File

@@ -0,0 +1,709 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 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. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenRA.FileFormats;
using OpenRA.Mods.Common.Widgets;
using OpenRA.Primitives;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class ReplayBrowserLogic
{
static Filter filter = new Filter();
Widget panel;
ScrollPanelWidget replayList, playerList;
ScrollItemWidget playerTemplate, playerHeader;
List<ReplayMetadata> replays;
Dictionary<ReplayMetadata, ReplayState> replayState = new Dictionary<ReplayMetadata, ReplayState>();
Dictionary<CPos, SpawnOccupant> selectedSpawns;
ReplayMetadata selectedReplay;
[ObjectCreator.UseCtor]
public ReplayBrowserLogic(Widget widget, Action onExit, Action onStart)
{
panel = widget;
playerList = panel.Get<ScrollPanelWidget>("PLAYER_LIST");
playerHeader = playerList.Get<ScrollItemWidget>("HEADER");
playerTemplate = playerList.Get<ScrollItemWidget>("TEMPLATE");
playerList.RemoveChildren();
panel.Get<ButtonWidget>("CANCEL_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); };
replayList = panel.Get<ScrollPanelWidget>("REPLAY_LIST");
var template = panel.Get<ScrollItemWidget>("REPLAY_TEMPLATE");
var mod = Game.ModData.Manifest.Mod;
var dir = Platform.ResolvePath("^", "Replays", mod.Id, mod.Version);
replayList.RemoveChildren();
if (Directory.Exists(dir))
{
using (new Support.PerfTimer("Load replays"))
{
replays = Directory
.GetFiles(dir, "*.rep")
.Select(ReplayMetadata.Read)
.Where(r => r != null)
.OrderByDescending(r => r.GameInfo.StartTimeUtc)
.ToList();
}
foreach (var replay in replays)
AddReplay(replay, template);
ApplyFilter();
}
else
replays = new List<ReplayMetadata>();
var watch = panel.Get<ButtonWidget>("WATCH_BUTTON");
watch.IsDisabled = () => selectedReplay == null || selectedReplay.GameInfo.MapPreview.Status != MapStatus.Available;
watch.OnClick = () => { WatchReplay(); onStart(); };
panel.Get("REPLAY_INFO").IsVisible = () => selectedReplay != null;
var preview = panel.Get<MapPreviewWidget>("MAP_PREVIEW");
preview.SpawnOccupants = () => selectedSpawns;
preview.Preview = () => selectedReplay != null ? selectedReplay.GameInfo.MapPreview : null;
var title = panel.GetOrNull<LabelWidget>("MAP_TITLE");
if (title != null)
title.GetText = () => selectedReplay != null ? selectedReplay.GameInfo.MapPreview.Title : null;
var type = panel.GetOrNull<LabelWidget>("MAP_TYPE");
if (type != null)
type.GetText = () => selectedReplay.GameInfo.MapPreview.Type;
panel.Get<LabelWidget>("DURATION").GetText = () => WidgetUtils.FormatTimeSeconds((int)selectedReplay.GameInfo.Duration.TotalSeconds);
SetupFilters();
SetupManagement();
}
void SetupFilters()
{
// Game type
{
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_GAMETYPE_DROPDOWNBUTTON");
if (ddb != null)
{
// Using list to maintain the order
var options = new List<Pair<GameType, string>>
{
Pair.New(GameType.Any, ddb.GetText()),
Pair.New(GameType.Singleplayer, "Singleplayer"),
Pair.New(GameType.Multiplayer, "Multiplayer")
};
var lookup = options.ToDictionary(kvp => kvp.First, kvp => kvp.Second);
ddb.GetText = () => lookup[filter.Type];
ddb.OnMouseDown = _ =>
{
Func<Pair<GameType, string>, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
{
var item = ScrollItemWidget.Setup(
tpl,
() => filter.Type == option.First,
() => { filter.Type = option.First; ApplyFilter(); });
item.Get<LabelWidget>("LABEL").GetText = () => option.Second;
return item;
};
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem);
};
}
}
// Date type
{
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_DATE_DROPDOWNBUTTON");
if (ddb != null)
{
// Using list to maintain the order
var options = new List<Pair<DateType, string>>
{
Pair.New(DateType.Any, ddb.GetText()),
Pair.New(DateType.Today, "Today"),
Pair.New(DateType.LastWeek, "Last 7 days"),
Pair.New(DateType.LastFortnight, "Last 14 days"),
Pair.New(DateType.LastMonth, "Last 30 days")
};
var lookup = options.ToDictionary(kvp => kvp.First, kvp => kvp.Second);
ddb.GetText = () => lookup[filter.Date];
ddb.OnMouseDown = _ =>
{
Func<Pair<DateType, string>, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
{
var item = ScrollItemWidget.Setup(
tpl,
() => filter.Date == option.First,
() => { filter.Date = option.First; ApplyFilter(); });
item.Get<LabelWidget>("LABEL").GetText = () => option.Second;
return item;
};
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem);
};
}
}
// Duration
{
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_DURATION_DROPDOWNBUTTON");
if (ddb != null)
{
// Using list to maintain the order
var options = new List<Pair<DurationType, string>>
{
Pair.New(DurationType.Any, ddb.GetText()),
Pair.New(DurationType.VeryShort, "Under 5 min"),
Pair.New(DurationType.Short, "Short (10 min)"),
Pair.New(DurationType.Medium, "Medium (30 min)"),
Pair.New(DurationType.Long, "Long (60+ min)")
};
var lookup = options.ToDictionary(kvp => kvp.First, kvp => kvp.Second);
ddb.GetText = () => lookup[filter.Duration];
ddb.OnMouseDown = _ =>
{
Func<Pair<DurationType, string>, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
{
var item = ScrollItemWidget.Setup(
tpl,
() => filter.Duration == option.First,
() => { filter.Duration = option.First; ApplyFilter(); });
item.Get<LabelWidget>("LABEL").GetText = () => option.Second;
return item;
};
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem);
};
}
}
// Map
{
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_MAPNAME_DROPDOWNBUTTON");
if (ddb != null)
{
var options = new HashSet<string>(replays.Select(r => r.GameInfo.MapTitle), StringComparer.OrdinalIgnoreCase).ToList();
options.Sort(StringComparer.OrdinalIgnoreCase);
options.Insert(0, null); // no filter
var anyText = ddb.GetText();
ddb.GetText = () => string.IsNullOrEmpty(filter.MapName) ? anyText : filter.MapName;
ddb.OnMouseDown = _ =>
{
Func<string, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
{
var item = ScrollItemWidget.Setup(
tpl,
() => string.Compare(filter.MapName, option, true) == 0,
() => { filter.MapName = option; ApplyFilter(); });
item.Get<LabelWidget>("LABEL").GetText = () => option ?? anyText;
return item;
};
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem);
};
}
}
// Players
{
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_PLAYER_DROPDOWNBUTTON");
if (ddb != null)
{
var options = new HashSet<string>(replays.SelectMany(r => r.GameInfo.Players.Select(p => p.Name)), StringComparer.OrdinalIgnoreCase).ToList();
options.Sort(StringComparer.OrdinalIgnoreCase);
options.Insert(0, null); // no filter
var anyText = ddb.GetText();
ddb.GetText = () => string.IsNullOrEmpty(filter.PlayerName) ? anyText : filter.PlayerName;
ddb.OnMouseDown = _ =>
{
Func<string, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
{
var item = ScrollItemWidget.Setup(
tpl,
() => string.Compare(filter.PlayerName, option, true) == 0,
() => { filter.PlayerName = option; ApplyFilter(); });
item.Get<LabelWidget>("LABEL").GetText = () => option ?? anyText;
return item;
};
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem);
};
}
}
// Outcome (depends on Player)
{
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_OUTCOME_DROPDOWNBUTTON");
if (ddb != null)
{
ddb.IsDisabled = () => string.IsNullOrEmpty(filter.PlayerName);
// Using list to maintain the order
var options = new List<Pair<WinState, string>>
{
Pair.New(WinState.Undefined, ddb.GetText()),
Pair.New(WinState.Lost, "Defeat"),
Pair.New(WinState.Won, "Victory")
};
var lookup = options.ToDictionary(kvp => kvp.First, kvp => kvp.Second);
ddb.GetText = () => lookup[filter.Outcome];
ddb.OnMouseDown = _ =>
{
Func<Pair<WinState, string>, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
{
var item = ScrollItemWidget.Setup(
tpl,
() => filter.Outcome == option.First,
() => { filter.Outcome = option.First; ApplyFilter(); });
item.Get<LabelWidget>("LABEL").GetText = () => option.Second;
return item;
};
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem);
};
}
}
// Faction (depends on Player)
{
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_FACTION_DROPDOWNBUTTON");
if (ddb != null)
{
ddb.IsDisabled = () => string.IsNullOrEmpty(filter.PlayerName);
var options = new HashSet<string>(replays.SelectMany(r => r.GameInfo.Players.Select(p => p.FactionName).Where(n => !string.IsNullOrEmpty(n))), StringComparer.OrdinalIgnoreCase).ToList();
options.Sort(StringComparer.OrdinalIgnoreCase);
options.Insert(0, null); // no filter
var anyText = ddb.GetText();
ddb.GetText = () => string.IsNullOrEmpty(filter.Faction) ? anyText : filter.Faction;
ddb.OnMouseDown = _ =>
{
Func<string, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
{
var item = ScrollItemWidget.Setup(
tpl,
() => string.Compare(filter.Faction, option, true) == 0,
() => { filter.Faction = option; ApplyFilter(); });
item.Get<LabelWidget>("LABEL").GetText = () => option ?? anyText;
return item;
};
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem);
};
}
}
// Reset button
{
var button = panel.Get<ButtonWidget>("FLT_RESET_BUTTON");
button.IsDisabled = () => filter.IsEmpty;
button.OnClick = () => { filter = new Filter(); ApplyFilter(); };
}
}
void SetupManagement()
{
{
var button = panel.Get<ButtonWidget>("MNG_RENSEL_BUTTON");
button.IsDisabled = () => selectedReplay == null;
button.OnClick = () =>
{
var r = selectedReplay;
var initialName = Path.GetFileNameWithoutExtension(r.FilePath);
var directoryName = Path.GetDirectoryName(r.FilePath);
var invalidChars = Path.GetInvalidFileNameChars();
ConfirmationDialogs.TextInputPrompt(
"Rename Replay",
"Enter a new file name:",
initialName,
onAccept: newName => RenameReplay(r, newName),
onCancel: null,
acceptText: "Rename",
cancelText: null,
inputValidator: newName =>
{
if (newName == initialName)
return false;
if (string.IsNullOrWhiteSpace(newName))
return false;
if (newName.IndexOfAny(invalidChars) >= 0)
return false;
if (File.Exists(Path.Combine(directoryName, newName)))
return false;
return true;
});
};
}
Action<ReplayMetadata, Action> onDeleteReplay = (r, after) =>
{
ConfirmationDialogs.PromptConfirmAction(
"Delete selected replay?",
"Delete replay '{0}'?".F(Path.GetFileNameWithoutExtension(r.FilePath)),
() =>
{
DeleteReplay(r);
if (after != null)
after.Invoke();
},
null,
"Delete");
};
{
var button = panel.Get<ButtonWidget>("MNG_DELSEL_BUTTON");
button.IsDisabled = () => selectedReplay == null;
button.OnClick = () =>
{
onDeleteReplay(selectedReplay, () =>
{
if (selectedReplay == null)
SelectFirstVisibleReplay();
});
};
}
{
var button = panel.Get<ButtonWidget>("MNG_DELALL_BUTTON");
button.IsDisabled = () => replayState.Count(kvp => kvp.Value.Visible) == 0;
button.OnClick = () =>
{
var list = replayState.Where(kvp => kvp.Value.Visible).Select(kvp => kvp.Key).ToList();
if (list.Count == 0)
return;
if (list.Count == 1)
{
onDeleteReplay(list[0], () => { if (selectedReplay == null) SelectFirstVisibleReplay(); });
return;
}
ConfirmationDialogs.PromptConfirmAction(
"Delete all selected replays?",
"Delete {0} replays?".F(list.Count),
() =>
{
list.ForEach(DeleteReplay);
if (selectedReplay == null)
SelectFirstVisibleReplay();
},
null,
"Delete All");
};
}
}
void RenameReplay(ReplayMetadata replay, string newFilenameWithoutExtension)
{
try
{
replay.RenameFile(newFilenameWithoutExtension);
replayState[replay].Item.Text = newFilenameWithoutExtension;
}
catch (Exception ex)
{
Log.Write("debug", ex.ToString());
return;
}
}
void DeleteReplay(ReplayMetadata replay)
{
try
{
File.Delete(replay.FilePath);
}
catch (Exception ex)
{
Game.Debug("Failed to delete replay file '{0}'. See the logs for details.", replay.FilePath);
Log.Write("debug", ex.ToString());
return;
}
if (replay == selectedReplay)
SelectReplay(null);
replayList.RemoveChild(replayState[replay].Item);
replays.Remove(replay);
replayState.Remove(replay);
}
bool EvaluateReplayVisibility(ReplayMetadata replay)
{
// Game type
if ((filter.Type == GameType.Multiplayer && replay.GameInfo.IsSinglePlayer) || (filter.Type == GameType.Singleplayer && !replay.GameInfo.IsSinglePlayer))
return false;
// Date type
if (filter.Date != DateType.Any)
{
TimeSpan t;
switch (filter.Date)
{
case DateType.Today:
t = TimeSpan.FromDays(1d);
break;
case DateType.LastWeek:
t = TimeSpan.FromDays(7d);
break;
case DateType.LastFortnight:
t = TimeSpan.FromDays(14d);
break;
case DateType.LastMonth:
default:
t = TimeSpan.FromDays(30d);
break;
}
if (replay.GameInfo.StartTimeUtc < DateTime.UtcNow - t)
return false;
}
// Duration
if (filter.Duration != DurationType.Any)
{
var minutes = replay.GameInfo.Duration.TotalMinutes;
switch (filter.Duration)
{
case DurationType.VeryShort:
if (minutes >= 5)
return false;
break;
case DurationType.Short:
if (minutes < 5 || minutes >= 20)
return false;
break;
case DurationType.Medium:
if (minutes < 20 || minutes >= 60)
return false;
break;
case DurationType.Long:
if (minutes < 60)
return false;
break;
}
}
// Map
if (!string.IsNullOrEmpty(filter.MapName) && string.Compare(filter.MapName, replay.GameInfo.MapTitle, true) != 0)
return false;
// Player
if (!string.IsNullOrEmpty(filter.PlayerName))
{
var player = replay.GameInfo.Players.FirstOrDefault(p => string.Compare(filter.PlayerName, p.Name, true) == 0);
if (player == null)
return false;
// Outcome
if (filter.Outcome != WinState.Undefined && filter.Outcome != player.Outcome)
return false;
// Faction
if (!string.IsNullOrEmpty(filter.Faction) && string.Compare(filter.Faction, player.FactionName, true) != 0)
return false;
}
return true;
}
void ApplyFilter()
{
foreach (var replay in replays)
replayState[replay].Visible = EvaluateReplayVisibility(replay);
if (selectedReplay == null || replayState[selectedReplay].Visible == false)
SelectFirstVisibleReplay();
replayList.Layout.AdjustChildren();
}
void SelectFirstVisibleReplay()
{
SelectReplay(replays.FirstOrDefault(r => replayState[r].Visible));
}
void SelectReplay(ReplayMetadata replay)
{
selectedReplay = replay;
selectedSpawns = (selectedReplay != null)
? LobbyUtils.GetSpawnOccupants(selectedReplay.GameInfo.Players, selectedReplay.GameInfo.MapPreview)
: new Dictionary<CPos, SpawnOccupant>();
if (replay == null)
return;
try
{
var players = replay.GameInfo.Players
.GroupBy(p => p.Team)
.OrderBy(g => g.Key);
var teams = new Dictionary<string, IEnumerable<GameInformation.Player>>();
var noTeams = players.Count() == 1;
foreach (var p in players)
{
var label = noTeams ? "Players" : p.Key == 0 ? "No Team" : "Team {0}".F(p.Key);
teams.Add(label, p);
}
playerList.RemoveChildren();
foreach (var kv in teams)
{
var group = kv.Key;
if (group.Length > 0)
{
var header = ScrollItemWidget.Setup(playerHeader, () => true, () => { });
header.Get<LabelWidget>("LABEL").GetText = () => group;
playerList.AddChild(header);
}
foreach (var option in kv.Value)
{
var o = option;
var color = o.Color.RGB;
var item = ScrollItemWidget.Setup(playerTemplate, () => false, () => { });
var label = item.Get<LabelWidget>("LABEL");
label.GetText = () => o.Name;
label.GetColor = () => color;
var flag = item.Get<ImageWidget>("FLAG");
flag.GetImageCollection = () => "flags";
flag.GetImageName = () => o.FactionId;
playerList.AddChild(item);
}
}
}
catch (Exception e)
{
Log.Write("debug", "Exception while parsing replay: {0}", e);
SelectReplay(null);
}
}
void WatchReplay()
{
if (selectedReplay != null && selectedReplay.GameInfo.MapPreview.Status == MapStatus.Available)
{
Game.JoinReplay(selectedReplay.FilePath);
Ui.CloseWindow();
}
}
void AddReplay(ReplayMetadata replay, ScrollItemWidget template)
{
var item = ScrollItemWidget.Setup(template,
() => selectedReplay == replay,
() => SelectReplay(replay),
() => WatchReplay());
replayState[replay] = new ReplayState
{
Item = item,
Visible = true
};
item.Text = Path.GetFileNameWithoutExtension(replay.FilePath);
item.Get<LabelWidget>("TITLE").GetText = () => item.Text;
item.IsVisible = () => replayState[replay].Visible;
replayList.AddChild(item);
}
class ReplayState
{
public bool Visible;
public ScrollItemWidget Item;
}
class Filter
{
public GameType Type;
public DateType Date;
public DurationType Duration;
public WinState Outcome;
public string PlayerName;
public string MapName;
public string Faction;
public bool IsEmpty
{
get
{
return Type == default(GameType)
&& Date == default(DateType)
&& Duration == default(DurationType)
&& Outcome == default(WinState)
&& string.IsNullOrEmpty(PlayerName)
&& string.IsNullOrEmpty(MapName)
&& string.IsNullOrEmpty(Faction);
}
}
}
enum GameType
{
Any,
Singleplayer,
Multiplayer
}
enum DateType
{
Any,
Today,
LastWeek,
LastFortnight,
LastMonth
}
enum DurationType
{
Any,
VeryShort,
Short,
Medium,
Long
}
}
}

View File

@@ -0,0 +1,396 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 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. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Text;
using OpenRA.Network;
using OpenRA.Server;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class ServerBrowserLogic
{
static readonly Action DoNothing = () => { };
GameServer currentServer;
ScrollItemWidget serverTemplate;
ScrollItemWidget headerTemplate;
Action onStart;
enum SearchStatus { Fetching, Failed, NoGames, Hidden }
SearchStatus searchStatus = SearchStatus.Fetching;
Download currentQuery;
Widget panel, serverList;
bool showWaiting = true;
bool showEmpty = true;
bool showStarted = false;
bool showProtected = true;
bool showIncompatible = false;
public string ProgressLabelText()
{
switch (searchStatus)
{
case SearchStatus.Failed: return "Failed to contact master server.";
case SearchStatus.NoGames: return "No games found.";
default: return "";
}
}
[ObjectCreator.UseCtor]
public ServerBrowserLogic(Widget widget, Action onStart, Action onExit, string directConnectHost, int directConnectPort)
{
panel = widget;
this.onStart = onStart;
serverList = panel.Get<ScrollPanelWidget>("SERVER_LIST");
headerTemplate = serverList.Get<ScrollItemWidget>("HEADER_TEMPLATE");
serverTemplate = serverList.Get<ScrollItemWidget>("SERVER_TEMPLATE");
// Menu buttons
var refreshButton = panel.Get<ButtonWidget>("REFRESH_BUTTON");
refreshButton.IsDisabled = () => searchStatus == SearchStatus.Fetching;
refreshButton.GetText = () => searchStatus == SearchStatus.Fetching ? "Refreshing..." : "Refresh";
refreshButton.OnClick = RefreshServerList;
panel.Get<ButtonWidget>("DIRECTCONNECT_BUTTON").OnClick = OpenDirectConnectPanel;
panel.Get<ButtonWidget>("CREATE_BUTTON").OnClick = OpenCreateServerPanel;
var join = panel.Get<ButtonWidget>("JOIN_BUTTON");
join.IsDisabled = () => currentServer == null || !currentServer.IsJoinable;
join.OnClick = () => Join(currentServer);
panel.Get<ButtonWidget>("BACK_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); };
// Display the progress label over the server list
// The text is only visible when the list is empty
var progressText = panel.Get<LabelWidget>("PROGRESS_LABEL");
progressText.IsVisible = () => searchStatus != SearchStatus.Hidden;
progressText.GetText = ProgressLabelText;
var showWaitingCheckbox = panel.GetOrNull<CheckboxWidget>("WAITING_FOR_PLAYERS");
if (showWaitingCheckbox != null)
{
showWaitingCheckbox.IsChecked = () => showWaiting;
showWaitingCheckbox.OnClick = () => { showWaiting ^= true; RefreshServerList(); };
}
var showEmptyCheckbox = panel.GetOrNull<CheckboxWidget>("EMPTY");
if (showEmptyCheckbox != null)
{
showEmptyCheckbox.IsChecked = () => showEmpty;
showEmptyCheckbox.OnClick = () => { showEmpty ^= true; RefreshServerList(); };
}
var showAlreadyStartedCheckbox = panel.GetOrNull<CheckboxWidget>("ALREADY_STARTED");
if (showAlreadyStartedCheckbox != null)
{
showAlreadyStartedCheckbox.IsChecked = () => showStarted;
showAlreadyStartedCheckbox.OnClick = () => { showStarted ^= true; RefreshServerList(); };
}
var showProtectedCheckbox = panel.GetOrNull<CheckboxWidget>("PASSWORD_PROTECTED");
if (showProtectedCheckbox != null)
{
showProtectedCheckbox.IsChecked = () => showProtected;
showProtectedCheckbox.OnClick = () => { showProtected ^= true; RefreshServerList(); };
}
var showIncompatibleCheckbox = panel.GetOrNull<CheckboxWidget>("INCOMPATIBLE_VERSION");
if (showIncompatibleCheckbox != null)
{
showIncompatibleCheckbox.IsChecked = () => showIncompatible;
showIncompatibleCheckbox.OnClick = () => { showIncompatible ^= true; RefreshServerList(); };
}
RefreshServerList();
if (directConnectHost != null)
{
// The connection window must be opened at the end of the tick for the widget hierarchy to
// work out, but we also want to prevent the server browser from flashing visible for one tick.
widget.Visible = false;
Game.RunAfterTick(() =>
{
ConnectionLogic.Connect(directConnectHost, directConnectPort, "", OpenLobby, DoNothing);
widget.Visible = true;
});
}
}
void RefreshServerList()
{
// Query in progress
if (currentQuery != null)
return;
searchStatus = SearchStatus.Fetching;
Action<DownloadDataCompletedEventArgs, bool> onComplete = (i, cancelled) =>
{
currentQuery = null;
if (i.Error != null || cancelled)
{
RefreshServerListInner(null);
return;
}
var data = Encoding.UTF8.GetString(i.Result);
var yaml = MiniYaml.FromString(data);
var games = yaml.Select(a => new GameServer(a.Value))
.Where(gs => gs.Address != null);
Game.RunAfterTick(() => RefreshServerListInner(games));
};
currentQuery = new Download(Game.Settings.Server.MasterServer + "games", _ => { }, onComplete);
}
int GroupSortOrder(GameServer testEntry)
{
// Games that we can't join are sorted last
if (!testEntry.IsCompatible)
return 0;
// Games for the current mod+version are sorted first
if (testEntry.ModId == Game.ModData.Manifest.Mod.Id)
return 2;
// Followed by games for different mods that are joinable
return 1;
}
void RefreshServerListInner(IEnumerable<GameServer> games)
{
if (games == null)
return;
var mods = games.GroupBy(g => g.Mods)
.OrderByDescending(g => GroupSortOrder(g.First()))
.ThenByDescending(g => g.Count());
var rows = new List<Widget>();
foreach (var modGames in mods)
{
if (modGames.All(Filtered))
continue;
var header = ScrollItemWidget.Setup(headerTemplate, () => true, () => { });
var headerTitle = modGames.First().ModLabel;
header.Get<LabelWidget>("LABEL").GetText = () => headerTitle;
rows.Add(header);
foreach (var loop in modGames.OrderByDescending(g => g.IsJoinable).ThenByDescending(g => g.Players))
{
var game = loop;
if (game == null || Filtered(game))
continue;
var canJoin = game.IsJoinable;
var compatible = game.IsCompatible;
var item = ScrollItemWidget.Setup(serverTemplate, () => currentServer == game, () => currentServer = game, () => Join(game));
var map = Game.ModData.MapCache[game.Map];
var preview = item.GetOrNull<MapPreviewWidget>("MAP_PREVIEW");
if (preview != null)
preview.Preview = () => map;
var title = item.GetOrNull<LabelWidget>("TITLE");
if (title != null)
{
title.GetText = () => game.Name;
title.GetColor = () => !compatible ? Color.DarkGray : !canJoin ? Color.LightGray : title.TextColor;
}
var maptitle = item.GetOrNull<LabelWidget>("MAP");
if (title != null)
{
maptitle.GetText = () => map.Title;
maptitle.GetColor = () => !compatible ? Color.DarkGray : !canJoin ? Color.LightGray : maptitle.TextColor;
}
var players = item.GetOrNull<LabelWidget>("PLAYERS");
if (players != null)
{
players.GetText = () => "{0} / {1}".F(game.Players, game.MaxPlayers)
+ (game.Spectators > 0 ? " ({0} Spectator{1})".F(game.Spectators, game.Spectators > 1 ? "s" : "") : "");
players.GetColor = () => !compatible ? Color.DarkGray : !canJoin ? Color.LightGray : players.TextColor;
}
var state = item.GetOrNull<LabelWidget>("STATE");
if (state != null)
{
state.GetText = () => GetStateLabel(game);
state.GetColor = () => GetStateColor(game, state, !compatible || !canJoin);
}
var ip = item.GetOrNull<LabelWidget>("IP");
if (ip != null)
{
ip.GetText = () => game.Address;
ip.GetColor = () => !compatible ? Color.DarkGray : !canJoin ? Color.LightGray : ip.TextColor;
}
var location = item.GetOrNull<LabelWidget>("LOCATION");
if (location != null)
{
var cachedServerLocation = LobbyUtils.LookupCountry(game.Address.Split(':')[0]);
location.GetText = () => cachedServerLocation;
location.GetColor = () => !compatible ? Color.DarkGray : !canJoin ? Color.LightGray : location.TextColor;
}
rows.Add(item);
}
}
Game.RunAfterTick(() =>
{
serverList.RemoveChildren();
currentServer = null;
if (games == null)
{
searchStatus = SearchStatus.Failed;
return;
}
if (!games.Any())
{
searchStatus = SearchStatus.NoGames;
return;
}
currentServer = games.FirstOrDefault();
searchStatus = SearchStatus.Hidden;
// Search for any unknown maps
if (Game.Settings.Game.AllowDownloading)
Game.ModData.MapCache.QueryRemoteMapDetails(games.Where(g => !Filtered(g)).Select(g => g.Map));
foreach (var row in rows)
serverList.AddChild(row);
});
}
void OpenLobby()
{
Game.OpenWindow("SERVER_LOBBY", new WidgetArgs
{
{ "onExit", Game.Disconnect },
{ "onStart", onStart },
{ "skirmishMode", false }
});
}
void OpenDirectConnectPanel()
{
Ui.OpenWindow("DIRECTCONNECT_PANEL", new WidgetArgs
{
{ "openLobby", OpenLobby },
{ "onExit", DoNothing }
});
}
void OpenCreateServerPanel()
{
Ui.OpenWindow("CREATESERVER_PANEL", new WidgetArgs
{
{ "openLobby", OpenLobby },
{ "onExit", DoNothing }
});
}
void Join(GameServer server)
{
if (server == null || !server.IsJoinable)
return;
var host = server.Address.Split(':')[0];
var port = Exts.ParseIntegerInvariant(server.Address.Split(':')[1]);
ConnectionLogic.Connect(host, port, "", OpenLobby, DoNothing);
}
static string GetStateLabel(GameServer game)
{
if (game == null)
return "";
if (game.State == (int)ServerState.GameStarted)
{
try
{
var runTime = DateTime.Now - System.DateTime.Parse(game.Started);
return "In progress for {0} minute{1}".F(runTime.Minutes, runTime.Minutes > 1 ? "s" : "");
}
catch (Exception)
{
return "In progress";
}
}
if (game.Protected)
return "Password protected";
if (game.State == (int)ServerState.WaitingPlayers)
return "Waiting for players";
if (game.State == (int)ServerState.ShuttingDown)
return "Server shutting down";
return "Unknown server state";
}
static Color GetStateColor(GameServer game, LabelWidget label, bool darkened)
{
if (game.Protected && game.State == (int)ServerState.WaitingPlayers)
return darkened ? Color.DarkRed : Color.Red;
if (game.State == (int)ServerState.WaitingPlayers)
return darkened ? Color.LimeGreen : Color.Lime;
if (game.State == (int)ServerState.GameStarted)
return darkened ? Color.Chocolate : Color.Orange;
return label.TextColor;
}
bool Filtered(GameServer game)
{
if ((game.State == (int)ServerState.GameStarted) && !showStarted)
return true;
if ((game.State == (int)ServerState.WaitingPlayers) && !showWaiting)
return true;
if ((game.Players == 0) && !showEmpty)
return true;
if (!game.IsCompatible && !showIncompatible)
return true;
if (game.Protected && !showProtected)
return true;
return false;
}
}
}

View File

@@ -0,0 +1,111 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 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. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Net;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class ServerCreationLogic
{
Widget panel;
Action onCreate;
Action onExit;
MapPreview preview = MapCache.UnknownMap;
bool advertiseOnline;
bool allowPortForward;
[ObjectCreator.UseCtor]
public ServerCreationLogic(Widget widget, Action onExit, Action openLobby)
{
panel = widget;
onCreate = openLobby;
this.onExit = onExit;
var settings = Game.Settings;
preview = Game.ModData.MapCache[WidgetUtils.ChooseInitialMap(Game.Settings.Server.Map)];
panel.Get<ButtonWidget>("BACK_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); };
panel.Get<ButtonWidget>("CREATE_BUTTON").OnClick = CreateAndJoin;
var mapButton = panel.GetOrNull<ButtonWidget>("MAP_BUTTON");
if (mapButton != null)
{
panel.Get<ButtonWidget>("MAP_BUTTON").OnClick = () =>
{
Ui.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs()
{
{ "initialMap", preview.Uid },
{ "onExit", () => { } },
{ "onSelect", (Action<string>)(uid => preview = Game.ModData.MapCache[uid]) }
});
};
panel.Get<MapPreviewWidget>("MAP_PREVIEW").Preview = () => preview;
panel.Get<LabelWidget>("MAP_NAME").GetText = () => preview.Title;
}
panel.Get<TextFieldWidget>("SERVER_NAME").Text = settings.Server.Name ?? "";
panel.Get<TextFieldWidget>("LISTEN_PORT").Text = settings.Server.ListenPort.ToString();
advertiseOnline = Game.Settings.Server.AdvertiseOnline;
var externalPort = panel.Get<TextFieldWidget>("EXTERNAL_PORT");
externalPort.Text = settings.Server.ExternalPort.ToString();
externalPort.IsDisabled = () => !advertiseOnline;
var advertiseCheckbox = panel.Get<CheckboxWidget>("ADVERTISE_CHECKBOX");
advertiseCheckbox.IsChecked = () => advertiseOnline;
advertiseCheckbox.OnClick = () => advertiseOnline ^= true;
allowPortForward = Game.Settings.Server.AllowPortForward;
var checkboxUPnP = panel.Get<CheckboxWidget>("UPNP_CHECKBOX");
checkboxUPnP.IsChecked = () => allowPortForward;
checkboxUPnP.OnClick = () => allowPortForward ^= true;
checkboxUPnP.IsDisabled = () => !Game.Settings.Server.NatDeviceAvailable;
var passwordField = panel.GetOrNull<PasswordFieldWidget>("PASSWORD");
if (passwordField != null)
passwordField.Text = Game.Settings.Server.Password;
}
void CreateAndJoin()
{
var name = panel.Get<TextFieldWidget>("SERVER_NAME").Text;
int listenPort, externalPort;
if (!Exts.TryParseIntegerInvariant(panel.Get<TextFieldWidget>("LISTEN_PORT").Text, out listenPort))
listenPort = 1234;
if (!Exts.TryParseIntegerInvariant(panel.Get<TextFieldWidget>("EXTERNAL_PORT").Text, out externalPort))
externalPort = 1234;
var passwordField = panel.GetOrNull<PasswordFieldWidget>("PASSWORD");
var password = passwordField != null ? passwordField.Text : "";
// Save new settings
Game.Settings.Server.Name = name;
Game.Settings.Server.ListenPort = listenPort;
Game.Settings.Server.ExternalPort = externalPort;
Game.Settings.Server.AdvertiseOnline = advertiseOnline;
Game.Settings.Server.AllowPortForward = allowPortForward;
Game.Settings.Server.Map = preview.Uid;
Game.Settings.Server.Password = password;
Game.Settings.Save();
// Take a copy so that subsequent changes don't affect the server
var settings = new ServerSettings(Game.Settings.Server);
// Create and join the server
Game.CreateServer(settings);
Ui.CloseWindow();
ConnectionLogic.Connect(IPAddress.Loopback.ToString(), Game.Settings.Server.ListenPort, password, onCreate, onExit);
}
}
}

View File

@@ -0,0 +1,78 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 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. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class TabCompletionLogic
{
IList<string> candidates = new List<string>();
int currentCandidateIndex = 0;
string lastCompleted;
string prefix;
string suffix;
public IList<string> Commands { get; set; }
public IList<string> Names { get; set; }
public string Complete(string text)
{
if (string.IsNullOrWhiteSpace(text))
return text;
if (lastCompleted == text)
{
lastCompleted = prefix + candidates[++currentCandidateIndex % candidates.Count] + suffix;
return lastCompleted;
}
var toComplete = "";
if (text.StartsWith("/") && Commands != null)
{
prefix = "/";
suffix = "";
toComplete = text.Substring(1);
candidates = Commands.Where(x => x.StartsWith(toComplete, StringComparison.InvariantCultureIgnoreCase)).ToList();
}
else if (Names != null)
{
var oneWord = text.Contains(' ');
if (oneWord)
{
prefix = text.Substring(0, text.LastIndexOf(' ') + 1);
suffix = "";
toComplete = text.Substring(prefix.Length);
}
else
{
prefix = "";
suffix = ": ";
toComplete = text;
}
candidates = Names.Where(x => x.StartsWith(toComplete, StringComparison.InvariantCultureIgnoreCase)).ToList();
}
else
return text;
currentCandidateIndex = 0;
if (candidates.Count == 0)
return text;
lastCompleted = prefix + candidates[currentCandidateIndex] + suffix;
return lastCompleted;
}
}
}