From 52f1ab09699398548555228f34851f19ef564e9e Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 3 Jun 2017 15:52:59 +0100 Subject: [PATCH] Add backend code for unit command bar. --- OpenRA.Game/Orders/GenericSelectTarget.cs | 2 +- OpenRA.Game/Selection.cs | 12 +- .../Widgets/Logic/Ingame/CommandBarLogic.cs | 259 ++++++++++++++++++ .../Logic/Ingame/StanceSelectorLogic.cs | 88 ++++++ 4 files changed, 355 insertions(+), 6 deletions(-) create mode 100644 OpenRA.Mods.Common/Widgets/Logic/Ingame/CommandBarLogic.cs create mode 100644 OpenRA.Mods.Common/Widgets/Logic/Ingame/StanceSelectorLogic.cs diff --git a/OpenRA.Game/Orders/GenericSelectTarget.cs b/OpenRA.Game/Orders/GenericSelectTarget.cs index 5938970e14..71c5a50353 100644 --- a/OpenRA.Game/Orders/GenericSelectTarget.cs +++ b/OpenRA.Game/Orders/GenericSelectTarget.cs @@ -15,8 +15,8 @@ namespace OpenRA.Orders { public class GenericSelectTarget : UnitOrderGenerator { + public readonly string OrderName; protected readonly IEnumerable Subjects; - protected readonly string OrderName; protected readonly string Cursor; protected readonly MouseButton ExpectedButton; diff --git a/OpenRA.Game/Selection.cs b/OpenRA.Game/Selection.cs index 250cade7a0..97087cb3fe 100644 --- a/OpenRA.Game/Selection.cs +++ b/OpenRA.Game/Selection.cs @@ -25,9 +25,10 @@ namespace OpenRA void UpdateHash() { - Hash = actors.Count << 16; - foreach (var a in actors) - Hash ^= Sync.HashActor(a); + // Not a real hash, but things checking this only care about checking when the selection has changed + // For this purpose, having a false positive (forcing a refresh when nothing changed) is much better + // than a false negative (selection state mismatch) + Hash += 1; } public void Add(World w, Actor a) @@ -107,8 +108,9 @@ namespace OpenRA public void Tick(World world) { - actors.RemoveWhere(a => !a.IsInWorld || (!a.Owner.IsAlliedWith(world.RenderPlayer) && world.FogObscures(a))); - UpdateHash(); + var removed = actors.RemoveWhere(a => !a.IsInWorld || (!a.Owner.IsAlliedWith(world.RenderPlayer) && world.FogObscures(a))); + if (removed > 0) + UpdateHash(); foreach (var cg in controlGroups.Values) { diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/CommandBarLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/CommandBarLogic.cs new file mode 100644 index 0000000000..6e87a20844 --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/CommandBarLogic.cs @@ -0,0 +1,259 @@ +#region Copyright & License Information +/* + * Copyright 2007-2017 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, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Drawing; +using System.Linq; +using OpenRA.Graphics; +using OpenRA.Mods.Common.Orders; +using OpenRA.Mods.Common.Traits; +using OpenRA.Orders; +using OpenRA.Primitives; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets +{ + /// Contains all functions that are unit-specific. + public class CommandBarLogic : ChromeLogic + { + readonly World world; + + int selectionHash; + Actor[] selectedActors = { }; + bool attackMoveDisabled = true; + bool forceMoveDisabled = true; + bool forceAttackDisabled = true; + bool guardDisabled = true; + bool scatterDisabled = true; + + int deployHighlighted; + int scatterHighlighted; + int stopHighlighted; + + TraitPair[] selectedDeploys = { }; + + [ObjectCreator.UseCtor] + public CommandBarLogic(Widget widget, World world) + { + this.world = world; + var ks = Game.Settings.Keys; + + var attackMoveButton = widget.GetOrNull("ATTACK_MOVE"); + if (attackMoveButton != null) + { + BindButtonIcon(attackMoveButton); + + attackMoveButton.GetKey = _ => ks.AttackMoveKey; + attackMoveButton.IsDisabled = () => { UpdateStateIfNecessary(); return attackMoveDisabled; }; + attackMoveButton.IsHighlighted = () => world.OrderGenerator is GenericSelectTarget + && ((GenericSelectTarget)world.OrderGenerator).OrderName == "AttackMove"; + + attackMoveButton.OnClick = () => + { + if (attackMoveButton.IsHighlighted()) + world.CancelInputMode(); + else + world.OrderGenerator = new GenericSelectTarget(selectedActors, + "AttackMove", "attackmove", Game.Settings.Game.MouseButtonPreference.Action); + }; + } + + var forceMoveButton = widget.GetOrNull("FORCE_MOVE"); + if (forceMoveButton != null) + { + BindButtonIcon(forceMoveButton); + + forceMoveButton.IsDisabled = () => { UpdateStateIfNecessary(); return forceMoveDisabled; }; + forceMoveButton.IsHighlighted = () => !forceMoveButton.IsDisabled() && IsForceModifiersActive(Modifiers.Alt); + forceMoveButton.OnClick = () => + { + if (forceMoveButton.IsHighlighted()) + world.CancelInputMode(); + else + world.OrderGenerator = new ForceModifiersOrderGenerator(Modifiers.Alt, true); + }; + } + + var forceAttackButton = widget.GetOrNull("FORCE_ATTACK"); + if (forceAttackButton != null) + { + BindButtonIcon(forceAttackButton); + + forceAttackButton.IsDisabled = () => { UpdateStateIfNecessary(); return forceAttackDisabled; }; + forceAttackButton.IsHighlighted = () => !forceAttackButton.IsDisabled() && IsForceModifiersActive(Modifiers.Ctrl); + forceAttackButton.OnClick = () => + { + if (forceAttackButton.IsHighlighted()) + world.CancelInputMode(); + else + world.OrderGenerator = new ForceModifiersOrderGenerator(Modifiers.Ctrl, true); + }; + } + + var guardButton = widget.GetOrNull("GUARD"); + if (guardButton != null) + { + BindButtonIcon(guardButton); + + guardButton.GetKey = _ => ks.GuardKey; + guardButton.IsDisabled = () => { UpdateStateIfNecessary(); return guardDisabled; }; + guardButton.IsHighlighted = () => world.OrderGenerator is GenericSelectTarget + && ((GenericSelectTarget)world.OrderGenerator).OrderName == "Guard"; + + guardButton.OnClick = () => + { + if (guardButton.IsHighlighted()) + world.CancelInputMode(); + else + world.OrderGenerator = new GuardOrderGenerator(selectedActors, + "Guard", "guard", Game.Settings.Game.MouseButtonPreference.Action); + }; + } + + var scatterButton = widget.GetOrNull("SCATTER"); + if (scatterButton != null) + { + BindButtonIcon(scatterButton); + + scatterButton.GetKey = _ => ks.ScatterKey; + scatterButton.IsDisabled = () => { UpdateStateIfNecessary(); return scatterDisabled; }; + scatterButton.IsHighlighted = () => scatterHighlighted > 0; + scatterButton.OnClick = () => PerformKeyboardOrderOnSelection(a => new Order("Scatter", a, false)); + scatterButton.OnKeyPress = ki => { scatterHighlighted = 2; scatterButton.OnClick(); }; + } + + var deployButton = widget.GetOrNull("DEPLOY"); + if (deployButton != null) + { + BindButtonIcon(deployButton); + + deployButton.GetKey = _ => ks.DeployKey; + deployButton.IsDisabled = () => { UpdateStateIfNecessary(); return !selectedDeploys.Any(Exts.IsTraitEnabled); }; + deployButton.IsHighlighted = () => deployHighlighted > 0; + deployButton.OnClick = PerformDeployOrderOnSelection; + deployButton.OnKeyPress = ki => { deployHighlighted = 2; deployButton.OnClick(); }; + } + + var stopButton = widget.GetOrNull("STOP"); + if (stopButton != null) + { + BindButtonIcon(stopButton); + + stopButton.GetKey = _ => ks.StopKey; + stopButton.IsDisabled = () => { UpdateStateIfNecessary(); return !selectedActors.Any(); }; + stopButton.IsHighlighted = () => stopHighlighted > 0; + stopButton.OnClick = () => PerformKeyboardOrderOnSelection(a => new Order("Stop", a, false)); + stopButton.OnKeyPress = ki => { stopHighlighted = 2; stopButton.OnClick(); }; + } + + var queueOrdersButton = widget.GetOrNull("QUEUE_ORDERS"); + if (queueOrdersButton != null) + { + BindButtonIcon(queueOrdersButton); + + queueOrdersButton.IsDisabled = () => { UpdateStateIfNecessary(); return !selectedActors.Any(); }; + queueOrdersButton.IsHighlighted = () => !queueOrdersButton.IsDisabled() && IsForceModifiersActive(Modifiers.Shift); + queueOrdersButton.OnClick = () => + { + if (queueOrdersButton.IsHighlighted()) + world.CancelInputMode(); + else + world.OrderGenerator = new ForceModifiersOrderGenerator(Modifiers.Shift, false); + }; + } + } + + public override void Tick() + { + if (deployHighlighted > 0) + deployHighlighted--; + + if (scatterHighlighted > 0) + scatterHighlighted--; + + if (stopHighlighted > 0) + stopHighlighted--; + + base.Tick(); + } + + void BindButtonIcon(ButtonWidget button) + { + var icon = button.Get("ICON"); + icon.GetImageName = () => button.IsDisabled() ? icon.ImageName + "-disabled" : icon.ImageName; + } + + bool IsForceModifiersActive(Modifiers modifiers) + { + var fmog = world.OrderGenerator as ForceModifiersOrderGenerator; + if (fmog != null && fmog.Modifiers.HasFlag(modifiers)) + return true; + + var uog = world.OrderGenerator as UnitOrderGenerator; + if (uog != null && Game.GetModifierKeys().HasFlag(modifiers)) + return true; + + return false; + } + + void UpdateStateIfNecessary() + { + if (selectionHash == world.Selection.Hash) + return; + + selectedActors = world.Selection.Actors + .Where(a => a.Owner == world.LocalPlayer && a.IsInWorld) + .ToArray(); + + attackMoveDisabled = !selectedActors.Any(a => a.Info.HasTraitInfo() && a.Info.HasTraitInfo()); + guardDisabled = !selectedActors.Any(a => a.Info.HasTraitInfo() && a.Info.HasTraitInfo()); + forceMoveDisabled = !selectedActors.Any(a => a.Info.HasTraitInfo() || a.Info.HasTraitInfo()); + forceAttackDisabled = !selectedActors.Any(a => a.Info.HasTraitInfo()); + scatterDisabled = !selectedActors.Any(a => a.Info.HasTraitInfo()); + + selectedDeploys = selectedActors + .SelectMany(a => a.TraitsImplementing() + .Select(d => new TraitPair(a, d))) + .ToArray(); + + selectionHash = world.Selection.Hash; + } + + void PerformKeyboardOrderOnSelection(Func f) + { + UpdateStateIfNecessary(); + + var orders = selectedActors + .Select(f) + .ToArray(); + + foreach (var o in orders) + world.IssueOrder(o); + + world.PlayVoiceForOrders(orders); + } + + void PerformDeployOrderOnSelection() + { + UpdateStateIfNecessary(); + + var orders = selectedDeploys + .Where(Exts.IsTraitEnabled) + .Select(d => d.Trait.IssueDeployOrder(d.Actor)) + .ToArray(); + + foreach (var o in orders) + world.IssueOrder(o); + + world.PlayVoiceForOrders(orders); + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/StanceSelectorLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/StanceSelectorLogic.cs new file mode 100644 index 0000000000..682fd5237c --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/StanceSelectorLogic.cs @@ -0,0 +1,88 @@ +#region Copyright & License Information +/* + * Copyright 2007-2017 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, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Linq; +using OpenRA.Mods.Common.Traits; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets +{ + public class StanceSelectorLogic : ChromeLogic + { + readonly World world; + + int selectionHash; + TraitPair[] actorStances = { }; + + [ObjectCreator.UseCtor] + public StanceSelectorLogic(Widget widget, World world) + { + this.world = world; + var ks = Game.Settings.Keys; + + var holdFireButton = widget.GetOrNull("STANCE_HOLDFIRE"); + if (holdFireButton != null) + BindStanceButton(holdFireButton, UnitStance.HoldFire, _ => ks.StanceHoldFireKey); + + var returnFireButton = widget.GetOrNull("STANCE_RETURNFIRE"); + if (returnFireButton != null) + BindStanceButton(returnFireButton, UnitStance.ReturnFire, _ => ks.StanceReturnFireKey); + + var defendButton = widget.GetOrNull("STANCE_DEFEND"); + if (defendButton != null) + BindStanceButton(defendButton, UnitStance.Defend, _ => ks.StanceDefendKey); + + var attackAnythingButton = widget.GetOrNull("STANCE_ATTACKANYTHING"); + if (attackAnythingButton != null) + BindStanceButton(attackAnythingButton, UnitStance.AttackAnything, _ => ks.StanceAttackAnythingKey); + } + + void BindStanceButton(ButtonWidget button, UnitStance stance, Func getHotkey) + { + var icon = button.Get("ICON"); + icon.GetImageName = () => button.IsDisabled() ? icon.ImageName + "-disabled" : + button.IsHighlighted() ? icon.ImageName + "-active" : icon.ImageName; + + button.GetKey = getHotkey; + button.IsDisabled = () => { UpdateStateIfNecessary(); return !actorStances.Any(); }; + button.IsHighlighted = () => actorStances.Any( + at => !at.Trait.IsTraitDisabled && at.Trait.PredictedStance == stance); + button.OnClick = () => SetSelectionStance(stance); + } + + void UpdateStateIfNecessary() + { + if (selectionHash == world.Selection.Hash) + return; + + actorStances = world.Selection.Actors + .Where(a => a.Owner == world.LocalPlayer && a.IsInWorld) + .SelectMany(a => a.TraitsImplementing() + .Where(at => at.Info.EnableStances) + .Select(at => new TraitPair(a, at))) + .ToArray(); + + selectionHash = world.Selection.Hash; + } + + void SetSelectionStance(UnitStance stance) + { + foreach (var at in actorStances) + { + if (!at.Trait.IsTraitDisabled) + at.Trait.PredictedStance = stance; + + world.IssueOrder(new Order("SetUnitStance", at.Actor, false) { ExtraData = (uint)stance }); + } + } + } +}