Skip to content

Commit cf0cf14

Browse files
authored
Add new public game modifiers 🙂 (#3500)
## Description: Adds 5 new public game modifiers to the Special game mode modifier pool: - **Ports Disabled** - disables port construction, focus on factories - **Nukes Disabled** - disables atom bombs, hydrogen bombs, MIRVs, missile silos and SAM launchers - **SAMs Disabled** - disables SAM launchers (thats funny, you cant protect against nukes, have to space out your stuff) (mutually exclusive with nukes disabled) - **1M Starting Gold** - gives all players 1M starting gold (was requested by people, new chill tier alongside existing 5M and 25M) - **4min Peace Time** - grants 4 minutes of PVP spawn immunity All `PublicGameModifiers` boolean fields are now optional - inactive modifiers are omitted from game JSON instead of being serialized as `false` (stop bloating the JSON size) I think we have enough modifiers now :) Good variety ## 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
1 parent 6e67c2b commit cf0cf14

File tree

5 files changed

+140
-45
lines changed

5 files changed

+140
-45
lines changed

‎resources/lang/en.json‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,15 @@
500500
"starting_gold_label": "Starting Gold",
501501
"gold_multiplier": "x{amount} Gold Multiplier",
502502
"disable_alliances": "Alliances Disabled",
503-
"disable_alliances_label": "Alliances"
503+
"disable_alliances_label": "Alliances",
504+
"ports_disabled": "Ports Disabled",
505+
"ports_disabled_label": "Ports",
506+
"nukes_disabled": "Nukes Disabled",
507+
"nukes_disabled_label": "Nukes",
508+
"sams_disabled": "SAMs Disabled",
509+
"sams_disabled_label": "SAMs",
510+
"peace_time": "4min Peace",
511+
"peace_time_label": "PVP Immunity"
504512
},
505513
"select_lang": {
506514
"title": "Select Language"

‎src/client/Utils.ts‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,30 @@ export function getActiveModifiers(
186186
formattedValue: translateText("common.disabled"),
187187
});
188188
}
189+
if (modifiers.isPortsDisabled) {
190+
result.push({
191+
labelKey: "public_game_modifier.ports_disabled_label",
192+
badgeKey: "public_game_modifier.ports_disabled",
193+
});
194+
}
195+
if (modifiers.isNukesDisabled) {
196+
result.push({
197+
labelKey: "public_game_modifier.nukes_disabled_label",
198+
badgeKey: "public_game_modifier.nukes_disabled",
199+
});
200+
}
201+
if (modifiers.isSAMsDisabled) {
202+
result.push({
203+
labelKey: "public_game_modifier.sams_disabled_label",
204+
badgeKey: "public_game_modifier.sams_disabled",
205+
});
206+
}
207+
if (modifiers.isPeaceTime) {
208+
result.push({
209+
labelKey: "public_game_modifier.peace_time_label",
210+
badgeKey: "public_game_modifier.peace_time",
211+
});
212+
}
189213
return result;
190214
}
191215

