mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-23 05:59:38 +00:00
nukes now reduce attacking troops and transport ships (same as rest of pop)
Also made NukeExecution build in two separate steps (build then execute) as for other execution. Otherwise it would instantly explode if I set high speeds. high speeds in necessary for some tests. It implied changing a bit some tests. This includes removing the test that nukes.length == 0 in missile silo which had nothing to do there as we are just checking cooldown. One test should test only one thing. Here it was breaking because I changed the NukeExecution which made no sense.
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
PlayerInfo,
|
||||
TerraNullius,
|
||||
Tick,
|
||||
Unit,
|
||||
UnitInfo,
|
||||
UnitType,
|
||||
} from "../game/Game";
|
||||
@@ -45,6 +46,11 @@ export interface ServerConfig {
|
||||
r2SecretKey(): string;
|
||||
}
|
||||
|
||||
export interface NukeMagnitude {
|
||||
inner: number;
|
||||
outer: number;
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
samHittingChance(): number;
|
||||
spawnImmunityDuration(): Tick;
|
||||
@@ -112,6 +118,9 @@ export interface Config {
|
||||
difficultyModifier(difficulty: Difficulty): number;
|
||||
// 0-1
|
||||
traitorDefenseDebuff(): number;
|
||||
nukeMagnitudes(unitType: UnitType): NukeMagnitude;
|
||||
defaultNukeSpeed(): number;
|
||||
nukeDeathFactor(humans: number, tilesOwned: number): number;
|
||||
}
|
||||
|
||||
export interface Theme {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
TerrainType,
|
||||
TerraNullius,
|
||||
Tick,
|
||||
Unit,
|
||||
UnitInfo,
|
||||
UnitType,
|
||||
} from "../game/Game";
|
||||
@@ -18,7 +19,7 @@ import { PlayerView } from "../game/GameView";
|
||||
import { UserSettings } from "../game/UserSettings";
|
||||
import { GameConfig, GameID } from "../Schemas";
|
||||
import { assertNever, simpleHash, within } from "../Util";
|
||||
import { Config, GameEnv, ServerConfig, Theme } from "./Config";
|
||||
import { Config, GameEnv, NukeMagnitude, ServerConfig, Theme } from "./Config";
|
||||
import { pastelTheme } from "./PastelTheme";
|
||||
import { pastelThemeDark } from "./PastelThemeDark";
|
||||
|
||||
@@ -592,4 +593,24 @@ export class DefaultConfig implements Config {
|
||||
}
|
||||
return adjustment;
|
||||
}
|
||||
|
||||
nukeMagnitudes(unitType: UnitType): NukeMagnitude {
|
||||
switch (unitType) {
|
||||
case UnitType.MIRVWarhead:
|
||||
return { inner: 25, outer: 30 };
|
||||
case UnitType.AtomBomb:
|
||||
return { inner: 12, outer: 30 };
|
||||
case UnitType.HydrogenBomb:
|
||||
return { inner: 80, outer: 100 };
|
||||
}
|
||||
}
|
||||
|
||||
defaultNukeSpeed(): number {
|
||||
return 4;
|
||||
}
|
||||
|
||||
// Humans can be population, soldiers attacking, soldiers in boat etc.
|
||||
nukeDeathFactor(humans: number, tilesOwned: number): number {
|
||||
return (5 * humans) / tilesOwned;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export class NukeExecution implements Execution {
|
||||
private senderID: PlayerID,
|
||||
private dst: TileRef,
|
||||
private src?: TileRef,
|
||||
private speed: number = 4,
|
||||
private speed: number = -1,
|
||||
private waitTicks = 0,
|
||||
) {}
|
||||
|
||||
@@ -41,6 +41,9 @@ export class NukeExecution implements Execution {
|
||||
this.mg = mg;
|
||||
this.player = mg.player(this.senderID);
|
||||
this.random = new PseudoRandom(ticks);
|
||||
if (this.speed == -1) {
|
||||
this.speed = this.mg.config().defaultNukeSpeed();
|
||||
}
|
||||
}
|
||||
|
||||
public target(): Player | TerraNullius {
|
||||
@@ -91,6 +94,7 @@ export class NukeExecution implements Execution {
|
||||
if (silo) {
|
||||
silo.setCooldown(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// make the nuke unactive if it was intercepted
|
||||
@@ -149,19 +153,7 @@ export class NukeExecution implements Execution {
|
||||
}
|
||||
|
||||
private detonate() {
|
||||
let magnitude;
|
||||
switch (this.type) {
|
||||
case UnitType.MIRVWarhead:
|
||||
magnitude = { inner: 25, outer: 30 };
|
||||
break;
|
||||
case UnitType.AtomBomb:
|
||||
magnitude = { inner: 12, outer: 30 };
|
||||
break;
|
||||
case UnitType.HydrogenBomb:
|
||||
magnitude = { inner: 80, outer: 100 };
|
||||
break;
|
||||
}
|
||||
|
||||
const magnitude = this.mg.config().nukeMagnitudes(this.type);
|
||||
const rand = new PseudoRandom(this.mg.ticks());
|
||||
const toDestroy = this.mg.bfs(this.dst, (_, n: TileRef) => {
|
||||
const d = this.mg.euclideanDist(this.dst, n);
|
||||
@@ -174,7 +166,22 @@ export class NukeExecution implements Execution {
|
||||
if (owner.isPlayer()) {
|
||||
const mp = this.mg.player(owner.id());
|
||||
mp.relinquish(tile);
|
||||
mp.removeTroops((5 * mp.population()) / mp.numTilesOwned());
|
||||
mp.removeTroops(
|
||||
this.mg.config().nukeDeathFactor(mp.population(), mp.numTilesOwned()),
|
||||
);
|
||||
mp.outgoingAttacks().forEach((attack) => {
|
||||
const deaths = this.mg
|
||||
.config()
|
||||
.nukeDeathFactor(attack.troops(), mp.numTilesOwned());
|
||||
attack.setTroops(attack.troops() - deaths);
|
||||
});
|
||||
mp.units(UnitType.TransportShip).forEach((attack) => {
|
||||
const deaths = this.mg
|
||||
.config()
|
||||
.nukeDeathFactor(attack.troops(), mp.numTilesOwned());
|
||||
attack.setTroops(attack.troops() - deaths);
|
||||
});
|
||||
|
||||
if (!attacked.has(mp)) {
|
||||
attacked.set(mp, 0);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import {
|
||||
Game,
|
||||
Player,
|
||||
PlayerInfo,
|
||||
PlayerType,
|
||||
UnitType,
|
||||
} from "../src/core/game/Game";
|
||||
import { SpawnExecution } from "../src/core/execution/SpawnExecution";
|
||||
import { setup } from "./util/Setup";
|
||||
import { constructionExecution } from "./util/utils";
|
||||
import { TransportShipExecution } from "../src/core/execution/TransportShipExecution";
|
||||
import { TileRef } from "../src/core/game/GameMap";
|
||||
import { AttackExecution } from "../src/core/execution/AttackExecution";
|
||||
import { TestConfig } from "./util/TestConfig";
|
||||
|
||||
let game: Game;
|
||||
let attacker: Player;
|
||||
let defender: Player;
|
||||
let defenderSpawn: TileRef;
|
||||
let attackerSpawn: TileRef;
|
||||
|
||||
function sendBoat(target: TileRef, troops: number) {
|
||||
game.addExecution(
|
||||
new TransportShipExecution(defender.id(), null, target, troops),
|
||||
);
|
||||
}
|
||||
|
||||
describe("Attack", () => {
|
||||
beforeEach(async () => {
|
||||
game = await setup("ocean_and_land", {
|
||||
infiniteGold: true,
|
||||
instantBuild: true,
|
||||
infiniteTroops: true,
|
||||
});
|
||||
const attackerInfo = new PlayerInfo(
|
||||
"us",
|
||||
"attacker dude",
|
||||
PlayerType.Human,
|
||||
null,
|
||||
"attacker_id",
|
||||
);
|
||||
game.addPlayer(attackerInfo, 1000);
|
||||
const defenderInfo = new PlayerInfo(
|
||||
"us",
|
||||
"defender dude",
|
||||
PlayerType.Human,
|
||||
null,
|
||||
"defender_id",
|
||||
);
|
||||
game.addPlayer(defenderInfo, 1000);
|
||||
|
||||
defenderSpawn = game.ref(0, 15);
|
||||
attackerSpawn = game.ref(0, 10);
|
||||
|
||||
game.addExecution(
|
||||
new SpawnExecution(game.player(attackerInfo.id).info(), attackerSpawn),
|
||||
new SpawnExecution(game.player(defenderInfo.id).info(), defenderSpawn),
|
||||
);
|
||||
|
||||
while (game.inSpawnPhase()) {
|
||||
game.executeNextTick();
|
||||
}
|
||||
|
||||
attacker = game.player(attackerInfo.id);
|
||||
defender = game.player(defenderInfo.id);
|
||||
|
||||
game.addExecution(new AttackExecution(100, defender.id(), null));
|
||||
game.executeNextTick();
|
||||
while (defender.outgoingAttacks().length > 0) {
|
||||
game.executeNextTick();
|
||||
}
|
||||
|
||||
(game.config() as TestConfig).setDefaultNukeSpeed(50);
|
||||
});
|
||||
|
||||
test("Nuke reduce attacking troop counts", async () => {
|
||||
// Not building exactly spawn to it's better protected from attacks (but still
|
||||
// on defender territory)
|
||||
constructionExecution(game, defender.id(), 1, 1, UnitType.MissileSilo);
|
||||
expect(defender.units(UnitType.MissileSilo)).toHaveLength(1);
|
||||
game.addExecution(new AttackExecution(100, attacker.id(), defender.id()));
|
||||
constructionExecution(game, defender.id(), 0, 15, UnitType.AtomBomb, 3);
|
||||
const nuke = defender.units(UnitType.AtomBomb)[0];
|
||||
expect(nuke.isActive()).toBe(true);
|
||||
|
||||
expect(attacker.outgoingAttacks()).toHaveLength(1);
|
||||
expect(attacker.outgoingAttacks()[0].troops()).toBe(98);
|
||||
|
||||
// Make the nuke go kaboom
|
||||
game.executeNextTick();
|
||||
expect(nuke.isActive()).toBe(false);
|
||||
expect(attacker.outgoingAttacks()[0].troops()).not.toBe(97);
|
||||
expect(attacker.outgoingAttacks()[0].troops()).toBeLessThan(90);
|
||||
});
|
||||
|
||||
test("Nuke reduce attacking boat troop count", async () => {
|
||||
constructionExecution(game, defender.id(), 1, 1, UnitType.MissileSilo);
|
||||
expect(defender.units(UnitType.MissileSilo)).toHaveLength(1);
|
||||
|
||||
sendBoat(game.ref(15, 8), 100);
|
||||
|
||||
constructionExecution(game, defender.id(), 0, 15, UnitType.AtomBomb, 3);
|
||||
const nuke = defender.units(UnitType.AtomBomb)[0];
|
||||
expect(nuke.isActive()).toBe(true);
|
||||
|
||||
const ship = defender.units(UnitType.TransportShip)[0];
|
||||
expect(ship.troops()).toBe(100);
|
||||
|
||||
game.executeNextTick();
|
||||
|
||||
expect(nuke.isActive()).toBe(false);
|
||||
expect(defender.units(UnitType.TransportShip)[0].troops()).toBeLessThan(90);
|
||||
});
|
||||
});
|
||||
@@ -56,12 +56,11 @@ describe("MissileSilo", () => {
|
||||
test("missilesilo should launch nuke", async () => {
|
||||
attackerBuildsNuke(null, game.ref(7, 7));
|
||||
expect(attacker.units(UnitType.AtomBomb)).toHaveLength(1);
|
||||
//because of initilization the nuke already moved so it should be at 204 when starting from 101
|
||||
expect(attacker.units(UnitType.AtomBomb)[0].tile()).toBe(
|
||||
game.map().ref(5, 1),
|
||||
expect(attacker.units(UnitType.AtomBomb)[0].tile()).not.toBe(
|
||||
game.map().ref(7, 7),
|
||||
);
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
game.executeNextTick();
|
||||
}
|
||||
expect(attacker.units(UnitType.AtomBomb)).toHaveLength(0);
|
||||
@@ -79,12 +78,7 @@ describe("MissileSilo", () => {
|
||||
attackerBuildsNuke(null, game.ref(50, 50));
|
||||
expect(attacker.units(UnitType.AtomBomb)).toHaveLength(1);
|
||||
|
||||
for (let i = 0; i < 24; i++) {
|
||||
game.executeNextTick();
|
||||
}
|
||||
expect(attacker.units(UnitType.AtomBomb)).toHaveLength(0);
|
||||
|
||||
for (let i = 0; i < game.config().SiloCooldown() - 25; i++) {
|
||||
for (let i = 0; i < game.config().SiloCooldown() - 1; i++) {
|
||||
game.executeNextTick();
|
||||
expect(attacker.units(UnitType.MissileSilo)[0].isCooldown()).toBeTruthy();
|
||||
}
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 120 B |
@@ -1,7 +1,17 @@
|
||||
import { NukeMagnitude } from "../../src/core/configuration/Config";
|
||||
import { DefaultConfig } from "../../src/core/configuration/DefaultConfig";
|
||||
import {
|
||||
Game,
|
||||
Player,
|
||||
TerraNullius,
|
||||
Tick,
|
||||
UnitType,
|
||||
} from "../../src/core/game/Game";
|
||||
import { TileRef } from "../../src/core/game/GameMap";
|
||||
|
||||
export class TestConfig extends DefaultConfig {
|
||||
_proximityBonusPortsNb: number = 0;
|
||||
private _proximityBonusPortsNb: number = 0;
|
||||
private _defaultNukeSpeed: number = 4;
|
||||
|
||||
samHittingChance(): number {
|
||||
return 1;
|
||||
@@ -19,4 +29,43 @@ export class TestConfig extends DefaultConfig {
|
||||
setProximityBonusPortsNb(nb: number): void {
|
||||
this._proximityBonusPortsNb = nb;
|
||||
}
|
||||
|
||||
nukeMagnitudes(_: UnitType): NukeMagnitude {
|
||||
return { inner: 1, outer: 1 };
|
||||
}
|
||||
|
||||
setDefaultNukeSpeed(speed: number): void {
|
||||
this._defaultNukeSpeed = speed;
|
||||
}
|
||||
|
||||
defaultNukeSpeed(): number {
|
||||
return this._defaultNukeSpeed;
|
||||
}
|
||||
|
||||
spawnImmunityDuration(): Tick {
|
||||
return 0;
|
||||
}
|
||||
|
||||
attackLogic(
|
||||
gm: Game,
|
||||
attackTroops: number,
|
||||
attacker: Player,
|
||||
defender: Player | TerraNullius,
|
||||
tileToConquer: TileRef,
|
||||
): {
|
||||
attackerTroopLoss: number;
|
||||
defenderTroopLoss: number;
|
||||
tilesPerTickUsed: number;
|
||||
} {
|
||||
return { attackerTroopLoss: 1, defenderTroopLoss: 1, tilesPerTickUsed: 1 };
|
||||
}
|
||||
|
||||
attackTilesPerTick(
|
||||
attackTroops: number,
|
||||
attacker: Player,
|
||||
defender: Player | TerraNullius,
|
||||
numAdjacentTilesWithEnemy: number,
|
||||
): number {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
+11
-7
@@ -13,14 +13,18 @@ export function constructionExecution(
|
||||
x: number,
|
||||
y: number,
|
||||
unit: UnitType,
|
||||
ticks = 4,
|
||||
) {
|
||||
game.addExecution(new ConstructionExecution(playerID, game.ref(x, y), unit));
|
||||
// Init exec
|
||||
game.executeNextTick();
|
||||
|
||||
// 4 ticks by default as it usually goes like this
|
||||
// Init of construction execution
|
||||
// Exec construction execution
|
||||
game.executeNextTick();
|
||||
// Add the execution related to the building
|
||||
game.executeNextTick();
|
||||
// First tick of the execution of the constructed structure/unit
|
||||
game.executeNextTick();
|
||||
// Tick of construction execution which adds the execution related to the building/unit
|
||||
// First tick of the execution of the constructed building/unit
|
||||
// (sometimes step 3 and 4 are merged in one)
|
||||
|
||||
for (let i = 0; i < ticks; i++) {
|
||||
game.executeNextTick();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user