Introduce ScreenMap trait for caching screen-coord queries.

This commit is contained in:
Paul Chote
2013-09-21 13:39:39 +12:00
parent cad46e43c5
commit dfd51c0caa
24 changed files with 281 additions and 96 deletions

View File

@@ -73,12 +73,11 @@ namespace OpenRA.Graphics
List<IRenderable> GenerateRenderables()
{
var bounds = Game.viewport.WorldBounds(world);
var comparer = new RenderableComparer(this);
var tl = bounds.TopLeftAsCPos();
var br = bounds.BottomRightAsCPos();
var actors = world.FindActorsInBox(tl, br)
var vb = Game.viewport.ViewBounds(world);
var tl = Game.viewport.ViewToWorldPx(new int2(vb.Left, vb.Top)).ToInt2();
var br = Game.viewport.ViewToWorldPx(new int2(vb.Right, vb.Bottom)).ToInt2();
var actors = world.ScreenMap.ActorsInBox(tl, br)
.Append(world.WorldActor)
.ToList();

View File

@@ -238,6 +238,7 @@
<Compile Include="Graphics\Theater.cs" />
<Compile Include="Traits\Player\PlayerColorPalette.cs" />
<Compile Include="Traits\Player\PlayerHighlightPalette.cs" />
<Compile Include="Traits\World\ScreenMap.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj">

View File

@@ -19,8 +19,8 @@ namespace OpenRA.Orders
{
public IEnumerable<Order> Order(World world, CPos xy, MouseInput mi)
{
var underCursor = world.FindUnitsAtMouse(mi.Location)
.Where(a => a.HasTrait<ITargetable>())
var underCursor = world.ScreenMap.ActorsAt(Game.viewport.ViewToWorldPx(mi.Location))
.Where(a => !world.FogObscures(a) && a.HasTrait<ITargetable>())
.OrderByDescending(a => a.Info.SelectionPriority())
.FirstOrDefault();
@@ -59,10 +59,9 @@ namespace OpenRA.Orders
public string GetCursor(World world, CPos xy, MouseInput mi)
{
bool useSelect = false;
var underCursor = world.FindUnitsAtMouse(mi.Location)
.Where(a => a.HasTrait<ITargetable>())
var useSelect = false;
var underCursor = world.ScreenMap.ActorsAt(Game.viewport.ViewToWorldPx(mi.Location))
.Where(a => !world.FogObscures(a) && a.HasTrait<ITargetable>())
.OrderByDescending(a => a.Info.SelectionPriority())
.FirstOrDefault();

View File

@@ -20,10 +20,7 @@ namespace OpenRA.Traits
{
public class FrozenActorLayerInfo : ITraitInfo
{
[Desc("Size of partition bins (screen pixels)")]
public readonly int BinSize = 250;
public object Create(ActorInitializer init) { return new FrozenActorLayer(init.world, this); }
public object Create(ActorInitializer init) { return new FrozenActorLayer(init.self); }
}
public class FrozenActor
@@ -98,34 +95,21 @@ namespace OpenRA.Traits
[Sync] public int VisibilityHash;
[Sync] public int FrozenHash;
readonly FrozenActorLayerInfo info;
readonly World world;
readonly Player owner;
Dictionary<uint, FrozenActor> frozen;
List<FrozenActor>[,] bins;
public FrozenActorLayer(World world, FrozenActorLayerInfo info)
public FrozenActorLayer(Actor self)
{
this.info = info;
world = self.World;
owner = self.Owner;
frozen = new Dictionary<uint, FrozenActor>();
bins = new List<FrozenActor>[
world.Map.MapSize.X * Game.CellSize / info.BinSize,
world.Map.MapSize.Y * Game.CellSize / info.BinSize];
for (var j = 0; j <= bins.GetUpperBound(1); j++)
for (var i = 0; i <= bins.GetUpperBound(0); i++)
bins[i, j] = new List<FrozenActor>();
}
public void Add(FrozenActor fa)
{
frozen.Add(fa.ID, fa);
var top = (int)Math.Max(0, fa.Bounds.Top / info.BinSize);
var left = (int)Math.Max(0, fa.Bounds.Left / info.BinSize);
var bottom = (int)Math.Min(bins.GetUpperBound(1), fa.Bounds.Bottom / info.BinSize);
var right = (int)Math.Min(bins.GetUpperBound(0), fa.Bounds.Right / info.BinSize);
for (var j = top; j <= bottom; j++)
for (var i = left; i <= right; i++)
bins[i, j].Add(fa);
world.ScreenMap.Add(owner, fa);
}
public void Tick(Actor self)
@@ -148,8 +132,7 @@ namespace OpenRA.Traits
foreach (var r in remove)
{
foreach (var bin in bins)
bin.Remove(frozen[r]);
world.ScreenMap.Remove(owner, frozen[r]);
frozen.Remove(r);
}
}
@@ -161,13 +144,6 @@ namespace OpenRA.Traits
.SelectMany(ff => ff.Render(wr));
}
public IEnumerable<FrozenActor> FrozenActorsAt(int2 pxPos)
{
var x = (pxPos.X / info.BinSize).Clamp(0, bins.GetUpperBound(0));
var y = (pxPos.Y / info.BinSize).Clamp(0, bins.GetUpperBound(1));
return bins[x, y].Where(p => p.Bounds.Contains(pxPos) && p.IsValid);
}
public FrozenActor FromID(uint id)
{
FrozenActor ret;

View File

@@ -18,7 +18,7 @@ namespace OpenRA.Traits
public object Create( ActorInitializer init ) { return new Waypoint( init ); }
}
class Waypoint : IOccupySpace, ISync
class Waypoint : IOccupySpace, ISync, INotifyAddedToWorld, INotifyRemovedFromWorld
{
[Sync] CPos location;
@@ -30,5 +30,15 @@ namespace OpenRA.Traits
public CPos TopLeft { get { return location; } }
public IEnumerable<Pair<CPos, SubCell>> OccupiedCells() { yield break; }
public WPos CenterPosition { get { return location.CenterPosition; } }
public void AddedToWorld(Actor self)
{
self.World.ScreenMap.Add(self);
}
public void RemovedFromWorld(Actor self)
{
self.World.ScreenMap.Remove(self);
}
}
}

View File

@@ -0,0 +1,137 @@
#region Copyright & License Information
/*
* Copyright 2007-2013 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 System.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA.FileFormats;
using OpenRA.Graphics;
using OpenRA.Traits;
namespace OpenRA.Traits
{
public class ScreenMapInfo : ITraitInfo
{
[Desc("Size of partition bins (world pixels)")]
public readonly int BinSize = 250;
public object Create(ActorInitializer init) { return new ScreenMap(init.world, this); }
}
public class ScreenMap
{
ScreenMapInfo info;
Cache<Player, List<FrozenActor>[]> frozen;
List<Actor>[] actors;
int rows, cols;
public ScreenMap(World world, ScreenMapInfo info)
{
this.info = info;
cols = world.Map.MapSize.X * Game.CellSize / info.BinSize + 1;
rows = world.Map.MapSize.Y * Game.CellSize / info.BinSize + 1;
frozen = new Cache<Player, List<FrozenActor>[]>(InitializeFrozenActors);
actors = new List<Actor>[rows * cols];
for (var j = 0; j < rows; j++)
for (var i = 0; i < cols; i++)
actors[j * cols + i] = new List<Actor>();
}
List<FrozenActor>[] InitializeFrozenActors(Player p)
{
var f = new List<FrozenActor>[rows * cols];
for (var j = 0; j < rows; j++)
for (var i = 0; i < cols; i++)
f[j * cols + i] = new List<FrozenActor>();
return f;
}
public void Add(Player viewer, FrozenActor fa)
{
var top = Math.Max(0, fa.Bounds.Top / info.BinSize);
var left = Math.Max(0, fa.Bounds.Left / info.BinSize);
var bottom = Math.Min(rows - 1, fa.Bounds.Bottom / info.BinSize);
var right = Math.Min(cols - 1, fa.Bounds.Right / info.BinSize);
for (var j = top; j <= bottom; j++)
for (var i = left; i <= right; i++)
frozen[viewer][j*cols + i].Add(fa);
}
public void Remove(Player viewer, FrozenActor fa)
{
foreach (var bin in frozen[viewer])
bin.Remove(fa);
}
public void Add(Actor a)
{
var b = a.Bounds.Value;
var top = Math.Max(0, b.Top / info.BinSize);
var left = Math.Max(0, b.Left / info.BinSize);
var bottom = Math.Min(rows - 1, b.Bottom / info.BinSize);
var right = Math.Min(cols - 1, b.Right / info.BinSize);
for (var j = top; j <= bottom; j++)
for (var i = left; i <= right; i++)
actors[j * cols + i].Add(a);
}
public void Remove(Actor a)
{
foreach (var bin in actors)
bin.Remove(a);
}
public void Update(Actor a)
{
Remove(a);
Add(a);
}
public IEnumerable<FrozenActor> FrozenActorsAt(Player viewer, int2 pxPos)
{
var i = (pxPos.X / info.BinSize).Clamp(0, cols - 1);
var j = (pxPos.Y / info.BinSize).Clamp(0, rows - 1);
return frozen[viewer][j*cols + i].Where(fa => fa.Bounds.Contains(pxPos) && fa.IsValid);
}
public IEnumerable<Actor> ActorsAt(int2 pxPos)
{
var i = (pxPos.X / info.BinSize).Clamp(0, cols - 1);
var j = (pxPos.Y / info.BinSize).Clamp(0, rows - 1);
return actors[j*cols + i].Where(a => a.Bounds.Value.Contains(pxPos) && a.IsInWorld);
}
// Legacy fallback
public IEnumerable<Actor> ActorsAt(PPos pxPos) { return ActorsAt(pxPos.ToInt2()); }
public IEnumerable<Actor> ActorsInBox(int2 a, int2 b)
{
return ActorsInBox(Rectangle.FromLTRB(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y), Math.Max(a.X, b.X), Math.Max(a.Y, b.Y)));
}
public IEnumerable<Actor> ActorsInBox(Rectangle r)
{
var left = (r.Left / info.BinSize).Clamp(0, cols - 1);
var right = (r.Right / info.BinSize).Clamp(0, cols - 1);
var top = (r.Top / info.BinSize).Clamp(0, rows - 1);
var bottom = (r.Bottom / info.BinSize).Clamp(0, rows - 1);
for (var j = top; j <= bottom; j++)
for (var i = left; i <= right; i++)
foreach (var a in actors[j*cols + i].Where(b => b.Bounds.Value.IntersectsWith(r) && b.IsInWorld))
yield return a;
}
}
}

View File

@@ -103,8 +103,8 @@ namespace OpenRA.Widgets
return;
}
var underCursor = world.FindUnitsAtMouse(Viewport.LastMousePos)
.Where(a => a.HasTrait<IToolTip>())
var underCursor = world.ScreenMap.ActorsAt(Game.viewport.ViewToWorldPx(Viewport.LastMousePos))
.Where(a => !world.FogObscures(a) && a.HasTrait<IToolTip>())
.OrderByDescending(a => a.Info.SelectionPriority())
.FirstOrDefault();

View File

@@ -90,7 +90,7 @@ namespace OpenRA.Widgets
{
if (MultiClick)
{
var unit = SelectActorsInBox(world, xy, xy, _ => true).FirstOrDefault();
var unit = world.ScreenMap.ActorsAt(xy).FirstOrDefault();
var visibleWorld = Game.viewport.ViewBounds(world);
var topLeft = Game.viewport.ViewToWorldPx(new int2(visibleWorld.Left, visibleWorld.Top));
@@ -185,7 +185,7 @@ namespace OpenRA.Widgets
static readonly Actor[] NoActors = {};
IEnumerable<Actor> SelectActorsInBox(World world, PPos a, PPos b, Func<Actor, bool> cond)
{
return world.FindActorsInBox(a.ToWPos(0), b.ToWPos(0))
return world.ScreenMap.ActorsInBox(a.ToInt2(), b.ToInt2())
.Where(x => x.HasTrait<Selectable>() && x.Trait<Selectable>().Info.Selectable && !world.FogObscures(x) && cond(x))
.GroupBy(x => x.GetSelectionPriority())
.OrderByDescending(g => g.Key)

View File

@@ -79,6 +79,7 @@ namespace OpenRA
public readonly Map Map;
public readonly TileSet TileSet;
public readonly ActorMap ActorMap;
public readonly ScreenMap ScreenMap;
public void IssueOrder( Order o ) { orderManager.IssueOrder( o ); } /* avoid exposing the OM to mod code */
@@ -121,8 +122,9 @@ namespace OpenRA
TileSet = Rules.TileSets[Map.Tileset];
SharedRandom = new XRandom(orderManager.LobbyInfo.GlobalSettings.RandomSeed);
WorldActor = CreateActor( "World", new TypeDictionary() );
WorldActor = CreateActor("World", new TypeDictionary());
ActorMap = new ActorMap(this);
ScreenMap = WorldActor.Trait<ScreenMap>();
// Add players
foreach (var cmp in WorldActor.TraitsImplementing<ICreatePlayers>())

View File

@@ -21,24 +21,13 @@ namespace OpenRA
{
public static class WorldUtils
{
public static IEnumerable<Actor> FindUnitsAtMouse(this World world, int2 mouseLocation)
{
var loc = Game.viewport.ViewToWorldPx(mouseLocation).ToWPos(0);
return FindActorsInBox(world, loc, loc).Where(a => !world.FogObscures(a));
}
public static readonly IEnumerable<FrozenActor> NoFrozenActors = new FrozenActor[0].AsEnumerable();
public static IEnumerable<FrozenActor> FindFrozenActorsAtMouse(this World world, int2 mouseLocation)
{
if (world.RenderPlayer == null)
return NoFrozenActors;
var frozenLayer = world.RenderPlayer.PlayerActor.TraitOrDefault<FrozenActorLayer>();
if (frozenLayer == null)
return NoFrozenActors;
var loc = Game.viewport.ViewToWorldPx(mouseLocation).ToInt2();
return frozenLayer.FrozenActorsAt(loc);
return world.ScreenMap.FrozenActorsAt(world.RenderPlayer, Game.viewport.ViewToWorldPx(mouseLocation).ToInt2());
}
public static IEnumerable<Actor> FindActorsInBox(this World world, CPos tl, CPos br)
@@ -73,13 +62,8 @@ namespace OpenRA
// Target ranges are calculated in 2D, so ignore height differences
var vec = new WVec(r, r, WRange.Zero);
var rSq = r.Range*r.Range;
return world.FindActorsInBox(origin - vec, origin + vec).Where(a =>
{
var pos = a.CenterPosition;
var dx = (long)(pos.X - origin.X);
var dy = (long)(pos.Y - origin.Y);
return dx*dx + dy*dy <= rSq;
});
return world.FindActorsInBox(origin - vec, origin + vec).Where(
a => (a.CenterPosition - origin).HorizontalLengthSquared <= rSq);
}
}

View File

@@ -36,7 +36,7 @@ namespace OpenRA.Mods.RA.Air
public int GetInitialFacing() { return InitialFacing; }
}
public class Aircraft : IFacing, IPositionable, ISync, INotifyKilled, IIssueOrder, IOrderVoice
public class Aircraft : IFacing, IPositionable, ISync, INotifyKilled, IIssueOrder, IOrderVoice, INotifyAddedToWorld, INotifyRemovedFromWorld
{
static readonly Pair<CPos, SubCell>[] NoCells = new Pair<CPos, SubCell>[] { };
@@ -101,15 +101,28 @@ namespace OpenRA.Mods.RA.Air
UnReserve();
}
public void SetPosition(Actor self, CPos cell)
public void SetPosition(Actor self, WPos pos)
{
// Changes position, but not altitude
CenterPosition = cell.CenterPosition + new WVec(0, 0, CenterPosition.Z);
CenterPosition = pos;
if (self.IsInWorld)
self.World.ScreenMap.Update(self);
}
public void SetPosition(Actor self, WPos pos) { CenterPosition = pos; }
// Changes position, but not altitude
public void SetPosition(Actor self, CPos cell) { SetPosition(self, cell.CenterPosition + new WVec(0, 0, CenterPosition.Z)); }
public void SetVisualPosition(Actor self, WPos pos) { SetPosition(self, pos); }
public void AddedToWorld(Actor self)
{
self.World.ScreenMap.Add(self);
}
public void RemovedFromWorld(Actor self)
{
self.World.ScreenMap.Remove(self);
}
public bool AircraftCanEnter(Actor a)
{
if (self.AppearsHostileTo(a))

View File

@@ -97,7 +97,7 @@ namespace OpenRA.Mods.RA.Buildings
}
}
public class Building : INotifyDamage, IOccupySpace, INotifyCapture, ISync, ITechTreePrerequisite
public class Building : INotifyDamage, IOccupySpace, INotifyCapture, ISync, ITechTreePrerequisite, INotifyAddedToWorld, INotifyRemovedFromWorld
{
readonly Actor self;
public readonly BuildingInfo Info;
@@ -157,5 +157,15 @@ namespace OpenRA.Mods.RA.Buildings
{
PlayerPower = newOwner.PlayerActor.Trait<PowerManager>();
}
public void AddedToWorld(Actor self)
{
self.World.ScreenMap.Add(self);
}
public void RemovedFromWorld(Actor self)
{
self.World.ScreenMap.Remove(self);
}
}
}

