mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-04 11:16:05 +00:00
Merge branch 'main' of github.com:openfrontio/OpenFrontIO
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) / Math.max(1, 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,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 (
|
||||
|
||||
Reference in New Issue
Block a user