Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LayMines: fixed occasional incorrect mine position when using BeginMinefield order #20968

Merged
merged 1 commit into from
Jul 26, 2023

Conversation

michaeldgg2
Copy link
Contributor

@michaeldgg2 michaeldgg2 commented Jul 19, 2023

When laying mine with PreLayDelay > 0, end activity's tick immediately. That means don't try to immediately move to next cell.

This change unifies the behavior with scenario when a mine is laid without any PreLayDelay.

@PunkPun
Copy link
Member

PunkPun commented Jul 25, 2023

Sorry, I don't understand the fix. It has nothing to do with PreLayDelay

@PunkPun
Copy link
Member

PunkPun commented Jul 25, 2023

Please also add a comment in-code explaining the fix

@michaeldgg2
Copy link
Contributor Author

Sorry, I don't understand the fix. It has nothing to do with PreLayDelay

It does. There are two scenarios:

  • PreLayDelay == 0
  • PreLayDelay > 0

In the first one, the mine is laid immediately (on line 107):

if (minelayer.Info.PreLayDelay == 0)
{
    if (LayMine(self) && minelayer.Info.AfterLayingDelay > 0)
        QueueChild(new Wait(minelayer.Info.AfterLayingDelay));
}

In the second one, the mine laying is delayed by queuing Wait activity:

else
{
    layingMine = true;
    QueueChild(new Wait(minelayer.Info.PreLayDelay));
}

These both scenarios end the tick by returning false:

if (minelayer.Info.PreLayDelay == 0)
{
    //
}
else
{
    //
}

return false;

(Thus the code for picking next cell (after the main if in Tick) is not run in this tick, but in the next one.)

Now when the delay (in the second scenario) finishes, the mine is laid. In bleed version, the cause of the bug is that the tick is not ended, when the mine is laid:

if (layingMine)
{
    layingMine = false;
    if (LayMine(self))
    {
        if (minelayer.Info.AfterLayingDelay > 0)
        {
            QueueChild(new Wait(minelayer.Info.AfterLayingDelay));
            return false;
        }
    }
}

By "laying a mine" I mean the stuff that LayMine does - i.e. it schedules FrameEndTask, when the actual mine actor is created. And here lies the problem: after the task is scheduled, the execution drops back to Tick, where it picks next cell and queues Move activity:

var nextCell = NextValidCell(self);
if (nextCell != null)
{
    QueueChild(movement.MoveTo(nextCell.Value, 0));
    return false;
}

Move immediately (i.e. within the same tick) sets minelayer's location to the next cell.

if (!mobile.Info.TurnsWhileMoving && firstFacing != mobile.Facing)
{
    path.Add(nextCell.Value.Cell);
    QueueChild(new Turn(self, firstFacing));
    mobile.TurnToMove = true;
    return false;
}

mobile.SetLocation(mobile.FromCell, mobile.FromSubCell, nextCell.Value.Cell, nextCell.Value.SubCell);

As a result the FrameEndTask, which creates mine actor, works with incorrect location of the minelayer:

self.World.AddFrameEndTask(w =>
{
    var mine = w.CreateActor(minelayer.Info.Mine, new TypeDictionary
    {
        new LocationInit(self.Location),
        new OwnerInit(self.Owner),
    });
    // ...
});

Since Move activity changes actor's location only if the facing to the next cell in path does not match actor's current facing, this bug (with mine's incorrect location) happens only if the minelayer doesn't have to turn to move to the next cell.

Another possible fix could be to store current minelayer's location (to a temporary variable that will be captured for the lambda) before scheduling FrameEndTask (and then use it to create the mine actor), but this feels like a workaround. I believe the LayMine's tick should end when a mine is laid. Even the original code before all my changes does this:

https://github.com/OpenRA/OpenRA/blob/bd2b3d97938e8507e29b0bb1a24e53c430b2a7b9/OpenRA.Mods.Cnc/Activities/LayMines.cs#L87C1-L90C18

LayMine(self);
QueueChild(new Wait(20)); // A little wait after placing each mine, for show
minefield.Remove(self.Location);
return false;

@PunkPun
Copy link
Member

PunkPun commented Jul 25, 2023

Tbh the situation feels highly improbable. How can move activity change location without even running? frameEndActions should be executed at the end of the tick, meaning that this actor's activities have no chance to run a second time before the mine is placed.

@PunkPun
Copy link
Member

PunkPun commented Jul 25, 2023

Move activity only sets mobile.ToCell in Tick function

@PunkPun
Copy link
Member

PunkPun commented Jul 25, 2023

I see, Activity has a way to immediately tick child activity

// Avoid a single tick delay if the childactivity was just queued.

In that case I'd prefer this fix

Another possible fix could be to store current minelayer's location (to a temporary variable that will be captured for the lambda) before scheduling FrameEndTask (and then use it to create the mine actor), but this feels like a workaround.

@michaeldgg2
Copy link
Contributor Author

Are you sure? To me it seems something like "corrupted state": when FrameEndTask for creating mine (in LayMine()) runs, it has to deal simultaneously with the previous state (i.e. tick where the mine laying was scheduled) and current state (where it should create the mine actor while the minelayer is already moving to the next cell).

@PunkPun
Copy link
Member

PunkPun commented Jul 25, 2023

You could create the actor on Tick, but only add it to world on FrameEndTask. As you might have noticed .CreateActor has an override specifically for this reason.

Though giving it more thought, the current solution is fine. An improvement on it would be adding a check on FrameEndTask to see if mine still can be placed

@michaeldgg2 michaeldgg2 force-pushed the MinelayingLocationFix branch 2 times, most recently from 91ddaf3 to e720e4a Compare July 26, 2023 15:56
…nefield order

When laying mine with PreLayDelay > 0, end activity's tick immediately. That means don't try to immediately move to next cell.

This change unifies the behavior with scenario when a mine is laid without any PreLayDelay.
@PunkPun PunkPun merged commit 66cf912 into OpenRA:bleed Jul 26, 2023
3 checks passed
@PunkPun
Copy link
Member

PunkPun commented Jul 26, 2023

Changelog

@michaeldgg2 michaeldgg2 deleted the MinelayingLocationFix branch September 20, 2023 18:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants