diff --git a/OpenRA.Game/TraitDictionary.cs b/OpenRA.Game/TraitDictionary.cs index a9201fc085..cad0cc5126 100644 --- a/OpenRA.Game/TraitDictionary.cs +++ b/OpenRA.Game/TraitDictionary.cs @@ -255,7 +255,7 @@ namespace OpenRA public void Reset() { index = -1; } public bool MoveNext() { return ++index < actors.Count; } - public TraitPair Current { get { return new TraitPair { Actor = actors[index], Trait = traits[index] }; } } + public TraitPair Current { get { return new TraitPair(actors[index], traits[index]); } } object System.Collections.IEnumerator.Current { get { return Current; } } public void Dispose() { } } diff --git a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs index 0cae87a5ee..5a142312f8 100644 --- a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs +++ b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs @@ -32,17 +32,18 @@ namespace OpenRA.Traits public readonly WPos CenterPosition; public readonly Rectangle Bounds; public readonly HashSet TargetTypes; - readonly IRemoveFrozenActor[] removeFrozenActors; readonly Actor actor; readonly Shroud shroud; - public Player Owner; + public Player Owner { get; private set; } - public ITooltipInfo TooltipInfo; - public Player TooltipOwner; + public ITooltipInfo TooltipInfo { get; private set; } + public Player TooltipOwner { get; private set; } + readonly ITooltip tooltip; - public int HP; - public DamageState DamageState; + public int HP { get; private set; } + public DamageState DamageState { get; private set; } + readonly IHealth health; public bool Visible = true; public bool Shrouded { get; private set; } @@ -57,7 +58,6 @@ namespace OpenRA.Traits actor = self; this.shroud = shroud; NeedRenderables = startsRevealed; - removeFrozenActors = self.TraitsImplementing().ToArray(); // Consider all cells inside the map area (ignoring the current map bounds) Footprint = footprint @@ -68,6 +68,9 @@ namespace OpenRA.Traits Bounds = self.Bounds; TargetTypes = self.GetEnabledTargetTypes().ToHashSet(); + tooltip = self.TraitsImplementing().FirstOrDefault(); + health = self.TraitOrDefault(); + UpdateVisibility(); } @@ -76,6 +79,23 @@ namespace OpenRA.Traits public ActorInfo Info { get { return actor.Info; } } public Actor Actor { get { return !actor.IsDead ? actor : null; } } + public void RefreshState() + { + Owner = actor.Owner; + + if (health != null) + { + HP = health.HP; + DamageState = health.DamageState; + } + + if (tooltip != null) + { + TooltipInfo = tooltip.TooltipInfo; + TooltipOwner = tooltip.Owner; + } + } + public void Tick() { if (flashTicks > 0) @@ -127,16 +147,6 @@ namespace OpenRA.Traits public bool HasRenderables { get { return !Shrouded && Renderables.Any(); } } - public bool ShouldBeRemoved(Player owner) - { - // PERF: Avoid LINQ. - foreach (var rfa in removeFrozenActors) - if (rfa.RemoveActor(actor, owner)) - return true; - - return false; - } - public override string ToString() { return "{0} {1}{2}".F(Info.Name, ID, IsValid ? "" : " (invalid)"); @@ -189,6 +199,13 @@ namespace OpenRA.Traits partitionedFrozenActorIds.Add(fa.ID, FootprintBounds(fa)); } + public void Remove(FrozenActor fa) + { + partitionedFrozenActorIds.Remove(fa.ID); + world.ScreenMap.Remove(owner, fa); + frozenActorsById.Remove(fa.ID); + } + Rectangle FootprintBounds(FrozenActor fa) { var p1 = fa.Footprint[0]; @@ -216,7 +233,7 @@ namespace OpenRA.Traits { UpdateDirtyFrozenActorsFromDirtyBins(); - var idsToRemove = new List(); + var frozenActorsToRemove = new List(); VisibilityHash = 0; FrozenHash = 0; @@ -231,22 +248,16 @@ namespace OpenRA.Traits if (dirtyFrozenActorIds.Contains(id)) frozenActor.UpdateVisibility(); - if (frozenActor.ShouldBeRemoved(owner)) - idsToRemove.Add(id); - else if (frozenActor.Visible) + if (frozenActor.Visible) VisibilityHash += hash; else if (frozenActor.Actor == null) - idsToRemove.Add(id); + frozenActorsToRemove.Add(frozenActor); } dirtyFrozenActorIds.Clear(); - foreach (var id in idsToRemove) - { - partitionedFrozenActorIds.Remove(id); - world.ScreenMap.Remove(owner, frozenActorsById[id]); - frozenActorsById.Remove(id); - } + foreach (var fa in frozenActorsToRemove) + Remove(fa); } void UpdateDirtyFrozenActorsFromDirtyBins() diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index 5ac5d3feb2..ffa931225a 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -388,11 +388,6 @@ namespace OpenRA.Traits void DoImpact(Target target, Actor firedBy, IEnumerable damageModifiers); } - public interface IRemoveFrozenActor - { - bool RemoveActor(Actor self, Player owner); - } - public interface IRulesetLoaded { void RulesetLoaded(Ruleset rules, TInfo info); } public interface IRulesetLoaded : IRulesetLoaded, ITraitInfoInterface { } } diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs index 1af06e23a5..eab43ae99e 100644 --- a/OpenRA.Game/World.cs +++ b/OpenRA.Game/World.cs @@ -408,14 +408,21 @@ namespace OpenRA } } - public struct TraitPair + public struct TraitPair : IEquatable> { - public Actor Actor; - public T Trait; + public readonly Actor Actor; + public readonly T Trait; - public override string ToString() - { - return "{0}->{1}".F(Actor.Info.Name, Trait.GetType().Name); - } + public TraitPair(Actor actor, T trait) { Actor = actor; Trait = trait; } + + public static bool operator ==(TraitPair me, TraitPair other) { return me.Actor == other.Actor && Equals(me.Trait, other.Trait); } + public static bool operator !=(TraitPair me, TraitPair other) { return !(me == other); } + + public override int GetHashCode() { return Actor.GetHashCode() ^ Trait.GetHashCode(); } + + public bool Equals(TraitPair other) { return this == other; } + public override bool Equals(object obj) { return obj is TraitPair && Equals((TraitPair)obj); } + + public override string ToString() { return "{0}->{1}".F(Actor.Info.Name, Trait.GetType().Name); } } } diff --git a/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs b/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs index e3a8dec8ae..a81b295f7f 100644 --- a/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs +++ b/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs @@ -236,7 +236,7 @@ namespace OpenRA.Mods.Common.Orders var blockers = allTiles.SelectMany(world.ActorMap.GetActorsAt) .Where(a => a.Owner == queue.Actor.Owner && a.IsIdle) - .Select(a => new TraitPair { Actor = a, Trait = a.TraitOrDefault() }); + .Select(a => new TraitPair(a, a.TraitOrDefault())); foreach (var blocker in blockers.Where(x => x.Trait != null)) { diff --git a/OpenRA.Mods.Common/Traits/Modifiers/FrozenUnderFog.cs b/OpenRA.Mods.Common/Traits/Modifiers/FrozenUnderFog.cs index 131edee0d0..969a644c5b 100644 --- a/OpenRA.Mods.Common/Traits/Modifiers/FrozenUnderFog.cs +++ b/OpenRA.Mods.Common/Traits/Modifiers/FrozenUnderFog.cs @@ -37,8 +37,6 @@ namespace OpenRA.Mods.Common.Traits readonly PPos[] footprint; PlayerDictionary frozenStates; - ITooltip tooltip; - Health health; bool isRendering; class FrozenState @@ -65,9 +63,6 @@ namespace OpenRA.Mods.Common.Traits public void Created(Actor self) { - tooltip = self.TraitsImplementing().FirstOrDefault(); - health = self.TraitOrDefault(); - frozenStates = new PlayerDictionary(self.World, (player, playerIndex) => { var frozenActor = new FrozenActor(self, footprint, player.Shroud, startsRevealed); @@ -81,20 +76,7 @@ namespace OpenRA.Mods.Common.Traits void UpdateFrozenActor(Actor self, FrozenActor frozenActor, int playerIndex) { VisibilityHash |= 1 << (playerIndex % 32); - - frozenActor.Owner = self.Owner; - - if (health != null) - { - frozenActor.HP = health.HP; - frozenActor.DamageState = health.DamageState; - } - - if (tooltip != null) - { - frozenActor.TooltipInfo = tooltip.TooltipInfo; - frozenActor.TooltipOwner = tooltip.Owner; - } + frozenActor.RefreshState(); } bool IsVisibleInner(Actor self, Player byPlayer) diff --git a/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs b/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs index b073884f95..a570446ae1 100644 --- a/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs +++ b/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs @@ -361,7 +361,7 @@ namespace OpenRA.Mods.Common.Traits public virtual TraitPair MostLikelyProducer() { var trait = self.TraitsImplementing().FirstOrDefault(p => p.Info.Produces.Contains(Info.Type)); - return new TraitPair { Actor = self, Trait = trait }; + return new TraitPair(self, trait); } // Builds a unit from the actor that holds this queue (1 queue per building) diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs index 2ed1d10c62..594b30558a 100644 --- a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs +++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs @@ -2889,6 +2889,16 @@ namespace OpenRA.Mods.Common.UtilityCommands node.Key = "AttackSuicides"; } + // Replaced GpsRemoveFrozenActor with FrozenUnderFogUpdatedByGps + if (engineVersion < 20160117) + { + if (node.Key == "GpsRemoveFrozenActor") + { + node.Key = "FrozenUnderFogUpdatedByGps"; + node.Value.Nodes.Clear(); + } + } + UpgradeActorRules(engineVersion, ref node.Value.Nodes, node, depth + 1); } } diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index b0998d18f0..10da51510c 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -90,7 +90,7 @@ - + diff --git a/OpenRA.Mods.RA/Traits/FrozenUnderFogUpdatedByGps.cs b/OpenRA.Mods.RA/Traits/FrozenUnderFogUpdatedByGps.cs new file mode 100644 index 0000000000..b403923e61 --- /dev/null +++ b/OpenRA.Mods.RA/Traits/FrozenUnderFogUpdatedByGps.cs @@ -0,0 +1,105 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 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 OpenRA.Mods.Common.Traits; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA.Traits +{ + using FrozenActorAction = Action; + + [Desc("Updates frozen actors of actors that change owners, are sold or die whilst having an active GPS power.")] + public class FrozenUnderFogUpdatedByGpsInfo : ITraitInfo, Requires + { + public object Create(ActorInitializer init) { return new FrozenUnderFogUpdatedByGps(init); } + } + + public class FrozenUnderFogUpdatedByGps : INotifyOwnerChanged, INotifyActorDisposing, IOnGpsRefreshed + { + static readonly FrozenActorAction Refresh = (fufubg, fal, gps, fa) => + { + // Refreshes the visual state of the frozen actor, so ownership changes can be seen. + fa.RefreshState(); + if (fa.HasRenderables) + fa.NeedRenderables = true; + }; + static readonly FrozenActorAction Remove = (fufubg, fal, gps, fa) => + { + // Removes the frozen actor. Once done, we no longer need to track GPS updates. + fal.Remove(fa); + gps.UnregisterForOnGpsRefreshed(fufubg.self, fufubg); + }; + + class Traits + { + public readonly FrozenActorLayer FrozenActorLayer; + public readonly GpsWatcher GpsWatcher; + public Traits(Player player, FrozenUnderFogUpdatedByGps frozenUnderFogUpdatedByGps) + { + FrozenActorLayer = player.PlayerActor.TraitOrDefault(); + GpsWatcher = player.PlayerActor.TraitOrDefault(); + GpsWatcher.RegisterForOnGpsRefreshed(frozenUnderFogUpdatedByGps.self, frozenUnderFogUpdatedByGps); + } + } + + readonly PlayerDictionary traits; + readonly Actor self; + + public FrozenUnderFogUpdatedByGps(ActorInitializer init) + { + self = init.Self; + traits = new PlayerDictionary(init.World, player => new Traits(player, this)); + } + + public void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner) + { + ActOnFrozenActorsForAllPlayers(Refresh); + } + + public void Disposing(Actor self) + { + ActOnFrozenActorsForAllPlayers(Remove); + } + + public void OnGpsRefresh(Actor self, Player player) + { + if (self.IsDead) + ActOnFrozenActorForPlayer(player, Remove); + else + ActOnFrozenActorForPlayer(player, Refresh); + } + + void ActOnFrozenActorsForAllPlayers(FrozenActorAction action) + { + for (var playerIndex = 0; playerIndex < traits.Count; playerIndex++) + ActOnFrozenActorForTraits(traits[playerIndex], action); + } + + void ActOnFrozenActorForPlayer(Player player, FrozenActorAction action) + { + ActOnFrozenActorForTraits(traits[player], action); + } + + void ActOnFrozenActorForTraits(Traits t, FrozenActorAction action) + { + if (t.FrozenActorLayer == null || t.GpsWatcher == null || + !t.GpsWatcher.Granted || !t.GpsWatcher.GrantedAllies) + return; + + var fa = t.FrozenActorLayer.FromID(self.ActorID); + if (fa == null) + return; + + action(this, t.FrozenActorLayer, t.GpsWatcher, fa); + } + } +} diff --git a/OpenRA.Mods.RA/Traits/GpsRemoveFrozenActor.cs b/OpenRA.Mods.RA/Traits/GpsRemoveFrozenActor.cs deleted file mode 100644 index 3b432096ff..0000000000 --- a/OpenRA.Mods.RA/Traits/GpsRemoveFrozenActor.cs +++ /dev/null @@ -1,55 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2015 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.Collections.Generic; -using System.Linq; -using OpenRA.Traits; - -namespace OpenRA.Mods.RA.Traits -{ - [Desc("Removes frozen actors of actors that are dead or sold," + - " when having an active GPS power.")] - public class GpsRemoveFrozenActorInfo : ITraitInfo - { - [Desc("Should this trait also affect allied players?")] - public bool GrantAllies = true; - - public object Create(ActorInitializer init) { return new GpsRemoveFrozenActor(init.Self, this); } - } - - public class GpsRemoveFrozenActor : IRemoveFrozenActor - { - readonly GpsWatcher[] watchers; - readonly GpsRemoveFrozenActorInfo info; - - public GpsRemoveFrozenActor(Actor self, GpsRemoveFrozenActorInfo info) - { - this.info = info; - watchers = self.World.ActorsWithTrait().Select(w => w.Trait).ToArray(); - } - - public bool RemoveActor(Actor self, Player owner) - { - if (!self.IsDead) - return false; - - foreach (var w in watchers) - { - if (w.Owner != owner && !(info.GrantAllies && w.Owner.IsAlliedWith(owner))) - continue; - - if (w.Launched) - return true; - } - - return false; - } - } -} diff --git a/OpenRA.Mods.RA/Traits/SupportPowers/GpsPower.cs b/OpenRA.Mods.RA/Traits/SupportPowers/GpsPower.cs index 5d411145e0..8a5930dd1b 100644 --- a/OpenRA.Mods.RA/Traits/SupportPowers/GpsPower.cs +++ b/OpenRA.Mods.RA/Traits/SupportPowers/GpsPower.cs @@ -25,12 +25,13 @@ namespace OpenRA.Mods.RA.Traits class GpsWatcher : ISync, IFogVisibilityModifier { - [Sync] public bool Launched = false; - [Sync] public bool GrantedAllies = false; - [Sync] public bool Granted = false; - public Player Owner; + [Sync] public bool Launched { get; private set; } + [Sync] public bool GrantedAllies { get; private set; } + [Sync] public bool Granted { get; private set; } + public readonly Player Owner; - List actors = new List { }; + readonly List actors = new List(); + readonly HashSet> notifyOnRefresh = new HashSet>(); public GpsWatcher(Player owner) { Owner = owner; } @@ -49,11 +50,11 @@ namespace OpenRA.Mods.RA.Traits public void Launch(Actor atek, SupportPowerInfo info) { atek.World.Add(new DelayedAction(((GpsPowerInfo)info).RevealDelay * 25, - () => - { - Launched = true; - RefreshGps(atek); - })); + () => + { + Launched = true; + RefreshGps(atek); + })); } public void RefreshGps(Actor atek) @@ -69,11 +70,18 @@ namespace OpenRA.Mods.RA.Traits void RefreshGranted() { + var wasGranted = Granted; + var wasGrantedAllies = GrantedAllies; + Granted = actors.Count > 0 && Launched; GrantedAllies = Owner.World.ActorsHavingTrait(g => g.Granted).Any(p => p.Owner.IsAlliedWith(Owner)); if (Granted || GrantedAllies) Owner.Shroud.ExploreAll(Owner.World); + + if (wasGranted != Granted || wasGrantedAllies != GrantedAllies) + foreach (var tp in notifyOnRefresh.ToList()) + tp.Trait.OnGpsRefresh(tp.Actor, Owner); } public bool HasFogVisibility() @@ -89,8 +97,20 @@ namespace OpenRA.Mods.RA.Traits return gpsDot.IsDotVisible(Owner); } + + public void RegisterForOnGpsRefreshed(Actor actor, IOnGpsRefreshed toBeNotified) + { + notifyOnRefresh.Add(new TraitPair(actor, toBeNotified)); + } + + public void UnregisterForOnGpsRefreshed(Actor actor, IOnGpsRefreshed toBeNotified) + { + notifyOnRefresh.Remove(new TraitPair(actor, toBeNotified)); + } } + interface IOnGpsRefreshed { void OnGpsRefresh(Actor self, Player player); } + class GpsPowerInfo : SupportPowerInfo { public readonly int RevealDelay = 0; diff --git a/mods/ra/rules/defaults.yaml b/mods/ra/rules/defaults.yaml index 21a3532603..d5cd53210c 100644 --- a/mods/ra/rules/defaults.yaml +++ b/mods/ra/rules/defaults.yaml @@ -430,7 +430,7 @@ Guardable: Range: 3c0 FrozenUnderFog: - GpsRemoveFrozenActor: + FrozenUnderFogUpdatedByGps: Tooltip: GenericName: Structure Demolishable: @@ -500,7 +500,7 @@ SellSounds: cashturn.aud Guardable: FrozenUnderFog: - GpsRemoveFrozenActor: + FrozenUnderFogUpdatedByGps: Health: HP: 100 Shape: Rectangle