Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions OpenRA.Game/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
3 changes: 3 additions & 0 deletions OpenRA.Mods.Common/Activities/Attack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
3 changes: 3 additions & 0 deletions OpenRA.Mods.Common/Activities/Enter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions OpenRA.Mods.Common/Activities/LayMines.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,13 @@ public override IEnumerable<TargetLineNode> TargetLineNodes(Actor self)
yield return new TargetLineNode(Target.FromCell(self.World, c), minelayer.Info.TargetLineColor, tile: minelayer.Tile);
}

static bool CanLayMine(Actor self, CPos p)
static bool CanLayMine(Actor self, CPos location)
{
if (self.IsDead || !self.IsInWorld)
return false;

// If there is no unit (other than me) here, we can place a mine here
return self.World.ActorMap.GetActorsAt(p).All(a => a == self);
return self.World.ActorMap.GetActorsAt(location).All(a => a == self);
}

bool StartLayingMine(Actor self)
Expand Down
22 changes: 19 additions & 3 deletions OpenRA.Mods.Common/Activities/Move/AttackMoveActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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.
Expand Down
80 changes: 77 additions & 3 deletions OpenRA.Mods.Common/Activities/Move/Move.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class Move : Activity

int carryoverProgress;
int lastMovePartCompletedTick;
readonly ResponsiveMoveSupport responsiveMove;

bool alreadyAtDestination;
List<CPos> path;
Expand All @@ -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 =>
{
Expand All @@ -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 =>
{
Expand All @@ -104,6 +107,7 @@ public Move(Actor self, Func<BlockedByActor, (bool AlreadyAtDestination, List<CP
{
// 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);

this.getPath = getPath;

Expand All @@ -119,10 +123,63 @@ public Move(Actor self, Func<BlockedByActor, (bool AlreadyAtDestination, List<CP
return (alreadyAtDestination, path);
}

bool TryResolveResponsiveCancel(Actor self)
{
if (!responsiveMove.TryResolveCancel(self, IsCanceling, NextActivity != null, out var resolution))
return false;

ApplyResponsiveCancelResolution(self, resolution);
return true;
}

void ApplyResponsiveCancelResolution(Actor self, in ResponsiveCancelResolution resolution)
{
path?.Clear();
hadNoPath = false;
hasWaited = false;
carryoverProgress = 0;
destination = resolution.Landing.Cell;

// Reserve the resolved endpoint without snapping CenterPosition.
mobile.SetLocation(resolution.Landing.Cell, resolution.Landing.SubCell, resolution.Landing.Cell, resolution.Landing.SubCell);
if (!resolution.HasReplacementActivity)
QueueResponsiveLandingDrag(self, resolution.Landing);

mobile.MoveResult = MoveResult.CompleteCanceled;
}

bool TryQueueResponsiveSettle(Actor self)
{
if (!responsiveMove.TryGetSettleLanding(self, out var landing))
return false;

return QueueResponsiveLandingDrag(self, landing);
}

bool QueueResponsiveLandingDrag(Actor self, in ResponsiveLanding landing)
{
var currentPosition = mobile.CenterPosition;
var delta = landing.Position - currentPosition;
if (delta.LengthSquared == 0)
return false;

var speed = mobile.MovementSpeedForCell(landing.Cell);
var length = speed > 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)
{
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 4 additions & 1 deletion OpenRA.Mods.Common/Activities/Move/MoveWithinRange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading