Merge branch 'main' of github.com:openfrontio/OpenFrontIO

This commit is contained in:
Evan
2025-03-31 12:46:36 -07:00
8 changed files with 277 additions and 57 deletions
+9
View File
@@ -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 {
+22 -1
View File
@@ -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) / Math.max(1, tilesOwned);
}
}
+67 -38
View File
@@ -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,12 +41,55 @@ 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 {
return this.mg.owner(this.dst);
}
private tilesToDestroy(): Set<TileRef> {
const magnitude = this.mg.config().nukeMagnitudes(this.nuke.type());
const rand = new PseudoRandom(this.mg.ticks());
return this.mg.bfs(this.dst, (_, n: TileRef) => {
const d = this.mg.euclideanDist(this.dst, n);
return (d <= magnitude.inner || rand.chance(2)) && d <= magnitude.outer;
});
}
private getAttackedTiles() {
const toDestroy = this.tilesToDestroy();
const attacked = new Map<Player, number>();
for (const tile of toDestroy) {
const owner = this.mg.owner(tile);
if (owner.isPlayer()) {
const mp = this.mg.player(owner.id());
const prev = attacked.get(mp) ?? 0;
attacked.set(mp, prev + 1);
}
}
return attacked;
}
private breakAlliances() {
for (const [other, tilesDestroyed] of this.getAttackedTiles()) {
if (tilesDestroyed > 100 && this.nuke.type() != UnitType.MIRVWarhead) {
// Mirv warheads shouldn't break alliances
const alliance = this.player.allianceWith(other);
if (alliance != null) {
this.player.breakAlliance(alliance);
}
if (other != this.player) {
other.updateRelation(this.player, -100);
}
}
}
}
tick(ticks: number): void {
if (this.nuke == null) {
const spawn = this.src ?? this.player.canBuild(this.type, this.dst);
@@ -91,6 +134,7 @@ export class NukeExecution implements Execution {
if (silo) {
silo.setCooldown(true);
}
return;
}
// make the nuke unactive if it was intercepted
@@ -100,6 +144,8 @@ export class NukeExecution implements Execution {
return;
}
this.breakAlliances();
if (this.waitTicks > 0) {
this.waitTicks--;
return;
@@ -149,55 +195,38 @@ 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.nuke.type());
const toDestroy = this.tilesToDestroy();
const rand = new PseudoRandom(this.mg.ticks());
const toDestroy = this.mg.bfs(this.dst, (_, n: TileRef) => {
const d = this.mg.euclideanDist(this.dst, n);
return (d <= magnitude.inner || rand.chance(2)) && d <= magnitude.outer;
});
const attacked = new Map<Player, number>();
for (const tile of toDestroy) {
const owner = this.mg.owner(tile);
if (owner.isPlayer()) {
const mp = this.mg.player(owner.id());
mp.relinquish(tile);
mp.removeTroops((5 * mp.population()) / mp.numTilesOwned());
if (!attacked.has(mp)) {
attacked.set(mp, 0);
}
const prev = attacked.get(mp);
attacked.set(mp, prev + 1);
mp.removeTroops(
this.mg.config().nukeDeathFactor(mp.troops(), mp.numTilesOwned()),
);
mp.removeWorkers(
this.mg.config().nukeDeathFactor(mp.workers(), 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 (this.mg.isLand(tile)) {
this.mg.setFallout(tile, true);
}
}
for (const [other, tilesDestroyed] of attacked) {
if (tilesDestroyed > 100 && this.nuke.type() != UnitType.MIRVWarhead) {
// Mirv warheads shouldn't break alliances
const alliance = this.player.allianceWith(other);
if (alliance != null) {
this.player.breakAlliance(alliance);
}
if (other != this.player) {
other.updateRelation(this.player, -100);
}
}
}
for (const unit of this.mg.units()) {
if (