From 895d6bbf7cf1679d9a49a030253f133f2320efdc Mon Sep 17 00:00:00 2001 From: darkademic <41052878+darkademic@users.noreply.github.com> Date: Sat, 9 May 2026 22:43:42 +0100 Subject: [PATCH 1/2] Between cell movement responsiveness. --- OpenRA.Mods.Common/Activities/Attack.cs | 3 + OpenRA.Mods.Common/Activities/Enter.cs | 3 + .../Activities/Move/AttackMoveActivity.cs | 22 +- OpenRA.Mods.Common/Activities/Move/Move.cs | 80 +++++- .../Activities/Move/MoveAdjacentTo.cs | 3 + .../Activities/Move/MoveWithinRange.cs | 5 +- .../Activities/Move/ResponsiveMoveSupport.cs | 270 ++++++++++++++++++ .../Traits/Attack/AttackBase.cs | 4 + OpenRA.Mods.Common/Traits/AttackMove.cs | 7 + OpenRA.Mods.Common/Traits/Mobile.cs | 7 + 10 files changed, 397 insertions(+), 7 deletions(-) create mode 100644 OpenRA.Mods.Common/Activities/Move/ResponsiveMoveSupport.cs diff --git a/OpenRA.Mods.Common/Activities/Attack.cs b/OpenRA.Mods.Common/Activities/Attack.cs index e8cb2cf743ff..c53472fd751b 100644 --- a/OpenRA.Mods.Common/Activities/Attack.cs +++ b/OpenRA.Mods.Common/Activities/Attack.cs @@ -94,6 +94,9 @@ protected virtual Target RecalculateTarget(Actor self, out bool targetIsHiddenAc public override bool Tick(Actor self) { + if (IsCanceling && NextActivity != null) + ResponsiveMoveForwarder.Notify(ChildActivity, ResponsiveCancelType.ReplacementActivity); + if (IsCanceling) return true; diff --git a/OpenRA.Mods.Common/Activities/Enter.cs b/OpenRA.Mods.Common/Activities/Enter.cs index 032cd3c7ef41..ab6643a85249 100644 --- a/OpenRA.Mods.Common/Activities/Enter.cs +++ b/OpenRA.Mods.Common/Activities/Enter.cs @@ -74,6 +74,9 @@ public override bool Tick(Actor self) if (!IsCanceling && useLastVisibleTarget && lastState == EnterState.Entering) Cancel(self, true); + if (IsCanceling && NextActivity != null) + ResponsiveMoveForwarder.Notify(ChildActivity, ResponsiveCancelType.ReplacementActivity); + TickInner(self, target, useLastVisibleTarget); // We need to wait for movement to finish before transitioning to diff --git a/OpenRA.Mods.Common/Activities/Move/AttackMoveActivity.cs b/OpenRA.Mods.Common/Activities/Move/AttackMoveActivity.cs index 076544dc1e13..b25241f5888a 100644 --- a/OpenRA.Mods.Common/Activities/Move/AttackMoveActivity.cs +++ b/OpenRA.Mods.Common/Activities/Move/AttackMoveActivity.cs @@ -54,7 +54,15 @@ protected override void OnFirstRun(Actor self) public override bool Tick(Actor self) { if (IsCanceling || attackMove == null || autoTarget == null) + { + // Immediate attack-move replacements are queued on this wrapper activity. + // Forward that information to the child Move so ResponsiveBetweenCells can treat + // the cancel as a redirect instead of a stop-style nearest-cell landing. + if (IsCanceling && NextActivity != null) + ResponsiveMoveForwarder.Notify(ChildActivity, ResponsiveCancelType.ReplacementActivity); + return TickChild(self); + } // We are currently not attacking, so scan for new targets. if (ChildActivity == null || runningMoveActivity) @@ -67,10 +75,18 @@ public override bool Tick(Actor self) if (target.Type != TargetType.Invalid) { runningMoveActivity = false; - ChildActivity?.Cancel(self); - foreach (var ab in autoTarget.ActiveAttackBases) - QueueChild(ab.GetAttackActivity(self, AttackSource.AttackMove, target, autoTarget.AllowMove, false)); + // Let ResponsiveBetweenCells finish resolving the current move before + // starting the attack activity. Queuing the attack directly onto the + // current move would make Move treat it as an in-flight redirect. + if (ChildActivity != null) + { + ResponsiveMoveForwarder.Notify(ChildActivity, preferredLandingPosition: target.CenterPosition); + ChildActivity.Cancel(self); + } + else + foreach (var ab in autoTarget.ActiveAttackBases) + QueueChild(ab.GetAttackActivity(self, AttackSource.AttackMove, target, autoTarget.AllowMove, false)); } // Continue with the move activity (or queue a new one) when there are no targets. diff --git a/OpenRA.Mods.Common/Activities/Move/Move.cs b/OpenRA.Mods.Common/Activities/Move/Move.cs index fffe0c63db38..36ea71744ecc 100644 --- a/OpenRA.Mods.Common/Activities/Move/Move.cs +++ b/OpenRA.Mods.Common/Activities/Move/Move.cs @@ -38,6 +38,7 @@ public class Move : Activity int carryoverProgress; int lastMovePartCompletedTick; + readonly ResponsiveMoveSupport responsiveMove; bool alreadyAtDestination; List path; @@ -58,6 +59,7 @@ public Move(Actor self, CPos destination, Color? targetLineColor = null) { // PERF: Because we can be sure that OccupiesSpace is Mobile here, we can save some performance by avoiding querying for the trait. mobile = (Mobile)self.OccupiesSpace; + responsiveMove = new ResponsiveMoveSupport(mobile); getPath = check => { @@ -78,6 +80,7 @@ public Move(Actor self, CPos destination, WDist nearEnough, Actor ignoreActor = { // PERF: Because we can be sure that OccupiesSpace is Mobile here, we can save some performance by avoiding querying for the trait. mobile = (Mobile)self.OccupiesSpace; + responsiveMove = new ResponsiveMoveSupport(mobile); getPath = check => { @@ -104,6 +107,7 @@ public Move(Actor self, Func 0 ? Math.Max(1, delta.Length / speed) : 1; + var facing = delta.HorizontalLengthSquared != 0 ? delta.Yaw : mobile.Facing; + QueueChild(new Drag(self, currentPosition, landing.Position, length, facing)); + return true; + } + + internal void NotifyResponsiveCancel(ResponsiveCancelType? type = null, WPos? preferredLandingPosition = null) + { + responsiveMove.NotifyCancel(type, preferredLandingPosition); + } + protected override void OnFirstRun(Actor self) { startTicks = self.World.WorldTick; mobile.MoveResult = MoveResult.InProgress; + responsiveMove.Reset(); if (evaluateNearestMovableCell && destination.HasValue) { @@ -156,12 +213,18 @@ public override bool Tick(Actor self) if (alreadyAtDestination) { + if (TryQueueResponsiveSettle(self)) + return false; + mobile.MoveResult = MoveResult.CompleteDestinationReached; return true; } if (destination == mobile.ToCell) { + if (TryQueueResponsiveSettle(self)) + return false; + if (hadNoPath) mobile.MoveResult = MoveResult.CompleteDestinationBlocked; else @@ -212,12 +275,18 @@ public override bool Tick(Actor self) return false; } + // A responsive redirect has already resolved us into a single cell, but the actor may + // still be physically between the old segment endpoints. Start the new segment from the + // live world position so the redirect stays smooth. + var startFromCurrentPosition = responsiveMove.ShouldStartNextSegmentFromCurrentPosition(self); + var currentPosition = mobile.CenterPosition; + mobile.SetLocation(mobile.FromCell, mobile.FromSubCell, nextCell.Value.Cell, nextCell.Value.SubCell); var map = self.World.Map; - var from = (mobile.FromCell.Layer == 0 ? map.CenterOfCell(mobile.FromCell) : - self.World.GetCustomMovementLayers()[mobile.FromCell.Layer].CenterOfCell(mobile.FromCell)) + - map.Grid.OffsetOfSubCell(mobile.FromSubCell); + var from = ResponsiveMoveSupport.CellCenterPosition(self, mobile.FromCell, mobile.FromSubCell); + if (startFromCurrentPosition) + from = currentPosition; var to = Util.BetweenCells(self.World, mobile.FromCell, mobile.ToCell) + (map.Grid.OffsetOfSubCell(mobile.FromSubCell) + map.Grid.OffsetOfSubCell(mobile.ToSubCell)) / 2; @@ -458,6 +527,11 @@ public override bool Tick(Actor self) { var mobile = Move.mobile; + // MovePart is the safe point where a responsive cancel can either settle or hand off + // directly into a replacement move without waiting for the current full cell traversal. + if (Move.TryResolveResponsiveCancel(self)) + return true; + // Only move by a full speed step if we didn't already move this tick. // If we did, we limit the move to any carried-over leftover progress. if (Move.lastMovePartCompletedTick < self.World.WorldTick) diff --git a/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs b/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs index b43f6c714656..7ea5606fc1ae 100644 --- a/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs +++ b/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs @@ -89,6 +89,9 @@ public override bool Tick(Actor self) // Target is hidden or dead, and we don't have a fallback position to move towards var noTarget = useLastVisibleTarget && !lastVisibleTarget.IsValidFor(self); + if (IsCanceling && NextActivity != null) + ResponsiveMoveForwarder.Notify(ChildActivity, ResponsiveCancelType.ReplacementActivity); + // Cancel the current path if the activity asks to stop. if (ShouldStop(self) || noTarget) Cancel(self, true); diff --git a/OpenRA.Mods.Common/Activities/Move/MoveWithinRange.cs b/OpenRA.Mods.Common/Activities/Move/MoveWithinRange.cs index 88cd2971cea6..9b58f0e861f3 100644 --- a/OpenRA.Mods.Common/Activities/Move/MoveWithinRange.cs +++ b/OpenRA.Mods.Common/Activities/Move/MoveWithinRange.cs @@ -39,7 +39,10 @@ protected override bool ShouldStop(Actor self) { // We are now in range. Don't move any further! // HACK: This works around the pathfinder not returning the shortest path - return AtCorrectRange(self.CenterPosition) && Mobile.CanInteractWithGroundLayer(self) && Mobile.CanStayInCell(self.Location); + return AtCorrectRange(self.CenterPosition) + && Mobile.CanInteractWithGroundLayer(self) + && Mobile.CanStayInCell(self.Location) + && !Mobile.IsMovingBetweenCells; } protected override bool ShouldRepath(Actor self, CPos targetLocation) diff --git a/OpenRA.Mods.Common/Activities/Move/ResponsiveMoveSupport.cs b/OpenRA.Mods.Common/Activities/Move/ResponsiveMoveSupport.cs new file mode 100644 index 000000000000..41caa0cbda74 --- /dev/null +++ b/OpenRA.Mods.Common/Activities/Move/ResponsiveMoveSupport.cs @@ -0,0 +1,270 @@ +#region Copyright & License Information +/* + * Copyright (c) The OpenRA Developers and Contributors + * 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, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using OpenRA.Activities; +using OpenRA.Mods.Common.Traits; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Activities +{ + internal enum ResponsiveCancelType + { + ReplacementActivity, + LandBeforeNextActivity + } + + internal readonly struct ResponsiveLanding + { + public readonly CPos Cell; + public readonly SubCell SubCell; + public readonly WPos Position; + + public ResponsiveLanding(CPos cell, SubCell subCell, WPos position) + { + Cell = cell; + SubCell = subCell; + Position = position; + } + } + + readonly struct ResponsiveCancelRequest + { + public readonly bool HasReplacementActivity; + public readonly WPos? PreferredLandingPosition; + + public ResponsiveCancelRequest(bool hasReplacementActivity, WPos? preferredLandingPosition) + { + HasReplacementActivity = hasReplacementActivity; + PreferredLandingPosition = preferredLandingPosition; + } + } + + internal readonly struct ResponsiveCancelResolution + { + public readonly ResponsiveLanding Landing; + public readonly bool HasReplacementActivity; + + public ResponsiveCancelResolution(ResponsiveLanding landing, bool hasReplacementActivity) + { + Landing = landing; + HasReplacementActivity = hasReplacementActivity; + } + } + + internal sealed class ResponsiveMoveSupport + { + readonly Mobile mobile; + bool hasReplacementActivityOnCancel; + bool forceLandingBeforeNextActivity; + WPos? preferredLandingPosition; + + public ResponsiveMoveSupport(Mobile mobile) + { + this.mobile = mobile; + } + + public void Reset() + { + hasReplacementActivityOnCancel = false; + forceLandingBeforeNextActivity = false; + preferredLandingPosition = null; + } + + public void NotifyCancel(ResponsiveCancelType? type = null, WPos? preferredLandingPosition = null) + { + if (type == ResponsiveCancelType.ReplacementActivity) + hasReplacementActivityOnCancel = true; + else if (type == ResponsiveCancelType.LandBeforeNextActivity) + forceLandingBeforeNextActivity = true; + + if (preferredLandingPosition.HasValue) + this.preferredLandingPosition = preferredLandingPosition; + } + + public bool TryResolveCancel(Actor self, bool isCanceling, bool hasNextActivity, out ResponsiveCancelResolution resolution) + { + resolution = default; + if (!isCanceling || !mobile.Info.ResponsiveBetweenCells) + return false; + + var request = ConsumeRequest(hasNextActivity); + var currentPosition = mobile.CenterPosition; + var hasFromLanding = TryGetLanding(self, mobile.FromCell, mobile.FromSubCell, request, out var fromLanding); + var hasToLanding = TryGetLanding(self, mobile.ToCell, mobile.ToSubCell, request, out var toLanding); + + if (!mobile.IsMovingBetweenCells) + hasFromLanding = false; + + if (!hasFromLanding && !hasToLanding) + return false; + + if (!hasFromLanding) + { + resolution = new ResponsiveCancelResolution(toLanding, request.HasReplacementActivity); + return true; + } + + if (!hasToLanding) + { + resolution = new ResponsiveCancelResolution(fromLanding, request.HasReplacementActivity); + return true; + } + + var useToLanding = request.PreferredLandingPosition.HasValue + ? ChooseLandingEndpoint(currentPosition, request.PreferredLandingPosition.Value, fromLanding.Position, toLanding.Position) + : (toLanding.Position - currentPosition).LengthSquared <= (fromLanding.Position - currentPosition).LengthSquared; + + resolution = new ResponsiveCancelResolution(useToLanding ? toLanding : fromLanding, request.HasReplacementActivity); + return true; + } + + public bool ShouldStartNextSegmentFromCurrentPosition(Actor self) + { + return TryGetResolvedSingleCellLanding(self, out _); + } + + public bool TryGetSettleLanding(Actor self, out ResponsiveLanding landing) + { + if (!TryGetResolvedSingleCellLanding(self, out landing)) + return false; + + if ((landing.Position - mobile.CenterPosition).LengthSquared == 0) + return false; + + return true; + } + + bool TryGetResolvedSingleCellLanding(Actor self, out ResponsiveLanding landing) + { + landing = default; + if (!mobile.Info.ResponsiveBetweenCells || mobile.FromCell != mobile.ToCell) + return false; + + landing = new ResponsiveLanding(mobile.ToCell, mobile.ToSubCell, CellCenterPosition(self, mobile.ToCell, mobile.ToSubCell)); + return true; + } + + ResponsiveCancelRequest ConsumeRequest(bool hasNextActivity) + { + var request = new ResponsiveCancelRequest( + !forceLandingBeforeNextActivity && (hasNextActivity || hasReplacementActivityOnCancel), + preferredLandingPosition); + + hasReplacementActivityOnCancel = false; + forceLandingBeforeNextActivity = false; + preferredLandingPosition = null; + + return request; + } + + bool TryGetLanding(Actor self, CPos cell, SubCell preferredSubCell, in ResponsiveCancelRequest request, out ResponsiveLanding landing) + { + landing = default; + if (!mobile.CanStayInCell(cell)) + return false; + + var subCell = ChooseLandingSubCell(self, cell, preferredSubCell, request); + if (subCell == SubCell.Invalid) + return false; + + landing = new ResponsiveLanding(cell, subCell, CellCenterPosition(self, cell, subCell)); + return true; + } + + SubCell ChooseLandingSubCell(Actor self, CPos cell, SubCell preferredSubCell, in ResponsiveCancelRequest request) + { + if (!mobile.Info.LocomotorInfo.SharesCell) + return SubCell.FullCell; + + var blockedBy = request.HasReplacementActivity ? BlockedByActor.Stationary : BlockedByActor.All; + + if (!request.PreferredLandingPosition.HasValue) + return mobile.GetAvailableSubCell(cell, preferredSubCell, self, blockedBy); + + var currentPosition = mobile.CenterPosition; + var currentPreferenceDistance = (currentPosition - request.PreferredLandingPosition.Value).LengthSquared; + var bestSubCell = SubCell.Invalid; + var bestImprovingSubCell = SubCell.Invalid; + var bestDistance = long.MaxValue; + var bestImprovingDistance = long.MaxValue; + + for (var i = 1; i < self.World.Map.Grid.SubCellOffsets.Length; i++) + { + var candidate = (SubCell)i; + if (mobile.GetAvailableSubCell(cell, candidate, self, blockedBy) != candidate) + continue; + + var candidatePosition = CellCenterPosition(self, cell, candidate); + var distance = (candidatePosition - request.PreferredLandingPosition.Value).LengthSquared; + var movementDistance = (candidatePosition - currentPosition).LengthSquared; + if (distance <= currentPreferenceDistance && movementDistance < bestImprovingDistance) + { + bestImprovingDistance = movementDistance; + bestImprovingSubCell = candidate; + } + + if (distance < bestDistance) + { + bestDistance = distance; + bestSubCell = candidate; + } + } + + if (bestImprovingSubCell != SubCell.Invalid) + return bestImprovingSubCell; + + return bestSubCell != SubCell.Invalid ? bestSubCell : mobile.GetAvailableSubCell(cell, preferredSubCell, self, blockedBy); + } + + static bool ChooseLandingEndpoint(WPos currentPosition, WPos preferredPosition, WPos fromPosition, WPos toPosition) + { + var currentPreferenceDistance = (currentPosition - preferredPosition).LengthSquared; + var fromPreferenceDistance = (fromPosition - preferredPosition).LengthSquared; + var toPreferenceDistance = (toPosition - preferredPosition).LengthSquared; + var fromImproves = fromPreferenceDistance <= currentPreferenceDistance; + var toImproves = toPreferenceDistance <= currentPreferenceDistance; + + if (fromImproves != toImproves) + return toImproves; + + if (fromImproves && toImproves) + return (toPosition - currentPosition).LengthSquared <= (fromPosition - currentPosition).LengthSquared; + + return toPreferenceDistance <= fromPreferenceDistance; + } + + public static WPos CellCenterPosition(Actor self, CPos cell, SubCell subCell) + { + var position = cell.Layer == 0 ? self.World.Map.CenterOfCell(cell) : + self.World.GetCustomMovementLayers()[cell.Layer].CenterOfCell(cell); + + position += self.World.Map.Grid.OffsetOfSubCell(subCell); + position -= new WVec(0, 0, self.World.Map.DistanceAboveTerrain(position).Length); + return position; + } + } + + internal static class ResponsiveMoveForwarder + { + public static void Notify(Activity activity, ResponsiveCancelType? type = null, WPos? preferredLandingPosition = null) + { + if (activity == null) + return; + + foreach (var move in activity.ActivitiesImplementing()) + { + move.NotifyResponsiveCancel(type, preferredLandingPosition); + break; + } + } + } +} diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs b/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs index f1640bee4e86..3efd7c9acd1c 100644 --- a/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs +++ b/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs @@ -392,6 +392,10 @@ public void AttackTarget(in Target target, AttackSource source, bool queued, boo return; var activity = GetAttackActivity(self, source, target, allowMove, forceAttack, targetLineColor); + + if (!queued && source == AttackSource.Default && self.CurrentActivity != null) + ResponsiveMoveForwarder.Notify(self.CurrentActivity, ResponsiveCancelType.LandBeforeNextActivity, target.CenterPosition); + self.QueueActivity(queued, activity); OnResolveAttackOrder(self, activity, target, queued, forceAttack); } diff --git a/OpenRA.Mods.Common/Traits/AttackMove.cs b/OpenRA.Mods.Common/Traits/AttackMove.cs index 8088170e9967..821879af48ea 100644 --- a/OpenRA.Mods.Common/Traits/AttackMove.cs +++ b/OpenRA.Mods.Common/Traits/AttackMove.cs @@ -92,6 +92,13 @@ public void ResolveOrder(Actor self, Order order) var targetLocation = move.NearestMoveableCell(cell); var assaultMoving = order.OrderString == "AssaultMove"; + var autoTarget = self.TraitOrDefault(); + var immediateTarget = !order.Queued && autoTarget != null + ? autoTarget.ScanForTarget(self, false, true, true) + : Target.Invalid; + + if (!order.Queued && immediateTarget.Type != TargetType.Invalid && self.CurrentActivity != null) + ResponsiveMoveForwarder.Notify(self.CurrentActivity, ResponsiveCancelType.LandBeforeNextActivity, immediateTarget.CenterPosition); // TODO: this should scale with unit selection group size. self.QueueActivity(order.Queued, new AttackMoveActivity(self, () => move.MoveTo(targetLocation, 8, targetLineColor: Info.TargetLineColor), assaultMoving)); diff --git a/OpenRA.Mods.Common/Traits/Mobile.cs b/OpenRA.Mods.Common/Traits/Mobile.cs index d43ef4a5ff87..26a8d43d4ecd 100644 --- a/OpenRA.Mods.Common/Traits/Mobile.cs +++ b/OpenRA.Mods.Common/Traits/Mobile.cs @@ -43,6 +43,10 @@ public class MobileInfo : PausableConditionalTraitInfo, IMoveInfo, IPositionable [Desc("If set to true, this unit won't stop to turn, it will turn while moving instead.")] public readonly bool TurnsWhileMoving = false; + [Desc("If set to true, immediate move and stop orders can resolve while the actor is still between cells.", + "Only supported when TurnsWhileMoving is enabled or TurnSpeed is less than 512.")] + public readonly bool ResponsiveBetweenCells = false; + [CursorReference] [Desc("Cursor to display when a move order can be issued at target location.")] public readonly string Cursor = "move"; @@ -104,6 +108,9 @@ IEnumerable IActorPreviewInitInfo.ActorPreviewInits(ActorInfo ai, Act public override void RulesetLoaded(Ruleset rules, ActorInfo ai) { + if (ResponsiveBetweenCells && !(TurnsWhileMoving || TurnSpeed.Angle >= 512)) + throw new YamlException("ResponsiveBetweenCells requires TurnsWhileMoving to be enabled or TurnSpeed to be less than 512."); + var locomotorInfos = rules.Actors[SystemActors.World].TraitInfos() .Where(li => li.Name == Locomotor).ToList(); if (locomotorInfos.Count == 0) From f6b1452937d0b7222c84165a03ae949d9434cfc3 Mon Sep 17 00:00:00 2001 From: Paul Schultz Date: Wed, 13 May 2026 17:46:01 -0500 Subject: [PATCH 2/2] Add IdleFactoryAlert player setting Add IdleFactoryAlert bool to GameSettings (default: true) and bind IDLE_FACTORY_ALERT_CHECKBOX in DisplaySettingsLogic. This allows the CA mod to offer a player-toggleable setting for idle factory alerts. --- OpenRA.Game/Settings.cs | 1 + .../Widgets/Logic/Settings/DisplaySettingsLogic.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/OpenRA.Game/Settings.cs b/OpenRA.Game/Settings.cs index b42890285d30..9734d8254ec0 100644 --- a/OpenRA.Game/Settings.cs +++ b/OpenRA.Game/Settings.cs @@ -297,6 +297,7 @@ public class GameSettings // added for CA public bool SelectionTooltip = true; + public bool IdleFactoryAlert = true; public TextNotificationPoolFilters TextNotificationPoolFilters = TextNotificationPoolFilters.Feedback | TextNotificationPoolFilters.Transients; } diff --git a/OpenRA.Mods.Common/Widgets/Logic/Settings/DisplaySettingsLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Settings/DisplaySettingsLogic.cs index 83a8c73172fb..9815cd18aee4 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Settings/DisplaySettingsLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Settings/DisplaySettingsLogic.cs @@ -145,6 +145,7 @@ Func InitPanel(Widget panel) // added for CA SettingsUtils.BindCheckboxPref(panel, "SELECTIONTOOLTIP_CHECKBOX", gs, "SelectionTooltip"); + SettingsUtils.BindCheckboxPref(panel, "IDLE_FACTORY_ALERT_CHECKBOX", gs, "IdleFactoryAlert"); SettingsUtils.BindCheckboxPref(panel, "CURSORDOUBLE_CHECKBOX", ds, "CursorDouble"); SettingsUtils.BindCheckboxPref(panel, "VSYNC_CHECKBOX", ds, "VSync");