From 73c16d5d9de96ee514ecce3bb1ae73e8f5641aea Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 31 Jan 2010 01:27:50 +1300 Subject: [PATCH] smart queries for World.Actors --- OpenRa.FileFormats/Collections/Set.cs | 54 +++++++++++++++ OpenRa.FileFormats/OpenRa.FileFormats.csproj | 1 + OpenRa.Game/Actor.cs | 11 ++++ OpenRa.Game/Chrome.cs | 8 +-- OpenRa.Game/GameRules/TechTree.cs | 2 +- OpenRa.Game/Graphics/Minimap.cs | 8 +-- OpenRa.Game/Graphics/Viewport.cs | 2 +- OpenRa.Game/Orders/RepairOrderGenerator.cs | 5 +- OpenRa.Game/Player.cs | 10 +-- OpenRa.Game/Shroud.cs | 4 +- OpenRa.Game/Traits/Activities/DeliverOre.cs | 5 +- OpenRa.Game/Traits/Activities/HeliReturn.cs | 3 +- OpenRa.Game/Traits/Activities/ReturnToBase.cs | 3 +- OpenRa.Game/Traits/ChronoshiftPower.cs | 20 +++--- OpenRa.Game/Traits/NukePower.cs | 10 +-- OpenRa.Game/Traits/Production.cs | 10 +-- OpenRa.Game/Traits/ProductionQueue.cs | 17 +++-- OpenRa.Game/Traits/ProvidesRadar.cs | 5 +- OpenRa.Game/Traits/UnitInfluence.cs | 3 +- OpenRa.Game/World.cs | 66 ++++++++++++++++++- OpenRa.Mods.Aftermath/ChronoshiftDeploy.cs | 4 +- OpenRa.Mods.RA/GpsPower.cs | 4 +- OpenRa.Mods.RA/IronCurtainPower.cs | 10 +-- 23 files changed, 201 insertions(+), 64 deletions(-) create mode 100755 OpenRa.FileFormats/Collections/Set.cs diff --git a/OpenRa.FileFormats/Collections/Set.cs b/OpenRa.FileFormats/Collections/Set.cs new file mode 100755 index 0000000000..d69064bf6b --- /dev/null +++ b/OpenRa.FileFormats/Collections/Set.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using IjwFramework.Types; +using System.Collections; + +namespace OpenRa.Collections +{ + public class Set : IEnumerable + { + Dictionary data = new Dictionary(); + + public void Add( T obj ) + { + data.Add( obj, false ); + if( OnAdd != null ) + OnAdd( obj ); + } + + public void Remove( T obj ) + { + data.Remove( obj ); + if( OnRemove != null ) + OnRemove( obj ); + } + + public event Action OnAdd; + public event Action OnRemove; + + public IEnumerator GetEnumerator() + { + return data.Keys.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + public class CachedView : Set + { + public CachedView( Set set, Func include, Func store ) + { + foreach( var t in set ) + if( include( t ) ) + Add( store( t ) ); + + set.OnAdd += obj => { if( include( obj ) ) Add( store( obj ) ); }; + set.OnRemove += obj => { if( include( obj ) ) Remove( store( obj ) ); }; + } + } +} diff --git a/OpenRa.FileFormats/OpenRa.FileFormats.csproj b/OpenRa.FileFormats/OpenRa.FileFormats.csproj index 617103e8ae..046c9fc74e 100644 --- a/OpenRa.FileFormats/OpenRa.FileFormats.csproj +++ b/OpenRa.FileFormats/OpenRa.FileFormats.csproj @@ -51,6 +51,7 @@ + diff --git a/OpenRa.Game/Actor.cs b/OpenRa.Game/Actor.cs index bcffb806a5..e9d6e271db 100755 --- a/OpenRa.Game/Actor.cs +++ b/OpenRa.Game/Actor.cs @@ -196,5 +196,16 @@ namespace OpenRa { return currentActivity; } + + public override int GetHashCode() + { + return (int)ActorID; + } + + public override bool Equals( object obj ) + { + var o = obj as Actor; + return ( o != null && o.ActorID == ActorID ); + } } } diff --git a/OpenRa.Game/Chrome.cs b/OpenRa.Game/Chrome.cs index b8ecc67d9a..1706dbb39d 100644 --- a/OpenRa.Game/Chrome.cs +++ b/OpenRa.Game/Chrome.cs @@ -503,9 +503,9 @@ namespace OpenRa void DrawRadar( World world ) { - var hasNewRadar = world.Actors.Any(a => a.Owner == world.LocalPlayer - && a.traits.Contains() - && a.traits.Get().IsActive); + var hasNewRadar = world.Queries.OwnedBy[world.LocalPlayer] + .WithTrait() + .Any(a => a.Trait.IsActive); if (hasNewRadar != hasRadar) { @@ -661,7 +661,7 @@ namespace OpenRa Rectangle repairRect = new Rectangle(buttonOrigin.X, buttonOrigin.Y, repairButton.Image.bounds.Width, repairButton.Image.bounds.Height); var repairDrawPos = new float2(repairRect.Location); - var hasFact = world.Actors.Any(a => a.Owner == world.LocalPlayer && a.traits.Contains()); + var hasFact = world.Queries.OwnedBy[world.LocalPlayer].WithTrait().Any(); if (Game.Settings.RepairRequiresConyard && !hasFact) repairButton.ReplaceAnim("disabled"); diff --git a/OpenRa.Game/GameRules/TechTree.cs b/OpenRa.Game/GameRules/TechTree.cs index 14899a7154..27faa536a5 100755 --- a/OpenRa.Game/GameRules/TechTree.cs +++ b/OpenRa.Game/GameRules/TechTree.cs @@ -23,7 +23,7 @@ namespace OpenRa.GameRules public Cache> GatherBuildings( Player player ) { var ret = new Cache>( x => new List() ); - foreach( var b in player.World.Actors.Where( x => x.Owner == player && x.Info.Traits.Contains() ) ) + foreach( var b in player.World.Queries.OwnedBy[player].Where( x=>x.Info.Traits.Contains() ) ) { ret[ b.Info.Name ].Add( b ); var buildable = b.Info.Traits.GetOrDefault(); diff --git a/OpenRa.Game/Graphics/Minimap.cs b/OpenRa.Game/Graphics/Minimap.cs index b5d7efe8e1..73b361938e 100644 --- a/OpenRa.Game/Graphics/Minimap.cs +++ b/OpenRa.Game/Graphics/Minimap.cs @@ -93,7 +93,7 @@ namespace OpenRa.Graphics mapOnlySheet.Texture.SetData(oreLayer); - if (!world.Actors.Any(a => a.Owner == world.LocalPlayer && a.traits.Contains())) + if (!world.Queries.OwnedBy[world.LocalPlayer].WithTrait().Any()) return; var bitmap = new Bitmap(oreLayer); @@ -114,9 +114,9 @@ namespace OpenRa.Graphics (b.Owner != null ? playerColors[(int)b.Owner.Palette] : colors[4]).ToArgb(); } - foreach (var a in world.Actors.Where(a => a.traits.Contains())) - *(c + (a.Location.Y * bitmapData.Stride >> 2) + a.Location.X) = - playerColors[(int)a.Owner.Palette].ToArgb(); + foreach (var a in world.Queries.WithTrait()) + *(c + (a.Actor.Location.Y * bitmapData.Stride >> 2) + a.Actor.Location.X) = + playerColors[(int)a.Actor.Owner.Palette].ToArgb(); unchecked { diff --git a/OpenRa.Game/Graphics/Viewport.cs b/OpenRa.Game/Graphics/Viewport.cs index 88d0e58cf9..69398c1972 100644 --- a/OpenRa.Game/Graphics/Viewport.cs +++ b/OpenRa.Game/Graphics/Viewport.cs @@ -126,7 +126,7 @@ namespace OpenRa.Graphics public void GoToStartLocation( Player player ) { - Center(player.World.Actors.Where(a => a.Owner == player && a.traits.Contains())); + Center( player.World.Queries.OwnedBy[ player ].WithTrait().Select( a => a.Actor ) ); } } } diff --git a/OpenRa.Game/Orders/RepairOrderGenerator.cs b/OpenRa.Game/Orders/RepairOrderGenerator.cs index 0dde392e40..8d977a9155 100644 --- a/OpenRa.Game/Orders/RepairOrderGenerator.cs +++ b/OpenRa.Game/Orders/RepairOrderGenerator.cs @@ -38,8 +38,9 @@ namespace OpenRa.Orders if (!Game.Settings.RepairRequiresConyard) return; - var hasFact = world.Actors - .Any(a => a.Owner == world.LocalPlayer && a.traits.Contains()); + var hasFact = world.Queries.OwnedBy[world.LocalPlayer] + .WithTrait() + .Any(); if (!hasFact) Game.controller.CancelInputMode(); diff --git a/OpenRa.Game/Player.cs b/OpenRa.Game/Player.cs index fc31c09936..33fc57eb44 100644 --- a/OpenRa.Game/Player.cs +++ b/OpenRa.Game/Player.cs @@ -49,12 +49,12 @@ namespace OpenRa PowerProvided = 0; PowerDrained = 0; - var myBuildings = World.Actors - .Where(a => a.Owner == this && a.traits.Contains()); + var myBuildings = World.Queries.OwnedBy[this] + .WithTrait(); foreach (var a in myBuildings) { - var p = a.traits.Get().GetPowerUsage(); + var p = a.Trait.GetPowerUsage(); if (p > 0) PowerProvided += p; else @@ -80,8 +80,8 @@ namespace OpenRa void UpdateOreCapacity() { - OreCapacity = World.Actors - .Where(a => a.Owner == this && a.traits.Contains()) + OreCapacity = World.Queries.OwnedBy[this] + .Where(a => a.traits.Contains()) .Select(a => a.Info.Traits.Get()) .Sum(b => b.Capacity); } diff --git a/OpenRa.Game/Shroud.cs b/OpenRa.Game/Shroud.cs index 4b56ff74e6..b4534b32ba 100644 --- a/OpenRa.Game/Shroud.cs +++ b/OpenRa.Game/Shroud.cs @@ -35,9 +35,9 @@ namespace OpenRa { // Clear active flags gapActive = new bool[128, 128]; - foreach (var a in world.Actors.Where(a => a.traits.Contains() && owner != a.Owner)) + foreach (var a in world.Queries.WithTrait().Where(a => owner != a.Actor.Owner)) { - foreach (var t in a.traits.Get().GetShroudedTiles()) + foreach (var t in a.Trait.GetShroudedTiles()) gapActive[t.X, t.Y] = true; } diff --git a/OpenRa.Game/Traits/Activities/DeliverOre.cs b/OpenRa.Game/Traits/Activities/DeliverOre.cs index 3717c7cf58..a1b80d4717 100644 --- a/OpenRa.Game/Traits/Activities/DeliverOre.cs +++ b/OpenRa.Game/Traits/Activities/DeliverOre.cs @@ -42,8 +42,9 @@ namespace OpenRa.Traits.Activities umt = mobile.GetMovementType(), checkForBlocked = false, }; - var refineries = self.World.Actors.Where( x => x.traits.Contains() - && x.Owner == self.Owner ).ToList(); + var refineries = self.World.Queries.OwnedBy[self.Owner] + .Where( x => x.traits.Contains()) + .ToList(); if( refinery != null ) search.AddInitialCell( self.World, refinery.Location + refineryDeliverOffset ); else diff --git a/OpenRa.Game/Traits/Activities/HeliReturn.cs b/OpenRa.Game/Traits/Activities/HeliReturn.cs index 6646d5e83c..a1fc3aa00d 100644 --- a/OpenRa.Game/Traits/Activities/HeliReturn.cs +++ b/OpenRa.Game/Traits/Activities/HeliReturn.cs @@ -9,9 +9,8 @@ namespace OpenRa.Traits.Activities static Actor ChooseHelipad(Actor self) { - return self.World.Actors.FirstOrDefault( + return self.World.Queries.OwnedBy[self.Owner].FirstOrDefault( a => a.Info.Name == "hpad" && - a.Owner == self.Owner && !Reservable.IsReserved(a)); } diff --git a/OpenRa.Game/Traits/Activities/ReturnToBase.cs b/OpenRa.Game/Traits/Activities/ReturnToBase.cs index 3374fe7128..d3126d51a5 100644 --- a/OpenRa.Game/Traits/Activities/ReturnToBase.cs +++ b/OpenRa.Game/Traits/Activities/ReturnToBase.cs @@ -18,9 +18,8 @@ namespace OpenRa.Traits.Activities Actor ChooseAirfield(Actor self) { - var airfield = self.World.Actors + var airfield = self.World.Queries.OwnedBy[self.Owner] .Where(a => a.Info.Name == "afld" - && a.Owner == self.Owner && !Reservable.IsReserved(a)) .FirstOrDefault(); diff --git a/OpenRa.Game/Traits/ChronoshiftPower.cs b/OpenRa.Game/Traits/ChronoshiftPower.cs index edb0172ddf..2116b96bf4 100644 --- a/OpenRa.Game/Traits/ChronoshiftPower.cs +++ b/OpenRa.Game/Traits/ChronoshiftPower.cs @@ -46,8 +46,10 @@ namespace OpenRa.Traits if (!movement.CanEnterCell(order.TargetLocation)) return; - var chronosphere = self.World.Actors.Where(a => a.Owner == self.Owner - && a.traits.Contains()).FirstOrDefault(); + var chronosphere = self.World.Queries + .OwnedBy[self.Owner] + .WithTrait() + .Select(x=>x.Actor).FirstOrDefault(); bool success = order.TargetActor.traits.Get().Activate(order.TargetActor, order.TargetLocation, @@ -60,8 +62,8 @@ namespace OpenRa.Traits Sound.Play("chrono2.aud"); // Trigger screen desaturate effect - foreach (var a in self.World.Actors.Where(a => a.traits.Contains())) - a.traits.Get().DoChronoshift(); + foreach (var a in self.World.Queries.WithTrait()) + a.Trait.DoChronoshift(); if (chronosphere != null) chronosphere.traits.Get().PlayCustomAnim(chronosphere, "active"); @@ -98,8 +100,9 @@ namespace OpenRa.Traits public void Tick( World world ) { - var hasChronosphere = world.Actors - .Any(a => a.Owner == world.LocalPlayer && a.traits.Contains()); + var hasChronosphere = world.Queries.OwnedBy[world.LocalPlayer] + .WithTrait() + .Any(); if (!hasChronosphere) Game.controller.CancelInputMode(); @@ -135,8 +138,9 @@ namespace OpenRa.Traits public void Tick(World world) { - var hasChronosphere = world.Actors - .Any(a => a.Owner == world.LocalPlayer && a.traits.Contains()); + var hasChronosphere = world.Queries.OwnedBy[world.LocalPlayer] + .WithTrait() + .Any(); if (!hasChronosphere) Game.controller.CancelInputMode(); diff --git a/OpenRa.Game/Traits/NukePower.cs b/OpenRa.Game/Traits/NukePower.cs index 93cd9bc739..7c5800022e 100644 --- a/OpenRa.Game/Traits/NukePower.cs +++ b/OpenRa.Game/Traits/NukePower.cs @@ -26,8 +26,9 @@ namespace OpenRa.Traits { if (order.OrderString == "NuclearMissile") { - var silo = self.World.Actors.Where(a => a.Owner == self.Owner - && a.traits.Contains()).FirstOrDefault(); + var silo = self.World.Queries.OwnedBy[self.Owner] + .Where(a => a.traits.Contains()) + .FirstOrDefault(); if (silo != null) silo.traits.Get().PlayCustomAnim(silo, "active"); @@ -70,8 +71,9 @@ namespace OpenRa.Traits public void Tick(World world) { - var hasStructure = world.Actors - .Any(a => a.Owner == world.LocalPlayer && a.traits.Contains()); + var hasStructure = world.Queries.OwnedBy[world.LocalPlayer] + .WithTrait() + .Any(); if (!hasStructure) Game.controller.CancelInputMode(); diff --git a/OpenRa.Game/Traits/Production.cs b/OpenRa.Game/Traits/Production.cs index a188e7ffec..cbfa7a493d 100755 --- a/OpenRa.Game/Traits/Production.cs +++ b/OpenRa.Game/Traits/Production.cs @@ -86,12 +86,12 @@ namespace OpenRa.Traits // Cancel existing primaries foreach (var p in self.Info.Traits.Get().Produces) { - foreach (var b in self.World.Actors.Where(x => x.traits.Contains() - && x.Owner == self.Owner - && x.traits.Get().IsPrimary == true - && (x.Info.Traits.Get().Produces.Contains(p)))) + foreach (var b in self.World.Queries.OwnedBy[self.Owner] + .WithTrait() + .Where(x => x.Trait.IsPrimary + && (x.Actor.Info.Traits.Get().Produces.Contains(p)))) { - b.traits.Get().SetPrimaryProducer(b, false); + b.Trait.SetPrimaryProducer(b.Actor, false); } } isPrimary = true; diff --git a/OpenRa.Game/Traits/ProductionQueue.cs b/OpenRa.Game/Traits/ProductionQueue.cs index 4bb5c357a2..ddadc26b6a 100755 --- a/OpenRa.Game/Traits/ProductionQueue.cs +++ b/OpenRa.Game/Traits/ProductionQueue.cs @@ -131,18 +131,17 @@ namespace OpenRa.Traits Actor producer = null; // Prioritise primary structure in build order - var primaryProducers = self.World.Actors - .Where(x => x.traits.Contains() - && producerTypes.Contains(x.Info) - && x.Owner == self.Owner - && x.traits.Get().IsPrimary == true); + var primaryProducers = self.World.Queries.OwnedBy[self.Owner] + .WithTrait() + .Where(x => producerTypes.Contains(x.Actor.Info) + && x.Trait.IsPrimary); foreach (var p in primaryProducers) { // Ignore buildings that are disabled - if (p.traits.Contains() && p.traits.Get().Disabled) + if (p.Actor.traits.Contains() && p.Actor.traits.Get().Disabled) continue; - producer = p; + producer = p.Actor; break; } @@ -152,8 +151,8 @@ namespace OpenRa.Traits // Pick the first available producer if (producer == null) { - producer = self.World.Actors - .Where( x => producerTypes.Contains( x.Info ) && x.Owner == self.Owner ) + producer = self.World.Queries.OwnedBy[self.Owner] + .Where( x => producerTypes.Contains( x.Info ) ) .FirstOrDefault(); } diff --git a/OpenRa.Game/Traits/ProvidesRadar.cs b/OpenRa.Game/Traits/ProvidesRadar.cs index 3a8ee03afc..03bc6abcfc 100644 --- a/OpenRa.Game/Traits/ProvidesRadar.cs +++ b/OpenRa.Game/Traits/ProvidesRadar.cs @@ -16,9 +16,8 @@ namespace OpenRa.Traits var b = self.traits.Get(); if (b != null && b.Disabled) return false; - var isJammed = self.World.Actors.Any(a => a.traits.Contains() - && self.Owner != a.Owner - && (self.Location - a.Location).Length < a.Info.Traits.Get().Range); + var isJammed = self.World.Queries.WithTrait().Any(a => self.Owner != a.Actor.Owner + && (self.Location - a.Actor.Location).Length < a.Actor.Info.Traits.Get().Range); return !isJammed; } diff --git a/OpenRa.Game/Traits/UnitInfluence.cs b/OpenRa.Game/Traits/UnitInfluence.cs index f119050985..4d679e186e 100755 --- a/OpenRa.Game/Traits/UnitInfluence.cs +++ b/OpenRa.Game/Traits/UnitInfluence.cs @@ -30,8 +30,9 @@ namespace OpenRa.Traits // Does this belong here? NO, but it's your mess. // Get the crushable actors - foreach (var a in self.World.Actors.Where(b => b.traits.Contains())) + foreach (var aa in self.World.Queries.WithTrait()) { + var a = aa.Actor; // Are there any units in the same cell that can crush this? foreach( var ios in a.traits.WithInterface() ) foreach( var cell in ios.OccupiedCells() ) diff --git a/OpenRa.Game/World.cs b/OpenRa.Game/World.cs index f57fff653d..41c1ff43d4 100644 --- a/OpenRa.Game/World.cs +++ b/OpenRa.Game/World.cs @@ -6,12 +6,13 @@ using OpenRa.Support; using OpenRa.FileFormats; using OpenRa.Graphics; using OpenRa.Traits; +using OpenRa.Collections; namespace OpenRa { public class World { - List actors = new List(); + Set actors = new Set(); List effects = new List(); List> frameEndActions = new List>(); @@ -77,6 +78,9 @@ namespace OpenRa WorldRenderer = new WorldRenderer(this, Game.renderer); Minimap = new Minimap(this, Game.renderer); Timer.Time( "renderer, minimap: {0}" ); + + Queries = new AllQueries( this ); + Timer.Time( "queries: {0}" ); Timer.Time( "----end World.ctor" ); } @@ -153,5 +157,65 @@ namespace OpenRa return ret; } } + + public class AllQueries + { + readonly World world; + + public readonly Dictionary OwnedBy = new Dictionary(); + readonly TypeDictionary hasTrait = new TypeDictionary(); + + public AllQueries( World world ) + { + this.world = world; + foreach( var p in world.players.Values ) + { + var player = p; + OwnedBy.Add( player, new OwnedByCachedView( world, world.actors, x => x.Owner == player ) ); + } + } + + public CachedView> WithTrait() + { + return WithTraitInner( world, hasTrait ); + } + + static CachedView> WithTraitInner( World world, TypeDictionary hasTrait ) + { + var ret = hasTrait.GetOrDefault>>(); + if( ret != null ) + return ret; + ret = new CachedView>( + world.actors, + x => x.traits.Contains(), + x => new TraitPair { Actor = x, Trait = x.traits.Get() } ); + hasTrait.Add( ret ); + return ret; } + + public struct TraitPair + { + public Actor Actor; + public T Trait; + } + + public class OwnedByCachedView : CachedView + { + readonly World world; + readonly TypeDictionary hasTrait = new TypeDictionary(); + + public OwnedByCachedView( World world, Set set, Func include ) + : base( set, include, a => a ) + { + this.world = world; + } + + public CachedView> WithTrait() + { + return WithTraitInner( world, hasTrait ); + } + } + } + + public readonly AllQueries Queries; } } diff --git a/OpenRa.Mods.Aftermath/ChronoshiftDeploy.cs b/OpenRa.Mods.Aftermath/ChronoshiftDeploy.cs index db74c1b9a0..dc44045f49 100644 --- a/OpenRa.Mods.Aftermath/ChronoshiftDeploy.cs +++ b/OpenRa.Mods.Aftermath/ChronoshiftDeploy.cs @@ -55,8 +55,8 @@ namespace OpenRa.Mods.Aftermath Sound.Play("chrotnk1.aud"); chargeTick = chargeLength; - foreach (var a in self.World.Actors.Where(a => a.traits.Contains())) - a.traits.Get().DoChronoshift(); + foreach (var a in self.World.Queries.WithTrait()) + a.Trait.DoChronoshift(); } } diff --git a/OpenRa.Mods.RA/GpsPower.cs b/OpenRa.Mods.RA/GpsPower.cs index a7673e97d7..0665d99174 100644 --- a/OpenRa.Mods.RA/GpsPower.cs +++ b/OpenRa.Mods.RA/GpsPower.cs @@ -18,8 +18,8 @@ namespace OpenRa.Mods.RA protected override void OnFinishCharging() { - var launchSite = Owner.World.Actors - .FirstOrDefault(a => a.Owner == Owner && a.traits.Contains()); + var launchSite = Owner.World.Queries.OwnedBy[Owner] + .FirstOrDefault(a => a.traits.Contains()); if (launchSite == null) return; diff --git a/OpenRa.Mods.RA/IronCurtainPower.cs b/OpenRa.Mods.RA/IronCurtainPower.cs index 3548e25aa1..0b746ff47a 100644 --- a/OpenRa.Mods.RA/IronCurtainPower.cs +++ b/OpenRa.Mods.RA/IronCurtainPower.cs @@ -29,8 +29,9 @@ namespace OpenRa.Mods.RA if (self.Owner == self.World.LocalPlayer) Game.controller.CancelInputMode(); - var curtain = self.World.Actors.Where(a => a.Owner != null - && a.traits.Contains()).FirstOrDefault(); + var curtain = self.World.Queries.WithTrait() + .Where(a => a.Actor.Owner != null) + .FirstOrDefault().Actor; if (curtain != null) curtain.traits.Get().PlayCustomAnim(curtain, "active"); @@ -73,8 +74,9 @@ namespace OpenRa.Mods.RA public void Tick(World world) { - var hasStructure = world.Actors - .Any(a => a.Owner == world.LocalPlayer && a.traits.Contains()); + var hasStructure = world.Queries.OwnedBy[world.LocalPlayer] + .WithTrait() + .Any(); if (!hasStructure) Game.controller.CancelInputMode();