From e965a0d3f043acaf4cef22a348a3644e33f5ec6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20Husti=C4=87?= Date: Mon, 1 Jun 2015 02:59:28 +0100 Subject: [PATCH] Touched up selection functionality. --- OpenRA.Game/Traits/Selectable.cs | 7 ++ .../WorldInteractionControllerWidget.cs | 78 +++++++++++++------ mods/d2k/rules/starport.yaml | 1 - mods/d2k/rules/vehicles.yaml | 7 ++ mods/ra/rules/defaults.yaml | 2 + mods/ra/rules/infantry.yaml | 3 + 6 files changed, 74 insertions(+), 24 deletions(-) diff --git a/OpenRA.Game/Traits/Selectable.cs b/OpenRA.Game/Traits/Selectable.cs index 5e23b4cab620..ed1df48ec1e5 100644 --- a/OpenRA.Game/Traits/Selectable.cs +++ b/OpenRA.Game/Traits/Selectable.cs @@ -21,11 +21,17 @@ public class SelectableInfo : ITraitInfo public readonly int Priority = 10; public readonly int[] Bounds = null; + [Desc("All units having the same selection class specified will be selected with select-by-type commands (e.g. double-click). " + + "Defaults to the actor name when not defined or inherited.")] + public readonly string Class = null; + public object Create(ActorInitializer init) { return new Selectable(init.Self, this); } } public class Selectable : IPostRenderSelection { + public readonly string Class = null; + public SelectableInfo Info; readonly Actor self; @@ -33,6 +39,7 @@ public Selectable(Actor self, SelectableInfo info) { this.self = self; Info = info; + Class = string.IsNullOrEmpty(info.Class) ? self.Info.Name : info.Class; } IEnumerable ActivityTargetPath() diff --git a/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs b/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs index e594d006f406..ca07f6d9fb6d 100644 --- a/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs +++ b/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs @@ -40,15 +40,17 @@ public override void Draw() { if (!IsDragging) { - foreach (var u in SelectActorsInBoxWithDeadzone(World, lastMousePosition, lastMousePosition, _ => true)) + // Render actors under the mouse pointer + foreach (var u in SelectActorsInBoxWithDeadzone(World, lastMousePosition, lastMousePosition)) worldRenderer.DrawRollover(u); return; } + // Render actors in the dragbox var selbox = SelectionBox; Game.Renderer.WorldLineRenderer.DrawRect(selbox.Value.First.ToFloat2(), selbox.Value.Second.ToFloat2(), Color.White); - foreach (var u in SelectActorsInBoxWithDeadzone(World, selbox.Value.First, selbox.Value.Second, _ => true)) + foreach (var u in SelectActorsInBoxWithDeadzone(World, selbox.Value.First, selbox.Value.Second)) worldRenderer.DrawRollover(u); } @@ -111,15 +113,20 @@ public override bool HandleMouseInput(MouseInput mi) if (unit != null && unit.Owner == (World.RenderPlayer ?? World.LocalPlayer)) { - var newSelection2 = SelectActorsInBox(World, worldRenderer.Viewport.TopLeft, worldRenderer.Viewport.BottomRight, - a => a.Owner == unit.Owner && a.Info.Name == unit.Info.Name); + var s = unit.TraitOrDefault(); + if (s != null) + { + // Select actors on the screen that have the same selection class as the actor under the mouse cursor + var newSelection = SelectActorsOnScreen(World, worldRenderer, new HashSet { s.Class }, unit.Owner); - World.Selection.Combine(World, newSelection2, true, false); + World.Selection.Combine(World, newSelection, true, false); + } } } else if (dragStart.HasValue) { - var newSelection = SelectActorsInBoxWithDeadzone(World, dragStart.Value, xy, _ => true); + // Select actors in the dragbox + var newSelection = SelectActorsInBoxWithDeadzone(World, dragStart.Value, xy); World.Selection.Combine(World, newSelection, mi.Modifiers.HasModifier(Modifiers.Shift), dragStart == xy); } } @@ -230,26 +237,28 @@ public override bool HandleKeyPress(KeyInput e) World.SetPauseState(!World.Paused); else if (key == Game.Settings.Keys.SelectAllUnitsKey) { - var ownUnitsOnScreen = SelectActorsInBox(World, worldRenderer.Viewport.TopLeft, worldRenderer.Viewport.BottomRight, - a => a.Owner == player); + // Select actors on the screen which belong to the current player + var ownUnitsOnScreen = SelectActorsOnScreen(World, worldRenderer, null, player); World.Selection.Combine(World, ownUnitsOnScreen, false, false); } else if (key == Game.Settings.Keys.SelectUnitsByTypeKey) { - var selectedTypes = World.Selection.Actors + // Get all the selected actors' selection classes + var selectedClasses = World.Selection.Actors .Where(x => x.Owner == player) - .Select(a => a.Info); + .Select(a => a.Trait().Class) + .ToHashSet(); - Func cond = a => a.Owner == player && selectedTypes.Contains(a.Info); - var tl = worldRenderer.Viewport.TopLeft; - var br = worldRenderer.Viewport.BottomRight; - var newSelection = SelectActorsInBox(World, tl, br, cond); + // Select actors on the screen that have the same selection class as one of the already selected actors + var newSelection = SelectActorsOnScreen(World, worldRenderer, selectedClasses, player).ToList(); - if (newSelection.Count() > selectedTypes.Count()) + // Check if selecting actors on the screen has selected new units + if (newSelection.Count() > World.Selection.Actors.Count()) Game.Debug("Selected across screen"); else { - newSelection = World.ActorMap.ActorsInWorld().Where(cond); + // Select actors in the world that have the same selection class as one of the already selected actors + newSelection = SelectActorsInWorld(World, selectedClasses, player).ToList(); Game.Debug("Selected across map"); } @@ -264,18 +273,41 @@ public override bool HandleKeyPress(KeyInput e) return false; } - static IEnumerable SelectActorsInBoxWithDeadzone(World world, int2 a, int2 b, Func cond) + static IEnumerable SelectActorsOnScreen(World world, WorldRenderer wr, IEnumerable selectionClasses, Player player) + { + return SelectActorsByPlayerByClass(world.ScreenMap.ActorsInBox(wr.Viewport.TopLeft, wr.Viewport.BottomRight), selectionClasses, player); + } + + static IEnumerable SelectActorsInWorld(World world, IEnumerable selectionClasses, Player player) + { + return SelectActorsByPlayerByClass(world.ActorMap.ActorsInWorld(), selectionClasses, player); + } + + static IEnumerable SelectActorsByPlayerByClass(IEnumerable actors, IEnumerable selectionClasses, Player player) { - if (a == b || (a - b).Length > Game.Settings.Game.SelectionDeadzone) - return SelectActorsInBox(world, a, b, cond); - else - return SelectActorsInBox(world, b, b, cond); + return actors.Where(a => + { + if (a.Owner != player) + return false; + var s = a.TraitOrDefault(); + + // sc == null means that units, that meet all other criteria, get selected + return s != null && s.Info.Selectable && (selectionClasses == null || selectionClasses.Contains(s.Class)); + }); } - static IEnumerable SelectActorsInBox(World world, int2 a, int2 b, Func cond) + static IEnumerable SelectActorsInBoxWithDeadzone(World world, int2 a, int2 b) { + // For dragboxes that are too small, shrink the dragbox to a single point (point b) + if ((a - b).Length <= Game.Settings.Game.SelectionDeadzone) + a = b; + return world.ScreenMap.ActorsInBox(a, b) - .Where(x => x.HasTrait() && x.Trait().Info.Selectable && (x.Owner.IsAlliedWith(world.RenderPlayer) || !world.FogObscures(x)) && cond(x)) + .Where(x => + { + var s = x.TraitOrDefault(); + return s != null && s.Info.Selectable && (x.Owner.IsAlliedWith(world.RenderPlayer) || !world.FogObscures(x)); + }) .GroupBy(x => x.GetSelectionPriority()) .OrderByDescending(g => g.Key) .Select(g => g.AsEnumerable()) diff --git a/mods/d2k/rules/starport.yaml b/mods/d2k/rules/starport.yaml index 9a66de1dbaa0..c548f5946b88 100644 --- a/mods/d2k/rules/starport.yaml +++ b/mods/d2k/rules/starport.yaml @@ -23,7 +23,6 @@ trike.starport: Inherits: trike Buildable: Queue: Starport - Prerequisites: starport Valued: Cost: 315 WithFacingSpriteBody: diff --git a/mods/d2k/rules/vehicles.yaml b/mods/d2k/rules/vehicles.yaml index f03413add4a8..d475b4480d81 100644 --- a/mods/d2k/rules/vehicles.yaml +++ b/mods/d2k/rules/vehicles.yaml @@ -10,6 +10,7 @@ mcv: Name: Mobile Construction Vehicle Description: Deploys into another Construction Yard\n Unarmed Selectable: + Class: mcv Priority: 3 Bounds: 42,42 Health: @@ -51,6 +52,7 @@ harvester: Name: Spice Harvester Description: Collects Spice for processing\n Unarmed Selectable: + Class: harvester Priority: 7 Bounds: 42,42 Harvester: @@ -96,6 +98,7 @@ trike: Name: Scout Trike Description: Fast Scout\n Strong vs Infantry\n Weak vs Tanks, Aircraft Selectable: + Class: trike Bounds: 24,24 Health: HP: 100 @@ -151,6 +154,7 @@ quad: Weapon: UnitExplodeTiny EmptyWeapon: UnitExplodeTiny Selectable: + Class: quad Bounds: 24,24 AttractsWorms: Intensity: 470 @@ -195,6 +199,7 @@ siegetank: AutoTarget: InitialStance: Defend Selectable: + Class: siegetank Bounds: 30,30 LeavesHusk: HuskActor: siegetank.husk @@ -235,6 +240,7 @@ missiletank: Weapon: UnitExplodeScale EmptyWeapon: UnitExplodeScale Selectable: + Class: missiletank Bounds: 30,30 LeavesHusk: HuskActor: missiletank.husk @@ -449,6 +455,7 @@ deviatortank: Weapon: UnitExplodeSmall EmptyWeapon: UnitExplodeSmall Selectable: + Class: combat Bounds: 30,30 AttractsWorms: Intensity: 520 diff --git a/mods/ra/rules/defaults.yaml b/mods/ra/rules/defaults.yaml index 79a9940f5efe..989a1ca48baf 100644 --- a/mods/ra/rules/defaults.yaml +++ b/mods/ra/rules/defaults.yaml @@ -462,6 +462,8 @@ ^CivInfantry: Inherits: ^Infantry + Selectable: + Class: CivInfantry Valued: Cost: 70 Tooltip: diff --git a/mods/ra/rules/infantry.yaml b/mods/ra/rules/infantry.yaml index b55812a846a9..7c6203478607 100644 --- a/mods/ra/rules/infantry.yaml +++ b/mods/ra/rules/infantry.yaml @@ -388,6 +388,8 @@ DELPHI: CHAN: Inherits: ^CivInfantry + Selectable: + Class: CHAN Tooltip: Name: Agent Chan @@ -396,6 +398,7 @@ GNRL: Tooltip: Name: General Selectable: + Class: GNRL Voiced: VoiceSet: StavrosVoice