diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index c35badf4cf..10da51510c 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -90,6 +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/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 6e21894c14..d5cd53210c 100644 --- a/mods/ra/rules/defaults.yaml +++ b/mods/ra/rules/defaults.yaml @@ -430,6 +430,7 @@ Guardable: Range: 3c0 FrozenUnderFog: + FrozenUnderFogUpdatedByGps: Tooltip: GenericName: Structure Demolishable: @@ -499,6 +500,7 @@ SellSounds: cashturn.aud Guardable: FrozenUnderFog: + FrozenUnderFogUpdatedByGps: Health: HP: 100 Shape: Rectangle