diff --git a/CHANGELOG b/CHANGELOG index c7ef319749..202e3792aa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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. diff --git a/OpenRA.Game/GameRules/Settings.cs b/OpenRA.Game/GameRules/Settings.cs index f1804f9fda..d388883f7f 100644 --- a/OpenRA.Game/GameRules/Settings.cs +++ b/OpenRA.Game/GameRules/Settings.cs @@ -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 diff --git a/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs b/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs index 9b2d5b83e9..fc7d673431 100644 --- a/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs +++ b/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs @@ -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) { diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index 1599515eab..8aabe63573 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -488,6 +488,8 @@ + + diff --git a/OpenRA.Mods.RA/Widgets/Logic/ControlGroupLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ControlGroupLogic.cs new file mode 100644 index 0000000000..09353fd932 --- /dev/null +++ b/OpenRA.Mods.RA/Widgets/Logic/ControlGroupLogic.cs @@ -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("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; + }; + } + } +} diff --git a/OpenRA.Mods.RA/Widgets/Logic/ObserverShroudSelectorLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ObserverShroudSelectorLogic.cs index 51e19b1aba..90e5d443c2 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/ObserverShroudSelectorLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/ObserverShroudSelectorLogic.cs @@ -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> teams; + class CameraOption { - public string Label; - public Func IsSelected; - public Action OnClick; + public readonly Player Player; + public readonly string Label; + public readonly Color Color; + public readonly string Race; + public readonly Func IsSelected; + public readonly Action OnClick; - public CameraOption(string label, Func 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>(); + + 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() { combined, disableShroud }); var shroudSelector = widget.Get("SHROUD_SELECTOR"); - shroudSelector.GetText = () => LabelForPlayer(world.RenderPlayer); shroudSelector.OnMouseDown = _ => { Func setupItem = (option, template) => { var item = ScrollItemWidget.Setup(template, option.IsSelected, option.OnClick); - item.Get("LABEL").GetText = () => option.Label; + var showFlag = option.Race != null; + + var label = item.Get("LABEL"); + label.IsVisible = () => showFlag; + label.GetText = () => option.Label; + label.GetColor = () => option.Color; + + var flag = item.Get("FLAG"); + flag.IsVisible = () => showFlag; + flag.GetImageCollection = () => "flags"; + flag.GetImageName = () => option.Race; + + var labelAlt = item.Get("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("LABEL"); + shroudLabel.IsVisible = () => selected.Race != null; + shroudLabel.GetText = () => selected.Label; + shroudLabel.GetColor = () => selected.Color; + + var shroudFlag = shroudSelector.Get("FLAG"); + shroudFlag.IsVisible = () => selected.Race != null; + shroudFlag.GetImageCollection = () => "flags"; + shroudFlag.GetImageName = () => selected.Race; + + var shroudLabelAlt = shroudSelector.Get("NOFLAG_LABEL"); + shroudLabelAlt.IsVisible = () => selected.Race == null; + shroudLabelAlt.GetText = () => selected.Label; + shroudLabelAlt.GetColor = () => selected.Color; + + var keyhandler = shroudSelector.Get("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; } } } diff --git a/OpenRA.Mods.RA/Widgets/Logic/SettingsLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/SettingsLogic.cs index d59cc7a376..ad4fb07e4c 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/SettingsLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/SettingsLogic.cs @@ -279,6 +279,12 @@ namespace OpenRA.Mods.RA.Widgets.Logic { "GuardKey", "Guard" } }; + var observerHotkeys = new Dictionary() + { + { "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("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("LABEL").GetText = () => "Unit Commands"; hotkeyList.AddChild(unitHeader); diff --git a/OpenRA.Mods.RA/Widgets/LogicKeyListenerWidget.cs b/OpenRA.Mods.RA/Widgets/LogicKeyListenerWidget.cs new file mode 100644 index 0000000000..5fca8c630d --- /dev/null +++ b/OpenRA.Mods.RA/Widgets/LogicKeyListenerWidget.cs @@ -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 OnKeyPress = _ => false; + + public override bool HandleKeyPress(KeyInput e) + { + return OnKeyPress(e); + } + } +} diff --git a/mods/cnc/chrome/dialogs.yaml b/mods/cnc/chrome/dialogs.yaml index 322b8d44af..c10968cc89 100644 --- a/mods/cnc/chrome/dialogs.yaml +++ b/mods/cnc/chrome/dialogs.yaml @@ -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 diff --git a/mods/cnc/chrome/ingame.yaml b/mods/cnc/chrome/ingame.yaml index f264c4cd39..4c2c4a8732 100644 --- a/mods/cnc/chrome/ingame.yaml +++ b/mods/cnc/chrome/ingame.yaml @@ -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 diff --git a/mods/d2k/chrome/dropdowns.yaml b/mods/d2k/chrome/dropdowns.yaml new file mode 100644 index 0000000000..450c7e2e14 --- /dev/null +++ b/mods/d2k/chrome/dropdowns.yaml @@ -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 diff --git a/mods/d2k/chrome/ingame-observer.yaml b/mods/d2k/chrome/ingame-observer.yaml new file mode 100644 index 0000000000..e6b4c3b3e4 --- /dev/null +++ b/mods/d2k/chrome/ingame-observer.yaml @@ -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 diff --git a/mods/d2k/chrome/ingame-player.yaml b/mods/d2k/chrome/ingame-player.yaml index 2d4731e64f..b4b2c54a68 100644 --- a/mods/d2k/chrome/ingame-player.yaml +++ b/mods/d2k/chrome/ingame-player.yaml @@ -1,5 +1,7 @@ Container@PLAYER_WIDGETS: Children: + LogicKeyListener@CONTROLGROUP_KEYHANDLER: + Logic:ControlGroupLogic LogicTicker@SIDEBAR_TICKER: Button@INGAME_DIPLOMACY_BUTTON: X:162 diff --git a/mods/d2k/mod.yaml b/mods/d2k/mod.yaml index f8e876d9c9..6fab881eb4 100644 --- a/mods/d2k/mod.yaml +++ b/mods/d2k/mod.yaml @@ -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 diff --git a/mods/ra/chrome/dropdowns.yaml b/mods/ra/chrome/dropdowns.yaml index 92b8ac3c3e..9bcc479319 100644 --- a/mods/ra/chrome/dropdowns.yaml +++ b/mods/ra/chrome/dropdowns.yaml @@ -40,4 +40,41 @@ ScrollPanel@TEAM_DROPDOWN_TEMPLATE: X:0 Width:PARENT_RIGHT Height:25 - Align:Center \ No newline at end of file + 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 diff --git a/mods/ra/chrome/ingame-observer.yaml b/mods/ra/chrome/ingame-observer.yaml index 8d6cd4fc45..fd36d32d45 100644 --- a/mods/ra/chrome/ingame-observer.yaml +++ b/mods/ra/chrome/ingame-observer.yaml @@ -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 diff --git a/mods/ra/chrome/ingame-player.yaml b/mods/ra/chrome/ingame-player.yaml index 7f1313dddb..e50f119644 100644 --- a/mods/ra/chrome/ingame-player.yaml +++ b/mods/ra/chrome/ingame-player.yaml @@ -1,5 +1,7 @@ Container@PLAYER_WIDGETS: Children: + LogicKeyListener@CONTROLGROUP_KEYHANDLER: + Logic:ControlGroupLogic LogicTicker@SIDEBAR_TICKER: Button@INGAME_DIPLOMACY_BUTTON: X:162