diff --git a/OpenRA.Game/Orders/GenericSelectTarget.cs b/OpenRA.Game/Orders/GenericSelectTarget.cs index 6dd8b47f41..cf8049ed9e 100644 --- a/OpenRA.Game/Orders/GenericSelectTarget.cs +++ b/OpenRA.Game/Orders/GenericSelectTarget.cs @@ -18,13 +18,14 @@ namespace OpenRA.Orders public class GenericSelectTarget : UnitOrderGenerator { public readonly string OrderName; - protected readonly IEnumerable Subjects; protected readonly string Cursor; protected readonly MouseButton ExpectedButton; + protected IEnumerable subjects; + public GenericSelectTarget(IEnumerable subjects, string order, string cursor, MouseButton button) { - Subjects = subjects; + this.subjects = subjects; OrderName = order; Cursor = cursor; ExpectedButton = button; @@ -53,7 +54,7 @@ namespace OpenRA.Orders world.CancelInputMode(); var queued = mi.Modifiers.HasModifier(Modifiers.Shift); - yield return new Order(OrderName, null, Target.FromCell(world, cell), queued, null, Subjects.ToArray()); + yield return new Order(OrderName, null, Target.FromCell(world, cell), queued, null, subjects.ToArray()); } } @@ -68,6 +69,11 @@ namespace OpenRA.Orders return true; } + public override void SelectionChanged(World world, IEnumerable selected) + { + subjects = selected; + } + public override bool ClearSelectionOnLeftClick { get { return false; } } } } diff --git a/OpenRA.Game/Orders/IOrderGenerator.cs b/OpenRA.Game/Orders/IOrderGenerator.cs index 91cf044d57..d32688cf46 100644 --- a/OpenRA.Game/Orders/IOrderGenerator.cs +++ b/OpenRA.Game/Orders/IOrderGenerator.cs @@ -24,5 +24,6 @@ namespace OpenRA string GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi); void Deactivate(); bool HandleKeyPress(KeyInput e); + void SelectionChanged(World world, IEnumerable selected); } } diff --git a/OpenRA.Game/Orders/UnitOrderGenerator.cs b/OpenRA.Game/Orders/UnitOrderGenerator.cs index 6245ffa058..59ccace228 100644 --- a/OpenRA.Game/Orders/UnitOrderGenerator.cs +++ b/OpenRA.Game/Orders/UnitOrderGenerator.cs @@ -124,6 +124,8 @@ namespace OpenRA.Orders return false; } + public virtual void SelectionChanged(World world, IEnumerable selected) { } + /// /// Returns the most appropriate order for a given actor and target. /// First priority is given to orders that interact with the given actors. diff --git a/OpenRA.Mods.Cnc/Traits/Minelayer.cs b/OpenRA.Mods.Cnc/Traits/Minelayer.cs index 597952537a..e8895caeb2 100644 --- a/OpenRA.Mods.Cnc/Traits/Minelayer.cs +++ b/OpenRA.Mods.Cnc/Traits/Minelayer.cs @@ -12,6 +12,7 @@ using System; using System.Collections.Generic; using System.Linq; +using OpenRA; using OpenRA.Graphics; using OpenRA.Mods.Cnc.Activities; using OpenRA.Mods.Common.Orders; @@ -211,9 +212,10 @@ namespace OpenRA.Mods.Cnc.Traits } } - protected override void Tick(World world) + protected override void SelectionChanged(World world, IEnumerable selected) { - minelayers.RemoveAll(minelayer => !minelayer.IsInWorld || minelayer.IsDead); + minelayers.Clear(); + minelayers.AddRange(selected.Where(s => s.Info.HasTraitInfo())); if (!minelayers.Any()) world.CancelInputMode(); } diff --git a/OpenRA.Mods.Cnc/Traits/PortableChrono.cs b/OpenRA.Mods.Cnc/Traits/PortableChrono.cs index 97ccc849c0..fff753d435 100644 --- a/OpenRA.Mods.Cnc/Traits/PortableChrono.cs +++ b/OpenRA.Mods.Cnc/Traits/PortableChrono.cs @@ -10,6 +10,7 @@ #endregion using System.Collections.Generic; +using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Cnc.Activities; using OpenRA.Mods.Common.Graphics; @@ -209,9 +210,9 @@ namespace OpenRA.Mods.Cnc.Traits } } - protected override void Tick(World world) + protected override void SelectionChanged(World world, IEnumerable selected) { - if (!self.IsInWorld || self.IsDead) + if (!selected.Contains(self)) world.CancelInputMode(); } diff --git a/OpenRA.Mods.Common/Orders/BeaconOrderGenerator.cs b/OpenRA.Mods.Common/Orders/BeaconOrderGenerator.cs index 40e241eea2..efdc85452d 100644 --- a/OpenRA.Mods.Common/Orders/BeaconOrderGenerator.cs +++ b/OpenRA.Mods.Common/Orders/BeaconOrderGenerator.cs @@ -25,7 +25,6 @@ namespace OpenRA.Mods.Common.Orders yield return new Order("PlaceBeacon", world.LocalPlayer.PlayerActor, Target.FromCell(world, cell), false) { SuppressVisualFeedback = true }; } - protected override void Tick(World world) { } protected override IEnumerable Render(WorldRenderer wr, World world) { yield break; } protected override IEnumerable RenderAboveShroud(WorldRenderer wr, World world) { yield break; } protected override IEnumerable RenderAnnotations(WorldRenderer wr, World world) { yield break; } diff --git a/OpenRA.Mods.Common/Orders/GuardOrderGenerator.cs b/OpenRA.Mods.Common/Orders/GuardOrderGenerator.cs index 5b1ead6684..82eadf7ac4 100644 --- a/OpenRA.Mods.Common/Orders/GuardOrderGenerator.cs +++ b/OpenRA.Mods.Common/Orders/GuardOrderGenerator.cs @@ -28,29 +28,31 @@ namespace OpenRA.Mods.Common.Orders yield break; var target = FriendlyGuardableUnits(world, mi).FirstOrDefault(); - if (target == null || Subjects.All(s => s.IsDead)) + if (target == null) yield break; world.CancelInputMode(); var queued = mi.Modifiers.HasModifier(Modifiers.Shift); - yield return new Order(OrderName, null, Target.FromActor(target), queued, null, Subjects.Where(s => s != target).ToArray()); + yield return new Order(OrderName, null, Target.FromActor(target), queued, null, subjects.Where(s => s != target).ToArray()); } - public override void Tick(World world) + public override void SelectionChanged(World world, IEnumerable selected) { - if (Subjects.All(s => s.IsDead || !s.Info.HasTraitInfo())) + // Guarding doesn't work without AutoTarget, so require at least one unit in the selection to have it + subjects = selected.Where(s => s.Info.HasTraitInfo()); + if (!subjects.Any(s => s.Info.HasTraitInfo())) world.CancelInputMode(); } public override string GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi) { - if (!Subjects.Any()) + if (!subjects.Any()) return null; - var multiple = Subjects.Count() > 1; + var multiple = subjects.Count() > 1; var canGuard = FriendlyGuardableUnits(world, mi) - .Any(a => multiple || a != Subjects.First()); + .Any(a => multiple || a != subjects.First()); return canGuard ? Cursor : "move-blocked"; } diff --git a/OpenRA.Mods.Common/Orders/OrderGenerator.cs b/OpenRA.Mods.Common/Orders/OrderGenerator.cs index b1d51f25a7..64201ee08e 100644 --- a/OpenRA.Mods.Common/Orders/OrderGenerator.cs +++ b/OpenRA.Mods.Common/Orders/OrderGenerator.cs @@ -32,12 +32,14 @@ namespace OpenRA.Mods.Common.Orders string IOrderGenerator.GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi) { return GetCursor(world, cell, worldPixel, mi); } void IOrderGenerator.Deactivate() { } bool IOrderGenerator.HandleKeyPress(KeyInput e) { return false; } + void IOrderGenerator.SelectionChanged(World world, IEnumerable selected) { SelectionChanged(world, selected); } - protected abstract void Tick(World world); + protected virtual void Tick(World world) { } protected abstract IEnumerable Render(WorldRenderer wr, World world); protected abstract IEnumerable RenderAboveShroud(WorldRenderer wr, World world); protected abstract IEnumerable RenderAnnotations(WorldRenderer wr, World world); protected abstract string GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi); protected abstract IEnumerable OrderInner(World world, CPos cell, int2 worldPixel, MouseInput mi); + protected virtual void SelectionChanged(World world, IEnumerable selected) { } } } diff --git a/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs b/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs index 341f632bfc..72b71993f8 100644 --- a/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs +++ b/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs @@ -222,6 +222,8 @@ namespace OpenRA.Mods.Common.Orders v.Preview.Tick(); } + void IOrderGenerator.SelectionChanged(World world, IEnumerable selected) { } + bool AcceptsPlug(CPos cell, PlugInfo plug) { var host = buildingInfluence.GetBuildingAt(cell); diff --git a/OpenRA.Mods.Common/Traits/AttackMove.cs b/OpenRA.Mods.Common/Traits/AttackMove.cs index 9ba24ee199..09b26c4745 100644 --- a/OpenRA.Mods.Common/Traits/AttackMove.cs +++ b/OpenRA.Mods.Common/Traits/AttackMove.cs @@ -84,7 +84,8 @@ namespace OpenRA.Mods.Common.Traits public class AttackMoveOrderGenerator : UnitOrderGenerator { - readonly TraitPair[] subjects; + TraitPair[] subjects; + readonly MouseButton expectedButton; public AttackMoveOrderGenerator(IEnumerable subjects, MouseButton button) @@ -120,9 +121,14 @@ namespace OpenRA.Mods.Common.Traits } } - public override void Tick(World world) + public override void SelectionChanged(World world, IEnumerable selected) { - if (subjects.All(s => s.Actor.IsDead)) + subjects = selected.SelectMany(a => a.TraitsImplementing() + .Select(am => new TraitPair(a, am))) + .ToArray(); + + // AttackMove doesn't work without AutoTarget, so require at least one unit in the selection to have it + if (!subjects.Any(s => s.Actor.Info.HasTraitInfo())) world.CancelInputMode(); } diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/SelectDirectionalTarget.cs b/OpenRA.Mods.Common/Traits/SupportPowers/SelectDirectionalTarget.cs index 33300bd6c4..d3a9d3e010 100644 --- a/OpenRA.Mods.Common/Traits/SupportPowers/SelectDirectionalTarget.cs +++ b/OpenRA.Mods.Common/Traits/SupportPowers/SelectDirectionalTarget.cs @@ -110,6 +110,8 @@ namespace OpenRA.Mods.Common.Traits world.CancelInputMode(); } + void IOrderGenerator.SelectionChanged(World world, IEnumerable selected) { } + bool IsOutsideDragZone { get { return dragStarted && dragDirection.Length > MinDragThreshold; } diff --git a/OpenRA.Mods.Common/Traits/World/Selection.cs b/OpenRA.Mods.Common/Traits/World/Selection.cs index 1473aabaa3..b03dbb7930 100644 --- a/OpenRA.Mods.Common/Traits/World/Selection.cs +++ b/OpenRA.Mods.Common/Traits/World/Selection.cs @@ -28,6 +28,7 @@ namespace OpenRA.Mods.Common.Traits public IEnumerable Actors { get { return actors; } } readonly HashSet actors = new HashSet(); + World world; IEnumerable rolloverActors; INotifySelection[] worldNotifySelection; @@ -37,6 +38,7 @@ namespace OpenRA.Mods.Common.Traits void INotifyCreated.Created(Actor self) { worldNotifySelection = self.TraitsImplementing().ToArray(); + world = self.World; } void UpdateHash() @@ -55,6 +57,7 @@ namespace OpenRA.Mods.Common.Traits foreach (var sel in a.TraitsImplementing()) sel.Selected(a); + Sync.RunUnsynced(Game.Settings.Debug.SyncCheckUnsyncedCode, world, () => world.OrderGenerator.SelectionChanged(world, actors)); foreach (var ns in worldNotifySelection) ns.SelectionChanged(); } @@ -64,6 +67,7 @@ namespace OpenRA.Mods.Common.Traits if (actors.Remove(a)) { UpdateHash(); + Sync.RunUnsynced(Game.Settings.Debug.SyncCheckUnsyncedCode, world, () => world.OrderGenerator.SelectionChanged(world, actors)); foreach (var ns in worldNotifySelection) ns.SelectionChanged(); } @@ -76,7 +80,7 @@ namespace OpenRA.Mods.Common.Traits // Remove the actor from the original owners selection // Call UpdateHash directly for everyone else so watchers can account for the owner change if needed - if (oldOwner == a.World.LocalPlayer) + if (oldOwner == world.LocalPlayer) Remove(a); else UpdateHash(); @@ -91,7 +95,8 @@ namespace OpenRA.Mods.Common.Traits { if (isClick) { - var adjNewSelection = newSelection.Take(1); // TODO: select BEST, not FIRST + // TODO: select BEST, not FIRST + var adjNewSelection = newSelection.Take(1); if (isCombine) actors.SymmetricExceptWith(adjNewSelection); else @@ -117,6 +122,7 @@ namespace OpenRA.Mods.Common.Traits foreach (var sel in a.TraitsImplementing()) sel.Selected(a); + Sync.RunUnsynced(Game.Settings.Debug.SyncCheckUnsyncedCode, world, () => world.OrderGenerator.SelectionChanged(world, actors)); foreach (var ns in worldNotifySelection) ns.SelectionChanged(); @@ -143,6 +149,9 @@ namespace OpenRA.Mods.Common.Traits { actors.Clear(); UpdateHash(); + Sync.RunUnsynced(Game.Settings.Debug.SyncCheckUnsyncedCode, world, () => world.OrderGenerator.SelectionChanged(world, actors)); + foreach (var ns in worldNotifySelection) + ns.SelectionChanged(); } public void SetRollover(IEnumerable rollover) @@ -157,18 +166,23 @@ namespace OpenRA.Mods.Common.Traits void ITick.Tick(Actor self) { - var removed = actors.RemoveWhere(a => !a.IsInWorld || (!a.Owner.IsAlliedWith(self.World.RenderPlayer) && self.World.FogObscures(a))); + var removed = actors.RemoveWhere(a => !a.IsInWorld || (!a.Owner.IsAlliedWith(world.RenderPlayer) && world.FogObscures(a))); if (removed > 0) + { UpdateHash(); + Sync.RunUnsynced(Game.Settings.Debug.SyncCheckUnsyncedCode, world, () => world.OrderGenerator.SelectionChanged(world, actors)); + foreach (var ns in worldNotifySelection) + ns.SelectionChanged(); + } foreach (var cg in controlGroups.Values) { // note: NOT `!a.IsInWorld`, since that would remove things that are in transports. - cg.RemoveAll(a => a.Disposed || a.Owner != self.World.LocalPlayer); + cg.RemoveAll(a => a.Disposed || a.Owner != world.LocalPlayer); } } - Cache> controlGroups = new Cache>(_ => new List()); + readonly Cache> controlGroups = new Cache>(_ => new List()); public void DoControlGroup(World world, WorldRenderer worldRenderer, int group, Modifiers mods, int multiTapCount) {