View File

@@ -24,7 +24,7 @@ namespace OpenRA.Mods.RA
}
// ITeleportable is required for paradrop
class Crate : ITick, IPositionable, ICrushable, ISync, INotifyParachuteLanded
class Crate : ITick, IPositionable, ICrushable, ISync, INotifyParachuteLanded, INotifyAddedToWorld, INotifyRemovedFromWorld
{
readonly Actor self;
readonly CrateInfo info;
@@ -115,12 +115,25 @@ namespace OpenRA.Mods.RA
rs.anim.PlayRepeating(seq);
if (self.IsInWorld)
{
self.World.ActorMap.Add(self, this);
self.World.ScreenMap.Update(self);
}
}
public bool CrushableBy(string[] crushClasses, Player owner)
{
return crushClasses.Contains("crate");
}
public void AddedToWorld(Actor self)
{
self.World.ScreenMap.Add(self);
}
public void RemovedFromWorld(Actor self)
{
self.World.ScreenMap.Remove(self);
}
}
}

View File

@@ -58,7 +58,7 @@ namespace OpenRA.Mods.RA
yield break;
}
var target = FriendlyGuardableUnitsAtMouse(world, mi).FirstOrDefault();
var target = FriendlyGuardableUnits(world, mi).FirstOrDefault();
if (target == null || subjects.All(s => s.IsDead()))
yield break;
@@ -79,19 +79,19 @@ namespace OpenRA.Mods.RA
public string GetCursor(World world, CPos xy, MouseInput mi)
{
if (world.Map.IsInMap(xy))
{
var targets = FriendlyGuardableUnitsAtMouse(world, mi);
if (targets.Any() && (subjects.Count() > 1 || (subjects.Count() == 1 && subjects.First() != targets.First())))
return "guard";
}
return "move-blocked";
var multiple = subjects.Count() > 1;
var canGuard = FriendlyGuardableUnits(world, mi)
.Any(a => multiple || a != subjects.First());
return canGuard ? "guard" : "move-blocked";
}
static IEnumerable<Actor> FriendlyGuardableUnitsAtMouse(World world, MouseInput mi)
static IEnumerable<Actor> FriendlyGuardableUnits(World world, MouseInput mi)
{
return world.FindUnitsAtMouse(mi.Location)
.Where(a => !a.IsDead() && a.AppearsFriendlyTo(world.LocalPlayer.PlayerActor) && a.HasTrait<Guardable>());
return world.ScreenMap.ActorsAt(Game.viewport.ViewToWorldPx(mi.Location))
.Where(a => !world.FogObscures(a) && !a.IsDead() &&
a.AppearsFriendlyTo(world.LocalPlayer.PlayerActor) &&
a.HasTrait<Guardable>());
}
}

