Merge pull request #4942 from pchote/more-replays

Overhaul observer shroud selector.
This commit is contained in:
Matthias Mailänder
2014-03-22 12:20:40 +01:00
17 changed files with 514 additions and 31 deletions

View File

@@ -24,7 +24,8 @@ NEW:
Order lines are now shown on unit selection.
Fixed chat synchronization in replays.
Added a combined shroud view of every player to the replay viewer and spectator mode.
Added pause, slowdown, play and fastforward buttons for replays.
Improved the observer/replay view selector with teams, factions, player colors, and hotkeys.
Added playback speed controls to replays.
Fixed the game sometimes crashing when deploying and activating the guard cursor at the same time.
Build time is now set when an item reaches the front of a queue, instead of immediately when queued.
The attack cursor now changes if the target is out of range.

View File

@@ -165,6 +165,9 @@ namespace OpenRA.GameRules
public Hotkey DeployKey = new Hotkey(Keycode.F, Modifiers.None);
public Hotkey StanceCycleKey = new Hotkey(Keycode.Z, Modifiers.None);
public Hotkey GuardKey = new Hotkey(Keycode.D, Modifiers.None);
public Hotkey ObserverCombinedView = new Hotkey(Keycode.MINUS, Modifiers.None);
public Hotkey ObserverWorldView = new Hotkey(Keycode.EQUALS, Modifiers.None);
}
public class IrcSettings

View File

