mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 11:40:42 +00:00
Lobby Gold Options (Starting Gold, Gold Multiplier) 💰 (#2915)
## Description: We might want to add this to v29 to have a third possible public game modifier from the beginning on 😄 Would be fun - Add starting gold option (0 to 1_000_000_000 allowed, also applies to nations) - Add gold multiplier option (0.1 to 1000 allowed, also applies to nations and bots) - Add third public game modifier (3% chance of starting with 5M gold) - Why 5M? It's enough gold to massively change the game start but not enough to insta-hydro someone (launcher + hydro is 6M) <img width="357" height="140" alt="image" src="https://github.com/user-attachments/assets/72acc15c-e788-4e04-8590-ac72dd9657c7" /> ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin
This commit is contained in:
+12
-3
@@ -171,7 +171,11 @@
|
||||
"max_timer_invalid": "Please enter a valid max timer value (1-120 minutes)",
|
||||
"disable_nukes": "Disable Nukes",
|
||||
"enables_title": "Enable Settings",
|
||||
"start": "Start Game"
|
||||
"start": "Start Game",
|
||||
"gold_multiplier": "Gold multiplier",
|
||||
"gold_multiplier_placeholder": "2.0x",
|
||||
"starting_gold": "Starting gold",
|
||||
"starting_gold_placeholder": "5000000"
|
||||
},
|
||||
"token_login_modal": {
|
||||
"title": "Logging in...",
|
||||
@@ -381,7 +385,11 @@
|
||||
"teams_Duos": "Duos (teams of 2)",
|
||||
"teams_Trios": "Trios (teams of 3)",
|
||||
"teams_Quads": "Quads (teams of 4)",
|
||||
"teams_Humans Vs Nations": "Humans vs Nations"
|
||||
"teams_Humans Vs Nations": "Humans vs Nations",
|
||||
"gold_multiplier": "Gold multiplier",
|
||||
"gold_multiplier_placeholder": "2.0x",
|
||||
"starting_gold": "Starting gold",
|
||||
"starting_gold_placeholder": "5000000"
|
||||
},
|
||||
"team_colors": {
|
||||
"red": "Red",
|
||||
@@ -411,7 +419,8 @@
|
||||
},
|
||||
"public_game_modifier": {
|
||||
"random_spawn": "Random Spawn",
|
||||
"compact_map": "Compact Map"
|
||||
"compact_map": "Compact Map",
|
||||
"starting_gold": "5M Starting Gold"
|
||||
},
|
||||
"select_lang": {
|
||||
"title": "Select Language"
|
||||
|
||||
@@ -60,6 +60,10 @@ export class HostLobbyModal extends BaseModal {
|
||||
@state() private instantBuild: boolean = false;
|
||||
@state() private randomSpawn: boolean = false;
|
||||
@state() private compactMap: boolean = false;
|
||||
@state() private goldMultiplier: boolean = false;
|
||||
@state() private goldMultiplierValue: number | undefined = undefined;
|
||||
@state() private startingGold: boolean = false;
|
||||
@state() private startingGoldValue: number | undefined = undefined;
|
||||
@state() private lobbyId = "";
|
||||
@state() private copySuccess = false;
|
||||
@state() private lobbyUrlSuffix = "";
|
||||
@@ -739,6 +743,158 @@ export class HostLobbyModal extends BaseModal {
|
||||
${translateText("host_modal.player_immunity_duration")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gold Multiplier -->
|
||||
<div
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click=${this.createToggleHandlers(
|
||||
() => this.goldMultiplier,
|
||||
(val) => (this.goldMultiplier = val),
|
||||
() => this.goldMultiplierValue,
|
||||
(val) => (this.goldMultiplierValue = val),
|
||||
2,
|
||||
).click}
|
||||
@keydown=${this.createToggleHandlers(
|
||||
() => this.goldMultiplier,
|
||||
(val) => (this.goldMultiplier = val),
|
||||
() => this.goldMultiplierValue,
|
||||
(val) => (this.goldMultiplierValue = val),
|
||||
2,
|
||||
).keydown}
|
||||
class="relative p-3 rounded-xl border transition-all duration-200 flex flex-col items-center justify-between gap-2 h-full cursor-pointer min-h-[100px] ${this
|
||||
.goldMultiplier
|
||||
? "bg-blue-500/20 border-blue-500/50"
|
||||
: "bg-white/5 border-white/10 hover:bg-white/10 hover:border-white/20"}"
|
||||
>
|
||||
<div class="flex items-center justify-center w-full mt-1">
|
||||
<div
|
||||
class="w-5 h-5 rounded border flex items-center justify-center transition-colors ${this
|
||||
.goldMultiplier
|
||||
? "bg-blue-500 border-blue-500"
|
||||
: "border-white/20 bg-white/5"}"
|
||||
>
|
||||
${this.goldMultiplier
|
||||
? html`<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-3 w-3 text-white"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${this.goldMultiplier
|
||||
? html`<input
|
||||
type="number"
|
||||
id="gold-multiplier-value"
|
||||
min="0.1"
|
||||
max="1000"
|
||||
step="any"
|
||||
value=${this.goldMultiplierValue ?? ""}
|
||||
class="w-full text-center rounded bg-black/60 text-white text-sm font-bold border border-white/20 focus:outline-none focus:border-blue-500 p-1 my-1"
|
||||
aria-label=${translateText(
|
||||
"single_modal.gold_multiplier",
|
||||
)}
|
||||
@change=${this.handleGoldMultiplierValueChanges}
|
||||
@keydown=${this.handleGoldMultiplierValueKeyDown}
|
||||
placeholder=${translateText(
|
||||
"single_modal.gold_multiplier_placeholder",
|
||||
)}
|
||||
/>`
|
||||
: html`<div
|
||||
class="h-[2px] w-4 bg-white/10 rounded my-3"
|
||||
></div>`}
|
||||
|
||||
<div
|
||||
class="text-[10px] uppercase font-bold text-white/60 tracking-wider text-center w-full leading-tight break-words hyphens-auto"
|
||||
>
|
||||
${translateText("single_modal.gold_multiplier")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Starting Gold -->
|
||||
<div
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click=${this.createToggleHandlers(
|
||||
() => this.startingGold,
|
||||
(val) => (this.startingGold = val),
|
||||
() => this.startingGoldValue,
|
||||
(val) => (this.startingGoldValue = val),
|
||||
5000000,
|
||||
).click}
|
||||
@keydown=${this.createToggleHandlers(
|
||||
() => this.startingGold,
|
||||
(val) => (this.startingGold = val),
|
||||
() => this.startingGoldValue,
|
||||
(val) => (this.startingGoldValue = val),
|
||||
5000000,
|
||||
).keydown}
|
||||
class="relative p-3 rounded-xl border transition-all duration-200 flex flex-col items-center justify-between gap-2 h-full cursor-pointer min-h-[100px] ${this
|
||||
.startingGold
|
||||
? "bg-blue-500/20 border-blue-500/50"
|
||||
: "bg-white/5 border-white/10 hover:bg-white/10 hover:border-white/20"}"
|
||||
>
|
||||
<div class="flex items-center justify-center w-full mt-1">
|
||||
<div
|
||||
class="w-5 h-5 rounded border flex items-center justify-center transition-colors ${this
|
||||
.startingGold
|
||||
? "bg-blue-500 border-blue-500"
|
||||
: "border-white/20 bg-white/5"}"
|
||||
>
|
||||
${this.startingGold
|
||||
? html`<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-3 w-3 text-white"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${this.startingGold
|
||||
? html`<input
|
||||
type="number"
|
||||
id="starting-gold-value"
|
||||
min="0"
|
||||
max="1000000000"
|
||||
step="100000"
|
||||
.value=${String(this.startingGoldValue ?? "")}
|
||||
class="w-full text-center rounded bg-black/60 text-white text-sm font-bold border border-white/20 focus:outline-none focus:border-blue-500 p-1 my-1"
|
||||
aria-label=${translateText(
|
||||
"single_modal.starting_gold",
|
||||
)}
|
||||
@input=${this.handleStartingGoldValueChanges}
|
||||
@keydown=${this.handleStartingGoldValueKeyDown}
|
||||
placeholder=${translateText(
|
||||
"single_modal.starting_gold_placeholder",
|
||||
)}
|
||||
/>`
|
||||
: html`<div
|
||||
class="h-[2px] w-4 bg-white/10 rounded my-3"
|
||||
></div>`}
|
||||
|
||||
<div
|
||||
class="text-[10px] uppercase font-bold text-white/60 tracking-wider text-center w-full leading-tight break-words hyphens-auto"
|
||||
>
|
||||
${translateText("single_modal.starting_gold")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -968,6 +1124,10 @@ export class HostLobbyModal extends BaseModal {
|
||||
this.lobbyCreatorClientID = "";
|
||||
this.lobbyIdVisible = true;
|
||||
this.nationCount = 0;
|
||||
this.goldMultiplier = false;
|
||||
this.goldMultiplierValue = undefined;
|
||||
this.startingGold = false;
|
||||
this.startingGoldValue = undefined;
|
||||
|
||||
this.leaveLobbyOnClose = true;
|
||||
}
|
||||
@@ -1036,6 +1196,44 @@ export class HostLobbyModal extends BaseModal {
|
||||
this.putGameConfig();
|
||||
}
|
||||
|
||||
private handleGoldMultiplierValueKeyDown(e: KeyboardEvent) {
|
||||
if (["+", "-", "e", "E"].includes(e.key)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private handleGoldMultiplierValueChanges(e: Event) {
|
||||
const input = e.target as HTMLInputElement;
|
||||
const value = parseFloat(input.value);
|
||||
|
||||
if (isNaN(value) || value < 0.1 || value > 1000) {
|
||||
this.goldMultiplierValue = undefined;
|
||||
input.value = "";
|
||||
} else {
|
||||
this.goldMultiplierValue = value;
|
||||
}
|
||||
this.putGameConfig();
|
||||
}
|
||||
|
||||
private handleStartingGoldValueKeyDown(e: KeyboardEvent) {
|
||||
if (["-", "+", "e", "E"].includes(e.key)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private handleStartingGoldValueChanges(e: Event) {
|
||||
const input = e.target as HTMLInputElement;
|
||||
input.value = input.value.replace(/[eE+-]/g, "");
|
||||
const value = parseInt(input.value);
|
||||
|
||||
if (isNaN(value) || value < 0 || value > 1000000000) {
|
||||
this.startingGoldValue = undefined;
|
||||
} else {
|
||||
this.startingGoldValue = value;
|
||||
}
|
||||
this.putGameConfig();
|
||||
}
|
||||
|
||||
private handleRandomSpawnChange = (val: boolean) => {
|
||||
this.randomSpawn = val;
|
||||
this.putGameConfig();
|
||||
@@ -1151,6 +1349,12 @@ export class HostLobbyModal extends BaseModal {
|
||||
}),
|
||||
maxTimerValue:
|
||||
this.maxTimer === true ? this.maxTimerValue : undefined,
|
||||
goldMultiplier:
|
||||
this.goldMultiplier === true
|
||||
? this.goldMultiplierValue
|
||||
: undefined,
|
||||
startingGold:
|
||||
this.startingGold === true ? this.startingGoldValue : undefined,
|
||||
} satisfies Partial<GameConfig>,
|
||||
},
|
||||
bubbles: true,
|
||||
|
||||
@@ -374,6 +374,9 @@ export class PublicLobby extends LitElement {
|
||||
if (publicGameModifiers.isCompact) {
|
||||
labels.push(translateText("public_game_modifier.compact_map"));
|
||||
}
|
||||
if (publicGameModifiers.startingGold) {
|
||||
labels.push(translateText("public_game_modifier.starting_gold"));
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,10 @@ export class SinglePlayerModal extends BaseModal {
|
||||
@state() private showAchievements: boolean = false;
|
||||
@state() private mapWins: Map<GameMapType, Set<Difficulty>> = new Map();
|
||||
@state() private userMeResponse: UserMeResponse | false = false;
|
||||
@state() private goldMultiplier: boolean = false;
|
||||
@state() private goldMultiplierValue: number | undefined = undefined;
|
||||
@state() private startingGold: boolean = false;
|
||||
@state() private startingGoldValue: number | undefined = undefined;
|
||||
|
||||
@state() private disabledUnits: UnitType[] = [];
|
||||
|
||||
@@ -601,6 +605,180 @@ export class SinglePlayerModal extends BaseModal {
|
||||
${translateText("single_modal.max_timer")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gold Multiplier -->
|
||||
<div
|
||||
class="relative p-3 rounded-xl border transition-all duration-200 flex flex-col items-center justify-between gap-2 h-full cursor-pointer min-h-[100px] ${this
|
||||
.goldMultiplier
|
||||
? "bg-blue-500/20 border-blue-500/50"
|
||||
: "bg-white/5 border-white/10 hover:bg-white/10 hover:border-white/20"}"
|
||||
@click=${(e: Event) => {
|
||||
if (
|
||||
(e.target as HTMLElement).tagName.toLowerCase() ===
|
||||
"input"
|
||||
)
|
||||
return;
|
||||
this.goldMultiplier = !this.goldMultiplier;
|
||||
if (!this.goldMultiplier) {
|
||||
this.goldMultiplierValue = undefined;
|
||||
} else {
|
||||
if (
|
||||
!this.goldMultiplierValue ||
|
||||
this.goldMultiplierValue <= 0
|
||||
) {
|
||||
this.goldMultiplierValue = 2;
|
||||
}
|
||||
setTimeout(() => {
|
||||
const input = this.renderRoot.querySelector(
|
||||
"#gold-multiplier-value",
|
||||
) as HTMLInputElement;
|
||||
if (input) {
|
||||
input.focus();
|
||||
input.select();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="flex items-center justify-center w-full mt-1">
|
||||
<div
|
||||
class="w-5 h-5 rounded border flex items-center justify-center transition-colors ${this
|
||||
.goldMultiplier
|
||||
? "bg-blue-500 border-blue-500"
|
||||
: "border-white/20 bg-white/5"}"
|
||||
>
|
||||
${this.goldMultiplier
|
||||
? html`<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-3 w-3 text-white"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${this.goldMultiplier
|
||||
? html`<input
|
||||
type="number"
|
||||
id="gold-multiplier-value"
|
||||
min="0.1"
|
||||
max="1000"
|
||||
step="any"
|
||||
value=${this.goldMultiplierValue ?? ""}
|
||||
class="w-full text-center rounded bg-black/60 text-white text-sm font-bold border border-white/20 focus:outline-none focus:border-blue-500 p-1 my-1"
|
||||
aria-label=${translateText(
|
||||
"single_modal.gold_multiplier",
|
||||
)}
|
||||
@change=${this.handleGoldMultiplierValueChanges}
|
||||
@keydown=${this.handleGoldMultiplierValueKeyDown}
|
||||
placeholder=${translateText(
|
||||
"single_modal.gold_multiplier_placeholder",
|
||||
)}
|
||||
/>`
|
||||
: html`<div
|
||||
class="h-[2px] w-4 bg-white/10 rounded my-3"
|
||||
></div>`}
|
||||
|
||||
<div
|
||||
class="text-[10px] uppercase font-bold text-white/60 tracking-wider text-center w-full leading-tight break-words hyphens-auto"
|
||||
>
|
||||
${translateText("single_modal.gold_multiplier")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Starting Gold -->
|
||||
<div
|
||||
class="relative p-3 rounded-xl border transition-all duration-200 flex flex-col items-center justify-between gap-2 h-full cursor-pointer min-h-[100px] ${this
|
||||
.startingGold
|
||||
? "bg-blue-500/20 border-blue-500/50"
|
||||
: "bg-white/5 border-white/10 hover:bg-white/10 hover:border-white/20"}"
|
||||
@click=${(e: Event) => {
|
||||
if (
|
||||
(e.target as HTMLElement).tagName.toLowerCase() ===
|
||||
"input"
|
||||
)
|
||||
return;
|
||||
this.startingGold = !this.startingGold;
|
||||
if (!this.startingGold) {
|
||||
this.startingGoldValue = undefined;
|
||||
} else {
|
||||
if (
|
||||
!this.startingGoldValue ||
|
||||
this.startingGoldValue < 0
|
||||
) {
|
||||
this.startingGoldValue = 5000000;
|
||||
}
|
||||
setTimeout(() => {
|
||||
const input = this.renderRoot.querySelector(
|
||||
"#starting-gold-value",
|
||||
) as HTMLInputElement;
|
||||
if (input) {
|
||||
input.focus();
|
||||
input.select();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="flex items-center justify-center w-full mt-1">
|
||||
<div
|
||||
class="w-5 h-5 rounded border flex items-center justify-center transition-colors ${this
|
||||
.startingGold
|
||||
? "bg-blue-500 border-blue-500"
|
||||
: "border-white/20 bg-white/5"}"
|
||||
>
|
||||
${this.startingGold
|
||||
? html`<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-3 w-3 text-white"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${this.startingGold
|
||||
? html`<input
|
||||
type="number"
|
||||
id="starting-gold-value"
|
||||
min="0"
|
||||
max="1000000000"
|
||||
step="100000"
|
||||
.value=${String(this.startingGoldValue ?? "")}
|
||||
class="w-full text-center rounded bg-black/60 text-white text-sm font-bold border border-white/20 focus:outline-none focus:border-blue-500 p-1 my-1"
|
||||
aria-label=${translateText(
|
||||
"single_modal.starting_gold",
|
||||
)}
|
||||
@input=${this.handleStartingGoldValueChanges}
|
||||
@keydown=${this.handleStartingGoldValueKeyDown}
|
||||
placeholder=${translateText(
|
||||
"single_modal.starting_gold_placeholder",
|
||||
)}
|
||||
/>`
|
||||
: html`<div
|
||||
class="h-[2px] w-4 bg-white/10 rounded my-3"
|
||||
></div>`}
|
||||
|
||||
<div
|
||||
class="text-[10px] uppercase font-bold text-white/60 tracking-wider text-center w-full leading-tight break-words hyphens-auto"
|
||||
>
|
||||
${translateText("single_modal.starting_gold")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -714,6 +892,10 @@ export class SinglePlayerModal extends BaseModal {
|
||||
this.randomSpawn = false;
|
||||
this.teamCount = 2;
|
||||
this.disabledUnits = [];
|
||||
this.goldMultiplier = false;
|
||||
this.goldMultiplierValue = undefined;
|
||||
this.startingGold = false;
|
||||
this.startingGoldValue = undefined;
|
||||
}
|
||||
|
||||
private handleSelectRandomMap() {
|
||||
@@ -767,6 +949,42 @@ export class SinglePlayerModal extends BaseModal {
|
||||
}
|
||||
}
|
||||
|
||||
private handleGoldMultiplierValueKeyDown(e: KeyboardEvent) {
|
||||
if (["+", "-", "e", "E"].includes(e.key)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private handleGoldMultiplierValueChanges(e: Event) {
|
||||
const input = e.target as HTMLInputElement;
|
||||
const value = parseFloat(input.value);
|
||||
|
||||
if (isNaN(value) || value < 0.1 || value > 1000) {
|
||||
this.goldMultiplierValue = undefined;
|
||||
input.value = "";
|
||||
} else {
|
||||
this.goldMultiplierValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
private handleStartingGoldValueKeyDown(e: KeyboardEvent) {
|
||||
if (["-", "+", "e", "E"].includes(e.key)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private handleStartingGoldValueChanges(e: Event) {
|
||||
const input = e.target as HTMLInputElement;
|
||||
input.value = input.value.replace(/[eE+-]/g, "");
|
||||
const value = parseInt(input.value);
|
||||
|
||||
if (isNaN(value) || value < 0 || value > 1000000000) {
|
||||
this.startingGoldValue = undefined;
|
||||
} else {
|
||||
this.startingGoldValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
private handleGameModeSelection(value: GameMode) {
|
||||
this.gameMode = value;
|
||||
}
|
||||
@@ -888,6 +1106,12 @@ export class SinglePlayerModal extends BaseModal {
|
||||
: {
|
||||
disableNations: this.disableNations,
|
||||
}),
|
||||
...(this.goldMultiplier && this.goldMultiplierValue
|
||||
? { goldMultiplier: this.goldMultiplierValue }
|
||||
: {}),
|
||||
...(this.startingGold && this.startingGoldValue !== undefined
|
||||
? { startingGold: this.startingGoldValue }
|
||||
: {}),
|
||||
},
|
||||
lobbyCreatedAt: Date.now(), // ms; server should be authoritative in MP
|
||||
},
|
||||
|
||||
@@ -190,6 +190,7 @@ export const GameConfigSchema = z.object({
|
||||
.object({
|
||||
isCompact: z.boolean(),
|
||||
isRandomSpawn: z.boolean(),
|
||||
startingGold: z.number().int().min(0).optional(),
|
||||
})
|
||||
.optional(),
|
||||
disableNations: z.boolean(),
|
||||
@@ -204,6 +205,8 @@ export const GameConfigSchema = z.object({
|
||||
spawnImmunityDuration: z.number().int().min(0).optional(), // In ticks
|
||||
disabledUnits: z.enum(UnitType).array().optional(),
|
||||
playerTeams: TeamCountConfigSchema.optional(),
|
||||
goldMultiplier: z.number().min(0.1).max(1000).optional(),
|
||||
startingGold: z.number().int().min(0).max(1000000000).optional(),
|
||||
});
|
||||
|
||||
export const TeamSchema = z.string();
|
||||
|
||||
@@ -76,6 +76,8 @@ export interface Config {
|
||||
numSpawnPhaseTurns(): number;
|
||||
userSettings(): UserSettings;
|
||||
playerTeams(): TeamCountConfig;
|
||||
goldMultiplier(): number;
|
||||
startingGold(playerInfo: PlayerInfo): Gold;
|
||||
|
||||
startManpower(playerInfo: PlayerInfo): number;
|
||||
troopIncreaseRate(player: Player | PlayerView): number;
|
||||
|
||||
@@ -245,6 +245,15 @@ export class DefaultConfig implements Config {
|
||||
donateTroops(): boolean {
|
||||
return this._gameConfig.donateTroops;
|
||||
}
|
||||
goldMultiplier(): number {
|
||||
return this._gameConfig.goldMultiplier ?? 1;
|
||||
}
|
||||
startingGold(playerInfo: PlayerInfo): Gold {
|
||||
if (playerInfo.playerType === PlayerType.Bot) {
|
||||
return 0n;
|
||||
}
|
||||
return BigInt(this._gameConfig.startingGold ?? 0);
|
||||
}
|
||||
|
||||
trainSpawnRate(numPlayerFactories: number): number {
|
||||
// hyperbolic decay, midpoint at 10 factories
|
||||
@@ -252,15 +261,21 @@ export class DefaultConfig implements Config {
|
||||
return (numPlayerFactories + 10) * 18;
|
||||
}
|
||||
trainGold(rel: "self" | "team" | "ally" | "other"): Gold {
|
||||
const multiplier = this.goldMultiplier();
|
||||
let baseGold: bigint;
|
||||
switch (rel) {
|
||||
case "ally":
|
||||
return 35_000n;
|
||||
baseGold = 35_000n;
|
||||
break;
|
||||
case "team":
|
||||
case "other":
|
||||
return 25_000n;
|
||||
baseGold = 25_000n;
|
||||
break;
|
||||
case "self":
|
||||
return 10_000n;
|
||||
baseGold = 10_000n;
|
||||
break;
|
||||
}
|
||||
return BigInt(Math.floor(Number(baseGold) * multiplier));
|
||||
}
|
||||
|
||||
trainStationMinRange(): number {
|
||||
@@ -281,7 +296,8 @@ 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));
|
||||
return BigInt(Math.floor(baseGold * bonus));
|
||||
const multiplier = this.goldMultiplier();
|
||||
return BigInt(Math.floor(baseGold * bonus * multiplier));
|
||||
}
|
||||
|
||||
// Probability of trade ship spawn = 1 / tradeShipSpawnRate
|
||||
@@ -791,10 +807,14 @@ export class DefaultConfig implements Config {
|
||||
}
|
||||
|
||||
goldAdditionRate(player: Player): Gold {
|
||||
const multiplier = this.goldMultiplier();
|
||||
let baseRate: bigint;
|
||||
if (player.type() === PlayerType.Bot) {
|
||||
return 50n;
|
||||
baseRate = 50n;
|
||||
} else {
|
||||
baseRate = 100n;
|
||||
}
|
||||
return 100n;
|
||||
return BigInt(Math.floor(Number(baseRate) * multiplier));
|
||||
}
|
||||
|
||||
nukeMagnitudes(unitType: UnitType): NukeMagnitude {
|
||||
|
||||
@@ -211,6 +211,7 @@ export enum GameMapSize {
|
||||
export interface PublicGameModifiers {
|
||||
isCompact: boolean;
|
||||
isRandomSpawn: boolean;
|
||||
startingGold?: number;
|
||||
}
|
||||
|
||||
export interface UnitInfo {
|
||||
|
||||
@@ -112,7 +112,7 @@ export class PlayerImpl implements Player {
|
||||
) {
|
||||
this._name = playerInfo.name;
|
||||
this._troops = toInt(startTroops);
|
||||
this._gold = 0n;
|
||||
this._gold = mg.config().startingGold(playerInfo);
|
||||
this._displayName = this._name;
|
||||
this._pseudo_random = new PseudoRandom(simpleHash(this.playerInfo.id));
|
||||
}
|
||||
|
||||
@@ -127,14 +127,18 @@ export class GameServer {
|
||||
if (gameConfig.gameMode !== undefined) {
|
||||
this.gameConfig.gameMode = gameConfig.gameMode;
|
||||
}
|
||||
|
||||
if (gameConfig.disabledUnits !== undefined) {
|
||||
this.gameConfig.disabledUnits = gameConfig.disabledUnits;
|
||||
}
|
||||
|
||||
if (gameConfig.playerTeams !== undefined) {
|
||||
this.gameConfig.playerTeams = gameConfig.playerTeams;
|
||||
}
|
||||
if (gameConfig.goldMultiplier !== undefined) {
|
||||
this.gameConfig.goldMultiplier = gameConfig.goldMultiplier;
|
||||
}
|
||||
if (gameConfig.startingGold !== undefined) {
|
||||
this.gameConfig.startingGold = gameConfig.startingGold;
|
||||
}
|
||||
}
|
||||
|
||||
public joinClient(client: Client) {
|
||||
|
||||
@@ -94,7 +94,9 @@ export class MapPlaylist {
|
||||
const playerTeams =
|
||||
mode === GameMode.Team ? this.getTeamCount() : undefined;
|
||||
|
||||
let { isCompact, isRandomSpawn } = this.getRandomPublicGameModifiers();
|
||||
const modifiers = this.getRandomPublicGameModifiers();
|
||||
const { startingGold } = modifiers;
|
||||
let { isCompact, isRandomSpawn } = modifiers;
|
||||
|
||||
// Duos, Trios, and Quads should not get random spawn (as it defeats the purpose)
|
||||
if (
|
||||
@@ -122,7 +124,8 @@ export class MapPlaylist {
|
||||
maxPlayers: await this.lobbyMaxPlayers(map, mode, playerTeams, isCompact),
|
||||
gameType: GameType.Public,
|
||||
gameMapSize: isCompact ? GameMapSize.Compact : GameMapSize.Normal,
|
||||
publicGameModifiers: { isCompact, isRandomSpawn },
|
||||
publicGameModifiers: { isCompact, isRandomSpawn, startingGold },
|
||||
startingGold,
|
||||
difficulty:
|
||||
playerTeams === HumansVsNations
|
||||
? Difficulty.Impossible
|
||||
@@ -198,6 +201,7 @@ export class MapPlaylist {
|
||||
return {
|
||||
isRandomSpawn: Math.random() < 0.1, // 10% chance
|
||||
isCompact: Math.random() < 0.05, // 5% chance
|
||||
startingGold: Math.random() < 0.03 ? 5_000_000 : undefined, // 3% chance
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user