diff --git a/resources/lang/en.json b/resources/lang/en.json
index c11ce34d9..9240303c8 100644
--- a/resources/lang/en.json
+++ b/resources/lang/en.json
@@ -139,6 +139,7 @@
"options_title": "Options",
"bots": "Bots: ",
"bots_disabled": "Disabled",
+ "nations": "Nations: ",
"disable_nations": "Disable Nations",
"instant_build": "Instant build",
"infinite_gold": "Infinite gold",
@@ -146,6 +147,7 @@
"compact_map": "Mini Map",
"max_timer": "Game length (minutes)",
"disable_nukes": "Disable Nukes",
+ "automatic_difficulty": "Automatic Difficulty",
"enables_title": "Enable Settings",
"start": "Start Game"
},
@@ -222,6 +224,7 @@
"teams_Duos": "Duos (teams of 2)",
"teams_Trios": "Trios (teams of 3)",
"teams_Quads": "Quads (teams of 4)",
+ "teams_hvn": "Humans Vs Nations",
"teams": "{num} teams"
},
"matchmaking_modal": {
@@ -244,6 +247,7 @@
"options_title": "Options",
"bots": "Bots: ",
"bots_disabled": "Disabled",
+ "nations": "Nations: ",
"disable_nations": "Disable Nations",
"max_timer": "Game length (minutes)",
"instant_build": "Instant build",
@@ -252,6 +256,7 @@
"infinite_troops": "Infinite troops",
"donate_troops": "Donate troops",
"compact_map": "Mini Map",
+ "automatic_difficulty": "Automatic Difficulty",
"enables_title": "Enable Settings",
"player": "Player",
"players": "Players",
diff --git a/src/client/HostLobbyModal.ts b/src/client/HostLobbyModal.ts
index a7a743ff1..4da086791 100644
--- a/src/client/HostLobbyModal.ts
+++ b/src/client/HostLobbyModal.ts
@@ -9,6 +9,7 @@ import {
GameMapSize,
GameMapType,
GameMode,
+ HumansVsNations,
Quads,
Trios,
UnitType,
@@ -284,7 +285,18 @@ export class HostLobbyModal extends LitElement {
${translateText("host_modal.team_count")}
- ${[2, 3, 4, 5, 6, 7, Quads, Trios, Duos].map(
+ ${[
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ Quads,
+ Trios,
+ Duos,
+ HumansVsNations,
+ ].map(
(o) => html`
${typeof o === "string"
- ? translateText(`public_lobby.teams_${o}`)
+ ? o === HumansVsNations
+ ? translateText("public_lobby.teams_hvn")
+ : translateText(`public_lobby.teams_${o}`)
: translateText("public_lobby.teams", {
num: o,
})}
@@ -313,42 +327,53 @@ export class HostLobbyModal extends LitElement {
${translateText("host_modal.options_title")}
-
-
-
- ${[2, 3, 4, 5, 6, 7, Quads, Trios, Duos].map(
+ ${[
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ Quads,
+ Trios,
+ Duos,
+ HumansVsNations,
+ ].map(
(o) => html`
${typeof o === "string"
- ? translateText(`public_lobby.teams_${o}`)
+ ? o === HumansVsNations
+ ? translateText("public_lobby.teams_hvn")
+ : translateText(`public_lobby.teams_${o}`)
: translateText(`public_lobby.teams`, { num: o })}
@@ -240,21 +254,29 @@ export class SinglePlayerModal extends LitElement {
-
-
-
-
- ${translateText("single_modal.disable_nations")}
-
-
+ ${!(
+ this.gameMode === GameMode.Team &&
+ this.teamCount === HumansVsNations
+ )
+ ? html`
+
+
+
+
+ ${translateText("single_modal.disable_nations")}
+
+
+ `
+ : ""}
+
Object.values(UnitType).find((ut) => ut === u))
.filter((ut): ut is UnitType => ut !== undefined),
+ ...(this.gameMode === GameMode.Team &&
+ this.teamCount === HumansVsNations
+ ? {
+ disableNPCs: false,
+ }
+ : {
+ disableNPCs: this.disableNPCs,
+ }),
},
},
} satisfies JoinLobbyEvent,
diff --git a/src/core/Schemas.ts b/src/core/Schemas.ts
index 86126db2b..a390e9912 100644
--- a/src/core/Schemas.ts
+++ b/src/core/Schemas.ts
@@ -14,6 +14,7 @@ import {
GameMapType,
GameMode,
GameType,
+ HumansVsNations,
Quads,
Trios,
UnitType,
@@ -149,6 +150,7 @@ const TeamCountConfigSchema = z.union([
z.literal(Duos),
z.literal(Trios),
z.literal(Quads),
+ z.literal(HumansVsNations),
]);
export type TeamCountConfig = z.infer;
diff --git a/src/core/configuration/ColorAllocator.ts b/src/core/configuration/ColorAllocator.ts
index 3be99c174..e62726bb0 100644
--- a/src/core/configuration/ColorAllocator.ts
+++ b/src/core/configuration/ColorAllocator.ts
@@ -47,6 +47,10 @@ export class ColorAllocator {
return greenTeamColors;
case ColoredTeams.Bot:
return botTeamColors;
+ case ColoredTeams.Humans:
+ return blueTeamColors;
+ case ColoredTeams.Nations:
+ return redTeamColors;
default:
return [this.assignColor(team)];
}
diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts
index ee7b8d1ab..af861ae47 100644
--- a/src/core/configuration/DefaultConfig.ts
+++ b/src/core/configuration/DefaultConfig.ts
@@ -8,6 +8,7 @@ import {
GameMode,
GameType,
Gold,
+ HumansVsNations,
Player,
PlayerInfo,
PlayerType,
@@ -195,6 +196,9 @@ export abstract class DefaultServerConfig implements ServerConfig {
case Quads:
p -= p % 4;
break;
+ case HumansVsNations:
+ // For HumansVsNations, return the base team player count
+ break;
default:
p -= p % numPlayerTeams;
break;
diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts
index 35fbea341..7353ddcc1 100644
--- a/src/core/game/Game.ts
+++ b/src/core/game/Game.ts
@@ -53,6 +53,7 @@ export type Team = string;
export const Duos = "Duos" as const;
export const Trios = "Trios" as const;
export const Quads = "Quads" as const;
+export const HumansVsNations = "Humans Vs Nations" as const;
export const ColoredTeams: Record = {
Red: "Red",
@@ -63,6 +64,8 @@ export const ColoredTeams: Record = {
Orange: "Orange",
Green: "Green",
Bot: "Bot",
+ Humans: "Humans",
+ Nations: "Nations",
} as const;
export enum GameMapType {
diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts
index b4712954a..cccb25902 100644
--- a/src/core/game/GameImpl.ts
+++ b/src/core/game/GameImpl.ts
@@ -15,6 +15,7 @@ import {
Game,
GameMode,
GameUpdates,
+ HumansVsNations,
MessageType,
MutableAlliance,
Nation,
@@ -105,6 +106,13 @@ export class GameImpl implements Game {
private populateTeams() {
let numPlayerTeams = this._config.playerTeams();
+
+ // HumansVsNations mode always has exactly 2 teams
+ if (numPlayerTeams === HumansVsNations) {
+ this.playerTeams = [ColoredTeams.Humans, ColoredTeams.Nations];
+ return;
+ }
+
if (typeof numPlayerTeams !== "number") {
const players = this._humans.length + this._nations.length;
switch (numPlayerTeams) {
@@ -139,11 +147,21 @@ export class GameImpl implements Game {
}
private addPlayers() {
- if (this.config().gameConfig().gameMode !== GameMode.Team) {
+ if (this.config().gameConfig().gameMode === GameMode.FFA) {
this._humans.forEach((p) => this.addPlayer(p));
this._nations.forEach((n) => this.addPlayer(n.playerInfo));
return;
}
+
+ if (this._config.playerTeams() === HumansVsNations) {
+ this._humans.forEach((p) => this.addPlayer(p, ColoredTeams.Humans));
+ this._nations.forEach((n) =>
+ this.addPlayer(n.playerInfo, ColoredTeams.Nations),
+ );
+ return;
+ }
+
+ // Team mode
const allPlayers = [
...this._humans,
...this._nations.map((n) => n.playerInfo),
diff --git a/src/server/MapPlaylist.ts b/src/server/MapPlaylist.ts
index 369efdc05..b84938b97 100644
--- a/src/server/MapPlaylist.ts
+++ b/src/server/MapPlaylist.ts
@@ -7,6 +7,7 @@ import {
GameMapType,
GameMode,
GameType,
+ HumansVsNations,
Quads,
Trios,
} from "../core/game/Game";
@@ -67,6 +68,7 @@ const TEAM_COUNTS = [
Duos,
Trios,
Quads,
+ HumansVsNations,
] as const satisfies TeamCountConfig[];
export class MapPlaylist {
@@ -93,7 +95,7 @@ export class MapPlaylist {
infiniteTroops: false,
maxTimerValue: undefined,
instantBuild: false,
- disableNPCs: mode === GameMode.Team,
+ disableNPCs: mode === GameMode.Team && playerTeams !== HumansVsNations,
gameMode: mode,
playerTeams,
bots: 400,
diff --git a/tests/Colors.test.ts b/tests/Colors.test.ts
index bbeccde57..e1178c9a2 100644
--- a/tests/Colors.test.ts
+++ b/tests/Colors.test.ts
@@ -90,6 +90,8 @@ describe("ColorAllocator", () => {
expect(allocator.assignTeamColor(ColoredTeams.Orange)).toEqual(orange);
expect(allocator.assignTeamColor(ColoredTeams.Green)).toEqual(green);
expect(allocator.assignTeamColor(ColoredTeams.Bot)).toEqual(botColor);
+ expect(allocator.assignTeamColor(ColoredTeams.Humans)).toEqual(blue);
+ expect(allocator.assignTeamColor(ColoredTeams.Nations)).toEqual(red);
});
test("assignTeamPlayerColor always returns the same color for the same playerID", () => {