‎src/core/Schemas.ts‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -225,13 +225,17 @@ export const GameConfigSchema = z.object({
225225
gameMapSize: z.enum(GameMapSize),
226226
publicGameModifiers: z
227227
.object({
228-
isCompact: z.boolean(),
229-
isRandomSpawn: z.boolean(),
230-
isCrowded: z.boolean(),
231-
isHardNations: z.boolean(),
228+
isCompact: z.boolean().optional(),
229+
isRandomSpawn: z.boolean().optional(),
230+
isCrowded: z.boolean().optional(),
231+
isHardNations: z.boolean().optional(),
232232
startingGold: z.number().int().min(0).optional(),
233233
goldMultiplier: z.number().min(0.1).max(1000).optional(),
234-
isAlliancesDisabled: z.boolean(),
234+
isAlliancesDisabled: z.boolean().optional(),
235+
isPortsDisabled: z.boolean().optional(),
236+
isNukesDisabled: z.boolean().optional(),
237+
isSAMsDisabled: z.boolean().optional(),
238+
isPeaceTime: z.boolean().optional(),
235239
})
236240
.optional(),
237241
nations: z

‎src/core/game/Game.ts‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -248,13 +248,17 @@ export enum GameMapSize {
248248
}
249249

250250
export interface PublicGameModifiers {
251-
isCompact: boolean;
252-
isRandomSpawn: boolean;
253-
isCrowded: boolean;
254-
isHardNations: boolean;
251+
isCompact?: boolean;
252+
isRandomSpawn?: boolean;
253+
isCrowded?: boolean;
254+
isHardNations?: boolean;
255255
startingGold?: number;
256256
goldMultiplier?: number;
257-
isAlliancesDisabled: boolean;
257+
isAlliancesDisabled?: boolean;
258+
isPortsDisabled?: boolean;
259+
isNukesDisabled?: boolean;
260+
isSAMsDisabled?: boolean;
261+
isPeaceTime?: boolean;
258262
}
259263

260264
export interface UnitInfo {

‎src/server/MapPlaylist.ts‎

Lines changed: 89 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
Quads,
1313
RankedType,
1414
Trios,
15+
UnitType,
1516
mapCategories,
1617
} from "../core/game/Game";
1718
import { PseudoRandom } from "../core/PseudoRandom";
@@ -103,27 +104,40 @@ type ModifierKey =
103104
| "isCompact"
104105
| "isCrowded"
105106
| "isHardNations"
106-
| "startingGold"
107-
| "startingGoldHigh"
107+
| "startingGold1M"
108+
| "startingGold5M"
109+
| "startingGold25M"
108110
| "goldMultiplier"
109-
| "isAlliancesDisabled";
111+
| "isAlliancesDisabled"
112+
| "isPortsDisabled"
113+
| "isNukesDisabled"
114+
| "isSAMsDisabled"
115+
| "isPeaceTime";
110116

111117
// Each entry represents one "ticket" in the pool. More tickets = higher chance of selection.
112118
const SPECIAL_MODIFIER_POOL: ModifierKey[] = [
113119
...Array<ModifierKey>(2).fill("isRandomSpawn"),
114-
...Array<ModifierKey>(8).fill("isCompact"),
115-
...Array<ModifierKey>(1).fill("isCrowded"),
120+
...Array<ModifierKey>(5).fill("isCompact"),
121+
...Array<ModifierKey>(2).fill("isCrowded"),
116122
...Array<ModifierKey>(1).fill("isHardNations"),
117-
...Array<ModifierKey>(8).fill("startingGold"),
118-
...Array<ModifierKey>(1).fill("startingGoldHigh"),
123+
...Array<ModifierKey>(3).fill("startingGold1M"),
124+
...Array<ModifierKey>(5).fill("startingGold5M"),
125+
...Array<ModifierKey>(1).fill("startingGold25M"),
119126
...Array<ModifierKey>(4).fill("goldMultiplier"),
120127
...Array<ModifierKey>(1).fill("isAlliancesDisabled"),
128+
...Array<ModifierKey>(1).fill("isPortsDisabled"),
129+
...Array<ModifierKey>(1).fill("isNukesDisabled"),
130+
...Array<ModifierKey>(1).fill("isSAMsDisabled"),
131+
...Array<ModifierKey>(1).fill("isPeaceTime"),
121132
];
122133

123134
// Modifiers that cannot be active at the same time.
124135
const MUTUALLY_EXCLUSIVE_MODIFIERS: [ModifierKey, ModifierKey][] = [
125-
["startingGold", "startingGoldHigh"],
126-
["isHardNations", "startingGoldHigh"],
136+
["startingGold5M", "startingGold25M"],
137+
["startingGold5M", "startingGold1M"],
138+
["startingGold25M", "startingGold1M"],
139+
["isHardNations", "startingGold25M"],
140+
["isNukesDisabled", "isSAMsDisabled"],
127141
];
128142

129143
export class MapPlaylist {
@@ -144,13 +158,14 @@ export class MapPlaylist {
144158
const playerTeams =
145159
mode === GameMode.Team ? this.getTeamCount(map) : undefined;
146160

147-
let isCompact = this.playlists[type].length % 3 === 0;
161+
let isCompact: boolean | undefined =
162+
this.playlists[type].length % 3 === 0 || undefined;
148163
if (
149164
isCompact &&
150165
mode === GameMode.Team &&
151166
!(await this.supportsCompactMapForTeams(map, playerTeams!))
152167
) {
153-
isCompact = false;
168+
isCompact = undefined;
154169
}
155170

156171
return {
@@ -162,10 +177,6 @@ export class MapPlaylist {
162177
gameMapSize: isCompact ? GameMapSize.Compact : GameMapSize.Normal,
163178
publicGameModifiers: {
164179
isCompact,
165-
isRandomSpawn: false,
166-
isCrowded: false,
167-
isHardNations: false,
168-
isAlliancesDisabled: false,
169180
},
170181
difficulty:
171182
playerTeams === HumansVsNations ? Difficulty.Hard : Difficulty.Medium,
@@ -216,7 +227,8 @@ export class MapPlaylist {
216227
excludedModifiers.push("isHardNations");
217228
}
218229
if (playerTeams === HumansVsNations) {
219-
excludedModifiers.push("startingGoldHigh"); // Nations are disabled if that modifier is active
230+
excludedModifiers.push("startingGold25M"); // Nations are disabled if that modifier is active (Because of PVP immunity)
231+
excludedModifiers.push("isPeaceTime"); // Nations don't have PVP immunity
220232
}
221233

222234
const poolResult = this.getRandomSpecialGameModifiers(excludedModifiers);
@@ -228,26 +240,34 @@ export class MapPlaylist {
228240
goldMultiplier,
229241
isAlliancesDisabled,
230242
isHardNations,
243+
isPortsDisabled,
244+
isNukesDisabled,
245+
isSAMsDisabled,
246+
isPeaceTime,
231247
} = poolResult;
232248

233249
// Crowded modifier: if the map's biggest player count (first number of calculateMapPlayerCounts) is 60 or lower (small maps),
234250
// set player count to MAX_PLAYER_COUNT (or 60 if compact map is also enabled)
235251
let crowdedMaxPlayers: number | undefined;
236252
if (isCrowded) {
237-
crowdedMaxPlayers = await this.getCrowdedMaxPlayers(map, isCompact);
253+
crowdedMaxPlayers = await this.getCrowdedMaxPlayers(map, !!isCompact);
238254
if (crowdedMaxPlayers !== undefined) {
239255
crowdedMaxPlayers = this.adjustForTeams(crowdedMaxPlayers, playerTeams);
240256
} else {
241257
// Map doesn't support crowded. Drop it and pick one replacement only
242258
// if it was the sole modifier, so the lobby always has at least one.
243-
isCrowded = false;
259+
isCrowded = undefined;
244260
if (
245261
!isRandomSpawn &&
246262
!isCompact &&
247263
!isHardNations &&
248264
startingGold === undefined &&
249265
goldMultiplier === undefined &&
250-
!isAlliancesDisabled
266+
!isAlliancesDisabled &&
267+
!isPortsDisabled &&
268+
!isNukesDisabled &&
269+
!isSAMsDisabled &&
270+
!isPeaceTime
251271
) {
252272
excludedModifiers.push("isCrowded");
253273
const fallback = this.getRandomSpecialGameModifiers(
@@ -260,6 +280,10 @@ export class MapPlaylist {
260280
startingGold,
261281
goldMultiplier,
262282
isAlliancesDisabled,
283+
isPortsDisabled,
284+
isNukesDisabled,
285+
isSAMsDisabled,
286+
isPeaceTime,
263287
} = fallback);
264288
({ isHardNations } = fallback);
265289
}
@@ -279,6 +303,28 @@ export class MapPlaylist {
279303
? "disabled"
280304
: "default";
281305

306+
// Build disabledUnits from modifiers
307+
const disabledUnits: UnitType[] = [];
308+
if (isPortsDisabled) {
309+
disabledUnits.push(UnitType.Port);
310+
}
311+
if (isNukesDisabled) {
312+
disabledUnits.push(
313+
UnitType.MissileSilo,
314+
UnitType.AtomBomb,
315+
UnitType.HydrogenBomb,
316+
UnitType.MIRV,
317+
UnitType.SAMLauncher,
318+
);
319+
}
320+
if (isSAMsDisabled) {
321+
disabledUnits.push(UnitType.SAMLauncher);
322+
}
323+
324+
// 3min peace = 180s = 1800 ticks
325+
// 4min peace = 240s = 2400 ticks
326+
const peaceTimeDuration = isPeaceTime ? 240 * 10 : undefined;
327+
282328
return {
283329
donateGold: mode === GameMode.Team,
284330
donateTroops: mode === GameMode.Team,
@@ -294,10 +340,14 @@ export class MapPlaylist {
294340
startingGold,
295341
goldMultiplier,
296342
isAlliancesDisabled,
343+
isPortsDisabled,
344+
isNukesDisabled,
345+
isSAMsDisabled,
346+
isPeaceTime,
297347
},
298348
startingGold,
299349
goldMultiplier,
300-
disableAlliances: isAlliancesDisabled,
350+
disableAlliances: isAlliancesDisabled ? true : undefined,
301351
difficulty:
302352
isHardNations || playerTeams === HumansVsNations
303353
? Difficulty.Hard
@@ -306,16 +356,15 @@ export class MapPlaylist {
306356
infiniteTroops: false,
307357
maxTimerValue: undefined,
308358
instantBuild: false,
309-
randomSpawn: isRandomSpawn,
359+
randomSpawn: isRandomSpawn ? true : false,
310360
nations,
311361
gameMode: mode,
312362
playerTeams,
313363
bots: isCompact ? 100 : 400,
314-
spawnImmunityDuration: this.getSpawnImmunityDuration(
315-
playerTeams,
316-
startingGold,
317-
),
318-
disabledUnits: [],
364+
spawnImmunityDuration:
365+
peaceTimeDuration ??
366+
this.getSpawnImmunityDuration(playerTeams, startingGold),
367+
disabledUnits,
319368
} satisfies GameConfig;
320369
}
321370

@@ -476,17 +525,23 @@ export class MapPlaylist {
476525
}
477526

478527
return {
479-
isRandomSpawn: selected.has("isRandomSpawn"),
480-
isCompact: selected.has("isCompact"),
481-
isCrowded: selected.has("isCrowded"),
482-
isHardNations: selected.has("isHardNations"),
483-
startingGold: selected.has("startingGoldHigh")
528+
isRandomSpawn: selected.has("isRandomSpawn") || undefined,
529+
isCompact: selected.has("isCompact") || undefined,
530+
isCrowded: selected.has("isCrowded") || undefined,
531+
isHardNations: selected.has("isHardNations") || undefined,
532+
startingGold: selected.has("startingGold25M")
484533
? 25_000_000
485-
: selected.has("startingGold")
534+
: selected.has("startingGold5M")
486535
? 5_000_000
487-
: undefined,
536+
: selected.has("startingGold1M")
537+
? 1_000_000
538+
: undefined,
488539
goldMultiplier: selected.has("goldMultiplier") ? 2 : undefined,
489-
isAlliancesDisabled: selected.has("isAlliancesDisabled"),
540+
isAlliancesDisabled: selected.has("isAlliancesDisabled") || undefined,
541+
isPortsDisabled: selected.has("isPortsDisabled") || undefined,
542+
isNukesDisabled: selected.has("isNukesDisabled") || undefined,
543+
isSAMsDisabled: selected.has("isSAMsDisabled") || undefined,
544+
isPeaceTime: selected.has("isPeaceTime") || undefined,
490545
};
491546
}
492547

0 commit comments

Comments
 (0)