@@ -198,13 +198,7 @@ namespace OpenRA.Widgets
{
if (e.Event == KeyInputEvent.Down)
{
if (e.Key >= Keycode.NUMBER_0 && e.Key <= Keycode.NUMBER_9)
{
var group = (int)e.Key - (int)Keycode.NUMBER_0;
World.Selection.DoControlGroup(World, worldRenderer, group, e.Modifiers, e.MultiTapCount);
return true;
}
else if (Hotkey.FromKeyInput(e) == Game.Settings.Keys.PauseKey && World.LocalPlayer != null) // Disable pausing for spectators
if (Hotkey.FromKeyInput(e) == Game.Settings.Keys.PauseKey && World.LocalPlayer != null) // Disable pausing for spectators
World.SetPauseState(!World.Paused);
else if (Hotkey.FromKeyInput(e) == Game.Settings.Keys.SelectAllUnitsKey)
{

View File

@@ -488,6 +488,8 @@
<Compile Include="Widgets\Logic\LobbyMapPreviewLogic.cs" />
<Compile Include="Widgets\Logic\ButtonTooltipLogic.cs" />
<Compile Include="Orders\BeaconOrderGenerator.cs" />
<Compile Include="Widgets\LogicKeyListenerWidget.cs" />
<Compile Include="Widgets\Logic\ControlGroupLogic.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj">

View File

@@ -0,0 +1,35 @@
#region Copyright & License Information
/*
* Copyright 2007-2014 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.Graphics;
using OpenRA.Widgets;
namespace OpenRA.Mods.RA.Widgets.Logic
{
public class ControlGroupLogic
{
[ObjectCreator.UseCtor]
public ControlGroupLogic(Widget widget, World world, WorldRenderer worldRenderer)
{
var keyhandler = widget.Get<LogicKeyListenerWidget>("CONTROLGROUP_KEYHANDLER");
keyhandler.OnKeyPress = e =>
{
if (e.Key >= Keycode.NUMBER_0 && e.Key <= Keycode.NUMBER_9)
{
var group = (int)e.Key - (int)Keycode.NUMBER_0;
world.Selection.DoControlGroup(world, worldRenderer, group, e.Modifiers, e.MultiTapCount);
return true;
}
return false;
};
}
}
}

View File

@@ -9,54 +9,160 @@
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA.Network;
using OpenRA.Widgets;
namespace OpenRA.Mods.RA.Widgets.Logic
{
public class ObserverShroudSelectorLogic
{
CameraOption selected;
CameraOption combined, disableShroud;
IOrderedEnumerable<IGrouping<int, CameraOption>> teams;
class CameraOption
{
public string Label;
public Func<bool> IsSelected;
public Action OnClick;
public readonly Player Player;
public readonly string Label;
public readonly Color Color;
public readonly string Race;
public readonly Func<bool> IsSelected;
public readonly Action OnClick;
public CameraOption(string label, Func<bool> isSelected, Action onClick)
public CameraOption(ObserverShroudSelectorLogic logic, Player p)
{
Label = label;
IsSelected = isSelected;
OnClick = onClick;
Player = p;
Label = p.PlayerName;
Color = p.Color.RGB;
Race = p.Country.Race;
IsSelected = () => p.World.RenderPlayer == p;
OnClick = () => { p.World.RenderPlayer = p; logic.selected = this; };
}
}
static string LabelForPlayer(Player p)
{
return p != null ? p.PlayerName == "Everyone" ? "Combined view" : "{0}'s view".F(p.PlayerName) : "World view";
public CameraOption(ObserverShroudSelectorLogic logic, World w, string label, Player p)
{
Player = p;
Label = label;
Color = Color.White;
Race = null;
IsSelected = () => w.RenderPlayer == p;
OnClick = () => { w.RenderPlayer = p; logic.selected = this; };
}
}
[ObjectCreator.UseCtor]
public ObserverShroudSelectorLogic(Widget widget, World world)
{
var views = world.Players.Where(p => (p.NonCombatant && p.Spectating)
|| !p.NonCombatant).Concat(new[] { (Player)null }).Select(
p => new CameraOption(LabelForPlayer(p),
() => world.RenderPlayer == p,
() => world.RenderPlayer = p
)).ToArray();
var groups = new Dictionary<string, IEnumerable<CameraOption>>();
teams = world.Players.Where(p => !p.NonCombatant)
.Select(p => new CameraOption(this, p))
.GroupBy(p => (world.LobbyInfo.ClientWithIndex(p.Player.ClientIndex) ?? new Session.Client()).Team)
.OrderBy(g => g.Key);
var noTeams = teams.Count() == 1;
foreach (var t in teams)
{
var label = noTeams ? "Players" : t.Key == 0 ? "No Team" : "Team {0}".F(t.Key);
groups.Add(label, t);
}
combined = new CameraOption(this, world, "All Players", world.Players.First(p => p.InternalName == "Everyone"));
disableShroud = new CameraOption(this, world, "Disable Shroud", null);
groups.Add("Other", new List<CameraOption>() { combined, disableShroud });
var shroudSelector = widget.Get<DropDownButtonWidget>("SHROUD_SELECTOR");
shroudSelector.GetText = () => LabelForPlayer(world.RenderPlayer);
shroudSelector.OnMouseDown = _ =>
{
Func<CameraOption, ScrollItemWidget, ScrollItemWidget> setupItem = (option, template) =>
{
var item = ScrollItemWidget.Setup(template, option.IsSelected, option.OnClick);
item.Get<LabelWidget>("LABEL").GetText = () => option.Label;
var showFlag = option.Race != null;
var label = item.Get<LabelWidget>("LABEL");
label.IsVisible = () => showFlag;
label.GetText = () => option.Label;
label.GetColor = () => option.Color;
var flag = item.Get<ImageWidget>("FLAG");
flag.IsVisible = () => showFlag;
flag.GetImageCollection = () => "flags";
flag.GetImageName = () => option.Race;
var labelAlt = item.Get<LabelWidget>("NOFLAG_LABEL");
labelAlt.IsVisible = () => !showFlag;
labelAlt.GetText = () => option.Label;
labelAlt.GetColor = () => option.Color;
return item;
};
shroudSelector.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", views.Length * 30, views, setupItem);
shroudSelector.ShowDropDown("SPECTATOR_DROPDOWN_TEMPLATE", 400, groups, setupItem);
};
var shroudLabel = shroudSelector.Get<LabelWidget>("LABEL");
shroudLabel.IsVisible = () => selected.Race != null;
shroudLabel.GetText = () => selected.Label;
shroudLabel.GetColor = () => selected.Color;
var shroudFlag = shroudSelector.Get<ImageWidget>("FLAG");
shroudFlag.IsVisible = () => selected.Race != null;
shroudFlag.GetImageCollection = () => "flags";
shroudFlag.GetImageName = () => selected.Race;
var shroudLabelAlt = shroudSelector.Get<LabelWidget>("NOFLAG_LABEL");
shroudLabelAlt.IsVisible = () => selected.Race == null;
shroudLabelAlt.GetText = () => selected.Label;
shroudLabelAlt.GetColor = () => selected.Color;
var keyhandler = shroudSelector.Get<LogicKeyListenerWidget>("SHROUD_KEYHANDLER");
keyhandler.OnKeyPress = HandleKeyPress;
selected = disableShroud;
}
public bool HandleKeyPress(KeyInput e)
{
if (e.Event == KeyInputEvent.Down)
{
var h = Hotkey.FromKeyInput(e);
if (h == Game.Settings.Keys.ObserverCombinedView)
{
selected = combined;
selected.OnClick();
return true;
}
if (h == Game.Settings.Keys.ObserverWorldView)
{
selected = disableShroud;
selected.OnClick();
return true;
}
if (e.Key >= Keycode.NUMBER_0 && e.Key <= Keycode.NUMBER_9)
{
var key = (int)e.Key - (int)Keycode.NUMBER_0;
var team = teams.Where(t => t.Key == key).SelectMany(s => s);
if (!team.Any())
return false;
if (e.Modifiers == Modifiers.Shift)
team = team.Reverse();
selected = team.SkipWhile(t => t.Player != selected.Player).Skip(1).FirstOrDefault() ?? team.FirstOrDefault();
selected.OnClick();
return true;
}
}
return false;
}
}
}

View File

@@ -279,6 +279,12 @@ namespace OpenRA.Mods.RA.Widgets.Logic
{ "GuardKey", "Guard" }
};
var observerHotkeys = new Dictionary<string, string>()
{
{ "ObserverCombinedView", "All Players" },
{ "ObserverWorldView", "Disable Shroud" }
};
var gs = Game.Settings.Game;
var ks = Game.Settings.Keys;
@@ -304,6 +310,13 @@ namespace OpenRA.Mods.RA.Widgets.Logic
foreach (var kv in specialHotkeys)
BindHotkeyPref(kv, ks, globalTemplate, hotkeyList);
var observerHeader = ScrollItemWidget.Setup(hotkeyHeader, () => true, () => {});
observerHeader.Get<LabelWidget>("LABEL").GetText = () => "Observer Commands";
hotkeyList.AddChild(observerHeader);
foreach (var kv in observerHotkeys)
BindHotkeyPref(kv, ks, globalTemplate, hotkeyList);
var unitHeader = ScrollItemWidget.Setup(hotkeyHeader, () => true, () => {});
unitHeader.Get<LabelWidget>("LABEL").GetText = () => "Unit Commands";
hotkeyList.AddChild(unitHeader);

View File

@@ -0,0 +1,25 @@
#region Copyright & License Information
/*
* Copyright 2007-2014 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.RA.Widgets
{
public class LogicKeyListenerWidget : Widget
{
public Func<KeyInput, bool> OnKeyPress = _ => false;
public override bool HandleKeyPress(KeyInput e)
{
return OnKeyPress(e);
}
}
}

View File

@@ -64,6 +64,43 @@ ScrollPanel@TEAM_DROPDOWN_TEMPLATE:
Height:25
Align:Center
ScrollPanel@SPECTATOR_DROPDOWN_TEMPLATE:
Width:DROPDOWN_WIDTH
Background:panel-black
Children:
ScrollItem@HEADER:
Width:PARENT_RIGHT-27
Height:13
X:2
Y:0
Visible:false
Children:
Label@LABEL:
Font:TinyBold
Width:PARENT_RIGHT
Height:10
Align:Center
ScrollItem@TEMPLATE:
Width:PARENT_RIGHT-27
Height:25
X:2
Y:0
Visible:false
Children:
Image@FLAG:
X:4
Y:4
Width:32
Height:16
Label@LABEL:
X:40
Width:60
Height:25
Label@NOFLAG_LABEL:
X:5
Width:PARENT_RIGHT
Height:25
Container@CONFIRM_PROMPT:
X:(WINDOW_RIGHT - WIDTH)/2
Y:(WINDOW_BOTTOM - 90)/2

View File

@@ -158,9 +158,26 @@ Container@OBSERVER_WIDGETS:
Width:168
Height:25
Font:Bold
Children:
LogicKeyListener@SHROUD_KEYHANDLER:
Image@FLAG:
X:4
Y:4
Width:32
Height:16
Label@LABEL:
X:40
Width:60
Height:25
Label@NOFLAG_LABEL:
X:5
Width:PARENT_RIGHT
Height:25
Container@PLAYER_WIDGETS:
Children:
LogicKeyListener@CONTROLGROUP_KEYHANDLER:
Logic:ControlGroupLogic
LogicTicker@SIDEBAR_TICKER:
WorldCommand:
Width:WINDOW_RIGHT

View File

@@ -0,0 +1,80 @@
ScrollPanel@LABEL_DROPDOWN_TEMPLATE:
Width:DROPDOWN_WIDTH
Children:
ScrollItem@HEADER:
BaseName:scrollheader
Width:PARENT_RIGHT-27
Height:13
X:2
Y:0
Visible:false
Children:
Label@LABEL:
Font:TinyBold
Width:PARENT_RIGHT
Height:10
Align:Center
ScrollItem@TEMPLATE:
Width:PARENT_RIGHT-27
Height:25
X:2
Y:0
Visible:false
Children:
Label@LABEL:
X:10
Width:PARENT_RIGHT-20
Height:25
ScrollPanel@TEAM_DROPDOWN_TEMPLATE:
Width:DROPDOWN_WIDTH
Children:
ScrollItem@TEMPLATE:
Width:PARENT_RIGHT-27
Height:25
X:2
Y:0
Visible:false
Children:
Label@LABEL:
X:0
Width:PARENT_RIGHT
Height:25
Align:Center
ScrollPanel@SPECTATOR_DROPDOWN_TEMPLATE:
Width:DROPDOWN_WIDTH
Children:
ScrollItem@HEADER:
BaseName:scrollheader
Width:PARENT_RIGHT-27
Height:13
X:2
Y:0
Visible:false
Children:
Label@LABEL:
Font:TinyBold
Width:PARENT_RIGHT
Height:10
Align:Center
ScrollItem@TEMPLATE:
Width:PARENT_RIGHT-27
Height:25
X:2
Y:0
Visible:false
Children:
Image@FLAG:
Width:23
Height:23
X:4
Y:2
Label@LABEL:
X:34
Width:60
Height:25
Label@NOFLAG_LABEL:
X:5
Width:PARENT_RIGHT
Height:25

View File

@@ -0,0 +1,114 @@
Container@OBSERVER_WIDGETS:
Children:
Button@INGAME_STATS_BUTTON:
X:162
Y:0
Width:160
Height:25
Text:Statistics (F1)
Font:Bold
Key:f1
Background@RADAR_BG:
X:WINDOW_RIGHT-255
Y:5
Width:250
Height:250
Children:
Radar@INGAME_RADAR:
X:10
Y:10
Width:PARENT_RIGHT-19
Height:PARENT_BOTTOM-19
WorldInteractionController:INTERACTION_CONTROLLER
Background@OBSERVER_CONTROL_BG:
X:WINDOW_RIGHT-255
Y:260
Width:250
Height:55
Children:
DropDownButton@SHROUD_SELECTOR:
Logic:ObserverShroudSelectorLogic
X:15
Y:15
Width:220
Height:25
Font:Bold
Children:
LogicKeyListener@SHROUD_KEYHANDLER:
Image@FLAG:
Width:23
Height:23
X:4
Y:2
Label@LABEL:
X:34
Width:60
Height:25
Label@NOFLAG_LABEL:
X:5
Width:PARENT_RIGHT
Height:25
Container@REPLAY_PLAYER:
Logic:ReplayControlBarLogic
X:PARENT_RIGHT/2 - 80
Y:35
Width:160
Height:35
Visible:false
Children:
Button@BUTTON_PAUSE:
X:15
Y:15
Width:25
Height:25
IgnoreChildMouseOver:true
Children:
Image@IMAGE_PAUSE:
X:0
Y:0
Width:25
Height:25
ImageCollection:music
ImageName:pause
Button@BUTTON_SLOW:
X:50
Y:15
Width:25
Height:25
IgnoreChildMouseOver:true
Children:
Image@IMAGE_SLOW:
X:4
Y:0
Width:25
Height:25
ImageCollection:music
ImageName:slowmo
Button@BUTTON_NORMALSPEED:
X:85
Y:15
Width:25
Height:25
IgnoreChildMouseOver:true
Children:
Image@IMAGE_PLAY:
X:0
Y:0
Width:25
Height:25
ImageCollection:music
ImageName:play
Button@BUTTON_FASTFORWARD:
X:120
Y:15
Width:25
Height:25
IgnoreChildMouseOver:true
Children:
Image@IMAGE_FASTFORWARD:
X:4
Y:0
Width:25
Height:25
ImageCollection:music
ImageName:fastforward

View File

@@ -1,5 +1,7 @@
Container@PLAYER_WIDGETS:
Children:
LogicKeyListener@CONTROLGROUP_KEYHANDLER:
Logic:ControlGroupLogic
LogicTicker@SIDEBAR_TICKER:
Button@INGAME_DIPLOMACY_BUTTON:
X:162

View File

@@ -62,7 +62,7 @@ ChromeLayout:
mods/ra/chrome/ingame-diplomacy.yaml
mods/ra/chrome/ingame-fmvplayer.yaml
mods/ra/chrome/ingame-menu.yaml
mods/ra/chrome/ingame-observer.yaml
mods/d2k/chrome/ingame-observer.yaml
mods/ra/chrome/ingame-observerstats.yaml
mods/d2k/chrome/ingame-player.yaml
mods/d2k/chrome/mainmenu.yaml
@@ -79,7 +79,7 @@ ChromeLayout:
mods/ra/chrome/connection.yaml
mods/ra/chrome/directconnect.yaml
mods/ra/chrome/replaybrowser.yaml
mods/ra/chrome/dropdowns.yaml
mods/d2k/chrome/dropdowns.yaml
mods/ra/chrome/modchooser.yaml
mods/ra/chrome/cheats.yaml
mods/ra/chrome/musicplayer.yaml

View File

@@ -40,4 +40,41 @@ ScrollPanel@TEAM_DROPDOWN_TEMPLATE:
X:0
Width:PARENT_RIGHT
Height:25
Align:Center
Align:Center
ScrollPanel@SPECTATOR_DROPDOWN_TEMPLATE:
Width:DROPDOWN_WIDTH
Children:
ScrollItem@HEADER:
BaseName:scrollheader
Width:PARENT_RIGHT-27
Height:13
X:2
Y:0
Visible:false
Children:
Label@LABEL:
Font:TinyBold
Width:PARENT_RIGHT
Height:10
Align:Center
ScrollItem@TEMPLATE:
Width:PARENT_RIGHT-27
Height:25
X:2
Y:0
Visible:false
Children:
Image@FLAG:
X:4
Y:4
Width:32
Height:16
Label@LABEL:
X:40
Width:60
Height:25
Label@NOFLAG_LABEL:
X:5
Width:PARENT_RIGHT
Height:25

View File

@@ -33,6 +33,21 @@ Container@OBSERVER_WIDGETS:
Width:220
Height:25
Font:Bold
Children:
LogicKeyListener@SHROUD_KEYHANDLER:
Image@FLAG:
X:4
Y:4
Width:32
Height:16
Label@LABEL:
X:40
Width:60
Height:25
Label@NOFLAG_LABEL:
X:5
Width:PARENT_RIGHT
Height:25
Container@REPLAY_PLAYER:
Logic:ReplayControlBarLogic
X:PARENT_RIGHT/2 - 80

View File

@@ -1,5 +1,7 @@
Container@PLAYER_WIDGETS:
Children:
LogicKeyListener@CONTROLGROUP_KEYHANDLER:
Logic:ControlGroupLogic
LogicTicker@SIDEBAR_TICKER:
Button@INGAME_DIPLOMACY_BUTTON:
X:162