mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-18 09:14:09 +00:00
AFK team mate v2: better ship handling + tests + bugfix (#2396)
## Description: See PR https://github.com/openfrontio/OpenFrontIO/pull/2203 It was reverted. This unreverts it, with an added fix for boat troops not getting returned to owner. And small comment updates. And a const for boatOwner to re-use. The added bugfix is check for this.sourceTile === null in the retreat() function in AttackExecution. A boat attack always sets removeTroops to false because the troops were already removed from owner troops when the boat departed. They don't have to be removed again in AttackExecution init, when the boat lands and the attack starts. But at the end of the attack, in retreat() in AttackExecution, the starting/boat troops still need to be returned to the owner. That's why even if removeTroops is false, when sourceTile is not null (only when it's a boat attack) we add back the troops to the owner. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: tryout33 --------- Co-authored-by: Fx Morin <28154542+FxMorin@users.noreply.github.com> Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com>
This commit is contained in:
co-authored by
Fx Morin
Ryan
parent
9130142d15
commit
9c24d29824
@@ -692,7 +692,7 @@ export class DefaultConfig implements Config {
|
||||
|
||||
if (attacker.isPlayer() && defender.isPlayer()) {
|
||||
if (defender.isDisconnected() && attacker.isOnSameTeam(defender)) {
|
||||
// No troop loss if defender is disconnected.
|
||||
// No troop loss if defender is disconnected and on same team
|
||||
mag = 0;
|
||||
}
|
||||
if (
|
||||
|
||||
@@ -181,6 +181,13 @@ export class AttackExecution implements Execution {
|
||||
this._owner.id(),
|
||||
);
|
||||
}
|
||||
if (this.removeTroops === false && this.sourceTile === null) {
|
||||
// startTroops are always added to attack troops at init but not always removed from owner troops
|
||||
// subtract startTroops from attack troops so we don't give back startTroops to owner that were never removed
|
||||
// boat attacks (sourceTile !== null) are the exception: troops were removed at departure and must be returned after attack still
|
||||
this.attack.setTroops(this.attack.troops() - (this.startTroops ?? 0));
|
||||
}
|
||||
|
||||
const survivors = this.attack.troops() - deaths;
|
||||
this._owner.addTroops(survivors);
|
||||
this.attack.delete();
|
||||
|
||||
@@ -33,13 +33,17 @@ export class TransportShipExecution implements Execution {
|
||||
|
||||
private pathFinder: PathFinder;
|
||||
|
||||
private originalOwner: Player;
|
||||
|
||||
constructor(
|
||||
private attacker: Player,
|
||||
private targetID: PlayerID | null,
|
||||
private ref: TileRef,
|
||||
private startTroops: number,
|
||||
private src: TileRef | null,
|
||||
) {}
|
||||
) {
|
||||
this.originalOwner = this.attacker;
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
@@ -173,11 +177,44 @@ export class TransportShipExecution implements Execution {
|
||||
}
|
||||
this.lastMove = ticks;
|
||||
|
||||
if (this.boat.retreating()) {
|
||||
this.dst = this.src!; // src is guaranteed to be set at this point
|
||||
// Team mate can conquer disconnected player and get their ships
|
||||
// captureUnit has changed the owner of the unit, now update attacker
|
||||
const boatOwner = this.boat.owner();
|
||||
if (
|
||||
this.originalOwner.isDisconnected() &&
|
||||
boatOwner !== this.originalOwner &&
|
||||
boatOwner.isOnSameTeam(this.originalOwner)
|
||||
) {
|
||||
this.attacker = boatOwner;
|
||||
this.originalOwner = boatOwner; // for when this owner disconnects too
|
||||
}
|
||||
|
||||
if (this.boat.targetTile() !== this.dst) {
|
||||
this.boat.setTargetTile(this.dst);
|
||||
if (this.boat.retreating()) {
|
||||
// Ensure retreat source is still valid for (new) owner
|
||||
if (this.mg.owner(this.src!) !== this.attacker) {
|
||||
// Use bestTransportShipSpawn, not canBuild because of its max boats check etc
|
||||
const newSrc = this.attacker.bestTransportShipSpawn(this.dst);
|
||||
if (newSrc === false) {
|
||||
this.src = null;
|
||||
} else {
|
||||
this.src = newSrc;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.src === null) {
|
||||
console.warn(
|
||||
`TransportShipExecution: retreating but no src found for new attacker`,
|
||||
);
|
||||
this.attacker.addTroops(this.boat.troops());
|
||||
this.boat.delete(false);
|
||||
this.active = false;
|
||||
return;
|
||||
} else {
|
||||
this.dst = this.src;
|
||||
|
||||
if (this.boat.targetTile() !== this.dst) {
|
||||
this.boat.setTargetTile(this.dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,10 +55,6 @@ export class WarshipExecution implements Execution {
|
||||
this.warship.delete();
|
||||
return;
|
||||
}
|
||||
if (this.warship.owner().isDisconnected()) {
|
||||
this.warship.delete();
|
||||
return;
|
||||
}
|
||||
|
||||
const hasPort = this.warship.owner().unitCount(UnitType.Port) > 0;
|
||||
if (hasPort) {
|
||||
@@ -93,7 +89,7 @@ export class WarshipExecution implements Execution {
|
||||
if (
|
||||
unit.owner() === this.warship.owner() ||
|
||||
unit === this.warship ||
|
||||
unit.owner().isFriendly(this.warship.owner()) ||
|
||||
unit.owner().isFriendly(this.warship.owner(), true) ||
|
||||
this.alreadySentShell.has(unit)
|
||||
) {
|
||||
continue;
|
||||
|
||||
@@ -595,7 +595,7 @@ export interface Player {
|
||||
decayRelations(): void;
|
||||
isOnSameTeam(other: Player): boolean;
|
||||
// Either allied or on same team.
|
||||
isFriendly(other: Player): boolean;
|
||||
isFriendly(other: Player, treatAFKFriendly?: boolean): boolean;
|
||||
team(): Team | null;
|
||||
clan(): string | null;
|
||||
incomingAllianceRequests(): AllianceRequest[];
|
||||
|
||||
@@ -895,6 +895,20 @@ export class GameImpl implements Game {
|
||||
return this._railNetwork;
|
||||
}
|
||||
conquerPlayer(conqueror: Player, conquered: Player) {
|
||||
if (conquered.isDisconnected() && conqueror.isOnSameTeam(conquered)) {
|
||||
const ships = conquered
|
||||
.units()
|
||||
.filter(
|
||||
(u) =>
|
||||
u.type() === UnitType.Warship ||
|
||||
u.type() === UnitType.TransportShip,
|
||||
);
|
||||
|
||||
for (const ship of ships) {
|
||||
conqueror.captureUnit(ship);
|
||||
}
|
||||
}
|
||||
|
||||
const gold = conquered.gold();
|
||||
this.displayMessage(
|
||||
`Conquered ${conquered.displayName()} received ${renderNumber(
|
||||
|
||||
@@ -789,8 +789,8 @@ export class PlayerImpl implements Player {
|
||||
return this._team === other.team();
|
||||
}
|
||||
|
||||
isFriendly(other: Player): boolean {
|
||||
if (other.isDisconnected()) {
|
||||
isFriendly(other: Player, treatAFKFriendly: boolean = false): boolean {
|
||||
if (other.isDisconnected() && !treatAFKFriendly) {
|
||||
return false;
|
||||
}
|
||||
return this.isOnSameTeam(other) || this.isAlliedWith(other);
|
||||
|
||||
@@ -148,6 +148,8 @@ export function bestShoreDeploymentSource(
|
||||
if (t === null) return false;
|
||||
|
||||
const candidates = candidateShoreTiles(gm, player, t);
|
||||
if (candidates.length === 0) return false;
|
||||
|
||||
const aStar = new MiniAStar(gm, gm.miniMap(), candidates, t, 1_000_000, 1);
|
||||
const result = aStar.compute();
|
||||
if (result !== PathFindResultType.Completed) {
|
||||
|
||||
Reference in New Issue
Block a user