View File

@@ -25,7 +25,7 @@ namespace OpenRA.Mods.RA
public int GetInitialFacing() { return 128; }
}
class Husk : IPositionable, IFacing, ISync
class Husk : IPositionable, IFacing, ISync, INotifyAddedToWorld, INotifyRemovedFromWorld
{
readonly HuskInfo info;
readonly Actor self;
@@ -64,7 +64,11 @@ namespace OpenRA.Mods.RA
}
public void SetPosition(Actor self, CPos cell) { SetPosition(self, cell.CenterPosition); }
public void SetVisualPosition(Actor self, WPos pos) { CenterPosition = pos; }
public void SetVisualPosition(Actor self, WPos pos)
{
CenterPosition = pos;
self.World.ScreenMap.Update(self);
}
public void SetPosition(Actor self, WPos pos)
{
@@ -72,6 +76,17 @@ namespace OpenRA.Mods.RA
CenterPosition = pos;
TopLeft = pos.ToCPos();
self.World.ActorMap.Add(self, this);
self.World.ScreenMap.Update(self);
}
public void AddedToWorld(Actor self)
{
self.World.ScreenMap.Add(self);
}
public void RemovedFromWorld(Actor self)
{
self.World.ScreenMap.Remove(self);
}
}

View File

@@ -26,7 +26,7 @@ namespace OpenRA.Mods.RA
public object Create(ActorInitializer init) { return new Mine(init, this); }
}
class Mine : ICrushable, IOccupySpace, ISync
class Mine : ICrushable, IOccupySpace, ISync, INotifyAddedToWorld, INotifyRemovedFromWorld
{
readonly Actor self;
readonly MineInfo info;
@@ -62,6 +62,16 @@ namespace OpenRA.Mods.RA
public IEnumerable<Pair<CPos, SubCell>> OccupiedCells() { yield return Pair.New(TopLeft, SubCell.FullCell); }
public WPos CenterPosition { get { return location.CenterPosition; } }
public void AddedToWorld(Actor self)
{
self.World.ScreenMap.Add(self);
}
public void RemovedFromWorld(Actor self)
{
self.World.ScreenMap.Remove(self);
}
}
/* tag trait for stuff that shouldnt trigger mines */

