mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:20:47 +00:00
Set a targetable status for nukes (#1174)
## Description: Set a targetable status for units (specifically atom bomb and hydro) A nuke is targetable near launch and target but is untargetable mid air. An untargetable unit is half transparent to show that it cannot be destroyed.  ## 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 - [x] I understand that submitting code with bugs that could have been caught through manual testing blocks releases and new features for all contributors ## Please put your Discord username so you can be contacted if a bug or regression is found: Vivacious Box
This commit is contained in:
@@ -535,6 +535,11 @@ export class UnitLayer implements Layer {
|
||||
);
|
||||
|
||||
if (unit.isActive()) {
|
||||
const targetable = unit.targetable();
|
||||
if (!targetable) {
|
||||
this.context.save();
|
||||
this.context.globalAlpha = 0.4;
|
||||
}
|
||||
this.context.drawImage(
|
||||
sprite,
|
||||
Math.round(x - sprite.width / 2),
|
||||
@@ -542,6 +547,9 @@ export class UnitLayer implements Layer {
|
||||
sprite.width,
|
||||
sprite.width,
|
||||
);
|
||||
if (!targetable) {
|
||||
this.context.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ import { ParabolaPathFinder } from "../pathfinding/PathFinding";
|
||||
import { PseudoRandom } from "../PseudoRandom";
|
||||
import { NukeType } from "../StatsSchemas";
|
||||
|
||||
const NUKE_TARGETABLE_RADIUS = 120;
|
||||
|
||||
const SPRITE_RADIUS = 16;
|
||||
|
||||
export class NukeExecution implements Execution {
|
||||
@@ -99,6 +101,7 @@ export class NukeExecution implements Execution {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.src = spawn;
|
||||
this.pathFinder.computeControlPoints(
|
||||
spawn,
|
||||
this.dst,
|
||||
@@ -163,10 +166,31 @@ export class NukeExecution implements Execution {
|
||||
this.detonate();
|
||||
return;
|
||||
} else {
|
||||
this.updateNukeTargetable();
|
||||
this.nuke.move(nextTile);
|
||||
}
|
||||
}
|
||||
|
||||
public getNuke(): Unit | null {
|
||||
return this.nuke;
|
||||
}
|
||||
|
||||
private updateNukeTargetable() {
|
||||
if (this.nuke === null || this.nuke.targetTile() === undefined) {
|
||||
return;
|
||||
}
|
||||
const targetRangeSquared = NUKE_TARGETABLE_RADIUS * NUKE_TARGETABLE_RADIUS;
|
||||
const targetTile = this.nuke.targetTile();
|
||||
this.nuke.setTargetable(
|
||||
this.mg.euclideanDistSquared(this.nuke.tile(), targetTile!) <
|
||||
targetRangeSquared ||
|
||||
(this.src !== undefined &&
|
||||
this.src !== null &&
|
||||
this.mg.euclideanDistSquared(this.src, this.nuke.tile()) <
|
||||
targetRangeSquared),
|
||||
);
|
||||
}
|
||||
|
||||
private detonate() {
|
||||
if (this.nuke === null) {
|
||||
throw new Error("Not initialized");
|
||||
|
||||
@@ -37,18 +37,6 @@ export class SAMLauncherExecution implements Execution {
|
||||
this.mg = mg;
|
||||
}
|
||||
|
||||
private nukeTargetInRange(nuke: Unit) {
|
||||
const targetTile = nuke.targetTile();
|
||||
if (this.sam === null || targetTile === undefined) {
|
||||
return false;
|
||||
}
|
||||
const targetRangeSquared = this.targetRangeRadius * this.targetRangeRadius;
|
||||
return (
|
||||
this.mg.euclideanDistSquared(this.sam.tile(), targetTile) <
|
||||
targetRangeSquared
|
||||
);
|
||||
}
|
||||
|
||||
private getSingleTarget(): Unit | null {
|
||||
if (this.sam === null) return null;
|
||||
const nukes = this.mg
|
||||
@@ -60,7 +48,7 @@ export class SAMLauncherExecution implements Execution {
|
||||
({ unit }) =>
|
||||
unit.owner() !== this.player &&
|
||||
!this.player.isFriendly(unit.owner()) &&
|
||||
this.nukeTargetInRange(unit),
|
||||
unit.isTargetable(),
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -383,6 +383,8 @@ export interface Unit {
|
||||
targetedBySAM(): boolean;
|
||||
setReachedTarget(): void;
|
||||
reachedTarget(): boolean;
|
||||
isTargetable(): boolean;
|
||||
setTargetable(targetable: boolean): void;
|
||||
|
||||
// Health
|
||||
hasHealth(): boolean;
|
||||
|
||||
@@ -76,6 +76,7 @@ export interface UnitUpdate {
|
||||
isActive: boolean;
|
||||
reachedTarget: boolean;
|
||||
retreating: boolean;
|
||||
targetable: boolean;
|
||||
targetUnitId?: number; // Only for trade ships
|
||||
targetTile?: TileRef; // Only for nukes
|
||||
health?: number;
|
||||
|
||||
@@ -72,6 +72,10 @@ export class UnitView {
|
||||
return this.data.id;
|
||||
}
|
||||
|
||||
targetable(): boolean {
|
||||
return this.data.targetable;
|
||||
}
|
||||
|
||||
type(): UnitType {
|
||||
return this.data.unitType;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ export class UnitImpl implements Unit {
|
||||
private _readyMissileCount: number = 1;
|
||||
private _patrolTile: TileRef | undefined;
|
||||
private _level: number = 1;
|
||||
private _targetable: boolean = true;
|
||||
|
||||
constructor(
|
||||
private _type: UnitType,
|
||||
private mg: GameImpl,
|
||||
@@ -63,6 +65,17 @@ export class UnitImpl implements Unit {
|
||||
}
|
||||
}
|
||||
|
||||
setTargetable(targetable: boolean): void {
|
||||
if (this._targetable !== targetable) {
|
||||
this._targetable = targetable;
|
||||
this.mg.addUpdate(this.toUpdate());
|
||||
}
|
||||
}
|
||||
|
||||
isTargetable(): boolean {
|
||||
return this._targetable;
|
||||
}
|
||||
|
||||
setPatrolTile(tile: TileRef): void {
|
||||
this._patrolTile = tile;
|
||||
}
|
||||
@@ -101,6 +114,7 @@ export class UnitImpl implements Unit {
|
||||
reachedTarget: this._reachedTarget,
|
||||
retreating: this._retreating,
|
||||
pos: this._tile,
|
||||
targetable: this._targetable,
|
||||
lastPos: this._lastTile,
|
||||
health: this.hasHealth() ? Number(this._health) : undefined,
|
||||
constructionType: this._constructionType,
|
||||
|
||||
+30
-3
@@ -133,10 +133,37 @@ describe("SAM", () => {
|
||||
expect([sam1, sam2].filter((s) => s.isInCooldown())).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("SAMs should target only nukes aimed at nearby targets", async () => {
|
||||
test("SAMs should target close to launch site", async () => {
|
||||
const targetDistance = 199;
|
||||
// Close SAM: should not intercept anything
|
||||
const sam1 = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 1), {});
|
||||
// Close SAM: should intercept the nuke
|
||||
const sam = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 1), {});
|
||||
game.addExecution(new SAMLauncherExecution(defender, null, sam));
|
||||
|
||||
const nukeExecution = new NukeExecution(
|
||||
UnitType.AtomBomb,
|
||||
attacker,
|
||||
game.ref(targetDistance, 1),
|
||||
null,
|
||||
);
|
||||
game.addExecution(nukeExecution);
|
||||
// Long distance nuke: compute the proper number of ticks
|
||||
const ticksToExecute = Math.ceil(
|
||||
targetDistance / game.config().defaultNukeSpeed(),
|
||||
);
|
||||
executeTicks(game, ticksToExecute);
|
||||
|
||||
expect(nukeExecution.isActive()).toBeFalsy();
|
||||
expect(sam.isInCooldown()).toBeTruthy();
|
||||
});
|
||||
|
||||
test("SAMs should target only nukes aimed at nearby targets if not close to launch site", async () => {
|
||||
const targetDistance = 199;
|
||||
// Middle SAM: should not intercept the nuke
|
||||
const sam1 = defender.buildUnit(
|
||||
UnitType.SAMLauncher,
|
||||
game.ref(targetDistance / 2, 1),
|
||||
{},
|
||||
);
|
||||
game.addExecution(new SAMLauncherExecution(defender, null, sam1));
|
||||
|
||||
// Far SAM: Should intercept the nuke. Use the far_defender so the SAM can be built
|
||||
|
||||
@@ -69,4 +69,27 @@ describe("NukeExecution", () => {
|
||||
expect(sam.touch).toHaveBeenCalled();
|
||||
expect(defensePost.touch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("nuke should only be targetable near src and dst", async () => {
|
||||
const nukeExec = new NukeExecution(
|
||||
UnitType.AtomBomb,
|
||||
player,
|
||||
game.ref(199, 199),
|
||||
game.ref(1, 1),
|
||||
);
|
||||
game.addExecution(nukeExec);
|
||||
// targetable distance is 14400
|
||||
|
||||
//near launch should be targetable (distance src < 14400)
|
||||
executeTicks(game, 2);
|
||||
expect(nukeExec.getNuke()!.isTargetable()).toBeTruthy();
|
||||
|
||||
//mid air should not be targetable (distance src > 14400, distance target > 14400)
|
||||
executeTicks(game, 38);
|
||||
expect(nukeExec.getNuke()!.isTargetable()).toBeFalsy();
|
||||
|
||||
//near target should be targetable (distance target < 14400)
|
||||
executeTicks(game, 10);
|
||||
expect(nukeExec.getNuke()!.isTargetable()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user