Touched up selection functionality.

This commit is contained in:
Matija Hustić
2015-06-01 02:59:28 +01:00
parent 9882a6e34d
commit e965a0d3f0
6 changed files with 74 additions and 24 deletions

View File

@@ -21,11 +21,17 @@ namespace OpenRA.Traits
public readonly int Priority = 10; public readonly int Priority = 10;
public readonly int[] Bounds = null; 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 object Create(ActorInitializer init) { return new Selectable(init.Self, this); }
} }
public class Selectable : IPostRenderSelection public class Selectable : IPostRenderSelection
{ {
public readonly string Class = null;
public SelectableInfo Info; public SelectableInfo Info;
readonly Actor self; readonly Actor self;
@@ -33,6 +39,7 @@ namespace OpenRA.Traits
{ {
this.self = self; this.self = self;
Info = info; Info = info;
Class = string.IsNullOrEmpty(info.Class) ? self.Info.Name : info.Class;
} }
IEnumerable<WPos> ActivityTargetPath() IEnumerable<WPos> ActivityTargetPath()

View File

@@ -40,15 +40,17 @@ namespace OpenRA.Widgets
{ {
if (!IsDragging) 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); worldRenderer.DrawRollover(u);
return; return;
} }
// Render actors in the dragbox
var selbox = SelectionBox; var selbox = SelectionBox;
Game.Renderer.WorldLineRenderer.DrawRect(selbox.Value.First.ToFloat2(), selbox.Value.Second.ToFloat2(), Color.White); 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); worldRenderer.DrawRollover(u);
} }
@@ -111,15 +113,20 @@ namespace OpenRA.Widgets
if (unit != null && unit.Owner == (World.RenderPlayer ?? World.LocalPlayer)) if (unit != null && unit.Owner == (World.RenderPlayer ?? World.LocalPlayer))
{ {
var newSelection2 = SelectActorsInBox(World, worldRenderer.Viewport.TopLeft, worldRenderer.Viewport.BottomRight, var s = unit.TraitOrDefault<Selectable>();
a => a.Owner == unit.Owner && a.Info.Name == unit.Info.Name); 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<string> { s.Class }, unit.Owner);
World.Selection.Combine(World, newSelection2, true, false); World.Selection.Combine(World, newSelection, true, false);
}
} }
} }
else if (dragStart.HasValue) 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); World.Selection.Combine(World, newSelection, mi.Modifiers.HasModifier(Modifiers.Shift), dragStart == xy);
} }
} }
@@ -230,26 +237,28 @@ namespace OpenRA.Widgets
World.SetPauseState(!World.Paused); World.SetPauseState(!World.Paused);
else if (key == Game.Settings.Keys.SelectAllUnitsKey) else if (key == Game.Settings.Keys.SelectAllUnitsKey)
{ {
var ownUnitsOnScreen = SelectActorsInBox(World, worldRenderer.Viewport.TopLeft, worldRenderer.Viewport.BottomRight, // Select actors on the screen which belong to the current player
a => a.Owner == player); var ownUnitsOnScreen = SelectActorsOnScreen(World, worldRenderer, null, player);
World.Selection.Combine(World, ownUnitsOnScreen, false, false); World.Selection.Combine(World, ownUnitsOnScreen, false, false);
} }
else if (key == Game.Settings.Keys.SelectUnitsByTypeKey) 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) .Where(x => x.Owner == player)
.Select(a => a.Info); .Select(a => a.Trait<Selectable>().Class)
.ToHashSet();
Func<Actor, bool> cond = a => a.Owner == player && selectedTypes.Contains(a.Info); // Select actors on the screen that have the same selection class as one of the already selected actors
var tl = worldRenderer.Viewport.TopLeft; var newSelection = SelectActorsOnScreen(World, worldRenderer, selectedClasses, player).ToList();
var br = worldRenderer.Viewport.BottomRight;
var newSelection = SelectActorsInBox(World, tl, br, cond);
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"); Game.Debug("Selected across screen");
else 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"); Game.Debug("Selected across map");
} }
@@ -264,18 +273,41 @@ namespace OpenRA.Widgets
return false; return false;
} }
static IEnumerable<Actor> SelectActorsInBoxWithDeadzone(World world, int2 a, int2 b, Func<Actor, bool> cond) static IEnumerable<Actor> SelectActorsOnScreen(World world, WorldRenderer wr, IEnumerable<string> selectionClasses, Player player)
{ {
if (a == b || (a - b).Length > Game.Settings.Game.SelectionDeadzone) return SelectActorsByPlayerByClass(world.ScreenMap.ActorsInBox(wr.Viewport.TopLeft, wr.Viewport.BottomRight), selectionClasses, player);
return SelectActorsInBox(world, a, b, cond);
else
return SelectActorsInBox(world, b, b, cond);
} }
static IEnumerable<Actor> SelectActorsInBox(World world, int2 a, int2 b, Func<Actor, bool> cond) static IEnumerable<Actor> SelectActorsInWorld(World world, IEnumerable<string> selectionClasses, Player player)
{ {
return SelectActorsByPlayerByClass(world.ActorMap.ActorsInWorld(), selectionClasses, player);
}
static IEnumerable<Actor> SelectActorsByPlayerByClass(IEnumerable<Actor> actors, IEnumerable<string> selectionClasses, Player player)
{
return actors.Where(a =>
{
if (a.Owner != player)
return false;
var s = a.TraitOrDefault<Selectable>();
// 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<Actor> 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) return world.ScreenMap.ActorsInBox(a, b)
.Where(x => x.HasTrait<Selectable>() && x.Trait<Selectable>().Info.Selectable && (x.Owner.IsAlliedWith(world.RenderPlayer) || !world.FogObscures(x)) && cond(x)) .Where(x =>
{
var s = x.TraitOrDefault<Selectable>();
return s != null && s.Info.Selectable && (x.Owner.IsAlliedWith(world.RenderPlayer) || !world.FogObscures(x));
})
.GroupBy(x => x.GetSelectionPriority()) .GroupBy(x => x.GetSelectionPriority())
.OrderByDescending(g => g.Key) .OrderByDescending(g => g.Key)
.Select(g => g.AsEnumerable()) .Select(g => g.AsEnumerable())

View File

@@ -23,7 +23,6 @@ trike.starport:
Inherits: trike Inherits: trike
Buildable: Buildable:
Queue: Starport Queue: Starport
Prerequisites: starport
Valued: Valued:
Cost: 315 Cost: 315
WithFacingSpriteBody: WithFacingSpriteBody:

View File

@@ -10,6 +10,7 @@ mcv:
Name: Mobile Construction Vehicle Name: Mobile Construction Vehicle
Description: Deploys into another Construction Yard\n Unarmed Description: Deploys into another Construction Yard\n Unarmed
Selectable: Selectable:
Class: mcv
Priority: 3 Priority: 3
Bounds: 42,42 Bounds: 42,42
Health: Health:
@@ -51,6 +52,7 @@ harvester:
Name: Spice Harvester Name: Spice Harvester
Description: Collects Spice for processing\n Unarmed Description: Collects Spice for processing\n Unarmed
Selectable: Selectable:
Class: harvester
Priority: 7 Priority: 7
Bounds: 42,42 Bounds: 42,42
Harvester: Harvester:
@@ -96,6 +98,7 @@ trike:
Name: Scout Trike Name: Scout Trike
Description: Fast Scout\n Strong vs Infantry\n Weak vs Tanks, Aircraft Description: Fast Scout\n Strong vs Infantry\n Weak vs Tanks, Aircraft
Selectable: Selectable:
Class: trike
Bounds: 24,24 Bounds: 24,24
Health: Health:
HP: 100 HP: 100
@@ -151,6 +154,7 @@ quad:
Weapon: UnitExplodeTiny Weapon: UnitExplodeTiny
EmptyWeapon: UnitExplodeTiny EmptyWeapon: UnitExplodeTiny
Selectable: Selectable:
Class: quad
Bounds: 24,24 Bounds: 24,24
AttractsWorms: AttractsWorms:
Intensity: 470 Intensity: 470
@@ -195,6 +199,7 @@ siegetank:
AutoTarget: AutoTarget:
InitialStance: Defend InitialStance: Defend
Selectable: Selectable:
Class: siegetank
Bounds: 30,30 Bounds: 30,30
LeavesHusk: LeavesHusk:
HuskActor: siegetank.husk HuskActor: siegetank.husk
@@ -235,6 +240,7 @@ missiletank:
Weapon: UnitExplodeScale Weapon: UnitExplodeScale
EmptyWeapon: UnitExplodeScale EmptyWeapon: UnitExplodeScale
Selectable: Selectable:
Class: missiletank
Bounds: 30,30 Bounds: 30,30
LeavesHusk: LeavesHusk:
HuskActor: missiletank.husk HuskActor: missiletank.husk
@@ -449,6 +455,7 @@ deviatortank:
Weapon: UnitExplodeSmall Weapon: UnitExplodeSmall
EmptyWeapon: UnitExplodeSmall EmptyWeapon: UnitExplodeSmall
Selectable: Selectable:
Class: combat
Bounds: 30,30 Bounds: 30,30
AttractsWorms: AttractsWorms:
Intensity: 520 Intensity: 520

View File

@@ -462,6 +462,8 @@
^CivInfantry: ^CivInfantry:
Inherits: ^Infantry Inherits: ^Infantry
Selectable:
Class: CivInfantry
Valued: Valued:
Cost: 70 Cost: 70
Tooltip: Tooltip:

View File

@@ -388,6 +388,8 @@ DELPHI:
CHAN: CHAN:
Inherits: ^CivInfantry Inherits: ^CivInfantry
Selectable:
Class: CHAN
Tooltip: Tooltip:
Name: Agent Chan Name: Agent Chan
@@ -396,6 +398,7 @@ GNRL:
Tooltip: Tooltip:
Name: General Name: General
Selectable: Selectable:
Class: GNRL
Voiced: Voiced:
VoiceSet: StavrosVoice VoiceSet: StavrosVoice