View File

@@ -106,7 +106,8 @@ namespace OpenRA.Mods.RA
yield break;
}
var underCursor = world.FindUnitsAtMouse(mi.Location)
var underCursor = world.ScreenMap.ActorsAt(Game.viewport.ViewToWorldPx(mi.Location))
.Where(a => !world.FogObscures(a))
.OrderByDescending(a => a.Info.Traits.Contains<SelectableInfo>()
? a.Info.Traits.Get<SelectableInfo>().Priority : int.MinValue)
.FirstOrDefault();

View File

@@ -144,7 +144,7 @@ namespace OpenRA.Mods.RA.Move
public int GetInitialFacing() { return InitialFacing; }
}
public class Mobile : IIssueOrder, IResolveOrder, IOrderVoice, IPositionable, IMove, IFacing, ISync
public class Mobile : IIssueOrder, IResolveOrder, IOrderVoice, IPositionable, IMove, IFacing, ISync, INotifyAddedToWorld, INotifyRemovedFromWorld
{
public readonly Actor self;
public readonly MobileInfo Info;
@@ -231,6 +231,18 @@ namespace OpenRA.Mods.RA.Move
public void SetVisualPosition(Actor self, WPos pos)
{
CenterPosition = pos;
if (self.IsInWorld)
self.World.ScreenMap.Update(self);
}
public void AddedToWorld(Actor self)
{
self.World.ScreenMap.Add(self);
}
public void RemovedFromWorld(Actor self)
{
self.World.ScreenMap.Remove(self);
}
public IEnumerable<IOrderTargeter> Orders { get { yield return new MoveOrderTargeter(Info); } }

View File

@@ -39,9 +39,8 @@ namespace OpenRA.Mods.RA.Orders
{
if (mi.Button == MouseButton.Left)
{
var underCursor = world.FindUnitsAtMouse(mi.Location)
.Where(a => a.Owner == world.LocalPlayer
&& a.HasTrait<T>()).FirstOrDefault();
var underCursor = world.ScreenMap.ActorsAt(Game.viewport.ViewToWorldPx(mi.Location))
.Where(a => a.Owner == world.LocalPlayer && a.HasTrait<T>()).FirstOrDefault();
if (underCursor != null)
yield return new Order(order, underCursor, false);

View File

@@ -30,8 +30,8 @@ namespace OpenRA.Mods.RA.Orders
{
if (mi.Button == MouseButton.Left)
{
var underCursor = world.FindUnitsAtMouse(mi.Location)
.Where(a => a.AppearsFriendlyTo(world.LocalPlayer.PlayerActor) && a.HasTrait<RepairableBuilding>()).FirstOrDefault();
var underCursor = world.ScreenMap.ActorsAt(Game.viewport.ViewToWorldPx(mi.Location))
.Where(a => !world.FogObscures(a) && a.AppearsFriendlyTo(world.LocalPlayer.PlayerActor) && a.HasTrait<RepairableBuilding>()).FirstOrDefault();
if (underCursor == null)
yield break;

View File

@@ -206,6 +206,7 @@ Player:
FrozenActorLayer:
World:
ScreenMap:
LoadWidgetAtGameStart:
Widget: INGAME_ROOT
CncMenuPaletteEffect:

View File

@@ -357,6 +357,7 @@ Player:
PlayerStatistics:
World:
ScreenMap:
LoadWidgetAtGameStart:
Widget: INGAME_ROOT
ScreenShaker:

View File

@@ -529,6 +529,7 @@ Player:
PlayerStatistics:
World:
ScreenMap:
LoadWidgetAtGameStart:
Widget: INGAME_ROOT
ScreenShaker:

View File

@@ -42,6 +42,7 @@ Player:
PlayerStatistics:
World:
ScreenMap:
LoadWidgetAtGameStart:
Widget: INGAME_ROOT
BuildingInfluence: