diff --git a/resources/lang/en.json b/resources/lang/en.json
index 0650ae6a31..c5254a1092 100644
--- a/resources/lang/en.json
+++ b/resources/lang/en.json
@@ -199,6 +199,10 @@
"options_changed_no_achievements": "Custom settings – achievements disabled",
"gold_multiplier": "Gold multiplier",
"gold_multiplier_placeholder": "2.0x",
+ "port_gold_multiplier": "Port trade gold multiplier",
+ "port_gold_multiplier_placeholder": "1.0x",
+ "factory_gold_multiplier": "Factory gold multiplier",
+ "factory_gold_multiplier_placeholder": "1.0x",
"starting_gold": "Starting gold",
"starting_gold_placeholder": "5000000"
},
@@ -424,6 +428,10 @@
"teams_Quads": "Quads (teams of 4)",
"teams_Humans Vs Nations": "Humans vs Nations",
"starting_gold": "Starting gold",
+ "port_gold_multiplier": "Port trade gold multiplier",
+ "port_gold_multiplier_placeholder": "1.0x",
+ "factory_gold_multiplier": "Factory gold multiplier",
+ "factory_gold_multiplier_placeholder": "1.0x",
"crowded": "Crowded modifier"
},
"team_colors": {
diff --git a/src/client/HostLobbyModal.ts b/src/client/HostLobbyModal.ts
index 71e661842e..92a662b31c 100644
--- a/src/client/HostLobbyModal.ts
+++ b/src/client/HostLobbyModal.ts
@@ -67,6 +67,10 @@ export class HostLobbyModal extends BaseModal {
@state() private compactMap: boolean = false;
@state() private goldMultiplier: boolean = false;
@state() private goldMultiplierValue: number | undefined = undefined;
+ @state() private portGoldMultiplier: boolean = false;
+ @state() private portGoldMultiplierValue: number | undefined = undefined;
+ @state() private factoryGoldMultiplier: boolean = false;
+ @state() private factoryGoldMultiplierValue: number | undefined = undefined;
@state() private startingGold: boolean = false;
@state() private startingGoldValue: number | undefined = undefined;
@state() private lobbyId = "";
@@ -188,6 +192,42 @@ export class HostLobbyModal extends BaseModal {
.onChange=${this.handleGoldMultiplierValueChanges}
.onKeyDown=${this.handleGoldMultiplierValueKeyDown}
>`,
+ html``,
+ html``,
html` {
+ this.portGoldMultiplier = checked;
+ this.portGoldMultiplierValue = toOptionalNumber(value);
+ this.putGameConfig();
+ };
+
+ private handleFactoryGoldMultiplierToggle = (
+ checked: boolean,
+ value: number | string | undefined,
+ ) => {
+ this.factoryGoldMultiplier = checked;
+ this.factoryGoldMultiplierValue = toOptionalNumber(value);
+ this.putGameConfig();
+ };
+
private handleStartingGoldToggle = (
checked: boolean,
value: number | string | undefined,
@@ -632,6 +694,14 @@ export class HostLobbyModal extends BaseModal {
preventDisallowedKeys(e, ["+", "-", "e", "E"]);
};
+ private handlePortGoldMultiplierValueKeyDown = (e: KeyboardEvent) => {
+ preventDisallowedKeys(e, ["+", "-", "e", "E"]);
+ };
+
+ private handleFactoryGoldMultiplierValueKeyDown = (e: KeyboardEvent) => {
+ preventDisallowedKeys(e, ["+", "-", "e", "E"]);
+ };
+
private handleGoldMultiplierValueChanges = (e: Event) => {
const input = e.target as HTMLInputElement;
const value = parseBoundedFloatFromInput(input, { min: 0.1, max: 1000 });
@@ -645,6 +715,32 @@ export class HostLobbyModal extends BaseModal {
this.putGameConfig();
};
+ private handlePortGoldMultiplierValueChanges = (e: Event) => {
+ const input = e.target as HTMLInputElement;
+ const value = parseBoundedFloatFromInput(input, { min: 0.1, max: 1000 });
+
+ if (value === undefined) {
+ this.portGoldMultiplierValue = undefined;
+ input.value = "";
+ } else {
+ this.portGoldMultiplierValue = value;
+ }
+ this.putGameConfig();
+ };
+
+ private handleFactoryGoldMultiplierValueChanges = (e: Event) => {
+ const input = e.target as HTMLInputElement;
+ const value = parseBoundedFloatFromInput(input, { min: 0.1, max: 1000 });
+
+ if (value === undefined) {
+ this.factoryGoldMultiplierValue = undefined;
+ input.value = "";
+ } else {
+ this.factoryGoldMultiplierValue = value;
+ }
+ this.putGameConfig();
+ };
+
private handleStartingGoldValueKeyDown = (e: KeyboardEvent) => {
preventDisallowedKeys(e, ["-", "+", "e", "E"]);
};
@@ -775,6 +871,14 @@ export class HostLobbyModal extends BaseModal {
this.goldMultiplier === true
? this.goldMultiplierValue
: undefined,
+ portGoldMultiplier:
+ this.portGoldMultiplier === true
+ ? this.portGoldMultiplierValue
+ : undefined,
+ factoryGoldMultiplier:
+ this.factoryGoldMultiplier === true
+ ? this.factoryGoldMultiplierValue
+ : undefined,
startingGold:
this.startingGold === true ? this.startingGoldValue : undefined,
} satisfies Partial,
diff --git a/src/client/SinglePlayerModal.ts b/src/client/SinglePlayerModal.ts
index 6a72726bc5..17ebba5ed7 100644
--- a/src/client/SinglePlayerModal.ts
+++ b/src/client/SinglePlayerModal.ts
@@ -53,6 +53,10 @@ const DEFAULT_OPTIONS = {
teamCount: 2 as TeamCountConfig,
goldMultiplier: false,
goldMultiplierValue: undefined as number | undefined,
+ portGoldMultiplier: false,
+ portGoldMultiplierValue: undefined as number | undefined,
+ factoryGoldMultiplier: false,
+ factoryGoldMultiplierValue: undefined as number | undefined,
startingGold: false,
startingGoldValue: undefined as number | undefined,
disabledUnits: [] as UnitType[],
@@ -82,6 +86,14 @@ export class SinglePlayerModal extends BaseModal {
@state() private goldMultiplier: boolean = DEFAULT_OPTIONS.goldMultiplier;
@state() private goldMultiplierValue: number | undefined =
DEFAULT_OPTIONS.goldMultiplierValue;
+ @state() private portGoldMultiplier: boolean =
+ DEFAULT_OPTIONS.portGoldMultiplier;
+ @state() private portGoldMultiplierValue: number | undefined =
+ DEFAULT_OPTIONS.portGoldMultiplierValue;
+ @state() private factoryGoldMultiplier: boolean =
+ DEFAULT_OPTIONS.factoryGoldMultiplier;
+ @state() private factoryGoldMultiplierValue: number | undefined =
+ DEFAULT_OPTIONS.factoryGoldMultiplierValue;
@state() private startingGold: boolean = DEFAULT_OPTIONS.startingGold;
@state() private startingGoldValue: number | undefined =
DEFAULT_OPTIONS.startingGoldValue;
@@ -200,6 +212,42 @@ export class SinglePlayerModal extends BaseModal {
.onChange=${this.handleGoldMultiplierValueChanges}
.onKeyDown=${this.handleGoldMultiplierValueKeyDown}
>`,
+ html``,
+ html``,
html` 0
);
@@ -402,6 +452,11 @@ export class SinglePlayerModal extends BaseModal {
this.disabledUnits = [...DEFAULT_OPTIONS.disabledUnits];
this.goldMultiplier = DEFAULT_OPTIONS.goldMultiplier;
this.goldMultiplierValue = DEFAULT_OPTIONS.goldMultiplierValue;
+ this.portGoldMultiplier = DEFAULT_OPTIONS.portGoldMultiplier;
+ this.portGoldMultiplierValue = DEFAULT_OPTIONS.portGoldMultiplierValue;
+ this.factoryGoldMultiplier = DEFAULT_OPTIONS.factoryGoldMultiplier;
+ this.factoryGoldMultiplierValue =
+ DEFAULT_OPTIONS.factoryGoldMultiplierValue;
this.startingGold = DEFAULT_OPTIONS.startingGold;
this.startingGoldValue = DEFAULT_OPTIONS.startingGoldValue;
}
@@ -514,6 +569,22 @@ export class SinglePlayerModal extends BaseModal {
this.goldMultiplierValue = toOptionalNumber(value);
};
+ private handlePortGoldMultiplierToggle = (
+ checked: boolean,
+ value: number | string | undefined,
+ ) => {
+ this.portGoldMultiplier = checked;
+ this.portGoldMultiplierValue = toOptionalNumber(value);
+ };
+
+ private handleFactoryGoldMultiplierToggle = (
+ checked: boolean,
+ value: number | string | undefined,
+ ) => {
+ this.factoryGoldMultiplier = checked;
+ this.factoryGoldMultiplierValue = toOptionalNumber(value);
+ };
+
private handleStartingGoldToggle = (
checked: boolean,
value: number | string | undefined,
@@ -550,6 +621,14 @@ export class SinglePlayerModal extends BaseModal {
preventDisallowedKeys(e, ["+", "-", "e", "E"]);
};
+ private handlePortGoldMultiplierValueKeyDown = (e: KeyboardEvent) => {
+ preventDisallowedKeys(e, ["+", "-", "e", "E"]);
+ };
+
+ private handleFactoryGoldMultiplierValueKeyDown = (e: KeyboardEvent) => {
+ preventDisallowedKeys(e, ["+", "-", "e", "E"]);
+ };
+
private handleGoldMultiplierValueChanges = (e: Event) => {
const input = e.target as HTMLInputElement;
const value = parseBoundedFloatFromInput(input, { min: 0.1, max: 1000 });
@@ -562,6 +641,30 @@ export class SinglePlayerModal extends BaseModal {
}
};
+ private handlePortGoldMultiplierValueChanges = (e: Event) => {
+ const input = e.target as HTMLInputElement;
+ const value = parseBoundedFloatFromInput(input, { min: 0.1, max: 1000 });
+
+ if (value === undefined) {
+ this.portGoldMultiplierValue = undefined;
+ input.value = "";
+ } else {
+ this.portGoldMultiplierValue = value;
+ }
+ };
+
+ private handleFactoryGoldMultiplierValueChanges = (e: Event) => {
+ const input = e.target as HTMLInputElement;
+ const value = parseBoundedFloatFromInput(input, { min: 0.1, max: 1000 });
+
+ if (value === undefined) {
+ this.factoryGoldMultiplierValue = undefined;
+ input.value = "";
+ } else {
+ this.factoryGoldMultiplierValue = value;
+ }
+ };
+
private handleStartingGoldValueKeyDown = (e: KeyboardEvent) => {
preventDisallowedKeys(e, ["-", "+", "e", "E"]);
};
@@ -689,6 +792,12 @@ export class SinglePlayerModal extends BaseModal {
...(this.goldMultiplier && this.goldMultiplierValue
? { goldMultiplier: this.goldMultiplierValue }
: {}),
+ ...(this.portGoldMultiplier && this.portGoldMultiplierValue
+ ? { portGoldMultiplier: this.portGoldMultiplierValue }
+ : {}),
+ ...(this.factoryGoldMultiplier && this.factoryGoldMultiplierValue
+ ? { factoryGoldMultiplier: this.factoryGoldMultiplierValue }
+ : {}),
...(this.startingGold && this.startingGoldValue !== undefined
? { startingGold: this.startingGoldValue }
: {}),
diff --git a/src/core/Schemas.ts b/src/core/Schemas.ts
index 0f4f2ebcb2..523719f448 100644
--- a/src/core/Schemas.ts
+++ b/src/core/Schemas.ts
@@ -228,6 +228,8 @@ export const GameConfigSchema = z.object({
disabledUnits: z.enum(UnitType).array().optional(),
playerTeams: TeamCountConfigSchema.optional(),
goldMultiplier: z.number().min(0.1).max(1000).optional(),
+ portGoldMultiplier: z.number().min(0.1).max(1000).optional(),
+ factoryGoldMultiplier: z.number().min(0.1).max(1000).optional(),
startingGold: z.number().int().min(0).max(1000000000).optional(),
});
diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts
index 8595b3a211..33ddef1aa9 100644
--- a/src/core/configuration/Config.ts
+++ b/src/core/configuration/Config.ts
@@ -78,6 +78,8 @@ export interface Config {
userSettings(): UserSettings;
playerTeams(): TeamCountConfig;
goldMultiplier(): number;
+ portGoldMultiplier(): number;
+ factoryGoldMultiplier(): number;
startingGold(playerInfo: PlayerInfo): Gold;
startManpower(playerInfo: PlayerInfo): number;
diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts
index 5a672f296e..1287f9b5a6 100644
--- a/src/core/configuration/DefaultConfig.ts
+++ b/src/core/configuration/DefaultConfig.ts
@@ -254,6 +254,12 @@ export class DefaultConfig implements Config {
goldMultiplier(): number {
return this._gameConfig.goldMultiplier ?? 1;
}
+ portGoldMultiplier(): number {
+ return this._gameConfig.portGoldMultiplier ?? 1;
+ }
+ factoryGoldMultiplier(): number {
+ return this._gameConfig.factoryGoldMultiplier ?? 1;
+ }
startingGold(playerInfo: PlayerInfo): Gold {
if (playerInfo.playerType === PlayerType.Bot) {
return 0n;
@@ -267,7 +273,7 @@ export class DefaultConfig implements Config {
return (numPlayerFactories + 10) * 18;
}
trainGold(rel: "self" | "team" | "ally" | "other"): Gold {
- const multiplier = this.goldMultiplier();
+ const multiplier = this.goldMultiplier() * this.factoryGoldMultiplier();
let baseGold: bigint;
switch (rel) {
case "ally":
@@ -302,7 +308,7 @@ export class DefaultConfig implements Config {
const numPortBonus = numPorts - 1;
// Hyperbolic decay, midpoint at 5 ports, 3x bonus max.
const bonus = 1 + 2 * (numPortBonus / (numPortBonus + 5));
- const multiplier = this.goldMultiplier();
+ const multiplier = this.goldMultiplier() * this.portGoldMultiplier();
return BigInt(Math.floor(baseGold * bonus * multiplier));
}
diff --git a/src/server/GameServer.ts b/src/server/GameServer.ts
index 341ece7bb5..cc1355cc8f 100644
--- a/src/server/GameServer.ts
+++ b/src/server/GameServer.ts
@@ -152,6 +152,12 @@ export class GameServer {
if (gameConfig.goldMultiplier !== undefined) {
this.gameConfig.goldMultiplier = gameConfig.goldMultiplier;
}
+ if (gameConfig.portGoldMultiplier !== undefined) {
+ this.gameConfig.portGoldMultiplier = gameConfig.portGoldMultiplier;
+ }
+ if (gameConfig.factoryGoldMultiplier !== undefined) {
+ this.gameConfig.factoryGoldMultiplier = gameConfig.factoryGoldMultiplier;
+ }
if (gameConfig.startingGold !== undefined) {
this.gameConfig.startingGold = gameConfig.startingGold;
}