diff --git a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs index fedc750b38..fd04eb0532 100644 --- a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs +++ b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs @@ -18,6 +18,11 @@ using OpenRA.Primitives; namespace OpenRA.Traits { + public interface ICreatesFrozenActors + { + void OnVisibilityChanged(FrozenActor frozen); + } + [Desc("Required for FrozenUnderFog to work. Attach this to the player actor.")] public class FrozenActorLayerInfo : Requires, ITraitInfo { @@ -32,6 +37,7 @@ namespace OpenRA.Traits public readonly PPos[] Footprint; public readonly WPos CenterPosition; readonly Actor actor; + readonly ICreatesFrozenActors frozenTrait; readonly Player viewer; readonly Shroud shroud; @@ -70,9 +76,10 @@ namespace OpenRA.Traits int flashTicks; - public FrozenActor(Actor actor, PPos[] footprint, Player viewer, bool startsRevealed) + public FrozenActor(Actor actor, ICreatesFrozenActors frozenTrait, PPos[] footprint, Player viewer, bool startsRevealed) { this.actor = actor; + this.frozenTrait = frozenTrait; this.viewer = viewer; shroud = viewer.Shroud; NeedRenderables = startsRevealed; @@ -153,6 +160,11 @@ namespace OpenRA.Traits Shrouded = false; } + // Force the backing trait to update so other actors can't + // query inconsistent state (both hidden or both visible) + if (Visible != wasVisible) + frozenTrait.OnVisibilityChanged(this); + NeedRenderables |= Visible && !wasVisible; } diff --git a/OpenRA.Mods.Common/Traits/Modifiers/FrozenUnderFog.cs b/OpenRA.Mods.Common/Traits/Modifiers/FrozenUnderFog.cs index dccc79e9de..16f2445788 100644 --- a/OpenRA.Mods.Common/Traits/Modifiers/FrozenUnderFog.cs +++ b/OpenRA.Mods.Common/Traits/Modifiers/FrozenUnderFog.cs @@ -9,6 +9,7 @@ */ #endregion +using System; using System.Collections.Generic; using System.Drawing; using System.Linq; @@ -27,7 +28,7 @@ namespace OpenRA.Mods.Common.Traits public object Create(ActorInitializer init) { return new FrozenUnderFog(init, this); } } - public class FrozenUnderFog : IRenderModifier, IDefaultVisibility, ITick, ITickRender, ISync, INotifyCreated + public class FrozenUnderFog : ICreatesFrozenActors, IRenderModifier, IDefaultVisibility, ITick, ITickRender, ISync, INotifyCreated { [Sync] public int VisibilityHash; @@ -37,6 +38,7 @@ namespace OpenRA.Mods.Common.Traits PlayerDictionary frozenStates; bool isRendering; + bool created; class FrozenState { @@ -67,7 +69,7 @@ namespace OpenRA.Mods.Common.Traits { frozenStates = new PlayerDictionary(self.World, (player, playerIndex) => { - var frozenActor = new FrozenActor(self, footprint, player, startsRevealed); + var frozenActor = new FrozenActor(self, this, footprint, player, startsRevealed); player.PlayerActor.Trait().Add(frozenActor); return new FrozenState(frozenActor) { IsVisible = startsRevealed }; }); @@ -75,6 +77,8 @@ namespace OpenRA.Mods.Common.Traits if (startsRevealed) for (var playerIndex = 0; playerIndex < frozenStates.Count; playerIndex++) UpdateFrozenActor(self, frozenStates[playerIndex].FrozenActor, playerIndex); + + created = true; } void UpdateFrozenActor(Actor self, FrozenActor frozenActor, int playerIndex) @@ -83,6 +87,17 @@ namespace OpenRA.Mods.Common.Traits frozenActor.RefreshState(); } + void ICreatesFrozenActors.OnVisibilityChanged(FrozenActor frozen) + { + // Ignore callbacks during initial setup + if (!created) + return; + + // Update state visibility to match the frozen actor to ensure consistency within the tick + // The rest of the state will be updated by ITick.Tick below + frozenStates[frozen.Viewer].IsVisible = !frozen.Visible; + } + bool IsVisibleInner(Actor self, Player byPlayer) { // If fog is disabled visibility is determined by shroud