Skip to content

Bot can send TransportShip to itself when tile ownership changes before execution #3865

@GhostArrays

Description

@GhostArrays

Bug

A bot can dispatch a TransportShipExecution targeting a tile it owns, causing a spurious warning and silently cancelling the ship.

How it happens

  1. Bot decides to attack an enemy tile and queues a TransportShipExecution with ref pointing to that tile.
  2. Before init() runs, the bot (or a teammate) conquers that tile — so mg.owner(this.ref) is now the attacker itself.
  3. this.target is set to the attacker → self-targeting.
  4. The guard at line 94 is supposed to catch this:
if (this.target.isPlayer() && !this.attacker.canAttackPlayer(this.target)) {
  this.active = false;
  return;
}
  1. But canAttackPlayer(self) returns true for bots, because:
// canAttackPlayer (bot branch)
return !this.isFriendly(player, treatAFKFriendly);

// isFriendly
return this.isOnSameTeam(other) || this.isAlliedWith(other);

// isOnSameTeam explicitly short-circuits self
if (other === this) return false;

So isFriendly(self)falsecanAttackPlayer(self)true. The guard passes, execution continues, and canBuild(UnitType.TransportShip, dst) fails because the destination is now friendly territory.

Observed output

Player:{name:Kazakhstan,...}] cannot send ship to Player:{name:Kazakhstan,...}], cannot find start tile

Both attacker and target are the same player.

Root cause

isFriendly has no self-check. isOnSameTeam intentionally returns false for other === this, but this causes isFriendly(self) to also return false, which makes a bot believe it can attack itself.

Suggested fix

Add a self-check to isFriendly in src/core/game/PlayerImpl.ts:

isFriendly(other: Player, treatAFKFriendly: boolean = false): boolean {
  if (other === this) return true;
  if (other.isDisconnected() && !treatAFKFriendly) return false;
  return this.isOnSameTeam(other) || this.isAlliedWith(other);
}

Alternatively, add an early exit in TransportShipExecution.init():

if (this.target === this.attacker) {
  this.active = false;
  return;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions