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. Order lines are now shown on unit selection.
Fixed chat synchronization in replays. Fixed chat synchronization in replays.
Added a combined shroud view of every player to the replay viewer and spectator mode. 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. 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. 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. 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 DeployKey = new Hotkey(Keycode.F, Modifiers.None);
public Hotkey StanceCycleKey = new Hotkey(Keycode.Z, Modifiers.None); public Hotkey StanceCycleKey = new Hotkey(Keycode.Z, Modifiers.None);
public Hotkey GuardKey = new Hotkey(Keycode.D, 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 public class IrcSettings

View File

@@ -198,13 +198,7 @@ namespace OpenRA.Widgets
{ {
if (e.Event == KeyInputEvent.Down) if (e.Event == KeyInputEvent.Down)
{ {
if (e.Key >= Keycode.NUMBER_0 && e.Key <= Keycode.NUMBER_9) if (Hotkey.FromKeyInput(e) == Game.Settings.Keys.PauseKey && World.LocalPlayer != null) // Disable pausing for spectators
{
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
World.SetPauseState(!World.Paused); World.SetPauseState(!World.Paused);
else if (Hotkey.FromKeyInput(e) == Game.Settings.Keys.SelectAllUnitsKey) 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\LobbyMapPreviewLogic.cs" />
<Compile Include="Widgets\Logic\ButtonTooltipLogic.cs" /> <Compile Include="Widgets\Logic\ButtonTooltipLogic.cs" />
<Compile Include="Orders\BeaconOrderGenerator.cs" /> <Compile Include="Orders\BeaconOrderGenerator.cs" />
<Compile Include="Widgets\LogicKeyListenerWidget.cs" />
<Compile Include="Widgets\Logic\ControlGroupLogic.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj"> <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 #endregion
using System; using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq; using System.Linq;
using OpenRA.Network;
using OpenRA.Widgets; using OpenRA.Widgets;
namespace OpenRA.Mods.RA.Widgets.Logic namespace OpenRA.Mods.RA.Widgets.Logic
{ {
public class ObserverShroudSelectorLogic public class ObserverShroudSelectorLogic
{ {
CameraOption selected;
CameraOption combined, disableShroud;
IOrderedEnumerable<IGrouping<int, CameraOption>> teams;
class CameraOption class CameraOption
{ {
public string Label; public readonly Player Player;
public Func<bool> IsSelected; public readonly string Label;
public Action OnClick; 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)
{ {
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; };
}
public CameraOption(ObserverShroudSelectorLogic logic, World w, string label, Player p)
{
Player = p;
Label = label; Label = label;
IsSelected = isSelected; Color = Color.White;
OnClick = onClick; Race = null;
IsSelected = () => w.RenderPlayer == p;
OnClick = () => { w.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";
}
[ObjectCreator.UseCtor] [ObjectCreator.UseCtor]
public ObserverShroudSelectorLogic(Widget widget, World world) public ObserverShroudSelectorLogic(Widget widget, World world)
{ {
var views = world.Players.Where(p => (p.NonCombatant && p.Spectating) var groups = new Dictionary<string, IEnumerable<CameraOption>>();
|| !p.NonCombatant).Concat(new[] { (Player)null }).Select(
p => new CameraOption(LabelForPlayer(p), teams = world.Players.Where(p => !p.NonCombatant)
() => world.RenderPlayer == p, .Select(p => new CameraOption(this, p))
() => world.RenderPlayer = p .GroupBy(p => (world.LobbyInfo.ClientWithIndex(p.Player.ClientIndex) ?? new Session.Client()).Team)
)).ToArray(); .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"); var shroudSelector = widget.Get<DropDownButtonWidget>("SHROUD_SELECTOR");
shroudSelector.GetText = () => LabelForPlayer(world.RenderPlayer);
shroudSelector.OnMouseDown = _ => shroudSelector.OnMouseDown = _ =>
{ {
Func<CameraOption, ScrollItemWidget, ScrollItemWidget> setupItem = (option, template) => Func<CameraOption, ScrollItemWidget, ScrollItemWidget> setupItem = (option, template) =>
{ {
var item = ScrollItemWidget.Setup(template, option.IsSelected, option.OnClick); 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; 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" } { "GuardKey", "Guard" }
}; };
var observerHotkeys = new Dictionary<string, string>()
{
{ "ObserverCombinedView", "All Players" },
{ "ObserverWorldView", "Disable Shroud" }
};
var gs = Game.Settings.Game; var gs = Game.Settings.Game;
var ks = Game.Settings.Keys; var ks = Game.Settings.Keys;
@@ -304,6 +310,13 @@ namespace OpenRA.Mods.RA.Widgets.Logic
foreach (var kv in specialHotkeys) foreach (var kv in specialHotkeys)
BindHotkeyPref(kv, ks, globalTemplate, hotkeyList); 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, () => {}); var unitHeader = ScrollItemWidget.Setup(hotkeyHeader, () => true, () => {});
unitHeader.Get<LabelWidget>("LABEL").GetText = () => "Unit Commands"; unitHeader.Get<LabelWidget>("LABEL").GetText = () => "Unit Commands";
hotkeyList.AddChild(unitHeader); 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 Height:25
Align:Center 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: Container@CONFIRM_PROMPT:
X:(WINDOW_RIGHT - WIDTH)/2 X:(WINDOW_RIGHT - WIDTH)/2
Y:(WINDOW_BOTTOM - 90)/2 Y:(WINDOW_BOTTOM - 90)/2

View File

@@ -158,9 +158,26 @@ Container@OBSERVER_WIDGETS:
Width:168 Width:168
Height:25 Height:25
Font:Bold 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: Container@PLAYER_WIDGETS:
Children: Children:
LogicKeyListener@CONTROLGROUP_KEYHANDLER:
Logic:ControlGroupLogic
LogicTicker@SIDEBAR_TICKER: LogicTicker@SIDEBAR_TICKER:
WorldCommand: WorldCommand:
Width:WINDOW_RIGHT 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: Container@PLAYER_WIDGETS:
Children: Children:
LogicKeyListener@CONTROLGROUP_KEYHANDLER:
Logic:ControlGroupLogic
LogicTicker@SIDEBAR_TICKER: LogicTicker@SIDEBAR_TICKER:
Button@INGAME_DIPLOMACY_BUTTON: Button@INGAME_DIPLOMACY_BUTTON:
X:162 X:162

View File

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

View File

@@ -41,3 +41,40 @@ ScrollPanel@TEAM_DROPDOWN_TEMPLATE:
Width:PARENT_RIGHT Width:PARENT_RIGHT
Height:25 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 Width:220
Height:25 Height:25
Font:Bold 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: Container@REPLAY_PLAYER:
Logic:ReplayControlBarLogic Logic:ReplayControlBarLogic
X:PARENT_RIGHT/2 - 80 X:PARENT_RIGHT/2 - 80

View File

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