mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-29 03:44:40 +00:00
Remove workers & troop ratio bar, only have troops (#1676)
## Description: The troop/worker ratio bar is almost never changed. so remove it and the entire concept of workers. Now there is just troops. Now players get a consistent 1k/s gold. ## 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 have read and accepted the CLA agreement (only required once). ## Please put your Discord username so you can be contacted if a bug or regression is found: evan
This commit is contained in:
@@ -35,7 +35,6 @@ export type Intent =
|
||||
| EmojiIntent
|
||||
| DonateGoldIntent
|
||||
| DonateTroopsIntent
|
||||
| TargetTroopRatioIntent
|
||||
| BuildUnitIntent
|
||||
| EmbargoIntent
|
||||
| QuickChatIntent
|
||||
@@ -59,9 +58,6 @@ export type EmojiIntent = z.infer<typeof EmojiIntentSchema>;
|
||||
export type DonateGoldIntent = z.infer<typeof DonateGoldIntentSchema>;
|
||||
export type DonateTroopsIntent = z.infer<typeof DonateTroopIntentSchema>;
|
||||
export type EmbargoIntent = z.infer<typeof EmbargoIntentSchema>;
|
||||
export type TargetTroopRatioIntent = z.infer<
|
||||
typeof TargetTroopRatioIntentSchema
|
||||
>;
|
||||
export type BuildUnitIntent = z.infer<typeof BuildUnitIntentSchema>;
|
||||
export type UpgradeStructureIntent = z.infer<
|
||||
typeof UpgradeStructureIntentSchema
|
||||
@@ -314,11 +310,6 @@ export const DonateTroopIntentSchema = BaseIntentSchema.extend({
|
||||
troops: z.number().nullable(),
|
||||
});
|
||||
|
||||
export const TargetTroopRatioIntentSchema = BaseIntentSchema.extend({
|
||||
type: z.literal("troop_ratio"),
|
||||
ratio: z.number().min(0).max(1),
|
||||
});
|
||||
|
||||
export const BuildUnitIntentSchema = BaseIntentSchema.extend({
|
||||
type: z.literal("build_unit"),
|
||||
unit: z.enum(UnitType),
|
||||
@@ -378,7 +369,6 @@ const IntentSchema = z.discriminatedUnion("type", [
|
||||
EmojiIntentSchema,
|
||||
DonateGoldIntentSchema,
|
||||
DonateTroopIntentSchema,
|
||||
TargetTroopRatioIntentSchema,
|
||||
BuildUnitIntentSchema,
|
||||
UpgradeStructureIntentSchema,
|
||||
EmbargoIntentSchema,
|
||||
|
||||
@@ -89,9 +89,8 @@ export interface Config {
|
||||
playerTeams(): TeamCountConfig;
|
||||
|
||||
startManpower(playerInfo: PlayerInfo): number;
|
||||
populationIncreaseRate(player: Player | PlayerView): number;
|
||||
troopIncreaseRate(player: Player | PlayerView): number;
|
||||
goldAdditionRate(player: Player | PlayerView): Gold;
|
||||
troopAdjustmentRate(player: Player): number;
|
||||
attackTilesPerTick(
|
||||
attckTroops: number,
|
||||
attacker: Player,
|
||||
@@ -114,8 +113,8 @@ export interface Config {
|
||||
// When computing likelihood of trading for any given port, the X closest port
|
||||
// are twice more likely to be selected. X is determined below.
|
||||
proximityBonusPortsNb(totalPorts: number): number;
|
||||
maxPopulation(player: Player | PlayerView): number;
|
||||
cityPopulationIncrease(): number;
|
||||
maxTroops(player: Player | PlayerView): number;
|
||||
cityTroopIncrease(): number;
|
||||
boatAttackAmount(attacker: Player, defender: Player | TerraNullius): number;
|
||||
shellLifetime(): number;
|
||||
boatMaxNumber(): number;
|
||||
@@ -162,7 +161,7 @@ export interface Config {
|
||||
nukeType: NukeType,
|
||||
humans: number,
|
||||
tilesOwned: number,
|
||||
maxPop: number,
|
||||
maxTroops: number,
|
||||
): number;
|
||||
structureMinDist(): number;
|
||||
isReplay(): boolean;
|
||||
|
||||
@@ -273,7 +273,7 @@ export class DefaultConfig implements Config {
|
||||
}
|
||||
}
|
||||
|
||||
cityPopulationIncrease(): number {
|
||||
cityTroopIncrease(): number {
|
||||
return 250_000;
|
||||
}
|
||||
|
||||
@@ -714,8 +714,8 @@ export class DefaultConfig implements Config {
|
||||
return this.infiniteTroops() ? 1_000_000 : 25_000;
|
||||
}
|
||||
|
||||
maxPopulation(player: Player | PlayerView): number {
|
||||
const maxPop =
|
||||
maxTroops(player: Player | PlayerView): number {
|
||||
const maxTroops =
|
||||
player.type() === PlayerType.Human && this.infiniteTroops()
|
||||
? 1_000_000_000
|
||||
: 2 * (Math.pow(player.numTilesOwned(), 0.6) * 1000 + 50000) +
|
||||
@@ -723,34 +723,34 @@ export class DefaultConfig implements Config {
|
||||
.units(UnitType.City)
|
||||
.map((city) => city.level())
|
||||
.reduce((a, b) => a + b, 0) *
|
||||
this.cityPopulationIncrease();
|
||||
this.cityTroopIncrease();
|
||||
|
||||
if (player.type() === PlayerType.Bot) {
|
||||
return maxPop / 2;
|
||||
return maxTroops / 2;
|
||||
}
|
||||
|
||||
if (player.type() === PlayerType.Human) {
|
||||
return maxPop;
|
||||
return maxTroops;
|
||||
}
|
||||
|
||||
switch (this._gameConfig.difficulty) {
|
||||
case Difficulty.Easy:
|
||||
return maxPop * 0.5;
|
||||
return maxTroops * 0.5;
|
||||
case Difficulty.Medium:
|
||||
return maxPop * 1;
|
||||
return maxTroops * 1;
|
||||
case Difficulty.Hard:
|
||||
return maxPop * 1.5;
|
||||
return maxTroops * 1.5;
|
||||
case Difficulty.Impossible:
|
||||
return maxPop * 2;
|
||||
return maxTroops * 2;
|
||||
}
|
||||
}
|
||||
|
||||
populationIncreaseRate(player: Player): number {
|
||||
const max = this.maxPopulation(player);
|
||||
troopIncreaseRate(player: Player): number {
|
||||
const max = this.maxTroops(player);
|
||||
|
||||
let toAdd = 10 + Math.pow(player.population(), 0.73) / 4;
|
||||
let toAdd = 10 + Math.pow(player.troops(), 0.73) / 4;
|
||||
|
||||
const ratio = 1 - player.population() / max;
|
||||
const ratio = 1 - player.troops() / max;
|
||||
toAdd *= ratio;
|
||||
|
||||
if (player.type() === PlayerType.Bot) {
|
||||
@@ -774,26 +774,11 @@ export class DefaultConfig implements Config {
|
||||
}
|
||||
}
|
||||
|
||||
return Math.min(player.population() + toAdd, max) - player.population();
|
||||
return Math.min(player.troops() + toAdd, max) - player.troops();
|
||||
}
|
||||
|
||||
goldAdditionRate(player: Player): Gold {
|
||||
return BigInt(Math.floor(0.045 * player.workers() ** 0.7));
|
||||
}
|
||||
|
||||
troopAdjustmentRate(player: Player): number {
|
||||
const maxDiff = this.maxPopulation(player) / 1000;
|
||||
const target = player.population() * player.targetTroopRatio();
|
||||
const diff = target - player.troops();
|
||||
if (Math.abs(diff) < maxDiff) {
|
||||
return diff;
|
||||
}
|
||||
const adjustment = maxDiff * Math.sign(diff);
|
||||
// Can ramp down troops much faster
|
||||
if (adjustment < 0) {
|
||||
return adjustment * 5;
|
||||
}
|
||||
return adjustment;
|
||||
return 100n;
|
||||
}
|
||||
|
||||
nukeMagnitudes(unitType: UnitType): NukeMagnitude {
|
||||
@@ -824,22 +809,22 @@ export class DefaultConfig implements Config {
|
||||
return 80;
|
||||
}
|
||||
|
||||
// Humans can be population, soldiers attacking, soldiers in boat etc.
|
||||
// Humans can be soldiers, soldiers attacking, soldiers in boat etc.
|
||||
nukeDeathFactor(
|
||||
nukeType: NukeType,
|
||||
humans: number,
|
||||
tilesOwned: number,
|
||||
maxPop: number,
|
||||
maxTroops: number,
|
||||
): number {
|
||||
if (nukeType !== UnitType.MIRVWarhead) {
|
||||
return (5 * humans) / Math.max(1, tilesOwned);
|
||||
}
|
||||
const targetPop = 0.03 * maxPop;
|
||||
const excessPop = Math.max(0, humans - targetPop);
|
||||
const targetTroops = 0.03 * maxTroops;
|
||||
const excessTroops = Math.max(0, humans - targetTroops);
|
||||
const scalingFactor = 500;
|
||||
|
||||
const steepness = 2;
|
||||
const normalizedExcess = excessPop / maxPop;
|
||||
const normalizedExcess = excessTroops / maxTroops;
|
||||
return scalingFactor * (1 - Math.exp(-steepness * normalizedExcess));
|
||||
}
|
||||
|
||||
|
||||
@@ -74,10 +74,6 @@ export class DevConfig extends DefaultConfig {
|
||||
// return 1
|
||||
// }
|
||||
|
||||
// populationIncreaseRate(player: Player): number {
|
||||
// return this.maxPopulation(player)
|
||||
// }
|
||||
|
||||
// boatMaxDistance(): number {
|
||||
// return 5000
|
||||
// }
|
||||
|
||||
@@ -31,7 +31,6 @@ export class BotExecution implements Execution {
|
||||
|
||||
init(mg: Game) {
|
||||
this.mg = mg;
|
||||
this.bot.setTargetTroopRatio(0.7);
|
||||
}
|
||||
|
||||
tick(ticks: number) {
|
||||
|
||||
@@ -21,7 +21,7 @@ export class DonateTroopsExecution implements Execution {
|
||||
this.recipient = mg.player(this.recipientID);
|
||||
this.troops ??= mg.config().defaultDonationAmount(this.sender);
|
||||
const maxDonation =
|
||||
mg.config().maxPopulation(this.recipient) - this.recipient.population();
|
||||
mg.config().maxTroops(this.recipient) - this.recipient.troops();
|
||||
this.troops = Math.min(this.troops, maxDonation);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import { MoveWarshipExecution } from "./MoveWarshipExecution";
|
||||
import { NoOpExecution } from "./NoOpExecution";
|
||||
import { QuickChatExecution } from "./QuickChatExecution";
|
||||
import { RetreatExecution } from "./RetreatExecution";
|
||||
import { SetTargetTroopRatioExecution } from "./SetTargetTroopRatioExecution";
|
||||
import { SpawnExecution } from "./SpawnExecution";
|
||||
import { TargetPlayerExecution } from "./TargetPlayerExecution";
|
||||
import { TransportShipExecution } from "./TransportShipExecution";
|
||||
@@ -98,8 +97,6 @@ export class Executor {
|
||||
);
|
||||
case "donate_gold":
|
||||
return new DonateGoldExecution(player, intent.recipient, intent.gold);
|
||||
case "troop_ratio":
|
||||
return new SetTargetTroopRatioExecution(player, intent.ratio);
|
||||
case "embargo":
|
||||
return new EmbargoExecution(player, intent.targetID, intent.action);
|
||||
case "build_unit":
|
||||
|
||||
@@ -153,13 +153,6 @@ export class FakeHumanExecution implements Execution {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this.player.troops() > 100_000 &&
|
||||
this.player.targetTroopRatio() > 0.7
|
||||
) {
|
||||
this.player.setTargetTroopRatio(0.7);
|
||||
}
|
||||
|
||||
this.updateRelationsFromEmbargos();
|
||||
this.behavior.handleAllianceRequests();
|
||||
this.handleUnits();
|
||||
|
||||
@@ -203,8 +203,8 @@ export class NukeExecution implements Execution {
|
||||
const toDestroy = this.tilesToDestroy();
|
||||
this.maybeBreakAlliances(toDestroy);
|
||||
|
||||
const maxPop = this.target().isPlayer()
|
||||
? this.mg.config().maxPopulation(this.target() as Player)
|
||||
const maxTroops = this.target().isPlayer()
|
||||
? this.mg.config().maxTroops(this.target() as Player)
|
||||
: 1;
|
||||
|
||||
for (const tile of toDestroy) {
|
||||
@@ -218,17 +218,7 @@ export class NukeExecution implements Execution {
|
||||
this.nukeType,
|
||||
owner.troops(),
|
||||
owner.numTilesOwned(),
|
||||
maxPop,
|
||||
),
|
||||
);
|
||||
owner.removeWorkers(
|
||||
this.mg
|
||||
.config()
|
||||
.nukeDeathFactor(
|
||||
this.nukeType,
|
||||
owner.workers(),
|
||||
owner.numTilesOwned(),
|
||||
maxPop,
|
||||
maxTroops,
|
||||
),
|
||||
);
|
||||
owner.outgoingAttacks().forEach((attack) => {
|
||||
@@ -239,7 +229,7 @@ export class NukeExecution implements Execution {
|
||||
this.nukeType,
|
||||
attack.troops(),
|
||||
owner.numTilesOwned(),
|
||||
maxPop,
|
||||
maxTroops,
|
||||
) ?? 0;
|
||||
attack.setTroops(attack.troops() - deaths);
|
||||
});
|
||||
@@ -251,7 +241,7 @@ export class NukeExecution implements Execution {
|
||||
this.nukeType,
|
||||
attack.troops(),
|
||||
owner.numTilesOwned(),
|
||||
maxPop,
|
||||
maxTroops,
|
||||
) ?? 0;
|
||||
attack.setTroops(attack.troops() - deaths);
|
||||
});
|
||||
|
||||
@@ -56,19 +56,14 @@ export class PlayerExecution implements Execution {
|
||||
return;
|
||||
}
|
||||
|
||||
const popInc = this.config.populationIncreaseRate(this.player);
|
||||
this.player.addWorkers(popInc * (1 - this.player.targetTroopRatio()));
|
||||
this.player.addTroops(popInc * this.player.targetTroopRatio());
|
||||
const troopInc = this.config.troopIncreaseRate(this.player);
|
||||
this.player.addTroops(troopInc);
|
||||
const goldFromWorkers = this.config.goldAdditionRate(this.player);
|
||||
this.player.addGold(goldFromWorkers);
|
||||
|
||||
// Record stats
|
||||
this.mg.stats().goldWork(this.player, goldFromWorkers);
|
||||
|
||||
const adjustRate = this.config.troopAdjustmentRate(this.player);
|
||||
this.player.addTroops(adjustRate);
|
||||
this.player.removeWorkers(adjustRate);
|
||||
|
||||
const alliances = Array.from(this.player.alliances());
|
||||
for (const alliance of alliances) {
|
||||
if (alliance.expiresAt() <= this.mg.ticks()) {
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { Execution, Game, Player } from "../game/Game";
|
||||
|
||||
export class SetTargetTroopRatioExecution implements Execution {
|
||||
private active = true;
|
||||
|
||||
constructor(
|
||||
private player: Player,
|
||||
private targetTroopsRatio: number,
|
||||
) {}
|
||||
|
||||
init(mg: Game, ticks: number): void {}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.targetTroopsRatio < 0 || this.targetTroopsRatio > 1) {
|
||||
console.warn(
|
||||
`target troop ratio of ${this.targetTroopsRatio} for player ${this.player} invalid`,
|
||||
);
|
||||
} else {
|
||||
this.player.setTargetTroopRatio(this.targetTroopsRatio);
|
||||
}
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -59,8 +59,8 @@ export class BotBehavior {
|
||||
}
|
||||
|
||||
private hasSufficientTroops(): boolean {
|
||||
const maxPop = this.game.config().maxPopulation(this.player);
|
||||
const ratio = this.player.population() / maxPop;
|
||||
const maxTroops = this.game.config().maxTroops(this.player);
|
||||
const ratio = this.player.troops() / maxTroops;
|
||||
return ratio >= this.triggerRatio;
|
||||
}
|
||||
|
||||
@@ -208,8 +208,7 @@ export class BotBehavior {
|
||||
|
||||
sendAttack(target: Player | TerraNullius) {
|
||||
if (target.isPlayer() && this.player.isOnSameTeam(target)) return;
|
||||
const maxPop = this.game.config().maxPopulation(this.player);
|
||||
const maxTroops = maxPop * this.player.targetTroopRatio();
|
||||
const maxTroops = this.game.config().maxTroops(this.player);
|
||||
const reserveRatio = target.isPlayer()
|
||||
? this.reserveRatio
|
||||
: this.expandRatio;
|
||||
|
||||
@@ -510,17 +510,11 @@ export interface Player {
|
||||
conquer(tile: TileRef): void;
|
||||
relinquish(tile: TileRef): void;
|
||||
|
||||
// Resources & Population
|
||||
// Resources & Troops
|
||||
gold(): Gold;
|
||||
population(): number;
|
||||
workers(): number;
|
||||
troops(): number;
|
||||
targetTroopRatio(): number;
|
||||
addGold(toAdd: Gold, tile?: TileRef): void;
|
||||
removeGold(toRemove: Gold): Gold;
|
||||
addWorkers(toAdd: number): void;
|
||||
removeWorkers(toRemove: number): void;
|
||||
setTargetTroopRatio(target: number): void;
|
||||
troops(): number;
|
||||
setTroops(troops: number): void;
|
||||
addTroops(troops: number): void;
|
||||
removeTroops(troops: number): number;
|
||||
|
||||
@@ -154,10 +154,7 @@ export interface PlayerUpdate {
|
||||
isDisconnected: boolean;
|
||||
tilesOwned: number;
|
||||
gold: Gold;
|
||||
population: number;
|
||||
workers: number;
|
||||
troops: number;
|
||||
targetTroopRatio: number;
|
||||
allies: number[];
|
||||
embargoes: Set<PlayerID>;
|
||||
isTraitor: boolean;
|
||||
|
||||
@@ -291,15 +291,6 @@ export class PlayerView {
|
||||
gold(): Gold {
|
||||
return this.data.gold;
|
||||
}
|
||||
population(): number {
|
||||
return this.data.population;
|
||||
}
|
||||
workers(): number {
|
||||
return this.data.workers;
|
||||
}
|
||||
targetTroopRatio(): number {
|
||||
return this.data.targetTroopRatio;
|
||||
}
|
||||
|
||||
troops(): number {
|
||||
return this.data.troops;
|
||||
|
||||
@@ -4,7 +4,6 @@ import { ClientID } from "../Schemas";
|
||||
import {
|
||||
assertNever,
|
||||
distSortUnit,
|
||||
maxInt,
|
||||
minInt,
|
||||
simpleHash,
|
||||
toInt,
|
||||
@@ -72,10 +71,6 @@ export class PlayerImpl implements Player {
|
||||
|
||||
private _gold: bigint;
|
||||
private _troops: bigint;
|
||||
private _workers: bigint;
|
||||
|
||||
// 0 to 100
|
||||
private _targetTroopRatio: bigint;
|
||||
|
||||
markedTraitorTick = -1;
|
||||
|
||||
@@ -115,9 +110,7 @@ export class PlayerImpl implements Player {
|
||||
private readonly _team: Team | null,
|
||||
) {
|
||||
this._name = sanitizeUsername(playerInfo.name);
|
||||
this._targetTroopRatio = 95n;
|
||||
this._troops = toInt(startTroops);
|
||||
this._workers = 0n;
|
||||
this._gold = 0n;
|
||||
this._displayName = this._name; // processName(this._name)
|
||||
this._pseudo_random = new PseudoRandom(simpleHash(this.playerInfo.id));
|
||||
@@ -144,10 +137,7 @@ export class PlayerImpl implements Player {
|
||||
isDisconnected: this.isDisconnected(),
|
||||
tilesOwned: this.numTilesOwned(),
|
||||
gold: this._gold,
|
||||
population: this.population(),
|
||||
workers: this.workers(),
|
||||
troops: this.troops(),
|
||||
targetTroopRatio: this.targetTroopRatio(),
|
||||
allies: this.alliances().map((a) => a.other(this).smallID()),
|
||||
embargoes: new Set([...this.embargoes.keys()].map((p) => p.toString())),
|
||||
isTraitor: this.isTraitor(),
|
||||
@@ -731,32 +721,6 @@ export class PlayerImpl implements Player {
|
||||
return actualRemoved;
|
||||
}
|
||||
|
||||
population(): number {
|
||||
return Number(this._troops + this._workers);
|
||||
}
|
||||
workers(): number {
|
||||
return Math.max(1, Number(this._workers));
|
||||
}
|
||||
addWorkers(toAdd: number): void {
|
||||
this._workers += toInt(toAdd);
|
||||
}
|
||||
removeWorkers(toRemove: number): void {
|
||||
this._workers = maxInt(1n, this._workers - toInt(toRemove));
|
||||
}
|
||||
|
||||
targetTroopRatio(): number {
|
||||
return Number(this._targetTroopRatio) / 100;
|
||||
}
|
||||
|
||||
setTargetTroopRatio(target: number): void {
|
||||
if (target < 0 || target > 1) {
|
||||
throw new Error(
|
||||
`invalid targetTroopRatio ${target} set on player ${PlayerImpl}`,
|
||||
);
|
||||
}
|
||||
this._targetTroopRatio = toInt(target * 100);
|
||||
}
|
||||
|
||||
troops(): number {
|
||||
return Number(this._troops);
|
||||
}
|
||||
@@ -1051,7 +1015,7 @@ export class PlayerImpl implements Player {
|
||||
|
||||
hash(): number {
|
||||
return (
|
||||
simpleHash(this.id()) * (this.population() + this.numTilesOwned()) +
|
||||
simpleHash(this.id()) * (this.troops() + this.numTilesOwned()) +
|
||||
this._units.reduce((acc, unit) => acc + unit.hash(), 0)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user