Make bots first-class players.

- Bots have their own Clients, with unique ClientIDs
 - Hosts can set bot team/color in the lobby
 - Bots are kicked when switching to a smaller map without enough slots
 - Order validator assumes that only client 0 has permission to issue bot orders
This commit is contained in:
Paul Chote
2011-06-19 02:39:34 +12:00
parent 0c9190a1af
commit 4f172d7ed8
12 changed files with 159 additions and 178 deletions

View File

@@ -34,8 +34,7 @@ namespace OpenRA.Network
public string FirstEmptySlot() public string FirstEmptySlot()
{ {
return Slots.FirstOrDefault(s => !s.Value.Closed && ClientInSlot(s.Key) == null return Slots.FirstOrDefault(s => !s.Value.Closed && ClientInSlot(s.Key) == null).Key;
&& s.Value.Bot == null).Key;
} }
public enum ClientState public enum ClientState
@@ -55,12 +54,12 @@ namespace OpenRA.Network
public ClientState State; public ClientState State;
public int Team; public int Team;
public string Slot; // slot ID, or null for observer public string Slot; // slot ID, or null for observer
public string Bot; // Bot type, null for real clients
} }
public class Slot public class Slot
{ {
public string PlayerReference; // playerReference to bind against. public string PlayerReference; // playerReference to bind against.
public string Bot; // trait name of the bot to initialize in this slot, or null otherwise.
public bool Closed; // host has explicitly closed this slot. public bool Closed; // host has explicitly closed this slot.
public bool AllowBots; public bool AllowBots;

View File

@@ -21,10 +21,9 @@ namespace OpenRA.Network
static Player FindPlayerByClient(this World world, Session.Client c) static Player FindPlayerByClient(this World world, Session.Client c)
{ {
/* todo: this is still a hack. /* todo: this is still a hack.
* the cases we're trying to avoid are the extra players on the host's client -- Neutral, other MapPlayers, * the cases we're trying to avoid are the extra players on the host's client -- Neutral, other MapPlayers,..*/
* bots,.. */
return world.Players.FirstOrDefault( return world.Players.FirstOrDefault(
p => (p.ClientIndex == c.Index && p.PlayerRef.Playable && !p.IsBot)); p => (p.ClientIndex == c.Index && p.PlayerRef.Playable));
} }
public static void ProcessOrder(OrderManager orderManager, World world, int clientId, Order order) public static void ProcessOrder(OrderManager orderManager, World world, int clientId, Order order)

View File

@@ -46,11 +46,13 @@ namespace OpenRA
PlayerRef = pr; PlayerRef = pr;
string botType = null; string botType = null;
// Real player or host-created bot
if (client != null) if (client != null)
{ {
ClientIndex = client.Index; ClientIndex = client.Index;
ColorRamp = client.ColorRamp; ColorRamp = client.ColorRamp;
PlayerName = client.Name; PlayerName = client.Name;
botType = client.Bot;
Country = world.GetCountries() Country = world.GetCountries()
.FirstOrDefault(c => client.Country == c.Race) .FirstOrDefault(c => client.Country == c.Race)
@@ -58,36 +60,21 @@ namespace OpenRA
} }
else else
{ {
// Map player or bot // Map player
ClientIndex = 0; /* it's a map player, "owned" by host */ ClientIndex = 0; /* it's a map player, "owned" by host */
ColorRamp = pr.ColorRamp; ColorRamp = pr.ColorRamp;
PlayerName = pr.Name; PlayerName = pr.Name;
NonCombatant = pr.NonCombatant; NonCombatant = pr.NonCombatant;
IsBot = pr.Bot != null;
botType = pr.Bot; botType = pr.Bot;
Country = world.GetCountries() Country = world.GetCountries()
.FirstOrDefault(c => pr.Race == c.Race) .FirstOrDefault(c => pr.Race == c.Race)
?? world.GetCountries().Random(world.SharedRandom); ?? world.GetCountries().Random(world.SharedRandom);
// Multiplayer bot
if (slot != null && slot.Bot != null)
{
IsBot = true;
botType = slot.Bot;
PlayerName = slot.Bot;
// pick a random color for the bot
var hue = (byte)world.SharedRandom.Next(255);
var sat = (byte)world.SharedRandom.Next(255);
var lum = (byte)world.SharedRandom.Next(51,255);
ColorRamp = new ColorRamp(hue, sat, lum, 10);
}
} }
PlayerActor = world.CreateActor("Player", new TypeDictionary { new OwnerInit(this) }); PlayerActor = world.CreateActor("Player", new TypeDictionary { new OwnerInit(this) });
// Enable the bot logic // Enable the bot logic on the host
IsBot = botType != null;
if (IsBot && Game.IsHost) if (IsBot && Game.IsHost)
{ {
var logic = PlayerActor.TraitsImplementing<IBot>() var logic = PlayerActor.TraitsImplementing<IBot>()

View File

@@ -129,10 +129,11 @@ namespace OpenRA.Server
* for manual spawnpoint choosing. * for manual spawnpoint choosing.
* - 256 max players is a dirty hack * - 256 max players is a dirty hack
*/ */
int ChooseFreePlayerIndex() public int ChooseFreePlayerIndex()
{ {
for (var i = 0; i < 256; i++) for (var i = 0; i < 256; i++)
if (conns.All(c => c.PlayerIndex != i) && preConns.All(c => c.PlayerIndex != i)) if (conns.All(c => c.PlayerIndex != i) && preConns.All(c => c.PlayerIndex != i)
&& lobbyInfo.Clients.All(c => c.Index != i))
return i; return i;
throw new InvalidOperationException("Already got 256 players"); throw new InvalidOperationException("Already got 256 players");

View File

@@ -18,10 +18,18 @@ namespace OpenRA.Traits
{ {
public bool OrderValidation(OrderManager orderManager, World world, int clientId, Order order) public bool OrderValidation(OrderManager orderManager, World world, int clientId, Order order)
{ {
// Drop exploiting orders if (order.Subject == null || order.Subject.Owner == null)
if (order.Subject != null && order.Subject.Owner.ClientIndex != clientId) return true;
var subjectClient = order.Subject.Owner.ClientIndex;
// Hack: Assumes bots always run on clientId 0.
var isBotOrder = orderManager.LobbyInfo.Clients[subjectClient].Bot != null && clientId == 0;
// Drop exploiting orders
if (subjectClient != clientId && !isBotOrder)
{ {
Game.Debug("Detected exploit order from {0}: {1}".F(clientId, order.OrderString)); Game.Debug("Detected exploit order from client {0}: {1}".F(clientId, order.OrderString));
return false; return false;
} }

View File

@@ -22,8 +22,8 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic
{ {
public class CncLobbyLogic public class CncLobbyLogic
{ {
Widget LocalPlayerTemplate, RemotePlayerTemplate, EmptySlotTemplate, BotTemplate, Widget LocalPlayerTemplate, RemotePlayerTemplate, EmptySlotTemplate,
LocalSpectatorTemplate, RemoteSpectatorTemplate, NewSpectatorTemplate; LocalSpectatorTemplate, RemoteSpectatorTemplate, NewSpectatorTemplate;
ScrollPanelWidget chatPanel; ScrollPanelWidget chatPanel;
Widget chatTemplate; Widget chatTemplate;
@@ -108,7 +108,6 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic
LocalPlayerTemplate = Players.GetWidget("TEMPLATE_LOCAL"); LocalPlayerTemplate = Players.GetWidget("TEMPLATE_LOCAL");
RemotePlayerTemplate = Players.GetWidget("TEMPLATE_REMOTE"); RemotePlayerTemplate = Players.GetWidget("TEMPLATE_REMOTE");
EmptySlotTemplate = Players.GetWidget("TEMPLATE_EMPTY"); EmptySlotTemplate = Players.GetWidget("TEMPLATE_EMPTY");
BotTemplate = Players.GetWidget("TEMPLATE_BOT");
LocalSpectatorTemplate = Players.GetWidget("TEMPLATE_LOCAL_SPECTATOR"); LocalSpectatorTemplate = Players.GetWidget("TEMPLATE_LOCAL_SPECTATOR");
RemoteSpectatorTemplate = Players.GetWidget("TEMPLATE_REMOTE_SPECTATOR"); RemoteSpectatorTemplate = Players.GetWidget("TEMPLATE_REMOTE_SPECTATOR");
NewSpectatorTemplate = Players.GetWidget("TEMPLATE_NEW_SPECTATOR"); NewSpectatorTemplate = Players.GetWidget("TEMPLATE_NEW_SPECTATOR");
@@ -150,7 +149,8 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic
return sc; return sc;
}; };
CountryNames = Rules.Info["world"].Traits.WithInterface<OpenRA.Traits.CountryInfo>().ToDictionary(a => a.Race, a => a.Name); CountryNames = Rules.Info["world"].Traits.WithInterface<OpenRA.Traits.CountryInfo>()
.ToDictionary(a => a.Race, a => a.Name);
CountryNames.Add("random", "Random"); CountryNames.Add("random", "Random");
var mapButton = lobby.GetWidget<ButtonWidget>("CHANGEMAP_BUTTON"); var mapButton = lobby.GetWidget<ButtonWidget>("CHANGEMAP_BUTTON");
@@ -296,25 +296,27 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic
} }
} }
bool ShowSlotDropDown(DropDownButtonWidget dropdown, Session.Slot slot) bool ShowSlotDropDown(DropDownButtonWidget dropdown, Session.Slot slot, Session.Client client)
{ {
var options = new List<SlotDropDownOption>() var options = new List<SlotDropDownOption>()
{ {
new SlotDropDownOption("Open", "slot_open "+slot.PlayerReference, () => (!slot.Closed && slot.Bot == null)), new SlotDropDownOption("Open", "slot_open "+slot.PlayerReference, () => (!slot.Closed && client == null)),
new SlotDropDownOption("Closed", "slot_close "+slot.PlayerReference, () => slot.Closed) new SlotDropDownOption("Closed", "slot_close "+slot.PlayerReference, () => slot.Closed)
}; };
if (slot.AllowBots) if (slot.AllowBots)
foreach (var b in Rules.Info["player"].Traits.WithInterface<IBotInfo>().Select(t => t.Name)) foreach (var b in Rules.Info["player"].Traits.WithInterface<IBotInfo>().Select(t => t.Name))
{ {
var bot = b; var bot = b;
options.Add(new SlotDropDownOption("Bot: {0}".F(bot), "slot_bot {0} {1}".F(slot.PlayerReference, bot), () => slot.Bot == bot)); options.Add(new SlotDropDownOption("Bot: {0}".F(bot),
"slot_bot {0} {1}".F(slot.PlayerReference, bot),
() => client != null && client.Bot == bot));
} }
Func<SlotDropDownOption, ScrollItemWidget, ScrollItemWidget> setupItem = (o, itemTemplate) => Func<SlotDropDownOption, ScrollItemWidget, ScrollItemWidget> setupItem = (o, itemTemplate) =>
{ {
var item = ScrollItemWidget.Setup(itemTemplate, var item = ScrollItemWidget.Setup(itemTemplate,
o.Selected, o.Selected,
() => orderManager.IssueOrder(Order.Command(o.Order))); () => orderManager.IssueOrder(Order.Command(o.Order)));
item.GetWidget<LabelWidget>("LABEL").GetText = () => o.Title; item.GetWidget<LabelWidget>("LABEL").GetText = () => o.Title;
return item; return item;
@@ -329,7 +331,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic
Func<string, ScrollItemWidget, ScrollItemWidget> setupItem = (race, itemTemplate) => Func<string, ScrollItemWidget, ScrollItemWidget> setupItem = (race, itemTemplate) =>
{ {
var item = ScrollItemWidget.Setup(itemTemplate, var item = ScrollItemWidget.Setup(itemTemplate,
() => client.Country == race, () => client.Country == race,
() => orderManager.IssueOrder(Order.Command("race {0} {1}".F(client.Index, race)))); () => orderManager.IssueOrder(Order.Command("race {0} {1}".F(client.Index, race))));
item.GetWidget<LabelWidget>("LABEL").GetText = () => CountryNames[race]; item.GetWidget<LabelWidget>("LABEL").GetText = () => CountryNames[race];
var flag = item.GetWidget<ImageWidget>("FLAG"); var flag = item.GetWidget<ImageWidget>("FLAG");
@@ -347,7 +349,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic
Func<int, ScrollItemWidget, ScrollItemWidget> setupItem = (ii, itemTemplate) => Func<int, ScrollItemWidget, ScrollItemWidget> setupItem = (ii, itemTemplate) =>
{ {
var item = ScrollItemWidget.Setup(itemTemplate, var item = ScrollItemWidget.Setup(itemTemplate,
() => client.Team == ii, () => client.Team == ii,
() => orderManager.IssueOrder(Order.Command("team {0} {1}".F(client.Index, ii)))); () => orderManager.IssueOrder(Order.Command("team {0} {1}".F(client.Index, ii))));
item.GetWidget<LabelWidget>("LABEL").GetText = () => ii == 0 ? "-" : ii.ToString(); item.GetWidget<LabelWidget>("LABEL").GetText = () => ii == 0 ? "-" : ii.ToString();
return item; return item;
@@ -377,7 +379,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic
{ {
{ "onSelect", onSelect }, { "onSelect", onSelect },
{ "onChange", onChange }, { "onChange", onChange },
{ "initialRamp", orderManager.LocalClient.ColorRamp } { "initialRamp", client.ColorRamp }
}); });
color.AttachPanel(colorChooser); color.AttachPanel(colorChooser);
@@ -398,7 +400,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic
Widget template; Widget template;
// Empty slot // Empty slot
if (client == null && slot.Bot == null) if (client == null)
{ {
template = EmptySlotTemplate.Clone(); template = EmptySlotTemplate.Clone();
Func<string> getText = () => slot.Closed ? "Closed" : "Open"; Func<string> getText = () => slot.Closed ? "Closed" : "Open";
@@ -408,7 +410,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic
var name = template.GetWidget<DropDownButtonWidget>("NAME_HOST"); var name = template.GetWidget<DropDownButtonWidget>("NAME_HOST");
name.IsVisible = () => true; name.IsVisible = () => true;
name.GetText = getText; name.GetText = getText;
name.OnMouseDown = _ => ShowSlotDropDown(name, slot); name.OnMouseDown = _ => ShowSlotDropDown(name, slot, client);
} }
else else
{ {
@@ -421,51 +423,43 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic
if (join != null) if (join != null)
{ {
join.OnMouseUp = _ => { orderManager.IssueOrder(Order.Command("slot " + key)); return true; }; join.OnMouseUp = _ => { orderManager.IssueOrder(Order.Command("slot " + key)); return true; };
join.IsVisible = () => !slot.Closed && slot.Bot == null && orderManager.LocalClient.State != Session.ClientState.Ready; join.IsVisible = () => !slot.Closed && orderManager.LocalClient.State != Session.ClientState.Ready;
}
}
// Bot
else if (client == null && slot.Bot != null)
{
template = BotTemplate.Clone();
Func<string> getText = () => slot.Bot;
if (Game.IsHost)
{
var name = template.GetWidget<DropDownButtonWidget>("NAME_HOST");
name.IsVisible = () => true;
name.GetText = getText;
name.OnMouseDown = _ => ShowSlotDropDown(name, slot);
}
else
{
var name = template.GetWidget<LabelWidget>("NAME");
name.IsVisible = () => true;
name.GetText = getText;
} }
} }
// Editable player in slot // Editable player in slot
else if (client.Index == orderManager.LocalClient.Index && client.State != Session.ClientState.Ready) else if ((client.Index == orderManager.LocalClient.Index && client.State != Session.ClientState.Ready) ||
(client.Bot != null && Game.IsHost))
{ {
template = LocalPlayerTemplate.Clone(); template = LocalPlayerTemplate.Clone();
var name = template.GetWidget<TextFieldWidget>("NAME"); if (client.Bot != null)
name.Text = client.Name;
name.OnEnterKey = () =>
{ {
name.Text = name.Text.Trim(); var name = template.GetWidget<DropDownButtonWidget>("BOT_DROPDOWN");
if (name.Text.Length == 0) name.IsVisible = () => true;
name.Text = client.Name; name.GetText = () => client.Name;
name.OnMouseDown = _ => ShowSlotDropDown(name, slot, client);
}
else
{
var name = template.GetWidget<TextFieldWidget>("NAME");
name.IsVisible = () => true;
name.Text = client.Name;
name.OnEnterKey = () =>
{
name.Text = name.Text.Trim();
if (name.Text.Length == 0)
name.Text = client.Name;
name.LoseFocus(); name.LoseFocus();
if (name.Text == client.Name) if (name.Text == client.Name)
return true;
orderManager.IssueOrder(Order.Command("name " + name.Text));
Game.Settings.Player.Name = name.Text;
Game.Settings.Save();
return true; return true;
};
orderManager.IssueOrder(Order.Command("name " + name.Text)); name.OnLoseFocus = () => name.OnEnterKey();
Game.Settings.Player.Name = name.Text; }
Game.Settings.Save();
return true;
};
name.OnLoseFocus = () => name.OnEnterKey();
var color = template.GetWidget<DropDownButtonWidget>("COLOR"); var color = template.GetWidget<DropDownButtonWidget>("COLOR");
color.IsDisabled = () => slot.LockColor; color.IsDisabled = () => slot.LockColor;
@@ -485,11 +479,12 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic
factionflag.GetImageCollection = () => "flags"; factionflag.GetImageCollection = () => "flags";
var team = template.GetWidget<DropDownButtonWidget>("TEAM"); var team = template.GetWidget<DropDownButtonWidget>("TEAM");
team.IsDisabled = () => slot.LockTeam; team.IsDisabled = () => slot.LockTeam || client.Bot != null;
team.OnMouseDown = _ => { if (slot.LockTeam) return true; return ShowTeamDropDown(team, client); }; team.OnMouseDown = _ => { if (team.IsDisabled()) return true; return ShowTeamDropDown(team, client); };
team.GetText = () => (client.Team == 0) ? "-" : client.Team.ToString(); team.GetText = () => (client.Team == 0) ? "-" : client.Team.ToString();
var status = template.GetWidget<CheckboxWidget>("STATUS"); var status = template.GetWidget<CheckboxWidget>("STATUS");
status.IsVisible = () => client.Bot == null;
status.IsChecked = () => client.State == Session.ClientState.Ready; status.IsChecked = () => client.State == Session.ClientState.Ready;
status.OnClick += CycleReady; status.OnClick += CycleReady;
} }
@@ -513,6 +508,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic
var status = template.GetWidget<CheckboxWidget>("STATUS"); var status = template.GetWidget<CheckboxWidget>("STATUS");
status.IsChecked = () => client.State == Session.ClientState.Ready; status.IsChecked = () => client.State == Session.ClientState.Ready;
status.IsVisible = () => client.Bot == null;
if (client.Index == orderManager.LocalClient.Index) if (client.Index == orderManager.LocalClient.Index)
status.OnClick += CycleReady; status.OnClick += CycleReady;
@@ -661,6 +657,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic
// Set the initial state // Set the initial state
updateSliders(); updateSliders();
onChange(ramp);
} }
} }
} }

View File

@@ -78,7 +78,8 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic
panel.GetWidget<LabelWidget>("MAP_TITLE").GetText = panel.GetWidget<LabelWidget>("MAP_TITLE").GetText =
() => currentMap != null ? currentMap.Title : "(Unknown Map)"; () => currentMap != null ? currentMap.Title : "(Unknown Map)";
var players = currentSummary.LobbyInfo.Slots.Count(s => currentSummary.LobbyInfo.ClientInSlot(s.Key) != null || s.Value.Bot != null); var players = currentSummary.LobbyInfo.Slots
.Count(s => currentSummary.LobbyInfo.ClientInSlot(s.Key) != null);
panel.GetWidget<LabelWidget>("PLAYERS").GetText = () => players.ToString(); panel.GetWidget<LabelWidget>("PLAYERS").GetText = () => players.ToString();
} }
catch (Exception e) catch (Exception e)

View File

@@ -34,13 +34,13 @@ namespace OpenRA.Mods.RA
foreach (var kv in w.LobbyInfo.Slots) foreach (var kv in w.LobbyInfo.Slots)
{ {
var client = w.LobbyInfo.ClientInSlot(kv.Key); var client = w.LobbyInfo.ClientInSlot(kv.Key);
if (client == null && kv.Value.Bot == null) if (client == null)
continue; continue;
var player = new Player(w, client, kv.Value, w.Map.Players[kv.Value.PlayerReference]); var player = new Player(w, client, kv.Value, w.Map.Players[kv.Value.PlayerReference]);
w.AddPlayer(player); w.AddPlayer(player);
if (client != null && client.Index == Game.LocalClientId) if (client.Index == Game.LocalClientId)
w.SetLocalPlayer(player.InternalName); w.SetLocalPlayer(player.InternalName);
} }

View File

@@ -82,8 +82,7 @@ namespace OpenRA.Mods.RA.Server
} }
var slot = server.lobbyInfo.Slots[s]; var slot = server.lobbyInfo.Slots[s];
if (slot.Closed || slot.Bot != null || if (slot.Closed || server.lobbyInfo.ClientInSlot(s) != null)
server.lobbyInfo.ClientInSlot(s) != null)
return false; return false;
client.Slot = s; client.Slot = s;
@@ -119,17 +118,20 @@ namespace OpenRA.Mods.RA.Server
var occupant = server.lobbyInfo.ClientInSlot(s); var occupant = server.lobbyInfo.ClientInSlot(s);
if (occupant != null) if (occupant != null)
{ {
var occupantConn = server.conns.FirstOrDefault( c => c.PlayerIndex == occupant.Index ); if (occupant.Bot != null)
if (occupantConn != null) server.lobbyInfo.Clients.Remove(occupant);
else
{ {
server.SendOrderTo(occupantConn, "ServerError", "Your slot was closed by the host"); var occupantConn = server.conns.FirstOrDefault( c => c.PlayerIndex == occupant.Index );
server.DropClient(occupantConn); if (occupantConn != null)
{
server.SendOrderTo(occupantConn, "ServerError", "Your slot was closed by the host");
server.DropClient(occupantConn);
}
} }
} }
var slot = server.lobbyInfo.Slots[s];
slot.Closed = true;
slot.Bot = null;
server.lobbyInfo.Slots[s].Closed = true;
server.SyncLobbyInfo(); server.SyncLobbyInfo();
return true; return true;
}}, }},
@@ -150,7 +152,11 @@ namespace OpenRA.Mods.RA.Server
var slot = server.lobbyInfo.Slots[s]; var slot = server.lobbyInfo.Slots[s];
slot.Closed = false; slot.Closed = false;
slot.Bot = null;
// Slot may have a bot in it
var occupant = server.lobbyInfo.ClientInSlot(s);
if (occupant != null && occupant.Bot != null)
server.lobbyInfo.Clients.Remove(occupant);
server.SyncLobbyInfo(); server.SyncLobbyInfo();
return true; return true;
@@ -178,10 +184,30 @@ namespace OpenRA.Mods.RA.Server
return true; return true;
} }
var botType = string.Join(" ", parts.Skip(1).ToArray() );
var slot = server.lobbyInfo.Slots[parts[0]]; var slot = server.lobbyInfo.Slots[parts[0]];
slot.Bot = string.Join(" ", parts.Skip(1).ToArray() );
slot.Closed = false; slot.Closed = false;
var bot = new Session.Client()
{
Index = server.ChooseFreePlayerIndex(),
Name = botType,
Bot = botType,
Slot = parts[0],
Country = "random",
SpawnPoint = 0,
Team = 0,
State = Session.ClientState.NotReady
};
// pick a random color for the bot
var hue = (byte)Game.CosmeticRandom.Next(255);
var sat = (byte)Game.CosmeticRandom.Next(255);
var lum = (byte)Game.CosmeticRandom.Next(51,255);
bot.ColorRamp = new ColorRamp(hue, sat, lum, 10);
S.SyncClientToPlayerReference(client, server.Map.Players[parts[0]]);
server.lobbyInfo.Clients.Add(bot);
server.SyncLobbyInfo(); server.SyncLobbyInfo();
return true; return true;
}}, }},
@@ -193,20 +219,29 @@ namespace OpenRA.Mods.RA.Server
server.SendChatTo( conn, "Only the host can change the map" ); server.SendChatTo( conn, "Only the host can change the map" );
return true; return true;
} }
server.lobbyInfo.GlobalSettings.Map = s; server.lobbyInfo.GlobalSettings.Map = s;
var oldSlots = server.lobbyInfo.Slots.Keys.ToArray();
LoadMap(server); LoadMap(server);
// Reassign players into slots // Reassign players into new slots based on their old slots:
// - Observers remain as observers
// - Players who now lack a slot are made observers
// - Bots who now lack a slot are dropped
var slots = server.lobbyInfo.Slots.Keys.ToArray();
int i = 0; int i = 0;
foreach(var c in server.lobbyInfo.Clients) foreach (var os in oldSlots)
{ {
var c = server.lobbyInfo.ClientInSlot(os);
if (c == null)
continue;
c.SpawnPoint = 0; c.SpawnPoint = 0;
c.State = Session.ClientState.NotReady; c.State = Session.ClientState.NotReady;
c.Slot = c.Slot == null || i >= server.lobbyInfo.Slots.Count ? c.Slot = i < slots.Length ? slots[i++] : null;
null : server.lobbyInfo.Slots.ElementAt(i++).Key;
if (c.Slot != null) if (c.Slot != null)
S.SyncClientToPlayerReference(c, server.Map.Players[c.Slot]); S.SyncClientToPlayerReference(c, server.Map.Players[c.Slot]);
else if (c.Bot != null)
server.lobbyInfo.Clients.Remove(c);
} }
server.SyncLobbyInfo(); server.SyncLobbyInfo();
@@ -282,7 +317,6 @@ namespace OpenRA.Mods.RA.Server
return new Session.Slot return new Session.Slot
{ {
PlayerReference = pr.Name, PlayerReference = pr.Name,
Bot = null,
Closed = false, Closed = false,
AllowBots = pr.AllowBots, AllowBots = pr.AllowBots,
LockRace = pr.LockRace, LockRace = pr.LockRace,

View File

@@ -35,7 +35,9 @@ namespace OpenRA.Mods.RA.Widgets.Logic
readonly OrderManager orderManager; readonly OrderManager orderManager;
readonly WorldRenderer worldRenderer; readonly WorldRenderer worldRenderer;
[ObjectCreator.UseCtor] [ObjectCreator.UseCtor]
internal LobbyLogic( [ObjectCreator.Param( "widget" )] Widget lobby, [ObjectCreator.Param] OrderManager orderManager, [ObjectCreator.Param] WorldRenderer worldRenderer) internal LobbyLogic([ObjectCreator.Param( "widget" )] Widget lobby,
[ObjectCreator.Param] OrderManager orderManager,
[ObjectCreator.Param] WorldRenderer worldRenderer)
{ {
this.orderManager = orderManager; this.orderManager = orderManager;
this.worldRenderer = worldRenderer; this.worldRenderer = worldRenderer;
@@ -93,13 +95,18 @@ namespace OpenRA.Mods.RA.Widgets.Logic
return sc; return sc;
}; };
CountryNames = Rules.Info["world"].Traits.WithInterface<OpenRA.Traits.CountryInfo>().ToDictionary(a => a.Race, a => a.Name); CountryNames = Rules.Info["world"].Traits.WithInterface<OpenRA.Traits.CountryInfo>()
.ToDictionary(a => a.Race, a => a.Name);
CountryNames.Add("random", "Random"); CountryNames.Add("random", "Random");
var mapButton = lobby.GetWidget("CHANGEMAP_BUTTON"); var mapButton = lobby.GetWidget("CHANGEMAP_BUTTON");
mapButton.OnMouseUp = mi => mapButton.OnMouseUp = mi =>
{ {
Widget.OpenWindow( "MAP_CHOOSER", new WidgetArgs() { { "orderManager", orderManager }, { "mapName", MapUid } } ); Widget.OpenWindow("MAP_CHOOSER", new WidgetArgs()
{
{ "orderManager", orderManager },
{ "mapName", MapUid }
});
return true; return true;
}; };
@@ -221,11 +228,11 @@ namespace OpenRA.Mods.RA.Widgets.Logic
} }
} }
bool ShowSlotDropDown(DropDownButtonWidget dropdown, Session.Slot slot) bool ShowSlotDropDown(DropDownButtonWidget dropdown, Session.Slot slot, Session.Client client)
{ {
var options = new List<SlotDropDownOption>() var options = new List<SlotDropDownOption>()
{ {
new SlotDropDownOption("Open", "slot_open "+slot.PlayerReference, () => (!slot.Closed && slot.Bot == null)), new SlotDropDownOption("Open", "slot_open "+slot.PlayerReference, () => (!slot.Closed && client == null)),
new SlotDropDownOption("Closed", "slot_close "+slot.PlayerReference, () => slot.Closed) new SlotDropDownOption("Closed", "slot_close "+slot.PlayerReference, () => slot.Closed)
}; };
@@ -233,7 +240,9 @@ namespace OpenRA.Mods.RA.Widgets.Logic
foreach (var b in Rules.Info["player"].Traits.WithInterface<IBotInfo>().Select(t => t.Name)) foreach (var b in Rules.Info["player"].Traits.WithInterface<IBotInfo>().Select(t => t.Name))
{ {
var bot = b; var bot = b;
options.Add(new SlotDropDownOption("Bot: {0}".F(bot), "slot_bot {0} {1}".F(slot.PlayerReference, bot), () => slot.Bot == bot)); options.Add(new SlotDropDownOption("Bot: {0}".F(bot),
"slot_bot {0} {1}".F(slot.PlayerReference, bot),
() => client != null && client.Bot == bot));
} }
Func<SlotDropDownOption, ScrollItemWidget, ScrollItemWidget> setupItem = (o, itemTemplate) => Func<SlotDropDownOption, ScrollItemWidget, ScrollItemWidget> setupItem = (o, itemTemplate) =>
@@ -327,32 +336,30 @@ namespace OpenRA.Mods.RA.Widgets.Logic
var c = orderManager.LobbyInfo.ClientInSlot(kv.Key); var c = orderManager.LobbyInfo.ClientInSlot(kv.Key);
Widget template; Widget template;
if (c == null) if (c == null || c.Bot != null)
{ {
if (Game.IsHost) if (Game.IsHost)
{ {
template = EmptySlotTemplateHost.Clone(); template = EmptySlotTemplateHost.Clone();
var name = template.GetWidget<DropDownButtonWidget>("NAME"); var name = template.GetWidget<DropDownButtonWidget>("NAME");
name.GetText = () => s.Closed ? "Closed" : (s.Bot == null) ? "Open" : s.Bot; name.GetText = () => s.Closed ? "Closed" : (c == null) ? "Open" : c.Bot;
name.OnMouseDown = _ => ShowSlotDropDown(name, s); name.OnMouseDown = _ => ShowSlotDropDown(name, s, c);
} }
else else
{ {
template = EmptySlotTemplate.Clone(); template = EmptySlotTemplate.Clone();
var name = template.GetWidget<LabelWidget>("NAME"); var name = template.GetWidget<LabelWidget>("NAME");
name.GetText = () => s.Closed ? "Closed" : (s.Bot == null) ? "Open" : s.Bot; name.GetText = () => s.Closed ? "Closed" : (c == null) ? "Open" : c.Bot;
} }
var join = template.GetWidget<ButtonWidget>("JOIN"); var join = template.GetWidget<ButtonWidget>("JOIN");
if (join != null) if (join != null)
{ {
join.OnMouseUp = _ => { orderManager.IssueOrder(Order.Command("slot " + s.PlayerReference)); return true; }; join.OnMouseUp = _ => { orderManager.IssueOrder(Order.Command("slot " + s.PlayerReference)); return true; };
join.IsVisible = () => !s.Closed && s.Bot == null && orderManager.LocalClient.State != Session.ClientState.Ready; join.IsVisible = () => !s.Closed && c == null && orderManager.LocalClient.State != Session.ClientState.Ready;
} }
var bot = template.GetWidget<LabelWidget>("BOT"); template.GetWidget<LabelWidget>("BOT").IsVisible = () => c != null;
if (bot != null)
bot.IsVisible = () => s.Bot != null;
} }
else if (c.Index == orderManager.LocalClient.Index && c.State != Session.ClientState.Ready) else if (c.Index == orderManager.LocalClient.Index && c.State != Session.ClientState.Ready)
{ {
@@ -401,11 +408,6 @@ namespace OpenRA.Mods.RA.Widgets.Logic
var status = template.GetWidget<CheckboxWidget>("STATUS"); var status = template.GetWidget<CheckboxWidget>("STATUS");
status.IsChecked = () => c.State == Session.ClientState.Ready; status.IsChecked = () => c.State == Session.ClientState.Ready;
status.OnClick = CycleReady; status.OnClick = CycleReady;
var spectator = template.GetWidget<LabelWidget>("SPECTATOR");
Session.Slot ss = s;
spectator.IsVisible = () => ss.Bot != null;
} }
else else
{ {
@@ -429,11 +431,6 @@ namespace OpenRA.Mods.RA.Widgets.Logic
if (c.Index == orderManager.LocalClient.Index) if (c.Index == orderManager.LocalClient.Index)
status.OnClick = CycleReady; status.OnClick = CycleReady;
var spectator = template.GetWidget<LabelWidget>("SPECTATOR");
Session.Slot ss = s;
spectator.IsVisible = () => ss.Bot != null;
var kickButton = template.GetWidget<ButtonWidget>("KICK"); var kickButton = template.GetWidget<ButtonWidget>("KICK");
kickButton.IsVisible = () => Game.IsHost && c.Index != orderManager.LocalClient.Index; kickButton.IsVisible = () => Game.IsHost && c.Index != orderManager.LocalClient.Index;
kickButton.OnMouseUp = mi => kickButton.OnMouseUp = mi =>

View File

@@ -67,6 +67,14 @@ Container@SERVER_LOBBY:
Width:150 Width:150
Height:25 Height:25
MaxLength:16 MaxLength:16
Visible:false
DropDownButton@BOT_DROPDOWN:
Id:BOT_DROPDOWN
Text:Name
Width:150
Height:25
Font:Regular
Visible:false
DropDownButton@COLOR: DropDownButton@COLOR:
Id:COLOR Id:COLOR
Width:80 Width:80
@@ -207,38 +215,6 @@ Container@SERVER_LOBBY:
Height:25 Height:25
X:160 X:160
Y:0 Y:0
Container@TEMPLATE_BOT:
Id:TEMPLATE_BOT
X:5
Y:0
Width:475
Height:25
Visible:false
Children:
DropDownButton@NAME_HOST:
Id:NAME_HOST
Text:Name
Width:150
Height:25
Font:Regular
Visible:false
Label@NAME:
Id:NAME
Text:Name
Width:145
Height:25
X:5
Y:0-1
Visible:false
Label@BOT:
Id:BOT
Text:Bot
Width:278
Height:25
X:160
Y:0
Align:Center
Font:Bold
Container@TEMPLATE_LOCAL_SPECTATOR: Container@TEMPLATE_LOCAL_SPECTATOR:
Id:TEMPLATE_LOCAL_SPECTATOR Id:TEMPLATE_LOCAL_SPECTATOR
X:5 X:5

View File

@@ -98,15 +98,6 @@ Background@SERVER_LOBBY:
Y:2 Y:2
Width:20 Width:20
Height:20 Height:20
Label@SPECTATOR:
Id:SPECTATOR
Text:Spectator
Width:278
Height:25
X:160
Y:0
Align:Center
Font:Bold
Container@TEMPLATE_REMOTE: Container@TEMPLATE_REMOTE:
Id:TEMPLATE_REMOTE Id:TEMPLATE_REMOTE
X:5 X:5
@@ -170,15 +161,6 @@ Background@SERVER_LOBBY:
Y:2 Y:2
Width:20 Width:20
Height:20 Height:20
Label@SPECTATOR:
Id:SPECTATOR
Text:Spectator
Width:278
Height:25
X:160
Y:0
Align:Center
Font:Bold
Container@TEMPLATE_EMPTY: Container@TEMPLATE_EMPTY:
Id:TEMPLATE_EMPTY Id:TEMPLATE_EMPTY
X:5 X:5