This commit is contained in:
Scott Anderson
2025-08-24 21:32:32 -04:00
parent eaefecb00f
commit 809d60ff58
95 changed files with 1385 additions and 1424 deletions
+4 -4
View File
@@ -213,10 +213,10 @@ export class ClientGameRunner {
}
const players: PlayerRecord[] = [
{
persistentID: getPersistentID(),
username: this.lobby.playerName,
clientID: this.lobby.clientID,
persistentID: getPersistentID(),
stats: update.allPlayersStats[this.lobby.clientID],
username: this.lobby.playerName,
},
];
@@ -312,8 +312,8 @@ export class ClientGameRunner {
}
while (turn.turnNumber - 1 > this.turnsSeen) {
this.worker.sendTurn({
turnNumber: this.turnsSeen,
intents: [],
turnNumber: this.turnsSeen,
});
this.turnsSeen++;
}
@@ -471,9 +471,9 @@ export class ClientGameRunner {
);
upgradeUnits.push({
distance,
unitId: bu.canUpgrade,
unitType: bu.type,
distance,
});
}
}
+6 -6
View File
@@ -38,16 +38,16 @@ export async function handlePurchase(priceId: string) {
const response = await fetch(
`${getApiBase()}/stripe/create-checkout-session`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
authorization: getAuthHeader(),
},
body: JSON.stringify({
cancelUrl: `${window.location.origin}#purchase-completed=false`,
priceId,
successUrl: `${window.location.origin}#purchase-completed=true`,
cancelUrl: `${window.location.origin}#purchase-completed=false`,
}),
headers: {
authorization: getAuthHeader(),
"Content-Type": "application/json",
},
method: "POST",
},
);
+1 -1
View File
@@ -36,9 +36,9 @@ export class FlagInput extends LitElement {
private dispatchFlagEvent() {
this.dispatchEvent(
new CustomEvent("flag-change", {
detail: { flag: this.flag },
bubbles: true,
composed: true,
detail: { flag: this.flag },
}),
);
}
+1 -1
View File
@@ -87,9 +87,9 @@ export class FlagInputModal extends LitElement {
localStorage.setItem("flag", flag);
this.dispatchEvent(
new CustomEvent("flag-change", {
detail: { flag },
bubbles: true,
composed: true,
detail: { flag },
}),
);
}
+23 -23
View File
@@ -544,12 +544,12 @@ export class HostLobbyModal extends LitElement {
.then(() => {
this.dispatchEvent(
new CustomEvent("join-lobby", {
detail: {
gameID: this.lobbyId,
clientID: this.lobbyCreatorClientID,
} as JoinLobbyEvent,
bubbles: true,
composed: true,
detail: {
clientID: this.lobbyCreatorClientID,
gameID: this.lobbyId,
} as JoinLobbyEvent,
}),
);
});
@@ -655,24 +655,24 @@ export class HostLobbyModal extends LitElement {
const response = await fetch(
`${window.location.origin}/${config.workerPath(this.lobbyId)}/api/game/${this.lobbyId}`,
{
method: "PUT",
body: JSON.stringify({
bots: this.bots,
difficulty: this.selectedDifficulty,
disabledUnits: this.disabledUnits,
disableNPCs: this.disableNPCs,
donateGold: this.donateGold,
donateTroops: this.donateTroops,
gameMap: this.selectedMap,
gameMode: this.gameMode,
infiniteGold: this.infiniteGold,
infiniteTroops: this.infiniteTroops,
instantBuild: this.instantBuild,
playerTeams: this.teamCount,
} satisfies Partial<GameConfig>),
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
gameMap: this.selectedMap,
difficulty: this.selectedDifficulty,
disableNPCs: this.disableNPCs,
bots: this.bots,
infiniteGold: this.infiniteGold,
donateGold: this.donateGold,
infiniteTroops: this.infiniteTroops,
donateTroops: this.donateTroops,
instantBuild: this.instantBuild,
gameMode: this.gameMode,
disabledUnits: this.disabledUnits,
playerTeams: this.teamCount,
} satisfies Partial<GameConfig>),
method: "PUT",
},
);
return response;
@@ -709,10 +709,10 @@ export class HostLobbyModal extends LitElement {
const response = await fetch(
`${window.location.origin}/${config.workerPath(this.lobbyId)}/api/start_game/${this.lobbyId}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
method: "POST",
},
);
return response;
@@ -736,10 +736,10 @@ export class HostLobbyModal extends LitElement {
private async pollPlayers() {
const config = await getServerConfigFromClient();
fetch(`/${config.workerPath(this.lobbyId)}/api/game/${this.lobbyId}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
method: "GET",
})
.then((response) => response.json())
.then(GameInfoSchema.parse)
@@ -754,9 +754,9 @@ export class HostLobbyModal extends LitElement {
// Dispatch event to be handled by WebSocket instead of HTTP
this.dispatchEvent(
new CustomEvent("kick-player", {
detail: { target: clientID },
bubbles: true,
composed: true,
detail: { target: clientID },
}),
);
}
@@ -769,10 +769,10 @@ async function createLobby(creatorClientID: string): Promise<GameInfo> {
const response = await fetch(
`/${config.workerPath(id)}/api/create_game/${id}?creatorClientID=${encodeURIComponent(creatorClientID)}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
method: "POST",
// body: JSON.stringify(data), // Include this if you need to send data
},
);
+9 -9
View File
@@ -145,20 +145,20 @@ export class InputHandler {
initialize() {
this.keybinds = {
toggleView: "Space",
centerCamera: "KeyC",
moveUp: "KeyW",
moveDown: "KeyS",
moveLeft: "KeyA",
moveRight: "KeyD",
zoomOut: "KeyQ",
zoomIn: "KeyE",
altKey: "AltLeft",
attackRatioDown: "Digit1",
attackRatioUp: "Digit2",
boatAttack: "KeyB",
centerCamera: "KeyC",
groundAttack: "KeyG",
modifierKey: "ControlLeft",
altKey: "AltLeft",
moveDown: "KeyS",
moveLeft: "KeyA",
moveRight: "KeyD",
moveUp: "KeyW",
toggleView: "Space",
zoomIn: "KeyE",
zoomOut: "KeyQ",
...(JSON.parse(localStorage.getItem("settings.keybinds") ?? "{}") ?? {}),
};
+13 -13
View File
@@ -145,9 +145,9 @@ export class JoinPrivateLobbyModal extends LitElement {
this.message = "";
this.dispatchEvent(
new CustomEvent("leave-lobby", {
detail: { lobby: this.lobbyIdInput.value },
bubbles: true,
composed: true,
detail: { lobby: this.lobbyIdInput.value },
}),
);
}
@@ -211,8 +211,8 @@ export class JoinPrivateLobbyModal extends LitElement {
const url = `/${config.workerPath(lobbyId)}/api/game/${lobbyId}/exists`;
const response = await fetch(url, {
method: "GET",
headers: { "Content-Type": "application/json" },
method: "GET",
});
const json = await response.json();
@@ -224,12 +224,12 @@ export class JoinPrivateLobbyModal extends LitElement {
this.dispatchEvent(
new CustomEvent("join-lobby", {
detail: {
gameID: lobbyId,
clientID: getClientID(lobbyId),
} as JoinLobbyEvent,
bubbles: true,
composed: true,
detail: {
clientID: getClientID(lobbyId),
gameID: lobbyId,
} as JoinLobbyEvent,
}),
);
@@ -245,8 +245,8 @@ export class JoinPrivateLobbyModal extends LitElement {
const archiveUrl = `/${config.workerPath(lobbyId)}/api/archived_game/${lobbyId}`;
const archiveResponse = await fetch(archiveUrl, {
method: "GET",
headers: { "Content-Type": "application/json" },
method: "GET",
});
const json = await archiveResponse.json();
@@ -268,13 +268,13 @@ export class JoinPrivateLobbyModal extends LitElement {
if (archiveData.exists) {
this.dispatchEvent(
new CustomEvent("join-lobby", {
detail: {
gameID: lobbyId,
gameRecord: archiveData.gameRecord,
clientID: getClientID(lobbyId),
} as JoinLobbyEvent,
bubbles: true,
composed: true,
detail: {
clientID: getClientID(lobbyId),
gameID: lobbyId,
gameRecord: archiveData.gameRecord,
} as JoinLobbyEvent,
}),
);
@@ -291,10 +291,10 @@ export class JoinPrivateLobbyModal extends LitElement {
fetch(
`/${config.workerPath(this.lobbyIdInput.value)}/api/game/${this.lobbyIdInput.value}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
method: "GET",
},
)
.then((response) => response.json())
+18 -18
View File
@@ -51,32 +51,32 @@ export class LangSelector extends LitElement {
ar,
bg,
bn,
cs,
da,
de,
en,
es,
eo,
es,
fi,
fr,
it,
gl,
he,
hi,
it,
ja,
ko,
nl,
pl,
"pt-BR": pt_BR,
ru,
sh,
tr,
tp,
uk,
cs,
he,
da,
fi,
"sv-SE": sv_SE,
"zh-CN": zh_CN,
ko,
gl,
sl,
sk,
sl,
"sv-SE": sv_SE,
tp,
tr,
uk,
"zh-CN": zh_CN,
};
createRenderRoot() {
@@ -148,8 +148,8 @@ export class LangSelector extends LitElement {
list.push({
code: langData.lang_code ?? langCode,
native: langData.native ?? langCode,
en: langData.en ?? langCode,
native: langData.native ?? langCode,
svg: langData.svg ?? langCode,
});
}
@@ -159,8 +159,8 @@ export class LangSelector extends LitElement {
if (this.debugKeyPressed) {
debugLang = {
code: "debug",
native: "Debug",
en: "Debug",
native: "Debug",
svg: "xx",
};
this.debugMode = true;
@@ -290,13 +290,13 @@ export class LangSelector extends LitElement {
(this.currentLang === "debug"
? {
code: "debug",
native: "Debug",
en: "Debug",
native: "Debug",
svg: "xx",
}
: {
native: "English",
en: "English",
native: "English",
svg: "uk_us_flag",
});
+1 -1
View File
@@ -54,9 +54,9 @@ export class LanguageModal extends LitElement {
private readonly selectLanguage = (lang: string) => {
this.dispatchEvent(
new CustomEvent("language-selected", {
detail: { lang },
bubbles: true,
composed: true,
detail: { lang },
}),
);
};
+1 -1
View File
@@ -12,9 +12,9 @@ import { replacer } from "../core/Util";
const LocalStatsDataSchema = z.record(
ID,
z.object({
lobby: GameConfigSchema.partial(),
// Only once the game is over
gameRecord: GameRecordSchema.optional(),
lobby: GameConfigSchema.partial(),
}),
);
type LocalStatsData = z.infer<typeof LocalStatsDataSchema>;
+8 -8
View File
@@ -76,9 +76,9 @@ export class LocalServer {
throw new Error("missing gameStartInfo");
}
this.clientMessage({
type: "start",
gameStartInfo: this.lobbyConfig.gameStartInfo,
turns: [],
type: "start",
} satisfies ServerStartGameMessage);
}
@@ -122,11 +122,11 @@ export class LocalServer {
}, server hash: ${archivedHash}`,
);
this.clientMessage({
type: "desync",
turn: clientMsg.turnNumber,
correctHash: archivedHash,
clientsWithCorrectHash: 0,
correctHash: archivedHash,
totalActiveClients: 1,
turn: clientMsg.turnNumber,
type: "desync",
yourHash: clientMsg.hash,
});
} else {
@@ -160,14 +160,14 @@ export class LocalServer {
this.intents = this.replayTurns[this.turns.length].intents;
}
const pastTurn: Turn = {
turnNumber: this.turns.length,
intents: this.intents,
turnNumber: this.turns.length,
};
this.turns.push(pastTurn);
this.intents = [];
this.clientMessage({
type: "turn",
turn: pastTurn,
type: "turn",
});
}
@@ -179,10 +179,10 @@ export class LocalServer {
}
const players: PlayerRecord[] = [
{
persistentID: getPersistentID(),
username: this.lobbyConfig.playerName,
clientID: this.lobbyConfig.clientID,
persistentID: getPersistentID(),
stats: this.allPlayersStats[this.lobbyConfig.clientID],
username: this.lobbyConfig.playerName,
},
];
if (this.lobbyConfig.gameStartInfo === undefined) {
+7 -7
View File
@@ -471,18 +471,18 @@ class Client {
this.gameStop = joinLobby(
this.eventBus,
{
gameID: lobby.gameID,
serverConfig: config,
pattern: this.userSettings.getSelectedPattern(),
clientID: getClientID(lobby.gameID),
flag:
this.flagInput === null || this.flagInput.getCurrentFlag() === "xx"
? ""
: this.flagInput.getCurrentFlag(),
playerName: this.usernameInput?.getCurrentUsername() ?? "",
token: getPlayToken(),
clientID: getClientID(lobby.gameID),
gameStartInfo: lobby.gameStartInfo ?? lobby.gameRecord?.info,
gameID: lobby.gameID,
gameRecord: lobby.gameRecord,
gameStartInfo: lobby.gameStartInfo ?? lobby.gameRecord?.info,
pattern: this.userSettings.getSelectedPattern(),
playerName: this.usernameInput?.getCurrentUsername() ?? "",
serverConfig: config,
token: getPlayToken(),
},
() => {
console.log("Closing modals");
+1 -1
View File
@@ -74,8 +74,8 @@ export class NewsModal extends LitElement {
<div class="news-container">
<div class="news-content">
${resolveMarkdown(this.markdown, {
includeImages: true,
includeCodeBlockClassNames: true,
includeImages: true,
})}
</div>
</div>
+5 -5
View File
@@ -206,20 +206,20 @@ export class PublicLobby extends LitElement {
this.currLobby = lobby;
this.dispatchEvent(
new CustomEvent("join-lobby", {
detail: {
gameID: lobby.gameID,
clientID: getClientID(lobby.gameID),
} as JoinLobbyEvent,
bubbles: true,
composed: true,
detail: {
clientID: getClientID(lobby.gameID),
gameID: lobby.gameID,
} as JoinLobbyEvent,
}),
);
} else {
this.dispatchEvent(
new CustomEvent("leave-lobby", {
detail: { lobby: this.currLobby },
bubbles: true,
composed: true,
detail: { lobby: this.currLobby },
}),
);
this.leaveLobby();
+20 -20
View File
@@ -439,43 +439,43 @@ export class SinglePlayerModal extends LitElement {
}
this.dispatchEvent(
new CustomEvent("join-lobby", {
bubbles: true,
composed: true,
detail: {
clientID,
gameID,
gameStartInfo: {
config: {
bots: this.bots,
difficulty: this.selectedDifficulty,
disabledUnits: this.disabledUnits
.map((u) => Object.values(UnitType).find((ut) => ut === u))
.filter((ut): ut is UnitType => ut !== undefined),
disableNPCs: this.disableNPCs,
donateGold: this.donateGold,
donateTroops: this.donateTroops,
gameMap: this.selectedMap,
gameMode: this.gameMode,
gameType: GameType.Singleplayer,
infiniteGold: this.infiniteGold,
infiniteTroops: this.infiniteTroops,
instantBuild: this.instantBuild,
playerTeams: this.teamCount,
},
gameID,
players: [
{
clientID,
username: usernameInput.getCurrentUsername(),
flag:
flagInput.getCurrentFlag() === "xx"
? ""
: flagInput.getCurrentFlag(),
pattern: this.userSettings.getSelectedPattern(),
username: usernameInput.getCurrentUsername(),
},
],
config: {
gameMap: this.selectedMap,
gameType: GameType.Singleplayer,
gameMode: this.gameMode,
playerTeams: this.teamCount,
difficulty: this.selectedDifficulty,
disableNPCs: this.disableNPCs,
bots: this.bots,
infiniteGold: this.infiniteGold,
donateGold: this.donateGold,
infiniteTroops: this.infiniteTroops,
donateTroops: this.donateTroops,
instantBuild: this.instantBuild,
disabledUnits: this.disabledUnits
.map((u) => Object.values(UnitType).find((ut) => ut === u))
.filter((ut): ut is UnitType => ut !== undefined),
},
},
} satisfies JoinLobbyEvent,
bubbles: true,
composed: true,
}),
);
this.close();
+40 -40
View File
@@ -375,14 +375,14 @@ export class Transport {
joinGame(numTurns: number) {
this.sendMsg({
type: "join",
gameID: this.lobbyConfig.gameID,
clientID: this.lobbyConfig.clientID,
lastTurn: numTurns,
token: this.lobbyConfig.token,
username: this.lobbyConfig.playerName,
flag: this.lobbyConfig.flag,
gameID: this.lobbyConfig.gameID,
lastTurn: numTurns,
pattern: this.lobbyConfig.pattern,
token: this.lobbyConfig.token,
type: "join",
username: this.lobbyConfig.playerName,
} satisfies ClientJoinMessage);
}
@@ -408,26 +408,26 @@ export class Transport {
private onSendAllianceRequest(event: SendAllianceRequestIntentEvent) {
this.sendIntent({
type: "allianceRequest",
clientID: this.lobbyConfig.clientID,
recipient: event.recipient.id(),
type: "allianceRequest",
});
}
private onAllianceRequestReplyUIEvent(event: SendAllianceReplyIntentEvent) {
this.sendIntent({
type: "allianceRequestReply",
accept: event.accepted,
clientID: this.lobbyConfig.clientID,
requestor: event.requestor.id(),
accept: event.accepted,
type: "allianceRequestReply",
});
}
private onBreakAllianceRequestUIEvent(event: SendBreakAllianceIntentEvent) {
this.sendIntent({
type: "breakAlliance",
clientID: this.lobbyConfig.clientID,
recipient: event.recipient.id(),
type: "breakAlliance",
});
}
@@ -435,114 +435,114 @@ export class Transport {
event: SendAllianceExtensionIntentEvent,
) {
this.sendIntent({
type: "allianceExtension",
clientID: this.lobbyConfig.clientID,
recipient: event.recipient.id(),
type: "allianceExtension",
});
}
private onSendSpawnIntentEvent(event: SendSpawnIntentEvent) {
this.sendIntent({
type: "spawn",
clientID: this.lobbyConfig.clientID,
flag: this.lobbyConfig.flag,
pattern: this.lobbyConfig.pattern,
name: this.lobbyConfig.playerName,
pattern: this.lobbyConfig.pattern,
playerType: PlayerType.Human,
tile: event.tile,
type: "spawn",
});
}
private onSendAttackIntent(event: SendAttackIntentEvent) {
this.sendIntent({
type: "attack",
clientID: this.lobbyConfig.clientID,
targetID: event.targetID,
troops: event.troops,
type: "attack",
});
}
private onSendBoatAttackIntent(event: SendBoatAttackIntentEvent) {
this.sendIntent({
type: "boat",
clientID: this.lobbyConfig.clientID,
targetID: event.targetID,
troops: event.troops,
dst: event.dst,
src: event.src,
targetID: event.targetID,
troops: event.troops,
type: "boat",
});
}
private onSendUpgradeStructureIntent(event: SendUpgradeStructureIntentEvent) {
this.sendIntent({
clientID: this.lobbyConfig.clientID,
type: "upgrade_structure",
unit: event.unitType,
clientID: this.lobbyConfig.clientID,
unitId: event.unitId,
});
}
private onSendTargetPlayerIntent(event: SendTargetPlayerIntentEvent) {
this.sendIntent({
type: "targetPlayer",
clientID: this.lobbyConfig.clientID,
target: event.targetID,
type: "targetPlayer",
});
}
private onSendEmojiIntent(event: SendEmojiIntentEvent) {
this.sendIntent({
type: "emoji",
clientID: this.lobbyConfig.clientID,
emoji: event.emoji,
recipient:
event.recipient === AllPlayers ? AllPlayers : event.recipient.id(),
emoji: event.emoji,
type: "emoji",
});
}
private onSendDonateGoldIntent(event: SendDonateGoldIntentEvent) {
this.sendIntent({
type: "donate_gold",
clientID: this.lobbyConfig.clientID,
recipient: event.recipient.id(),
gold: event.gold,
recipient: event.recipient.id(),
type: "donate_gold",
});
}
private onSendDonateTroopIntent(event: SendDonateTroopsIntentEvent) {
this.sendIntent({
type: "donate_troops",
clientID: this.lobbyConfig.clientID,
recipient: event.recipient.id(),
troops: event.troops,
type: "donate_troops",
});
}
private onSendQuickChatIntent(event: SendQuickChatEvent) {
this.sendIntent({
type: "quick_chat",
clientID: this.lobbyConfig.clientID,
recipient: event.recipient.id(),
quickChatKey: event.quickChatKey,
recipient: event.recipient.id(),
target: event.target,
type: "quick_chat",
});
}
private onSendEmbargoIntent(event: SendEmbargoIntentEvent) {
this.sendIntent({
type: "embargo",
action: event.action,
clientID: this.lobbyConfig.clientID,
targetID: event.target.id(),
action: event.action,
type: "embargo",
});
}
private onBuildUnitIntent(event: BuildUnitIntentEvent) {
this.sendIntent({
type: "build_unit",
clientID: this.lobbyConfig.clientID,
unit: event.unit,
tile: event.tile,
type: "build_unit",
unit: event.unit,
});
}
@@ -561,9 +561,9 @@ export class Transport {
private onSendWinnerEvent(event: SendWinnerEvent) {
if (this.isLocal || this.socket?.readyState === WebSocket.OPEN) {
this.sendMsg({
allPlayersStats: event.allPlayersStats,
type: "winner",
winner: event.winner,
allPlayersStats: event.allPlayersStats,
} satisfies ClientSendWinnerMessage);
} else {
console.log(
@@ -577,9 +577,9 @@ export class Transport {
private onSendHashEvent(event: SendHashEvent) {
if (this.isLocal || this.socket?.readyState === WebSocket.OPEN) {
this.sendMsg({
type: "hash",
turnNumber: event.tick,
hash: event.hash,
turnNumber: event.tick,
type: "hash",
} satisfies ClientHashMessage);
} else {
console.log(
@@ -592,50 +592,50 @@ export class Transport {
private onCancelAttackIntentEvent(event: CancelAttackIntentEvent) {
this.sendIntent({
type: "cancel_attack",
clientID: this.lobbyConfig.clientID,
attackID: event.attackID,
clientID: this.lobbyConfig.clientID,
type: "cancel_attack",
});
}
private onCancelBoatIntentEvent(event: CancelBoatIntentEvent) {
this.sendIntent({
type: "cancel_boat",
clientID: this.lobbyConfig.clientID,
type: "cancel_boat",
unitID: event.unitID,
});
}
private onMoveWarshipEvent(event: MoveWarshipIntentEvent) {
this.sendIntent({
type: "move_warship",
clientID: this.lobbyConfig.clientID,
unitId: event.unitId,
tile: event.tile,
type: "move_warship",
unitId: event.unitId,
});
}
private onSendDeleteUnitIntent(event: SendDeleteUnitIntentEvent) {
this.sendIntent({
type: "delete_unit",
clientID: this.lobbyConfig.clientID,
type: "delete_unit",
unitId: event.unitId,
});
}
private onSendKickPlayerIntent(event: SendKickPlayerIntentEvent) {
this.sendIntent({
type: "kick_player",
clientID: this.lobbyConfig.clientID,
target: event.target,
type: "kick_player",
});
}
private sendIntent(intent: Intent) {
if (this.isLocal || this.socket?.readyState === WebSocket.OPEN) {
const msg = {
type: "intent",
intent,
type: "intent",
} satisfies ClientIntentMessage;
this.sendMsg(msg);
} else {
+1 -1
View File
@@ -100,9 +100,9 @@ export class UserSettingModal extends LitElement {
this.dispatchEvent(
new CustomEvent("dark-mode-changed", {
detail: { darkMode: enabled },
bubbles: true,
composed: true,
detail: { darkMode: enabled },
}),
);
+1 -1
View File
@@ -93,9 +93,9 @@ export class UsernameInput extends LitElement {
private dispatchUsernameEvent() {
this.dispatchEvent(
new CustomEvent("username-change", {
detail: { username: this.username },
bubbles: true,
composed: true,
detail: { username: this.username },
}),
);
}
+4 -4
View File
@@ -150,11 +150,11 @@ export const translateText = (
* Severity colors mapping for message types
*/
export const severityColors: Record<string, string> = {
fail: "text-red-400",
warn: "text-yellow-400",
success: "text-green-400",
info: "text-gray-200",
blue: "text-blue-400",
fail: "text-red-400",
info: "text-gray-200",
success: "text-green-400",
warn: "text-yellow-400",
white: "text-white",
};
+22 -22
View File
@@ -6,35 +6,35 @@ import { translateText } from "../Utils";
// Add map descriptions
export const MapDescription: Record<keyof typeof GameMapType, string> = {
World: "World",
GiantWorldMap: "Giant World Map",
Africa: "Africa",
Asia: "Asia",
Australia: "Australia",
Baikal: "Baikal",
BetweenTwoSeas: "Between Two Seas",
BlackSea: "Black Sea",
Britannia: "Britannia",
DeglaciatedAntarctica: "Deglaciated Antarctica",
EastAsia: "East Asia",
Europe: "Europe",
EuropeClassic: "Europe Classic",
FalklandIslands: "Falkland Islands",
FaroeIslands: "Faroe Islands",
GatewayToTheAtlantic: "Gateway to the Atlantic",
GiantWorldMap: "Giant World Map",
Halkidiki: "Halkidiki",
Iceland: "Iceland",
Italia: "Italia",
Mars: "Mars",
MarsRevised: "Mars Revised",
Mena: "MENA",
NorthAmerica: "North America",
Oceania: "Oceania",
BlackSea: "Black Sea",
Africa: "Africa",
Pangaea: "Pangaea",
Asia: "Asia",
Mars: "Mars",
MarsRevised: "Mars Revised",
SouthAmerica: "South America",
Britannia: "Britannia",
GatewayToTheAtlantic: "Gateway to the Atlantic",
Australia: "Australia",
Iceland: "Iceland",
EastAsia: "East Asia",
BetweenTwoSeas: "Between Two Seas",
FaroeIslands: "Faroe Islands",
DeglaciatedAntarctica: "Deglaciated Antarctica",
FalklandIslands: "Falkland Islands",
Baikal: "Baikal",
Halkidiki: "Halkidiki",
StraitOfGibraltar: "Strait of Gibraltar",
Italia: "Italia",
Yenisei: "Yenisei",
Pluto: "Pluto",
SouthAmerica: "South America",
StraitOfGibraltar: "Strait of Gibraltar",
World: "World",
Yenisei: "Yenisei",
};
@customElement("map-display")
@@ -23,8 +23,8 @@ export class OButton extends LitElement {
"c-button": true,
"c-button--block": this.block,
"c-button--blockDesktop": this.blockDesktop,
"c-button--secondary": this.secondary,
"c-button--disabled": this.disable,
"c-button--secondary": this.secondary,
})}
?disabled=${this.disable}
>
@@ -6,7 +6,7 @@ import { translateText } from "../../../../client/Utils";
export class SettingKeybind extends LitElement {
@property() label = "Setting";
@property() description = "";
@property({ type: String, reflect: true }) action = "";
@property({ reflect: true, type: String }) action = "";
@property({ type: String }) defaultKey = "";
@property({ type: String }) value = "";
@property({ type: Boolean }) easter = false;
@@ -80,9 +80,9 @@ export class SettingKeybind extends LitElement {
this.dispatchEvent(
new CustomEvent("change", {
detail: { action: this.action, value: code },
bubbles: true,
composed: true,
detail: { action: this.action, value: code },
}),
);
@@ -94,9 +94,9 @@ export class SettingKeybind extends LitElement {
this.value = this.defaultKey;
this.dispatchEvent(
new CustomEvent("change", {
detail: { action: this.action, value: this.defaultKey },
bubbles: true,
composed: true,
detail: { action: this.action, value: this.defaultKey },
}),
);
}
@@ -105,9 +105,9 @@ export class SettingKeybind extends LitElement {
this.value = "";
this.dispatchEvent(
new CustomEvent("change", {
detail: { action: this.action, value: "Null" },
bubbles: true,
composed: true,
detail: { action: this.action, value: "Null" },
}),
);
this.requestUpdate();
@@ -21,9 +21,9 @@ export class SettingNumber extends LitElement {
this.dispatchEvent(
new CustomEvent("change", {
detail: { value: newValue },
bubbles: true,
composed: true,
detail: { value: newValue },
}),
);
}
@@ -21,9 +21,9 @@ export class SettingSlider extends LitElement {
this.dispatchEvent(
new CustomEvent("change", {
detail: { value: this.value },
bubbles: true,
composed: true,
detail: { value: this.value },
}),
);
}
@@ -6,7 +6,7 @@ export class SettingToggle extends LitElement {
@property() label = "Setting";
@property() description = "";
@property() id = "";
@property({ type: Boolean, reflect: true }) checked = false;
@property({ reflect: true, type: Boolean }) checked = false;
@property({ type: Boolean }) easter = false;
createRenderRoot() {
@@ -18,9 +18,9 @@ export class SettingToggle extends LitElement {
this.checked = input.checked;
this.dispatchEvent(
new CustomEvent("change", {
detail: { checked: this.checked },
bubbles: true,
composed: true,
detail: { checked: this.checked },
}),
);
}
+22 -22
View File
@@ -27,103 +27,103 @@ type AnimatedSpriteConfig = {
const ANIMATED_SPRITE_CONFIG: Partial<Record<FxType, AnimatedSpriteConfig>> = {
[FxType.MiniFire]: {
url: miniFire,
frameWidth: 7,
frameCount: 6,
frameDuration: 100,
frameWidth: 7,
looping: true,
originX: 3,
originY: 11,
url: miniFire,
},
[FxType.MiniSmoke]: {
url: miniSmoke,
frameWidth: 11,
frameCount: 4,
frameDuration: 120,
frameWidth: 11,
looping: true,
originX: 2,
originY: 10,
url: miniSmoke,
},
[FxType.MiniBigSmoke]: {
url: miniBigSmoke,
frameWidth: 24,
frameCount: 5,
frameDuration: 120,
frameWidth: 24,
looping: true,
originX: 9,
originY: 14,
url: miniBigSmoke,
},
[FxType.MiniSmokeAndFire]: {
url: miniSmokeAndFire,
frameWidth: 24,
frameCount: 5,
frameDuration: 120,
frameWidth: 24,
looping: true,
originX: 9,
originY: 14,
url: miniSmokeAndFire,
},
[FxType.MiniExplosion]: {
url: miniExplosion,
frameWidth: 13,
frameCount: 4,
frameDuration: 70,
frameWidth: 13,
looping: false,
originX: 6,
originY: 6,
url: miniExplosion,
},
[FxType.Dust]: {
url: dust,
frameWidth: 9,
frameCount: 3,
frameDuration: 100,
frameWidth: 9,
looping: false,
originX: 4,
originY: 5,
url: dust,
},
[FxType.UnitExplosion]: {
url: unitExplosion,
frameWidth: 19,
frameCount: 4,
frameDuration: 70,
frameWidth: 19,
looping: false,
originX: 9,
originY: 9,
url: unitExplosion,
},
[FxType.SinkingShip]: {
url: sinkingShip,
frameWidth: 16,
frameCount: 14,
frameDuration: 90,
frameWidth: 16,
looping: false,
originX: 7,
originY: 7,
url: sinkingShip,
},
[FxType.Nuke]: {
url: nuke,
frameWidth: 60,
frameCount: 9,
frameDuration: 70,
frameWidth: 60,
looping: false,
originX: 30,
originY: 30,
url: nuke,
},
[FxType.SAMExplosion]: {
url: SAMExplosion,
frameWidth: 48,
frameCount: 9,
frameDuration: 70,
frameWidth: 48,
looping: false,
originX: 23,
originY: 19,
url: SAMExplosion,
},
[FxType.Conquest]: {
url: conquestSword,
frameWidth: 21,
frameCount: 10,
frameDuration: 90,
frameWidth: 21,
looping: false,
originX: 10,
originY: 16,
url: conquestSword,
},
};
+11 -11
View File
@@ -56,9 +56,9 @@ export function placeName(game: Game, player: Player): NameViewData {
center = new Cell(center.x, center.y - fontSize / 3);
return {
size: fontSize,
x: Math.ceil(center.x),
y: Math.ceil(center.y),
size: fontSize,
};
}
@@ -69,14 +69,14 @@ export function createGrid(
scalingFactor: number,
): boolean[][] {
const scaledBoundingBox: { min: Point; max: Point } = {
min: {
x: Math.floor(boundingBox.min.x / scalingFactor),
y: Math.floor(boundingBox.min.y / scalingFactor),
},
max: {
x: Math.floor(boundingBox.max.x / scalingFactor),
y: Math.floor(boundingBox.max.y / scalingFactor),
},
min: {
x: Math.floor(boundingBox.min.x / scalingFactor),
y: Math.floor(boundingBox.min.y / scalingFactor),
},
};
const width = scaledBoundingBox.max.x - scaledBoundingBox.min.x + 1;
@@ -105,7 +105,7 @@ export function findLargestInscribedRectangle(grid: boolean[][]): Rectangle {
const rows = grid[0].length;
const cols = grid.length;
const heights: number[] = new Array<number>(cols).fill(0);
let largestRect: Rectangle = { x: 0, y: 0, width: 0, height: 0 };
let largestRect: Rectangle = { height: 0, width: 0, x: 0, y: 0 };
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
@@ -123,10 +123,10 @@ export function findLargestInscribedRectangle(grid: boolean[][]): Rectangle {
largestRect.width * largestRect.height
) {
largestRect = {
height: rectForRow.height,
width: rectForRow.width,
x: rectForRow.x,
y: row - rectForRow.height + 1,
width: rectForRow.width,
height: rectForRow.height,
};
}
}
@@ -137,7 +137,7 @@ export function findLargestInscribedRectangle(grid: boolean[][]): Rectangle {
export function largestRectangleInHistogram(widths: number[]): Rectangle {
const stack: number[] = [];
let maxArea = 0;
let largestRect: Rectangle = { x: 0, y: 0, width: 0, height: 0 };
let largestRect: Rectangle = { height: 0, width: 0, x: 0, y: 0 };
for (let i = 0; i <= widths.length; i++) {
const h = i === widths.length ? 0 : widths[i];
@@ -151,10 +151,10 @@ export function largestRectangleInHistogram(widths: number[]): Rectangle {
if (height * width > maxArea) {
maxArea = height * width;
largestRect = {
height,
width,
x: stack.length === 0 ? 0 : stack[stack.length - 1] + 1,
y: 0,
width,
height,
};
}
}
+1 -1
View File
@@ -15,8 +15,8 @@ import { UnitView } from "../../core/game/GameView";
// Can't reuse TrainType because "loaded" is not a type, just an attribute
const TrainTypeSprite = {
Engine: "Engine",
Carriage: "Carriage",
Engine: "Engine",
LoadedCarriage: "LoadedCarriage",
} as const;
+4 -4
View File
@@ -88,10 +88,10 @@ export function nukeFxFactory(
radiusFactor: number;
density: number;
}> = [
{ type: FxType.MiniFire, radiusFactor: 1.0, density: 1 / 25 },
{ type: FxType.MiniSmoke, radiusFactor: 1.0, density: 1 / 28 },
{ type: FxType.MiniBigSmoke, radiusFactor: 0.9, density: 1 / 70 },
{ type: FxType.MiniSmokeAndFire, radiusFactor: 0.9, density: 1 / 70 },
{ density: 1 / 25, radiusFactor: 1.0, type: FxType.MiniFire },
{ density: 1 / 28, radiusFactor: 1.0, type: FxType.MiniSmoke },
{ density: 1 / 70, radiusFactor: 0.9, type: FxType.MiniBigSmoke },
{ density: 1 / 70, radiusFactor: 0.9, type: FxType.MiniSmokeAndFire },
];
for (const { type, radiusFactor, density } of debrisPlan) {
+2 -2
View File
@@ -11,9 +11,9 @@ export class TextFx implements Fx {
private readonly riseDistance = 30,
private readonly font = "11px sans-serif",
private readonly color: { r: number; g: number; b: number } = {
r: 255,
g: 255,
b: 255,
g: 255,
r: 255,
},
) {}
+1 -1
View File
@@ -12,7 +12,7 @@ export class Timeline {
private timeElapsed = 0;
add(delay: number, action: () => void): Timeline {
this.tasks.push({ delay, action, triggered: false });
this.tasks.push({ action, delay, triggered: false });
return this;
}
+3 -3
View File
@@ -18,9 +18,9 @@ export class UnitExplosionFx implements Fx {
game: GameView,
) {
const config = [
{ dx: 0, dy: 0, delay: 0, type: FxType.UnitExplosion },
{ dx: 4, dy: -6, delay: 80, type: FxType.UnitExplosion },
{ dx: -6, dy: 4, delay: 160, type: FxType.UnitExplosion },
{ delay: 0, dx: 0, dy: 0, type: FxType.UnitExplosion },
{ delay: 80, dx: 4, dy: -6, type: FxType.UnitExplosion },
{ delay: 160, dx: -6, dy: 4, type: FxType.UnitExplosion },
];
for (const { dx, dy, delay, type } of config) {
this.timeline.add(delay, () => {
+32 -32
View File
@@ -46,75 +46,75 @@ export type BuildItemDisplay = {
export const buildTable: BuildItemDisplay[][] = [
[
{
unitType: UnitType.AtomBomb,
icon: atomBombIcon,
countable: false,
description: "build_menu.desc.atom_bomb",
icon: atomBombIcon,
key: "unit_type.atom_bomb",
countable: false,
unitType: UnitType.AtomBomb,
},
{
unitType: UnitType.MIRV,
icon: mirvIcon,
countable: false,
description: "build_menu.desc.mirv",
icon: mirvIcon,
key: "unit_type.mirv",
countable: false,
unitType: UnitType.MIRV,
},
{
unitType: UnitType.HydrogenBomb,
icon: hydrogenBombIcon,
countable: false,
description: "build_menu.desc.hydrogen_bomb",
icon: hydrogenBombIcon,
key: "unit_type.hydrogen_bomb",
countable: false,
unitType: UnitType.HydrogenBomb,
},
{
unitType: UnitType.Warship,
icon: warshipIcon,
countable: true,
description: "build_menu.desc.warship",
icon: warshipIcon,
key: "unit_type.warship",
countable: true,
unitType: UnitType.Warship,
},
{
unitType: UnitType.Port,
icon: portIcon,
countable: true,
description: "build_menu.desc.port",
icon: portIcon,
key: "unit_type.port",
countable: true,
unitType: UnitType.Port,
},
{
unitType: UnitType.MissileSilo,
icon: missileSiloIcon,
description: "build_menu.desc.missile_silo",
key: "unit_type.missile_silo",
countable: true,
description: "build_menu.desc.missile_silo",
icon: missileSiloIcon,
key: "unit_type.missile_silo",
unitType: UnitType.MissileSilo,
},
// needs new icon
{
unitType: UnitType.SAMLauncher,
icon: samlauncherIcon,
countable: true,
description: "build_menu.desc.sam_launcher",
icon: samlauncherIcon,
key: "unit_type.sam_launcher",
countable: true,
unitType: UnitType.SAMLauncher,
},
{
unitType: UnitType.DefensePost,
icon: shieldIcon,
countable: true,
description: "build_menu.desc.defense_post",
icon: shieldIcon,
key: "unit_type.defense_post",
countable: true,
unitType: UnitType.DefensePost,
},
{
unitType: UnitType.City,
icon: cityIcon,
countable: true,
description: "build_menu.desc.city",
icon: cityIcon,
key: "unit_type.city",
countable: true,
unitType: UnitType.City,
},
{
unitType: UnitType.Factory,
icon: factoryIcon,
description: "build_menu.desc.factory",
key: "unit_type.factory",
countable: true,
description: "build_menu.desc.factory",
icon: factoryIcon,
key: "unit_type.factory",
unitType: UnitType.Factory,
},
],
];
+2 -2
View File
@@ -65,8 +65,8 @@ export class ChatDisplay extends LitElement implements Layer {
}
this.addEvent({
description: event.message,
createdAt: this.game.ticks(),
description: event.message,
highlight: true,
unsafeDescription: true,
});
@@ -97,9 +97,9 @@ export class ChatDisplay extends LitElement implements Layer {
this.chatEvents = [
...this.chatEvents,
{
createdAt: this.game.ticks(),
description: msg.message,
unsafeDescription: true,
createdAt: this.game.ticks(),
},
];
}
+16 -16
View File
@@ -49,18 +49,6 @@ export class ChatIntegration {
const phraseText = translateText(`chat.${category.id}.${phrase.key}`);
return {
id: `phrase-${category.id}-${phrase.key}`,
name: phraseText,
disabled: () => false,
text: this.shortenText(phraseText),
fontSize: "10px",
color: categoryColor,
tooltipItems: [
{
text: phraseText,
className: "description",
},
],
action: (params: MenuElementParams) => {
if (phrase.requiresPlayer) {
this.ctModal.openWithSelection(
@@ -79,18 +67,30 @@ export class ChatIntegration {
);
}
},
color: categoryColor,
disabled: () => false,
fontSize: "10px",
id: `phrase-${category.id}-${phrase.key}`,
name: phraseText,
text: this.shortenText(phraseText),
tooltipItems: [
{
className: "description",
text: phraseText,
},
],
};
},
);
return {
_action: () => {}, // Empty action placeholder for RadialMenu
color: categoryColor,
disabled: () => false,
id: `chat-category-${category.id}`,
name: categoryTranslation,
disabled: () => false,
text: categoryTranslation,
color: categoryColor,
_action: () => {}, // Empty action placeholder for RadialMenu
subMenu: () => phraseItems,
text: categoryTranslation,
};
});
}
+5 -5
View File
@@ -49,11 +49,11 @@ export class ChatModal extends LitElement {
string,
Array<{ text: string; requiresPlayer: boolean }>
> = {
help: [{ text: "Please give me troops!", requiresPlayer: false }],
attack: [{ text: "Attack [P1]!", requiresPlayer: true }],
defend: [{ text: "Defend [P1]!", requiresPlayer: true }],
greet: [{ text: "Hello!", requiresPlayer: false }],
misc: [{ text: "Let's go!", requiresPlayer: false }],
attack: [{ requiresPlayer: true, text: "Attack [P1]!" }],
defend: [{ requiresPlayer: true, text: "Defend [P1]!" }],
greet: [{ requiresPlayer: false, text: "Hello!" }],
help: [{ requiresPlayer: false, text: "Please give me troops!" }],
misc: [{ requiresPlayer: false, text: "Let's go!" }],
};
public categories = [
+89 -89
View File
@@ -279,35 +279,35 @@ export class EventsDisplay extends LitElement implements Layer {
if (!other.isAlive()) continue;
this.addEvent({
description: translateText("events_display.about_to_expire", {
name: other.name(),
}),
type: MessageType.RENEW_ALLIANCE,
duration: this.game.config().allianceExtensionPromptOffset() - 3 * 10, // 3 second buffer
buttons: [
{
text: translateText("events_display.focus"),
className: "btn-gray",
action: () => this.eventBus?.emit(new GoToPlayerEvent(other)),
className: "btn-gray",
preventClose: true,
text: translateText("events_display.focus"),
},
{
action: () =>
this.eventBus?.emit(new SendAllianceExtensionIntentEvent(other)),
className: "btn",
text: translateText("events_display.renew_alliance", {
name: other.name(),
}),
className: "btn",
action: () =>
this.eventBus?.emit(new SendAllianceExtensionIntentEvent(other)),
},
{
text: translateText("events_display.ignore"),
className: "btn-info",
action: () => {},
className: "btn-info",
text: translateText("events_display.ignore"),
},
],
highlight: true,
createdAt: this.game.ticks(),
description: translateText("events_display.about_to_expire", {
name: other.name(),
}),
duration: this.game.config().allianceExtensionPromptOffset() - 3 * 10, // 3 second buffer
focusID: other.smallID(),
highlight: true,
type: MessageType.RENEW_ALLIANCE,
});
}
}
@@ -374,8 +374,8 @@ export class EventsDisplay extends LitElement implements Layer {
}
this.addEvent({
description,
createdAt: this.game.ticks(),
description,
highlight: true,
type: event.messageType,
unsafeDescription: true,
@@ -417,11 +417,11 @@ export class EventsDisplay extends LitElement implements Layer {
}
this.addEvent({
description: translateText(event.isFrom ? "chat.from" : "chat.to", {
user: otherPlayerDiplayName,
msg: translatedMessage,
}),
createdAt: this.game.ticks(),
description: translateText(event.isFrom ? "chat.from" : "chat.to", {
msg: translatedMessage,
user: otherPlayerDiplayName,
}),
highlight: true,
type: MessageType.CHAT,
unsafeDescription: false,
@@ -443,43 +443,43 @@ export class EventsDisplay extends LitElement implements Layer {
) as PlayerView;
this.addEvent({
description: translateText("events_display.request_alliance", {
name: requestor.name(),
}),
buttons: [
{
text: translateText("events_display.focus"),
className: "btn-gray",
action: () => this.eventBus?.emit(new GoToPlayerEvent(requestor)),
className: "btn-gray",
preventClose: true,
text: translateText("events_display.focus"),
},
{
text: translateText("events_display.accept_alliance"),
className: "btn",
action: () =>
this.eventBus?.emit(
new SendAllianceReplyIntentEvent(requestor, recipient, true),
),
className: "btn",
text: translateText("events_display.accept_alliance"),
},
{
text: translateText("events_display.reject_alliance"),
className: "btn-info",
action: () =>
this.eventBus?.emit(
new SendAllianceReplyIntentEvent(requestor, recipient, false),
),
className: "btn-info",
text: translateText("events_display.reject_alliance"),
},
],
highlight: true,
type: MessageType.ALLIANCE_REQUEST,
createdAt: this.game.ticks(),
description: translateText("events_display.request_alliance", {
name: requestor.name(),
}),
duration: 150,
focusID: update.requestorID,
highlight: true,
onDelete: () =>
this.eventBus?.emit(
new SendAllianceReplyIntentEvent(requestor, recipient, false),
),
priority: 0,
duration: 150,
focusID: update.requestorID,
type: MessageType.ALLIANCE_REQUEST,
});
}
@@ -511,18 +511,18 @@ export class EventsDisplay extends LitElement implements Layer {
update.request.recipientID,
) as PlayerView;
this.addEvent({
createdAt: this.game.ticks(),
description: translateText("events_display.alliance_request_status", {
name: recipient.name(),
status: update.accepted
? translateText("events_display.alliance_accepted")
: translateText("events_display.alliance_rejected"),
}),
focusID: update.request.recipientID,
highlight: true,
type: update.accepted
? MessageType.ALLIANCE_ACCEPTED
: MessageType.ALLIANCE_REJECTED,
highlight: true,
createdAt: this.game.ticks(),
focusID: update.request.recipientID,
});
}
@@ -552,34 +552,34 @@ export class EventsDisplay extends LitElement implements Layer {
});
this.addEvent({
description: translateText("events_display.betrayal_description", {
name: betrayed.name(),
malusPercent,
durationText,
}),
type: MessageType.ALLIANCE_BROKEN,
highlight: true,
createdAt: this.game.ticks(),
description: translateText("events_display.betrayal_description", {
durationText,
malusPercent,
name: betrayed.name(),
}),
focusID: update.betrayedID,
highlight: true,
type: MessageType.ALLIANCE_BROKEN,
});
} else if (betrayed === myPlayer) {
const buttons = [
{
text: translateText("events_display.focus"),
className: "btn-gray",
action: () => this.eventBus?.emit(new GoToPlayerEvent(traitor)),
className: "btn-gray",
preventClose: true,
text: translateText("events_display.focus"),
},
];
this.addEvent({
buttons,
createdAt: this.game.ticks(),
description: translateText("events_display.betrayed_you", {
name: traitor.name(),
}),
type: MessageType.ALLIANCE_BROKEN,
highlight: true,
createdAt: this.game.ticks(),
focusID: update.traitorID,
buttons,
highlight: true,
type: MessageType.ALLIANCE_BROKEN,
});
}
}
@@ -600,13 +600,13 @@ export class EventsDisplay extends LitElement implements Layer {
if (!other || !myPlayer.isAlive() || !other.isAlive()) return;
this.addEvent({
createdAt: this.game.ticks(),
description: translateText("events_display.alliance_expired", {
name: other.name(),
}),
type: MessageType.ALLIANCE_EXPIRED,
highlight: true,
createdAt: this.game.ticks(),
focusID: otherID,
highlight: true,
type: MessageType.ALLIANCE_EXPIRED,
});
}
@@ -619,14 +619,14 @@ export class EventsDisplay extends LitElement implements Layer {
const target = this.game.playerBySmallID(event.targetID) as PlayerView;
this.addEvent({
createdAt: this.game.ticks(),
description: translateText("events_display.attack_request", {
name: other.name(),
target: target.name(),
}),
type: MessageType.ATTACK_REQUEST,
highlight: true,
createdAt: this.game.ticks(),
focusID: event.targetID,
highlight: true,
type: MessageType.ATTACK_REQUEST,
});
}
@@ -674,24 +674,24 @@ export class EventsDisplay extends LitElement implements Layer {
if (recipient === myPlayer) {
this.addEvent({
description: `${sender.displayName()}: ${update.emoji.message}`,
unsafeDescription: true,
type: MessageType.CHAT,
highlight: true,
createdAt: this.game.ticks(),
description: `${sender.displayName()}: ${update.emoji.message}`,
focusID: update.emoji.senderID,
highlight: true,
type: MessageType.CHAT,
unsafeDescription: true,
});
} else if (sender === myPlayer && recipient !== AllPlayers) {
this.addEvent({
description: translateText("events_display.sent_emoji", {
name: (recipient as PlayerView).displayName(),
emoji: update.emoji.message,
}),
unsafeDescription: true,
type: MessageType.CHAT,
highlight: true,
createdAt: this.game.ticks(),
description: translateText("events_display.sent_emoji", {
emoji: update.emoji.message,
name: (recipient as PlayerView).displayName(),
}),
focusID: recipient.smallID(),
highlight: true,
type: MessageType.CHAT,
unsafeDescription: true,
});
}
}
@@ -707,12 +707,12 @@ export class EventsDisplay extends LitElement implements Layer {
const unitView = this.game.unit(event.unitID);
this.addEvent({
description: event.message,
type: event.messageType,
unsafeDescription: false,
highlight: true,
createdAt: this.game.ticks(),
description: event.message,
highlight: true,
type: event.messageType,
unitView,
unsafeDescription: false,
});
}
@@ -754,6 +754,7 @@ export class EventsDisplay extends LitElement implements Layer {
const attacker = this.game?.playerBySmallID(attack.attackerID);
return html`
${this.renderButton({
className: "text-left text-red-400",
content: html`
${renderTroops(attack.troops)}
${attacker?.isPlayer() ? attacker.name() : "unknown"}
@@ -764,7 +765,6 @@ export class EventsDisplay extends LitElement implements Layer {
}
`,
onClick: () => this.attackWarningOnClick(attack),
className: "text-left text-red-400",
translate: false,
})}
`;
@@ -786,22 +786,22 @@ export class EventsDisplay extends LitElement implements Layer {
return html`
<div class="inline-flex items-center gap-1">
${this.renderButton({
className: "text-left text-blue-400",
content: html`
${renderTroops(attack.troops)}
${target?.isPlayer() ? target.name() : "unknown"}
`,
onClick: async () => this.attackWarningOnClick(attack),
className: "text-left text-blue-400",
translate: false,
})}
${
!attack.retreating
? this.renderButton({
className: "text-left flex-shrink-0",
content: "❌",
disabled: attack.retreating,
onClick: () =>
this.emitCancelAttackIntent(attack.id),
className: "text-left flex-shrink-0",
disabled: attack.retreating,
})
: html`<span class="flex-shrink-0 text-blue-400"
>(${translateText(
@@ -829,19 +829,19 @@ export class EventsDisplay extends LitElement implements Layer {
(landAttack) => html`
<div class="inline-flex items-center gap-1">
${this.renderButton({
className: "text-left text-gray-400",
content: html`${renderTroops(landAttack.troops)}
${translateText("help_modal.ui_wilderness")}`,
className: "text-left text-gray-400",
translate: false,
})}
${
!landAttack.retreating
? this.renderButton({
className: "text-left flex-shrink-0",
content: "❌",
disabled: landAttack.retreating,
onClick: () =>
this.emitCancelAttackIntent(landAttack.id),
className: "text-left flex-shrink-0",
disabled: landAttack.retreating,
})
: html`<span class="flex-shrink-0 text-blue-400"
>(${translateText(
@@ -869,19 +869,19 @@ export class EventsDisplay extends LitElement implements Layer {
(boat) => html`
<div class="inline-flex items-center gap-1">
${this.renderButton({
className: "text-left text-blue-400",
content: html`${translateText("events_display.boat")}:
${renderTroops(boat.troops())}`,
onClick: () => this.emitGoToUnitEvent(boat),
className: "text-left text-blue-400",
translate: false,
})}
${
!boat.retreating()
? this.renderButton({
content: "❌",
onClick: () => this.emitBoatCancelIntent(boat.id()),
className: "text-left flex-shrink-0",
content: "❌",
disabled: boat.retreating(),
onClick: () => this.emitBoatCancelIntent(boat.id()),
})
: html`<span class="flex-shrink-0 text-blue-400"
>(${translateText(
@@ -948,6 +948,9 @@ export class EventsDisplay extends LitElement implements Layer {
? html`
<div class="relative w-fit lg:bottom-2.5 lg:right-2.5 z-50">
${this.renderButton({
className:
"text-white cursor-pointer pointer-events-auto w-fit p-2 " +
"lg:p-3 rounded-md bg-gray-800/70 backdrop-blur",
content: html`
Events
<span
@@ -958,9 +961,6 @@ export class EventsDisplay extends LitElement implements Layer {
>
`,
onClick: this.toggleHidden,
className:
"text-white cursor-pointer pointer-events-auto w-fit p-2 " +
"lg:p-3 rounded-md bg-gray-800/70 backdrop-blur",
})}
</div>
`
@@ -976,6 +976,7 @@ export class EventsDisplay extends LitElement implements Layer {
<div class="flex justify-between items-center">
<div class="flex gap-4">
${this.renderButton({
className: "cursor-pointer pointer-events-auto",
content: html`<img
src="${swordIcon}"
class="w-5 h-5"
@@ -987,9 +988,9 @@ export class EventsDisplay extends LitElement implements Layer {
/>`,
onClick: () =>
this.toggleEventFilter(MessageCategory.ATTACK),
className: "cursor-pointer pointer-events-auto",
})}
${this.renderButton({
className: "cursor-pointer pointer-events-auto",
content: html`<img
src="${donateGoldIcon}"
class="w-5 h-5"
@@ -1001,9 +1002,9 @@ export class EventsDisplay extends LitElement implements Layer {
/>`,
onClick: () =>
this.toggleEventFilter(MessageCategory.TRADE),
className: "cursor-pointer pointer-events-auto",
})}
${this.renderButton({
className: "cursor-pointer pointer-events-auto",
content: html`<img
src="${allianceIcon}"
class="w-5 h-5"
@@ -1015,9 +1016,9 @@ export class EventsDisplay extends LitElement implements Layer {
/>`,
onClick: () =>
this.toggleEventFilter(MessageCategory.ALLIANCE),
className: "cursor-pointer pointer-events-auto",
})}
${this.renderButton({
className: "cursor-pointer pointer-events-auto",
content: html`<img
src="${chatIcon}"
class="w-5 h-5"
@@ -1029,7 +1030,6 @@ export class EventsDisplay extends LitElement implements Layer {
/>`,
onClick: () =>
this.toggleEventFilter(MessageCategory.CHAT),
className: "cursor-pointer pointer-events-auto",
})}
</div>
<div class="flex items-center gap-3">
@@ -1051,10 +1051,10 @@ export class EventsDisplay extends LitElement implements Layer {
: ""
}
${this.renderButton({
content: translateText("leaderboard.hide"),
onClick: this.toggleHidden,
className:
"text-white cursor-pointer pointer-events-auto",
content: translateText("leaderboard.hide"),
onClick: this.toggleHidden,
})}
</div>
</div>
@@ -1083,6 +1083,7 @@ export class EventsDisplay extends LitElement implements Layer {
${
event.focusID
? this.renderButton({
className: "text-left",
content: this.getEventDescription(event),
onClick: () => {
event.focusID &&
@@ -1090,10 +1091,10 @@ export class EventsDisplay extends LitElement implements Layer {
event.focusID,
);
},
className: "text-left",
})
: event.unitView
? this.renderButton({
className: "text-left",
content:
this.getEventDescription(event),
onClick: () => {
@@ -1102,7 +1103,6 @@ export class EventsDisplay extends LitElement implements Layer {
event.unitView,
);
},
className: "text-left",
})
: this.getEventDescription(event)
}
+2 -2
View File
@@ -96,12 +96,12 @@ export class GutterAdModal extends LitElement implements Layer {
window.ramp.que.push(() => {
window.ramp.spaAddAds([
{
type: this.leftAdType,
selectorId: this.leftContainerId,
type: this.leftAdType,
},
{
type: this.rightAdType,
selectorId: this.rightContainerId,
type: this.rightAdType,
},
]);
this.adLoaded = true;
+6 -6
View File
@@ -107,15 +107,15 @@ export class Leaderboard extends LitElement implements Layer {
troops = 0;
}
return {
gold: renderNumber(player.gold()),
isMyPlayer: player === myPlayer,
name: player.displayName(),
player,
position: index + 1,
score: formatPercentage(
player.numTilesOwned() / numTilesWithoutFallout,
),
gold: renderNumber(player.gold()),
troops: renderNumber(troops),
isMyPlayer: player === myPlayer,
player,
};
});
@@ -137,15 +137,15 @@ export class Leaderboard extends LitElement implements Layer {
}
this.players.pop();
this.players.push({
gold: renderNumber(myPlayer.gold()),
isMyPlayer: true,
name: myPlayer.displayName(),
player: myPlayer,
position: place,
score: formatPercentage(
myPlayer.numTilesOwned() / this.game.numLandTiles(),
),
gold: renderNumber(myPlayer.gold()),
troops: renderNumber(myPlayerTroops),
isMyPlayer: true,
player: myPlayer,
});
}
+8 -8
View File
@@ -114,18 +114,18 @@ export class MainRadialMenu extends LitElement implements Layer {
}
const params: MenuElementParams = {
myPlayer,
selected: recipient,
tile,
playerActions: actions,
game: this.game,
buildMenu: this.buildMenu,
emojiTable: this.emojiTable,
playerActionHandler: this.playerActionHandler,
playerPanel: this.playerPanel,
chatIntegration: this.chatIntegration,
closeMenu: () => this.closeMenu(),
emojiTable: this.emojiTable,
eventBus: this.eventBus,
game: this.game,
myPlayer,
playerActionHandler: this.playerActionHandler,
playerActions: actions,
playerPanel: this.playerPanel,
selected: recipient,
tile,
};
this.radialMenu.setParams(params);
+18 -18
View File
@@ -181,10 +181,10 @@ export class OptionsMenu extends LitElement implements Layer {
>
<div class="flex items-stretch gap-1 lg:gap-2">
${button({
children: this.isPaused ? "▶️" : "⏸",
classes: !this.showPauseButton ? "hidden" : "",
onClick: this.onPauseButtonClick,
title: this.isPaused ? "Resume game" : "Pause game",
children: this.isPaused ? "▶️" : "⏸",
})}
<div
class="w-[55px] h-8 lg:w-24 lg:h-10 flex items-center justify-center
@@ -194,14 +194,14 @@ export class OptionsMenu extends LitElement implements Layer {
${secondsToHms(this.timer)}
</div>
${button({
children: "❌",
onClick: this.onExitButtonClick,
title: "Exit game",
children: "❌",
})}
${button({
children: "⚙️",
onClick: this.onSettingsButtonClick,
title: "Settings",
children: "⚙️",
})}
</div>
</div>
@@ -212,65 +212,65 @@ export class OptionsMenu extends LitElement implements Layer {
${!this.showSettings ? "hidden" : ""}"
>
${button({
children: "🌲: " + (this.alternateView ? "On" : "Off"),
onClick: this.onTerrainButtonClick,
title: "Toggle Terrain",
children: "🌲: " + (this.alternateView ? "On" : "Off"),
})}
${button({
children: "🙂: " + (this.userSettings.emojis() ? "On" : "Off"),
onClick: this.onToggleEmojisButtonClick,
title: "Toggle Emojis",
children: "🙂: " + (this.userSettings.emojis() ? "On" : "Off"),
})}
${button({
children: "🚨: " + (this.userSettings.alertFrame() ? "On" : "Off"),
onClick: this.onToggleAlertFrameButtonClick,
title: "Toggle Alert frame",
children: "🚨: " + (this.userSettings.alertFrame() ? "On" : "Off"),
})}
${button({
children: "💥: " + (this.userSettings.fxLayer() ? "On" : "Off"),
onClick: this.onToggleSpecialEffectsButtonClick,
title: "Toggle Special effects",
children: "💥: " + (this.userSettings.fxLayer() ? "On" : "Off"),
})}
${button({
onClick: this.onToggleTerritoryPatterns,
title: "Territory Patterns",
children:
"🏳️: " + (this.userSettings.territoryPatterns() ? "On" : "Off"),
onClick: this.onToggleTerritoryPatterns,
title: "Territory Patterns",
})}
${button({
children: "🌙: " + (this.userSettings.darkMode() ? "On" : "Off"),
onClick: this.onToggleDarkModeButtonClick,
title: "Dark Mode",
children: "🌙: " + (this.userSettings.darkMode() ? "On" : "Off"),
})}
${button({
onClick: this.onToggleRandomNameModeButtonClick,
title: "Random name mode",
children:
"🥷: " + (this.userSettings.anonymousNames() ? "On" : "Off"),
onClick: this.onToggleRandomNameModeButtonClick,
title: "Random name mode",
})}
${button({
onClick: this.onToggleLeftClickOpensMenu,
title: "Left click",
children:
"🖱️: " +
(this.userSettings.leftClickOpensMenu()
? "Opens menu"
: "Attack"),
onClick: this.onToggleLeftClickOpensMenu,
title: "Left click",
})}
${button({
onClick: this.onTogglePerformanceOverlayButtonClick,
title: "Performance Overlay",
children:
"🚀: " + (this.userSettings.performanceOverlay() ? "On" : "Off"),
onClick: this.onTogglePerformanceOverlayButtonClick,
title: "Performance Overlay",
})}
<!-- ${button({
onClick: this.onToggleFocusLockedButtonClick,
title: "Lock Focus",
children:
"🗺: " +
(this.userSettings.focusLocked()
? "Focus locked"
: "Hover focus"),
onClick: this.onToggleFocusLockedButtonClick,
title: "Lock Focus",
})} -->
</div>
</div>
+7 -7
View File
@@ -91,17 +91,17 @@ export class RadialMenu implements Layer {
config: RadialMenuConfig = {},
) {
this.config = {
menuSize: config.menuSize ?? 190,
submenuScale: config.submenuScale ?? 1.5,
centerButtonIcon: config.centerButtonIcon ?? "",
centerButtonSize: config.centerButtonSize ?? 30,
iconSize: config.iconSize ?? 32,
centerIconSize: config.centerIconSize ?? 48,
disabledColor: config.disabledColor ?? d3.rgb(128, 128, 128).toString(),
menuTransitionDuration: config.menuTransitionDuration ?? 300,
mainMenuInnerRadius: config.mainMenuInnerRadius ?? 40,
centerButtonIcon: config.centerButtonIcon ?? "",
maxNestedLevels: config.maxNestedLevels ?? 3,
iconSize: config.iconSize ?? 32,
innerRadiusIncrement: config.innerRadiusIncrement ?? 20,
mainMenuInnerRadius: config.mainMenuInnerRadius ?? 40,
maxNestedLevels: config.maxNestedLevels ?? 3,
menuSize: config.menuSize ?? 190,
menuTransitionDuration: config.menuTransitionDuration ?? 300,
submenuScale: config.submenuScale ?? 1.5,
tooltipStyle: config.tooltipStyle ?? "",
};
this.originalCenterButtonIcon = this.config.centerButtonIcon;
+178 -179
View File
@@ -67,32 +67,32 @@ export type CenterButtonElement = {
};
export const COLORS = {
ally: "#53ac75",
attack: "#ff0000",
boat: "#3f6ab1",
breakAlly: "#c74848",
build: "#ebe250",
building: "#2c2c2c",
boat: "#3f6ab1",
ally: "#53ac75",
breakAlly: "#c74848",
chat: {
attack: "#f44336",
default: "#66c",
defend: "#2196f3",
greet: "#ff9800",
help: "#4caf50",
misc: "#9c27b0",
warnings: "#e3c532",
},
delete: "#ff0000",
embargo: "#6600cc",
info: "#64748B",
target: "#ff0000",
attack: "#ff0000",
infoDetails: "#7f8c8d",
infoEmoji: "#f1c40f",
trade: "#008080",
embargo: "#6600cc",
target: "#ff0000",
tooltip: {
cost: "#ffd700",
count: "#aaa",
},
chat: {
default: "#66c",
help: "#4caf50",
attack: "#f44336",
defend: "#2196f3",
greet: "#ff9800",
misc: "#9c27b0",
warnings: "#e3c532",
},
trade: "#008080",
};
export enum Slot {
@@ -107,11 +107,11 @@ export enum Slot {
/* eslint-disable @typescript-eslint/no-non-null-assertion */
const infoChatElement: MenuElement = {
color: COLORS.chat.default,
disabled: () => false,
icon: chatIcon,
id: "info_chat",
name: "chat",
disabled: () => false,
color: COLORS.chat.default,
icon: chatIcon,
subMenu: (params: MenuElementParams) =>
params.chatIntegration
.createQuickChatMenu(params.selected!)
@@ -124,59 +124,51 @@ const infoChatElement: MenuElement = {
};
const allyTargetElement: MenuElement = {
id: "ally_target",
name: "target",
disabled: (params: MenuElementParams): boolean => {
if (params.selected === null) return true;
return !params.playerActions.interaction?.canTarget;
},
color: COLORS.target,
icon: targetIcon,
action: (params: MenuElementParams) => {
params.playerActionHandler.handleTargetPlayer(params.selected!.id());
params.closeMenu();
},
color: COLORS.target,
disabled: (params: MenuElementParams): boolean => {
if (params.selected === null) return true;
return !params.playerActions.interaction?.canTarget;
},
icon: targetIcon,
id: "ally_target",
name: "target",
};
const allyTradeElement: MenuElement = {
id: "ally_trade",
name: "trade",
disabled: (params: MenuElementParams) =>
!!params.playerActions?.interaction?.canEmbargo,
displayed: (params: MenuElementParams) =>
!params.playerActions?.interaction?.canEmbargo,
color: COLORS.trade,
text: translateText("player_panel.start_trade"),
action: (params: MenuElementParams) => {
params.playerActionHandler.handleEmbargo(params.selected!, "stop");
params.closeMenu();
},
color: COLORS.trade,
disabled: (params: MenuElementParams) =>
!!params.playerActions?.interaction?.canEmbargo,
displayed: (params: MenuElementParams) =>
!params.playerActions?.interaction?.canEmbargo,
id: "ally_trade",
name: "trade",
text: translateText("player_panel.start_trade"),
};
const allyEmbargoElement: MenuElement = {
id: "ally_embargo",
name: "embargo",
disabled: (params: MenuElementParams) =>
!params.playerActions?.interaction?.canEmbargo,
displayed: (params: MenuElementParams) =>
!!params.playerActions?.interaction?.canEmbargo,
color: COLORS.embargo,
text: translateText("player_panel.stop_trade"),
action: (params: MenuElementParams) => {
params.playerActionHandler.handleEmbargo(params.selected!, "start");
params.closeMenu();
},
color: COLORS.embargo,
disabled: (params: MenuElementParams) =>
!params.playerActions?.interaction?.canEmbargo,
displayed: (params: MenuElementParams) =>
!!params.playerActions?.interaction?.canEmbargo,
id: "ally_embargo",
name: "embargo",
text: translateText("player_panel.stop_trade"),
};
const allyRequestElement: MenuElement = {
id: "ally_request",
name: "request",
disabled: (params: MenuElementParams) =>
!params.playerActions?.interaction?.canSendAllianceRequest,
displayed: (params: MenuElementParams) =>
!params.playerActions?.interaction?.canBreakAlliance,
color: COLORS.ally,
icon: allianceIcon,
action: (params: MenuElementParams) => {
params.playerActionHandler.handleAllianceRequest(
params.myPlayer,
@@ -184,17 +176,17 @@ const allyRequestElement: MenuElement = {
);
params.closeMenu();
},
color: COLORS.ally,
disabled: (params: MenuElementParams) =>
!params.playerActions?.interaction?.canSendAllianceRequest,
displayed: (params: MenuElementParams) =>
!params.playerActions?.interaction?.canBreakAlliance,
icon: allianceIcon,
id: "ally_request",
name: "request",
};
const allyBreakElement: MenuElement = {
id: "ally_break",
name: "break",
disabled: (params: MenuElementParams) =>
!params.playerActions?.interaction?.canBreakAlliance,
displayed: (params: MenuElementParams) =>
!!params.playerActions?.interaction?.canBreakAlliance,
color: COLORS.breakAlly,
icon: traitorIcon,
action: (params: MenuElementParams) => {
params.playerActionHandler.handleBreakAlliance(
params.myPlayer,
@@ -202,59 +194,62 @@ const allyBreakElement: MenuElement = {
);
params.closeMenu();
},
color: COLORS.breakAlly,
disabled: (params: MenuElementParams) =>
!params.playerActions?.interaction?.canBreakAlliance,
displayed: (params: MenuElementParams) =>
!!params.playerActions?.interaction?.canBreakAlliance,
icon: traitorIcon,
id: "ally_break",
name: "break",
};
const allyDonateGoldElement: MenuElement = {
id: "ally_donate_gold",
name: "donate gold",
disabled: (params: MenuElementParams) =>
!params.playerActions?.interaction?.canDonateGold,
color: COLORS.ally,
icon: donateGoldIcon,
action: (params: MenuElementParams) => {
params.playerActionHandler.handleDonateGold(params.selected!);
params.closeMenu();
},
color: COLORS.ally,
disabled: (params: MenuElementParams) =>
!params.playerActions?.interaction?.canDonateGold,
icon: donateGoldIcon,
id: "ally_donate_gold",
name: "donate gold",
};
const allyDonateTroopsElement: MenuElement = {
id: "ally_donate_troops",
name: "donate troops",
disabled: (params: MenuElementParams) =>
!params.playerActions?.interaction?.canDonateTroops,
color: COLORS.ally,
icon: donateTroopIcon,
action: (params: MenuElementParams) => {
params.playerActionHandler.handleDonateTroops(params.selected!);
params.closeMenu();
},
color: COLORS.ally,
disabled: (params: MenuElementParams) =>
!params.playerActions?.interaction?.canDonateTroops,
icon: donateTroopIcon,
id: "ally_donate_troops",
name: "donate troops",
};
const infoPlayerElement: MenuElement = {
id: "info_player",
name: "player",
disabled: () => false,
color: COLORS.info,
icon: infoIcon,
action: (params: MenuElementParams) => {
params.playerPanel.show(params.playerActions, params.tile);
},
color: COLORS.info,
disabled: () => false,
icon: infoIcon,
id: "info_player",
name: "player",
};
const infoEmojiElement: MenuElement = {
color: COLORS.infoEmoji,
disabled: () => false,
icon: emojiIcon,
id: "info_emoji",
name: "emoji",
disabled: () => false,
color: COLORS.infoEmoji,
icon: emojiIcon,
subMenu: (params: MenuElementParams) => {
const emojiElements: MenuElement[] = [
{
id: "emoji_more",
name: "more",
disabled: () => false,
color: COLORS.infoEmoji,
icon: emojiIcon,
action: (params: MenuElementParams) => {
params.emojiTable.showTable((emoji) => {
const targetPlayer =
@@ -268,17 +263,17 @@ const infoEmojiElement: MenuElement = {
params.emojiTable.hideTable();
});
},
color: COLORS.infoEmoji,
disabled: () => false,
icon: emojiIcon,
id: "emoji_more",
name: "more",
},
];
const emojiCount = 8;
for (let i = 0; i < emojiCount; i++) {
emojiElements.push({
id: `emoji_${i}`,
name: flattenedEmojiTable[i],
text: flattenedEmojiTable[i],
disabled: () => false,
fontSize: "25px",
action: (params: MenuElementParams) => {
const targetPlayer =
params.selected === params.game.myPlayer()
@@ -287,6 +282,11 @@ const infoEmojiElement: MenuElement = {
params.playerActionHandler.handleEmoji(targetPlayer!, i);
params.closeMenu();
},
disabled: () => false,
fontSize: "25px",
id: `emoji_${i}`,
name: flattenedEmojiTable[i],
text: flattenedEmojiTable[i],
});
}
@@ -296,15 +296,15 @@ const infoEmojiElement: MenuElement = {
/* eslint-enable @typescript-eslint/no-non-null-assertion */
export const infoMenuElement: MenuElement = {
id: Slot.Info,
name: "info",
disabled: (params: MenuElementParams) =>
!params.selected || params.game.inSpawnPhase(),
icon: infoIcon,
color: COLORS.info,
action: (params: MenuElementParams) => {
params.playerPanel.show(params.playerActions, params.tile);
},
color: COLORS.info,
disabled: (params: MenuElementParams) =>
!params.selected || params.game.inSpawnPhase(),
icon: infoIcon,
id: Slot.Info,
name: "info",
};
function getAllEnabledUnits(myPlayer: boolean, config: Config): Set<UnitType> {
@@ -359,34 +359,6 @@ function createMenuElements(
: !ATTACK_UNIT_TYPES.includes(item.unitType)),
)
.map((item: BuildItemDisplay) => ({
id: `${elementIdPrefix}_${item.unitType}`,
name: item.key
? item.key.replace("unit_type.", "")
: item.unitType.toString(),
disabled: (params: MenuElementParams) =>
!params.buildMenu.canBuildOrUpgrade(item),
color: params.buildMenu.canBuildOrUpgrade(item)
? filterType === "attack"
? COLORS.attack
: COLORS.building
: undefined,
icon: item.icon,
tooltipItems: [
{ text: translateText(item.key ?? ""), className: "title" },
{
text: translateText(item.description ?? ""),
className: "description",
},
{
text: `${renderNumber(params.buildMenu.cost(item))} ${translateText("player_panel.gold")}`,
className: "cost",
},
item.countable
? { text: `${params.buildMenu.count(item)}x`, className: "count" }
: null,
].filter(
(tooltipItem): tooltipItem is TooltipItem => tooltipItem !== null,
),
action: (params: MenuElementParams) => {
const buildableUnit = params.playerActions.buildableUnits.find(
(bu) => bu.type === item.unitType,
@@ -399,15 +371,43 @@ function createMenuElements(
}
params.closeMenu();
},
color: params.buildMenu.canBuildOrUpgrade(item)
? filterType === "attack"
? COLORS.attack
: COLORS.building
: undefined,
disabled: (params: MenuElementParams) =>
!params.buildMenu.canBuildOrUpgrade(item),
icon: item.icon,
id: `${elementIdPrefix}_${item.unitType}`,
name: item.key
? item.key.replace("unit_type.", "")
: item.unitType.toString(),
tooltipItems: [
{ className: "title", text: translateText(item.key ?? "") },
{
className: "description",
text: translateText(item.description ?? ""),
},
{
className: "cost",
text: `${renderNumber(params.buildMenu.cost(item))} ${translateText("player_panel.gold")}`,
},
item.countable
? { className: "count", text: `${params.buildMenu.count(item)}x` }
: null,
].filter(
(tooltipItem): tooltipItem is TooltipItem => tooltipItem !== null,
),
}));
}
export const attackMenuElement: MenuElement = {
id: Slot.Attack,
name: "radial_attack",
color: COLORS.attack,
disabled: (params: MenuElementParams) => params.game.inSpawnPhase(),
icon: swordIcon,
color: COLORS.attack,
id: Slot.Attack,
name: "radial_attack",
subMenu: (params: MenuElementParams) => {
if (params === undefined) return [];
@@ -416,8 +416,29 @@ export const attackMenuElement: MenuElement = {
};
export const deleteUnitElement: MenuElement = {
id: Slot.Delete,
name: "delete",
action: (params: MenuElementParams) => {
const DELETE_SELECTION_RADIUS = 5;
const myUnits = params.myPlayer
.units()
.filter(
(unit) =>
params.game.manhattanDist(unit.tile(), params.tile) <=
DELETE_SELECTION_RADIUS,
);
if (myUnits.length > 0) {
myUnits.sort(
(a, b) =>
params.game.manhattanDist(a.tile(), params.tile) -
params.game.manhattanDist(b.tile(), params.tile),
);
params.playerActionHandler.handleDeleteUnit(myUnits[0].id());
}
params.closeMenu();
},
color: COLORS.delete,
disabled: (params: MenuElementParams) => {
const tileOwner = params.game.owner(params.tile);
const isLand = params.game.isLand(params.tile);
@@ -450,47 +471,26 @@ export const deleteUnitElement: MenuElement = {
return myUnits.length === 0;
},
icon: xIcon,
color: COLORS.delete,
id: Slot.Delete,
name: "delete",
tooltipKeys: [
{
key: "radial_menu.delete_unit_title",
className: "title",
key: "radial_menu.delete_unit_title",
},
{
key: "radial_menu.delete_unit_description",
className: "description",
key: "radial_menu.delete_unit_description",
},
],
action: (params: MenuElementParams) => {
const DELETE_SELECTION_RADIUS = 5;
const myUnits = params.myPlayer
.units()
.filter(
(unit) =>
params.game.manhattanDist(unit.tile(), params.tile) <=
DELETE_SELECTION_RADIUS,
);
if (myUnits.length > 0) {
myUnits.sort(
(a, b) =>
params.game.manhattanDist(a.tile(), params.tile) -
params.game.manhattanDist(b.tile(), params.tile),
);
params.playerActionHandler.handleDeleteUnit(myUnits[0].id());
}
params.closeMenu();
},
};
export const buildMenuElement: MenuElement = {
id: Slot.Build,
name: "build",
color: COLORS.build,
disabled: (params: MenuElementParams) => params.game.inSpawnPhase(),
icon: buildIcon,
color: COLORS.build,
id: Slot.Build,
name: "build",
subMenu: (params: MenuElementParams) => {
if (params === undefined) return [];
@@ -499,15 +499,6 @@ export const buildMenuElement: MenuElement = {
};
export const boatMenuElement: MenuElement = {
id: Slot.Boat,
name: "boat",
disabled: (params: MenuElementParams) =>
!params.playerActions.buildableUnits.some(
(unit) => unit.type === UnitType.TransportShip && unit.canBuild,
),
icon: boatIcon,
color: COLORS.boat,
action: async (params: MenuElementParams) => {
const spawn = await params.playerActionHandler.findBestTransportShipSpawn(
params.myPlayer,
@@ -523,9 +514,28 @@ export const boatMenuElement: MenuElement = {
params.closeMenu();
},
color: COLORS.boat,
disabled: (params: MenuElementParams) =>
!params.playerActions.buildableUnits.some(
(unit) => unit.type === UnitType.TransportShip && unit.canBuild,
),
icon: boatIcon,
id: Slot.Boat,
name: "boat",
};
export const centerButtonElement: CenterButtonElement = {
action: (params: MenuElementParams) => {
if (params.game.inSpawnPhase()) {
params.playerActionHandler.handleSpawn(params.tile);
} else {
params.playerActionHandler.handleAttack(
params.myPlayer,
params.selected?.id() ?? null,
);
}
params.closeMenu();
},
disabled: (params: MenuElementParams): boolean => {
const tileOwner = params.game.owner(params.tile);
const isLand = params.game.isLand(params.tile);
@@ -540,25 +550,14 @@ export const centerButtonElement: CenterButtonElement = {
}
return !params.playerActions.canAttack;
},
action: (params: MenuElementParams) => {
if (params.game.inSpawnPhase()) {
params.playerActionHandler.handleSpawn(params.tile);
} else {
params.playerActionHandler.handleAttack(
params.myPlayer,
params.selected?.id() ?? null,
);
}
params.closeMenu();
},
};
export const rootMenuElement: MenuElement = {
id: "root",
name: "root",
color: COLORS.info,
disabled: () => false,
icon: infoIcon,
color: COLORS.info,
id: "root",
name: "root",
subMenu: (params: MenuElementParams) => {
let ally = allyRequestElement;
if (params.selected?.isAlliedWith(params.myPlayer)) {
+3 -3
View File
@@ -123,9 +123,9 @@ export class RailroadLayer implements Layer {
railTile.lastOwnerId = currentOwner;
} else {
this.existingRailroads.set(railRoad.tile, {
tile: railRoad,
numOccurence: 1,
lastOwnerId: currentOwner,
numOccurence: 1,
tile: railRoad,
});
this.railTileList.push(railRoad.tile);
this.paintRail(railRoad);
@@ -156,7 +156,7 @@ export class RailroadLayer implements Layer {
const recipient = owner.isPlayer() ? owner : null;
const color = recipient
? this.theme.railroadColor(recipient)
: new Colord({ r: 255, g: 255, b: 255, a: 1 });
: new Colord({ a: 1, b: 255, g: 255, r: 255 });
if (this.context === undefined) throw new Error("Not initialized");
this.context.fillStyle = color.toRgbString();
this.paintRailRects(x, y, railRoad.railType);
+1 -1
View File
@@ -79,8 +79,8 @@ export class SpawnAd extends LitElement implements Layer {
window.ramp.que.push(() => {
window.ramp.spaAddAds([
{
type: AD_TYPE,
selectorId: AD_CONTAINER_ID,
type: AD_TYPE,
},
]);
this.adLoaded = true;
@@ -68,20 +68,20 @@ export class StructureIconsLayer implements Layer {
UnitType,
{ visible: boolean; iconPath: string; image: HTMLImageElement | null }
> = new Map([
[UnitType.City, { visible: true, iconPath: cityIcon, image: null }],
[UnitType.Factory, { visible: true, iconPath: factoryIcon, image: null }],
[UnitType.City, { iconPath: cityIcon, image: null, visible: true }],
[UnitType.Factory, { iconPath: factoryIcon, image: null, visible: true }],
[
UnitType.DefensePost,
{ visible: true, iconPath: shieldIcon, image: null },
{ iconPath: shieldIcon, image: null, visible: true },
],
[UnitType.Port, { visible: true, iconPath: anchorIcon, image: null }],
[UnitType.Port, { iconPath: anchorIcon, image: null, visible: true }],
[
UnitType.MissileSilo,
{ visible: true, iconPath: missileSiloIcon, image: null },
{ iconPath: missileSiloIcon, image: null, visible: true },
],
[
UnitType.SAMLauncher,
{ visible: true, iconPath: SAMMissileIcon, image: null },
{ iconPath: SAMMissileIcon, image: null, visible: true },
],
]);
private renderSprites = true;
@@ -119,14 +119,14 @@ export class StructureIconsLayer implements Layer {
this.dotsStage.setSize(this.pixicanvas.width, this.pixicanvas.height);
await this.renderer.init({
canvas: this.pixicanvas,
resolution: 1,
width: this.pixicanvas.width,
height: this.pixicanvas.height,
antialias: false,
clearBeforeRender: true,
backgroundAlpha: 0,
backgroundColor: 0x00000000,
canvas: this.pixicanvas,
clearBeforeRender: true,
height: this.pixicanvas.height,
resolution: 1,
width: this.pixicanvas.width,
});
}
@@ -249,10 +249,10 @@ export class StructureIconsLayer implements Layer {
render.dotContainer.alpha = structureInfos.visible ? 1 : 0.3;
if (structureInfos.visible && focusStructure) {
render.iconContainer.filters = [
new OutlineFilter({ thickness: 2, color: "rgb(255, 255, 255)" }),
new OutlineFilter({ color: "rgb(255, 255, 255)", thickness: 2 }),
];
render.dotContainer.filters = [
new OutlineFilter({ thickness: 2, color: "rgb(255, 255, 255)" }),
new OutlineFilter({ color: "rgb(255, 255, 255)", thickness: 2 }),
];
} else {
render.iconContainer.filters = [];
@@ -490,11 +490,11 @@ export class StructureIconsLayer implements Layer {
if (renderIcon) {
const SHAPE_OFFSETS = {
triangle: [6, 11],
square: [5, 5],
circle: [6, 6],
octagon: [6, 6],
pentagon: [7, 7],
circle: [6, 6],
square: [5, 5],
triangle: [6, 11],
};
const [offsetX, offsetY] = SHAPE_OFFSETS[shape] || [0, 0];
context.drawImage(
@@ -509,24 +509,24 @@ export class StructureIconsLayer implements Layer {
private createLevelSprite(unit: UnitView): PIXI.Container {
if (this.levelsStage === undefined) throw new Error("Not initialized");
return this.createUnitContainer(unit, {
type: "level",
stage: this.levelsStage,
type: "level",
});
}
private createDotSprite(unit: UnitView): PIXI.Container {
if (this.dotsStage === undefined) throw new Error("Not initialized");
return this.createUnitContainer(unit, {
type: "dot",
stage: this.dotsStage,
type: "dot",
});
}
private createIconSprite(unit: UnitView): PIXI.Container {
if (this.iconsStage === undefined) throw new Error("Not initialized");
return this.createUnitContainer(unit, {
type: "icon",
stage: this.iconsStage,
type: "icon",
});
}
@@ -557,11 +557,11 @@ export class StructureIconsLayer implements Layer {
// Add level text if needed
if ((type === "icon" || type === "level") && unit.level() > 1) {
const text = new PIXI.BitmapText({
text: unit.level().toString(),
style: {
fontFamily: "round_6x6_modified",
fontSize: 14,
},
text: unit.level().toString(),
});
text.anchor.set(0.5);
+7 -7
View File
@@ -14,7 +14,7 @@ import { GameView, UnitView } from "../../../core/game/GameView";
import { TransformHandler } from "../TransformHandler";
import { Layer } from "./Layer";
const underConstructionColor = colord({ r: 150, g: 150, b: 150 });
const underConstructionColor = colord({ b: 150, g: 150, r: 150 });
// Base radius values and scaling factor for unit borders and territories
const BASE_BORDER_RADIUS = 16.5;
@@ -39,33 +39,33 @@ export class StructureLayer implements Layer {
// Configuration for supported unit types only
private readonly unitConfigs: Partial<Record<UnitType, UnitRenderConfig>> = {
[UnitType.Port]: {
icon: anchorIcon,
borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR,
icon: anchorIcon,
territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR,
},
[UnitType.City]: {
icon: cityIcon,
borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR,
icon: cityIcon,
territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR,
},
[UnitType.Factory]: {
icon: factoryIcon,
borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR,
icon: factoryIcon,
territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR,
},
[UnitType.MissileSilo]: {
icon: missileSiloIcon,
borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR,
icon: missileSiloIcon,
territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR,
},
[UnitType.DefensePost]: {
icon: shieldIcon,
borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR,
icon: shieldIcon,
territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR,
},
[UnitType.SAMLauncher]: {
icon: SAMMissileIcon,
borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR,
icon: SAMMissileIcon,
territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR,
},
};
+6 -6
View File
@@ -89,17 +89,17 @@ export class TeamStats extends LitElement implements Layer {
const totalScorePercent = totalScoreSort / this.game.numLandTiles();
return {
teamName: teamStr,
totalScoreStr: formatPercentage(totalScorePercent),
totalScoreSort,
totalGold: renderNumber(totalGold),
totalTroops: renderNumber(totalTroops / 10),
players: teamPlayers,
teamName: teamStr,
totalCities: renderNumber(totalCities),
totalGold: renderNumber(totalGold),
totalLaunchers: renderNumber(totalLaunchers),
totalSAMs: renderNumber(totalSAMs),
totalScoreSort,
totalScoreStr: formatPercentage(totalScorePercent),
totalTroops: renderNumber(totalTroops / 10),
totalWarShips: renderNumber(totalWarShips),
totalCities: renderNumber(totalCities),
};
})
.sort((a, b) => b.totalScoreSort - a.totalScoreSort);
+1 -1
View File
@@ -544,8 +544,8 @@ export class TerritoryLayer implements Layer {
enqueueTile(tile: TileRef) {
this.tileToRenderQueue.push({
tile,
lastUpdate: this.game.ticks() + this.random.nextFloat(0, 0.5),
tile,
});
}
+2 -2
View File
@@ -252,9 +252,9 @@ export class UILayer implements Layer {
// Store current selection box position for next cleanup
this.lastSelectionBoxCenter = {
size: selectionSize,
x: centerX,
y: centerY,
size: selectionSize,
};
}
@@ -349,8 +349,8 @@ export class UILayer implements Layer {
0,
);
this.allProgressBars.set(unit.id(), {
unit,
progressBar,
unit,
});
}
}
+1 -1
View File
@@ -300,7 +300,7 @@ export class UnitLayer implements Layer {
private handleWarShipEvent(unit: UnitView) {
if (unit.targetUnitId()) {
this.drawSprite(unit, colord({ r: 200, b: 0, g: 0 }));
this.drawSprite(unit, colord({ b: 0, g: 0, r: 200 }));
} else {
this.drawSprite(unit);
}
+3 -3
View File
@@ -85,10 +85,10 @@ export async function logOut(allSessions = false) {
const response = await fetch(
getApiBase() + (allSessions ? "/revoke" : "/logout"),
{
method: "POST",
headers: {
authorization: `Bearer ${token}`,
},
method: "POST",
},
);
@@ -179,7 +179,7 @@ function _isLoggedIn(): IsLoggedInResponse {
}
const claims = result.data;
return { token, claims };
return { claims, token };
} catch (e) {
console.log(e);
return false;
@@ -193,10 +193,10 @@ export async function postRefresh(): Promise<boolean> {
// Refresh the JWT
const response = await fetch(getApiBase() + "/refresh", {
method: "POST",
headers: {
authorization: `Bearer ${token}`,
},
method: "POST",
});
if (response.status === 401) {
clearToken();
+10 -10
View File
@@ -9,16 +9,16 @@ export type UnitTypeRenderContext = {
};
const unitOptions: { type: UnitType; translationKey: string }[] = [
{ type: UnitType.City, translationKey: "unit_type.city" },
{ type: UnitType.DefensePost, translationKey: "unit_type.defense_post" },
{ type: UnitType.Port, translationKey: "unit_type.port" },
{ type: UnitType.Warship, translationKey: "unit_type.warship" },
{ type: UnitType.MissileSilo, translationKey: "unit_type.missile_silo" },
{ type: UnitType.SAMLauncher, translationKey: "unit_type.sam_launcher" },
{ type: UnitType.AtomBomb, translationKey: "unit_type.atom_bomb" },
{ type: UnitType.HydrogenBomb, translationKey: "unit_type.hydrogen_bomb" },
{ type: UnitType.MIRV, translationKey: "unit_type.mirv" },
{ type: UnitType.Factory, translationKey: "unit_type.factory" },
{ translationKey: "unit_type.city", type: UnitType.City },
{ translationKey: "unit_type.defense_post", type: UnitType.DefensePost },
{ translationKey: "unit_type.port", type: UnitType.Port },
{ translationKey: "unit_type.warship", type: UnitType.Warship },
{ translationKey: "unit_type.missile_silo", type: UnitType.MissileSilo },
{ translationKey: "unit_type.sam_launcher", type: UnitType.SAMLauncher },
{ translationKey: "unit_type.atom_bomb", type: UnitType.AtomBomb },
{ translationKey: "unit_type.hydrogen_bomb", type: UnitType.HydrogenBomb },
{ translationKey: "unit_type.mirv", type: UnitType.MIRV },
{ translationKey: "unit_type.factory", type: UnitType.Factory },
];
export function renderUnitTypeOptions({
+20 -21
View File
@@ -8,8 +8,11 @@ export const RefreshResponseSchema = z.object({
});
export type RefreshResponse = z.infer<typeof RefreshResponseSchema>;
/* eslint-disable sort-keys */
export const TokenPayloadSchema = z.object({
aud: z.string(),
exp: z.number(),
iat: z.number(),
iss: z.string(),
jti: z.string(),
sub: z
.string()
@@ -27,41 +30,37 @@ export const TokenPayloadSchema = z.object({
if (!uuid) throw new Error("Invalid base64 UUID");
return uuid;
}),
iat: z.number(),
iss: z.string(),
aud: z.string(),
exp: z.number(),
});
export type TokenPayload = z.infer<typeof TokenPayloadSchema>;
export const UserMeResponseSchema = z.object({
user: z.object({
id: z.string(),
avatar: z.string().nullable(),
username: z.string(),
global_name: z.string().nullable(),
discriminator: z.string(),
locale: z.string().optional(),
}),
player: z.object({
flares: z.string().array().optional(),
publicId: z.string(),
roles: z.string().array().optional(),
flares: z.string().array().optional(),
}),
user: z.object({
avatar: z.string().nullable(),
discriminator: z.string(),
global_name: z.string().nullable(),
id: z.string(),
locale: z.string().optional(),
username: z.string(),
}),
});
export type UserMeResponse = z.infer<typeof UserMeResponseSchema>;
export const StripeCreateCheckoutSessionResponseSchema = z.object({
id: z.string(),
object: z.literal("checkout.session"),
url: z.string(),
payment_status: z.enum(["paid", "unpaid", "no_payment_required"]),
status: z.enum(["open", "complete", "expired"]),
client_reference_id: z.string().optional(),
customer: z.string().optional(),
payment_intent: z.string().optional(),
subscription: z.string().optional(),
id: z.string(),
metadata: z.partialRecord(z.string(), z.string()),
object: z.literal("checkout.session"),
payment_intent: z.string().optional(),
payment_status: z.enum(["paid", "unpaid", "no_payment_required"]),
status: z.enum(["open", "complete", "expired"]),
subscription: z.string().optional(),
url: z.string(),
});
export type StripeCreateCheckoutSessionResponse = z.infer<
typeof StripeCreateCheckoutSessionResponseSchema
+12 -15
View File
@@ -2,11 +2,10 @@ import { z } from "zod";
import { RequiredPatternSchema } from "./Schemas";
export const ProductSchema = z.object({
productId: z.string(),
/* eslint-disable sort-keys */
priceId: z.string(),
price: z.string(),
/* eslint-enable sort-keys */
priceId: z.string(),
productId: z.string(),
});
const PatternSchema = z.object({
@@ -17,28 +16,26 @@ const PatternSchema = z.object({
// Schema for resources/cosmetics/cosmetics.json
export const CosmeticsSchema = z.object({
patterns: z.record(z.string(), PatternSchema),
/* eslint-disable sort-keys */
flag: z
.object({
layers: z.record(
z.string(),
z.object({
name: z.string(),
flares: z.string().array().optional(),
}),
),
color: z.record(
z.string(),
z.object({
color: z.string(),
name: z.string(),
flares: z.string().array().optional(),
name: z.string(),
}),
),
layers: z.record(
z.string(),
z.object({
flares: z.string().array().optional(),
name: z.string(),
}),
),
})
.optional(),
/* eslint-enable sort-keys */
patterns: z.record(z.string(), PatternSchema),
});
export type Cosmetics = z.infer<typeof CosmeticsSchema>;
export type Pattern = z.infer<typeof PatternSchema>;
+2 -2
View File
@@ -28,8 +28,8 @@ export function renderPlayerFlag(
const code = flag.slice("!".length);
const layers = code.split("_").map((segment) => {
const [layerKey, colorKey] = segment.split("-");
// eslint-disable-next-line sort-keys
return { layerKey, colorKey };
return { colorKey, layerKey };
});
target.innerHTML = "";
+1 -1
View File
@@ -138,8 +138,8 @@ export type TeamCountConfig = z.infer<typeof TeamCountConfigSchema>;
export const GameConfigSchema = z.object({
bots: z.number().int().min(0).max(400),
difficulty: z.enum(Difficulty),
disableNPCs: z.boolean(),
disabledUnits: z.enum(UnitType).array().optional(),
disableNPCs: z.boolean(),
donateGold: z.boolean(),
donateTroops: z.boolean(),
gameMap: z.enum(GameMapType),
+1 -2
View File
@@ -89,8 +89,7 @@ export function calculateBoundingBox(
maxY = Math.max(maxY, cell.y);
});
// eslint-disable-next-line sort-keys
return { min: new Cell(minX, minY), max: new Cell(maxX, maxY) };
return { max: new Cell(maxX, maxY), min: new Cell(minX, minY) };
}
export function calculateBoundingBoxCenter(
+318 -319
View File
@@ -5,15 +5,14 @@ import lchPlugin from "colord/plugins/lch";
extend([lchPlugin]);
extend([labPlugin]);
/* eslint-disable sort-keys */
export const red = colord({ h: 0, s: 82, l: 56 });
export const blue = colord({ h: 224, s: 100, l: 58 });
export const teal = colord({ h: 172, s: 66, l: 50 });
export const purple = colord({ h: 271, s: 81, l: 56 });
export const yellow = colord({ h: 45, s: 93, l: 47 });
export const orange = colord({ h: 25, s: 95, l: 53 });
export const green = colord({ h: 128, s: 49, l: 50 });
export const botColor = colord({ h: 36, s: 10, l: 80 });
export const red = colord({ h: 0, l: 56, s: 82 });
export const blue = colord({ h: 224, l: 58, s: 100 });
export const teal = colord({ h: 172, l: 50, s: 66 });
export const purple = colord({ h: 271, l: 56, s: 81 });
export const yellow = colord({ h: 45, l: 47, s: 93 });
export const orange = colord({ h: 25, l: 53, s: 95 });
export const green = colord({ h: 128, l: 50, s: 49 });
export const botColor = colord({ h: 36, l: 80, s: 10 });
export const redTeamColors: Colord[] = generateTeamColors(red);
export const blueTeamColors: Colord[] = generateTeamColors(blue);
@@ -36,331 +35,331 @@ function generateTeamColors(baseColor: Colord): Colord[] {
return colord({
h: baseHue,
s: saturation,
l: lightness,
s: saturation,
});
});
}
export const nationColors: Colord[] = [
colord({ r: 230, g: 100, b: 100 }), // Bright Red
colord({ r: 100, g: 180, b: 230 }), // Sky Blue
colord({ r: 230, g: 180, b: 80 }), // Golden Yellow
colord({ r: 180, g: 100, b: 230 }), // Purple
colord({ r: 80, g: 200, b: 120 }), // Emerald Green
colord({ r: 230, g: 130, b: 180 }), // Pink
colord({ r: 100, g: 160, b: 80 }), // Olive Green
colord({ r: 230, g: 150, b: 100 }), // Peach
colord({ r: 80, g: 130, b: 190 }), // Navy Blue
colord({ r: 210, g: 210, b: 100 }), // Lime Yellow
colord({ r: 190, g: 100, b: 130 }), // Maroon
colord({ r: 100, g: 210, b: 210 }), // Turquoise
colord({ r: 210, g: 140, b: 80 }), // Light Orange
colord({ r: 150, g: 110, b: 190 }), // Lavender
colord({ r: 180, g: 210, b: 120 }), // Light Green
colord({ r: 210, g: 100, b: 160 }), // Hot Pink
colord({ r: 100, g: 140, b: 110 }), // Sea Green
colord({ r: 230, g: 180, b: 180 }), // Light Pink
colord({ r: 120, g: 120, b: 190 }), // Periwinkle
colord({ r: 190, g: 170, b: 100 }), // Sand
colord({ r: 100, g: 180, b: 160 }), // Aquamarine
colord({ r: 210, g: 160, b: 200 }), // Orchid
colord({ r: 170, g: 190, b: 100 }), // Yellow Green
colord({ r: 100, g: 130, b: 150 }), // Steel Blue
colord({ r: 230, g: 140, b: 140 }), // Salmon
colord({ r: 140, g: 180, b: 220 }), // Light Blue
colord({ r: 200, g: 160, b: 110 }), // Tan
colord({ r: 180, g: 130, b: 180 }), // Plum
colord({ r: 130, g: 200, b: 130 }), // Light Sea Green
colord({ r: 220, g: 120, b: 120 }), // Coral
colord({ r: 120, g: 160, b: 200 }), // Cornflower Blue
colord({ r: 200, g: 200, b: 140 }), // Khaki
colord({ r: 160, g: 120, b: 160 }), // Purple Gray
colord({ r: 140, g: 180, b: 140 }), // Dark Sea Green
colord({ r: 200, g: 130, b: 110 }), // Dark Salmon
colord({ r: 130, g: 170, b: 190 }), // Cadet Blue
colord({ r: 190, g: 180, b: 160 }), // Tan Gray
colord({ r: 170, g: 140, b: 190 }), // Medium Purple
colord({ r: 160, g: 190, b: 160 }), // Pale Green
colord({ r: 190, g: 150, b: 130 }), // Rosy Brown
colord({ r: 140, g: 150, b: 180 }), // Light Slate Gray
colord({ r: 180, g: 170, b: 140 }), // Dark Khaki
colord({ r: 150, g: 130, b: 150 }), // Thistle
colord({ r: 170, g: 190, b: 180 }), // Pale Blue Green
colord({ r: 190, g: 140, b: 150 }), // Puce
colord({ r: 130, g: 180, b: 170 }), // Medium Aquamarine
colord({ r: 180, g: 160, b: 180 }), // Mauve
colord({ r: 160, g: 180, b: 140 }), // Dark Olive Green
colord({ r: 170, g: 150, b: 170 }), // Dusty Rose
colord({ r: 100, g: 180, b: 230 }), // Sky Blue
colord({ r: 230, g: 180, b: 80 }), // Golden Yellow
colord({ r: 180, g: 100, b: 230 }), // Purple
colord({ r: 80, g: 200, b: 120 }), // Emerald Green
colord({ r: 230, g: 130, b: 180 }), // Pink
colord({ r: 100, g: 160, b: 80 }), // Olive Green
colord({ r: 230, g: 150, b: 100 }), // Peach
colord({ r: 80, g: 130, b: 190 }), // Navy Blue
colord({ r: 210, g: 210, b: 100 }), // Lime Yellow
colord({ r: 190, g: 100, b: 130 }), // Maroon
colord({ r: 100, g: 210, b: 210 }), // Turquoise
colord({ r: 210, g: 140, b: 80 }), // Light Orange
colord({ r: 150, g: 110, b: 190 }), // Lavender
colord({ r: 180, g: 210, b: 120 }), // Light Green
colord({ r: 210, g: 100, b: 160 }), // Hot Pink
colord({ r: 100, g: 140, b: 110 }), // Sea Green
colord({ r: 230, g: 180, b: 180 }), // Light Pink
colord({ r: 120, g: 120, b: 190 }), // Periwinkle
colord({ r: 190, g: 170, b: 100 }), // Sand
colord({ r: 100, g: 180, b: 160 }), // Aquamarine
colord({ r: 210, g: 160, b: 200 }), // Orchid
colord({ r: 170, g: 190, b: 100 }), // Yellow Green
colord({ r: 100, g: 130, b: 150 }), // Steel Blue
colord({ r: 230, g: 140, b: 140 }), // Salmon
colord({ r: 140, g: 180, b: 220 }), // Light Blue
colord({ r: 200, g: 160, b: 110 }), // Tan
colord({ r: 180, g: 130, b: 180 }), // Plum
colord({ r: 130, g: 200, b: 130 }), // Light Sea Green
colord({ r: 220, g: 120, b: 120 }), // Coral
colord({ r: 120, g: 160, b: 200 }), // Cornflower Blue
colord({ r: 200, g: 200, b: 140 }), // Khaki
colord({ r: 160, g: 120, b: 160 }), // Purple Gray
colord({ r: 140, g: 180, b: 140 }), // Dark Sea Green
colord({ r: 200, g: 130, b: 110 }), // Dark Salmon
colord({ r: 130, g: 170, b: 190 }), // Cadet Blue
colord({ r: 190, g: 180, b: 160 }), // Tan Gray
colord({ r: 170, g: 140, b: 190 }), // Medium Purple
colord({ r: 160, g: 190, b: 160 }), // Pale Green
colord({ r: 190, g: 150, b: 130 }), // Rosy Brown
colord({ r: 140, g: 150, b: 180 }), // Light Slate Gray
colord({ r: 180, g: 170, b: 140 }), // Dark Khaki
colord({ r: 150, g: 130, b: 150 }), // Thistle
colord({ r: 170, g: 190, b: 180 }), // Pale Blue Green
colord({ r: 190, g: 140, b: 150 }), // Puce
colord({ r: 130, g: 180, b: 170 }), // Medium Aquamarine
colord({ r: 180, g: 160, b: 180 }), // Mauve
colord({ r: 160, g: 180, b: 140 }), // Dark Olive Green
colord({ r: 170, g: 150, b: 170 }), // Dusty Rose
colord({ b: 100, g: 100, r: 230 }), // Bright Red
colord({ b: 230, g: 180, r: 100 }), // Sky Blue
colord({ b: 80, g: 180, r: 230 }), // Golden Yellow
colord({ b: 230, g: 100, r: 180 }), // Purple
colord({ b: 120, g: 200, r: 80 }), // Emerald Green
colord({ b: 180, g: 130, r: 230 }), // Pink
colord({ b: 80, g: 160, r: 100 }), // Olive Green
colord({ b: 100, g: 150, r: 230 }), // Peach
colord({ b: 190, g: 130, r: 80 }), // Navy Blue
colord({ b: 100, g: 210, r: 210 }), // Lime Yellow
colord({ b: 130, g: 100, r: 190 }), // Maroon
colord({ b: 210, g: 210, r: 100 }), // Turquoise
colord({ b: 80, g: 140, r: 210 }), // Light Orange
colord({ b: 190, g: 110, r: 150 }), // Lavender
colord({ b: 120, g: 210, r: 180 }), // Light Green
colord({ b: 160, g: 100, r: 210 }), // Hot Pink
colord({ b: 110, g: 140, r: 100 }), // Sea Green
colord({ b: 180, g: 180, r: 230 }), // Light Pink
colord({ b: 190, g: 120, r: 120 }), // Periwinkle
colord({ b: 100, g: 170, r: 190 }), // Sand
colord({ b: 160, g: 180, r: 100 }), // Aquamarine
colord({ b: 200, g: 160, r: 210 }), // Orchid
colord({ b: 100, g: 190, r: 170 }), // Yellow Green
colord({ b: 150, g: 130, r: 100 }), // Steel Blue
colord({ b: 140, g: 140, r: 230 }), // Salmon
colord({ b: 220, g: 180, r: 140 }), // Light Blue
colord({ b: 110, g: 160, r: 200 }), // Tan
colord({ b: 180, g: 130, r: 180 }), // Plum
colord({ b: 130, g: 200, r: 130 }), // Light Sea Green
colord({ b: 120, g: 120, r: 220 }), // Coral
colord({ b: 200, g: 160, r: 120 }), // Cornflower Blue
colord({ b: 140, g: 200, r: 200 }), // Khaki
colord({ b: 160, g: 120, r: 160 }), // Purple Gray
colord({ b: 140, g: 180, r: 140 }), // Dark Sea Green
colord({ b: 110, g: 130, r: 200 }), // Dark Salmon
colord({ b: 190, g: 170, r: 130 }), // Cadet Blue
colord({ b: 160, g: 180, r: 190 }), // Tan Gray
colord({ b: 190, g: 140, r: 170 }), // Medium Purple
colord({ b: 160, g: 190, r: 160 }), // Pale Green
colord({ b: 130, g: 150, r: 190 }), // Rosy Brown
colord({ b: 180, g: 150, r: 140 }), // Light Slate Gray
colord({ b: 140, g: 170, r: 180 }), // Dark Khaki
colord({ b: 150, g: 130, r: 150 }), // Thistle
colord({ b: 180, g: 190, r: 170 }), // Pale Blue Green
colord({ b: 150, g: 140, r: 190 }), // Puce
colord({ b: 170, g: 180, r: 130 }), // Medium Aquamarine
colord({ b: 180, g: 160, r: 180 }), // Mauve
colord({ b: 140, g: 180, r: 160 }), // Dark Olive Green
colord({ b: 170, g: 150, r: 170 }), // Dusty Rose
colord({ b: 230, g: 180, r: 100 }), // Sky Blue
colord({ b: 80, g: 180, r: 230 }), // Golden Yellow
colord({ b: 230, g: 100, r: 180 }), // Purple
colord({ b: 120, g: 200, r: 80 }), // Emerald Green
colord({ b: 180, g: 130, r: 230 }), // Pink
colord({ b: 80, g: 160, r: 100 }), // Olive Green
colord({ b: 100, g: 150, r: 230 }), // Peach
colord({ b: 190, g: 130, r: 80 }), // Navy Blue
colord({ b: 100, g: 210, r: 210 }), // Lime Yellow
colord({ b: 130, g: 100, r: 190 }), // Maroon
colord({ b: 210, g: 210, r: 100 }), // Turquoise
colord({ b: 80, g: 140, r: 210 }), // Light Orange
colord({ b: 190, g: 110, r: 150 }), // Lavender
colord({ b: 120, g: 210, r: 180 }), // Light Green
colord({ b: 160, g: 100, r: 210 }), // Hot Pink
colord({ b: 110, g: 140, r: 100 }), // Sea Green
colord({ b: 180, g: 180, r: 230 }), // Light Pink
colord({ b: 190, g: 120, r: 120 }), // Periwinkle
colord({ b: 100, g: 170, r: 190 }), // Sand
colord({ b: 160, g: 180, r: 100 }), // Aquamarine
colord({ b: 200, g: 160, r: 210 }), // Orchid
colord({ b: 100, g: 190, r: 170 }), // Yellow Green
colord({ b: 150, g: 130, r: 100 }), // Steel Blue
colord({ b: 140, g: 140, r: 230 }), // Salmon
colord({ b: 220, g: 180, r: 140 }), // Light Blue
colord({ b: 110, g: 160, r: 200 }), // Tan
colord({ b: 180, g: 130, r: 180 }), // Plum
colord({ b: 130, g: 200, r: 130 }), // Light Sea Green
colord({ b: 120, g: 120, r: 220 }), // Coral
colord({ b: 200, g: 160, r: 120 }), // Cornflower Blue
colord({ b: 140, g: 200, r: 200 }), // Khaki
colord({ b: 160, g: 120, r: 160 }), // Purple Gray
colord({ b: 140, g: 180, r: 140 }), // Dark Sea Green
colord({ b: 110, g: 130, r: 200 }), // Dark Salmon
colord({ b: 190, g: 170, r: 130 }), // Cadet Blue
colord({ b: 160, g: 180, r: 190 }), // Tan Gray
colord({ b: 190, g: 140, r: 170 }), // Medium Purple
colord({ b: 160, g: 190, r: 160 }), // Pale Green
colord({ b: 130, g: 150, r: 190 }), // Rosy Brown
colord({ b: 180, g: 150, r: 140 }), // Light Slate Gray
colord({ b: 140, g: 170, r: 180 }), // Dark Khaki
colord({ b: 150, g: 130, r: 150 }), // Thistle
colord({ b: 180, g: 190, r: 170 }), // Pale Blue Green
colord({ b: 150, g: 140, r: 190 }), // Puce
colord({ b: 170, g: 180, r: 130 }), // Medium Aquamarine
colord({ b: 180, g: 160, r: 180 }), // Mauve
colord({ b: 140, g: 180, r: 160 }), // Dark Olive Green
colord({ b: 170, g: 150, r: 170 }), // Dusty Rose
];
// Bright pastel theme with 64 colors
export const humanColors: Colord[] = [
colord({ r: 16, g: 185, b: 129 }), // Sea Green
colord({ r: 34, g: 197, b: 94 }), // Emerald
colord({ r: 45, g: 212, b: 191 }), // Turquoise
colord({ r: 48, g: 178, b: 180 }), // Teal
colord({ r: 52, g: 211, b: 153 }), // Spearmint
colord({ r: 56, g: 189, b: 248 }), // Light Blue
colord({ r: 59, g: 130, b: 246 }), // Royal Blue
colord({ r: 67, g: 190, b: 84 }), // Fresh Green
colord({ r: 74, g: 222, b: 128 }), // Mint
colord({ r: 79, g: 70, b: 229 }), // Indigo
colord({ r: 82, g: 183, b: 136 }), // Jade
colord({ r: 96, g: 165, b: 250 }), // Sky Blue
colord({ r: 99, g: 202, b: 253 }), // Azure
colord({ r: 110, g: 231, b: 183 }), // Seafoam
colord({ r: 124, g: 58, b: 237 }), // Royal Purple
colord({ r: 125, g: 211, b: 252 }), // Crystal Blue
colord({ r: 132, g: 204, b: 22 }), // Lime
colord({ r: 133, g: 77, b: 14 }), // Chocolate
colord({ r: 134, g: 239, b: 172 }), // Light Green
colord({ r: 147, g: 51, b: 234 }), // Bright Purple
colord({ r: 147, g: 197, b: 253 }), // Powder Blue
colord({ r: 151, g: 255, b: 187 }), // Fresh Mint
colord({ r: 163, g: 230, b: 53 }), // Yellow Green
colord({ r: 167, g: 139, b: 250 }), // Periwinkle
colord({ r: 168, g: 85, b: 247 }), // Vibrant Purple
colord({ r: 179, g: 136, b: 255 }), // Light Purple
colord({ r: 186, g: 255, b: 201 }), // Pale Emerald
colord({ r: 190, g: 92, b: 251 }), // Amethyst
colord({ r: 192, g: 132, b: 252 }), // Lavender
colord({ r: 202, g: 138, b: 4 }), // Rich Gold
colord({ r: 202, g: 225, b: 255 }), // Baby Blue
colord({ r: 204, g: 204, b: 255 }), // Soft Lavender Blue
colord({ r: 217, g: 70, b: 239 }), // Fuchsia
colord({ r: 220, g: 38, b: 38 }), // Ruby
colord({ r: 220, g: 220, b: 255 }), // Meringue Blue
colord({ r: 220, g: 240, b: 250 }), // Ice Blue
colord({ r: 230, g: 250, b: 210 }), // Pastel Lime
colord({ r: 230, g: 255, b: 250 }), // Mint Whisper
colord({ r: 233, g: 213, b: 255 }), // Light Lilac
colord({ r: 234, g: 88, b: 12 }), // Burnt Orange
colord({ r: 234, g: 179, b: 8 }), // Sunflower
colord({ r: 235, g: 75, b: 75 }), // Bright Red
colord({ r: 236, g: 72, b: 153 }), // Deep Pink
colord({ r: 239, g: 68, b: 68 }), // Crimson
colord({ r: 240, g: 171, b: 252 }), // Orchid
colord({ r: 240, g: 240, b: 200 }), // Light Khaki
colord({ r: 244, g: 114, b: 182 }), // Rose
colord({ r: 245, g: 101, b: 101 }), // Coral
colord({ r: 245, g: 158, b: 11 }), // Amber
colord({ r: 248, g: 113, b: 113 }), // Warm Red
colord({ r: 249, g: 115, b: 22 }), // Tangerine
colord({ r: 250, g: 215, b: 225 }), // Cotton Candy
colord({ r: 250, g: 250, b: 210 }), // Pastel Lemon
colord({ r: 251, g: 113, b: 133 }), // Watermelon
colord({ r: 251, g: 146, b: 60 }), // Light Orange
colord({ r: 251, g: 191, b: 36 }), // Marigold
colord({ r: 251, g: 235, b: 245 }), // Rose Powder
colord({ r: 252, g: 165, b: 165 }), // Peach
colord({ r: 252, g: 211, b: 77 }), // Golden
colord({ r: 253, g: 164, b: 175 }), // Salmon Pink
colord({ r: 255, g: 204, b: 229 }), // Blush Pink
colord({ r: 255, g: 223, b: 186 }), // Apricot Cream
colord({ r: 255, g: 240, b: 200 }), // Vanilla
colord({ b: 129, g: 185, r: 16 }), // Sea Green
colord({ b: 94, g: 197, r: 34 }), // Emerald
colord({ b: 191, g: 212, r: 45 }), // Turquoise
colord({ b: 180, g: 178, r: 48 }), // Teal
colord({ b: 153, g: 211, r: 52 }), // Spearmint
colord({ b: 248, g: 189, r: 56 }), // Light Blue
colord({ b: 246, g: 130, r: 59 }), // Royal Blue
colord({ b: 84, g: 190, r: 67 }), // Fresh Green
colord({ b: 128, g: 222, r: 74 }), // Mint
colord({ b: 229, g: 70, r: 79 }), // Indigo
colord({ b: 136, g: 183, r: 82 }), // Jade
colord({ b: 250, g: 165, r: 96 }), // Sky Blue
colord({ b: 253, g: 202, r: 99 }), // Azure
colord({ b: 183, g: 231, r: 110 }), // Seafoam
colord({ b: 237, g: 58, r: 124 }), // Royal Purple
colord({ b: 252, g: 211, r: 125 }), // Crystal Blue
colord({ b: 22, g: 204, r: 132 }), // Lime
colord({ b: 14, g: 77, r: 133 }), // Chocolate
colord({ b: 172, g: 239, r: 134 }), // Light Green
colord({ b: 234, g: 51, r: 147 }), // Bright Purple
colord({ b: 253, g: 197, r: 147 }), // Powder Blue
colord({ b: 187, g: 255, r: 151 }), // Fresh Mint
colord({ b: 53, g: 230, r: 163 }), // Yellow Green
colord({ b: 250, g: 139, r: 167 }), // Periwinkle
colord({ b: 247, g: 85, r: 168 }), // Vibrant Purple
colord({ b: 255, g: 136, r: 179 }), // Light Purple
colord({ b: 201, g: 255, r: 186 }), // Pale Emerald
colord({ b: 251, g: 92, r: 190 }), // Amethyst
colord({ b: 252, g: 132, r: 192 }), // Lavender
colord({ b: 4, g: 138, r: 202 }), // Rich Gold
colord({ b: 255, g: 225, r: 202 }), // Baby Blue
colord({ b: 255, g: 204, r: 204 }), // Soft Lavender Blue
colord({ b: 239, g: 70, r: 217 }), // Fuchsia
colord({ b: 38, g: 38, r: 220 }), // Ruby
colord({ b: 255, g: 220, r: 220 }), // Meringue Blue
colord({ b: 250, g: 240, r: 220 }), // Ice Blue
colord({ b: 210, g: 250, r: 230 }), // Pastel Lime
colord({ b: 250, g: 255, r: 230 }), // Mint Whisper
colord({ b: 255, g: 213, r: 233 }), // Light Lilac
colord({ b: 12, g: 88, r: 234 }), // Burnt Orange
colord({ b: 8, g: 179, r: 234 }), // Sunflower
colord({ b: 75, g: 75, r: 235 }), // Bright Red
colord({ b: 153, g: 72, r: 236 }), // Deep Pink
colord({ b: 68, g: 68, r: 239 }), // Crimson
colord({ b: 252, g: 171, r: 240 }), // Orchid
colord({ b: 200, g: 240, r: 240 }), // Light Khaki
colord({ b: 182, g: 114, r: 244 }), // Rose
colord({ b: 101, g: 101, r: 245 }), // Coral
colord({ b: 11, g: 158, r: 245 }), // Amber
colord({ b: 113, g: 113, r: 248 }), // Warm Red
colord({ b: 22, g: 115, r: 249 }), // Tangerine
colord({ b: 225, g: 215, r: 250 }), // Cotton Candy
colord({ b: 210, g: 250, r: 250 }), // Pastel Lemon
colord({ b: 133, g: 113, r: 251 }), // Watermelon
colord({ b: 60, g: 146, r: 251 }), // Light Orange
colord({ b: 36, g: 191, r: 251 }), // Marigold
colord({ b: 245, g: 235, r: 251 }), // Rose Powder
colord({ b: 165, g: 165, r: 252 }), // Peach
colord({ b: 77, g: 211, r: 252 }), // Golden
colord({ b: 175, g: 164, r: 253 }), // Salmon Pink
colord({ b: 229, g: 204, r: 255 }), // Blush Pink
colord({ b: 186, g: 223, r: 255 }), // Apricot Cream
colord({ b: 200, g: 240, r: 255 }), // Vanilla
];
export const botColors: Colord[] = [
colord({ r: 190, g: 120, b: 120 }), // Muted Red
colord({ r: 120, g: 160, b: 190 }), // Muted Sky Blue
colord({ r: 190, g: 160, b: 100 }), // Muted Golden Yellow
colord({ r: 160, g: 120, b: 190 }), // Muted Purple
colord({ r: 100, g: 170, b: 130 }), // Muted Emerald Green
colord({ r: 190, g: 130, b: 160 }), // Muted Pink
colord({ r: 120, g: 150, b: 100 }), // Muted Olive Green
colord({ r: 190, g: 140, b: 120 }), // Muted Peach
colord({ r: 100, g: 120, b: 160 }), // Muted Navy Blue
colord({ r: 170, g: 170, b: 120 }), // Muted Lime Yellow
colord({ r: 160, g: 120, b: 130 }), // Muted Maroon
colord({ r: 120, g: 170, b: 170 }), // Muted Turquoise
colord({ r: 170, g: 140, b: 100 }), // Muted Light Orange
colord({ r: 140, g: 120, b: 160 }), // Muted Lavender
colord({ r: 150, g: 170, b: 130 }), // Muted Light Green
colord({ r: 170, g: 120, b: 140 }), // Muted Hot Pink
colord({ r: 120, g: 140, b: 120 }), // Muted Sea Green
colord({ r: 180, g: 160, b: 160 }), // Muted Light Pink
colord({ r: 130, g: 130, b: 160 }), // Muted Periwinkle
colord({ r: 160, g: 150, b: 120 }), // Muted Sand
colord({ r: 120, g: 160, b: 150 }), // Muted Aquamarine
colord({ r: 170, g: 150, b: 170 }), // Muted Orchid
colord({ r: 150, g: 160, b: 120 }), // Muted Yellow Green
colord({ r: 120, g: 130, b: 140 }), // Muted Steel Blue
colord({ r: 180, g: 140, b: 140 }), // Muted Salmon
colord({ r: 140, g: 160, b: 170 }), // Muted Light Blue
colord({ r: 170, g: 150, b: 130 }), // Muted Tan
colord({ r: 160, g: 130, b: 160 }), // Muted Plum
colord({ r: 130, g: 170, b: 130 }), // Muted Light Sea Green
colord({ r: 170, g: 130, b: 130 }), // Muted Coral
colord({ r: 130, g: 150, b: 170 }), // Muted Cornflower Blue
colord({ r: 170, g: 170, b: 140 }), // Muted Khaki
colord({ r: 150, g: 130, b: 150 }), // Muted Purple Gray
colord({ r: 140, g: 160, b: 140 }), // Muted Dark Sea Green
colord({ r: 170, g: 130, b: 120 }), // Muted Dark Salmon
colord({ r: 130, g: 150, b: 160 }), // Muted Cadet Blue
colord({ r: 160, g: 160, b: 150 }), // Muted Tan Gray
colord({ r: 150, g: 140, b: 160 }), // Muted Medium Purple
colord({ r: 150, g: 170, b: 150 }), // Muted Pale Green
colord({ r: 160, g: 140, b: 130 }), // Muted Rosy Brown
colord({ r: 140, g: 150, b: 160 }), // Muted Light Slate Gray
colord({ r: 160, g: 150, b: 140 }), // Muted Dark Khaki
colord({ r: 140, g: 130, b: 140 }), // Muted Thistle
colord({ r: 150, g: 160, b: 160 }), // Muted Pale Blue Green
colord({ r: 160, g: 140, b: 150 }), // Muted Puce
colord({ r: 130, g: 160, b: 150 }), // Muted Medium Aquamarine
colord({ r: 160, g: 150, b: 160 }), // Muted Mauve
colord({ r: 150, g: 160, b: 140 }), // Muted Dark Olive Green
colord({ r: 150, g: 140, b: 150 }), // Muted Dusty Rose
colord({ b: 120, g: 120, r: 190 }), // Muted Red
colord({ b: 190, g: 160, r: 120 }), // Muted Sky Blue
colord({ b: 100, g: 160, r: 190 }), // Muted Golden Yellow
colord({ b: 190, g: 120, r: 160 }), // Muted Purple
colord({ b: 130, g: 170, r: 100 }), // Muted Emerald Green
colord({ b: 160, g: 130, r: 190 }), // Muted Pink
colord({ b: 100, g: 150, r: 120 }), // Muted Olive Green
colord({ b: 120, g: 140, r: 190 }), // Muted Peach
colord({ b: 160, g: 120, r: 100 }), // Muted Navy Blue
colord({ b: 120, g: 170, r: 170 }), // Muted Lime Yellow
colord({ b: 130, g: 120, r: 160 }), // Muted Maroon
colord({ b: 170, g: 170, r: 120 }), // Muted Turquoise
colord({ b: 100, g: 140, r: 170 }), // Muted Light Orange
colord({ b: 160, g: 120, r: 140 }), // Muted Lavender
colord({ b: 130, g: 170, r: 150 }), // Muted Light Green
colord({ b: 140, g: 120, r: 170 }), // Muted Hot Pink
colord({ b: 120, g: 140, r: 120 }), // Muted Sea Green
colord({ b: 160, g: 160, r: 180 }), // Muted Light Pink
colord({ b: 160, g: 130, r: 130 }), // Muted Periwinkle
colord({ b: 120, g: 150, r: 160 }), // Muted Sand
colord({ b: 150, g: 160, r: 120 }), // Muted Aquamarine
colord({ b: 170, g: 150, r: 170 }), // Muted Orchid
colord({ b: 120, g: 160, r: 150 }), // Muted Yellow Green
colord({ b: 140, g: 130, r: 120 }), // Muted Steel Blue
colord({ b: 140, g: 140, r: 180 }), // Muted Salmon
colord({ b: 170, g: 160, r: 140 }), // Muted Light Blue
colord({ b: 130, g: 150, r: 170 }), // Muted Tan
colord({ b: 160, g: 130, r: 160 }), // Muted Plum
colord({ b: 130, g: 170, r: 130 }), // Muted Light Sea Green
colord({ b: 130, g: 130, r: 170 }), // Muted Coral
colord({ b: 170, g: 150, r: 130 }), // Muted Cornflower Blue
colord({ b: 140, g: 170, r: 170 }), // Muted Khaki
colord({ b: 150, g: 130, r: 150 }), // Muted Purple Gray
colord({ b: 140, g: 160, r: 140 }), // Muted Dark Sea Green
colord({ b: 120, g: 130, r: 170 }), // Muted Dark Salmon
colord({ b: 160, g: 150, r: 130 }), // Muted Cadet Blue
colord({ b: 150, g: 160, r: 160 }), // Muted Tan Gray
colord({ b: 160, g: 140, r: 150 }), // Muted Medium Purple
colord({ b: 150, g: 170, r: 150 }), // Muted Pale Green
colord({ b: 130, g: 140, r: 160 }), // Muted Rosy Brown
colord({ b: 160, g: 150, r: 140 }), // Muted Light Slate Gray
colord({ b: 140, g: 150, r: 160 }), // Muted Dark Khaki
colord({ b: 140, g: 130, r: 140 }), // Muted Thistle
colord({ b: 160, g: 160, r: 150 }), // Muted Pale Blue Green
colord({ b: 150, g: 140, r: 160 }), // Muted Puce
colord({ b: 150, g: 160, r: 130 }), // Muted Medium Aquamarine
colord({ b: 160, g: 150, r: 160 }), // Muted Mauve
colord({ b: 140, g: 160, r: 150 }), // Muted Dark Olive Green
colord({ b: 150, g: 140, r: 150 }), // Muted Dusty Rose
];
// Fallback colors for when the color palette is exhausted. Currently 100 colors.
export const fallbackColors: Colord[] = [
colord({ r: 0, g: 5, b: 0 }), // Black Mint
colord({ r: 0, g: 15, b: 0 }), // Deep Forest
colord({ r: 0, g: 25, b: 0 }), // Jungle
colord({ r: 0, g: 35, b: 0 }), // Dark Emerald
colord({ r: 0, g: 45, b: 0 }), // Green Moss
colord({ r: 0, g: 55, b: 0 }), // Moss Shadow
colord({ r: 0, g: 65, b: 0 }), // Dark Meadow
colord({ r: 0, g: 75, b: 0 }), // Forest Fern
colord({ r: 0, g: 85, b: 0 }), // Pine Leaf
colord({ r: 0, g: 95, b: 0 }), // Shadow Grass
colord({ r: 0, g: 105, b: 0 }), // Classic Green
colord({ r: 0, g: 115, b: 0 }), // Deep Lime
colord({ r: 0, g: 125, b: 0 }), // Dense Leaf
colord({ r: 0, g: 135, b: 0 }), // Basil Green
colord({ r: 0, g: 145, b: 0 }), // Organic Green
colord({ r: 0, g: 155, b: 0 }), // Bitter Herb
colord({ r: 0, g: 165, b: 0 }), // Raw Spinach
colord({ r: 0, g: 175, b: 0 }), // Woodland
colord({ r: 0, g: 185, b: 0 }), // Spring Weed
colord({ r: 0, g: 195, b: 5 }), // Apple Stem
colord({ r: 0, g: 205, b: 10 }), // Crisp Lettuce
colord({ r: 0, g: 215, b: 15 }), // Vibrant Green
colord({ r: 0, g: 225, b: 20 }), // Bright Herb
colord({ r: 0, g: 235, b: 25 }), // Green Splash
colord({ r: 0, g: 245, b: 30 }), // Mint Leaf
colord({ r: 0, g: 255, b: 35 }), // Fresh Mint
colord({ r: 10, g: 255, b: 45 }), // Neon Grass
colord({ r: 20, g: 255, b: 55 }), // Lemon Balm
colord({ r: 30, g: 255, b: 65 }), // Juicy Green
colord({ r: 40, g: 255, b: 75 }), // Pear Tint
colord({ r: 50, g: 255, b: 85 }), // Avocado Pastel
colord({ r: 60, g: 255, b: 95 }), // Lime Glow
colord({ r: 70, g: 255, b: 105 }), // Light Leaf
colord({ r: 80, g: 255, b: 115 }), // Soft Fern
colord({ r: 90, g: 255, b: 125 }), // Pastel Green
colord({ r: 100, g: 255, b: 135 }), // Green Melon
colord({ r: 110, g: 255, b: 145 }), // Herbal Mist
colord({ r: 120, g: 255, b: 155 }), // Kiwi Foam
colord({ r: 130, g: 255, b: 165 }), // Aloe Fresh
colord({ r: 140, g: 255, b: 175 }), // Light Mint
colord({ r: 150, g: 200, b: 255 }), // Cornflower Mist
colord({ r: 150, g: 255, b: 185 }), // Green Sorbet
colord({ r: 160, g: 215, b: 255 }), // Powder Blue
colord({ r: 160, g: 255, b: 195 }), // Pastel Apple
colord({ r: 170, g: 190, b: 255 }), // Periwinkle Ice
colord({ r: 170, g: 225, b: 255 }), // Baby Sky
colord({ r: 170, g: 255, b: 205 }), // Aloe Breeze
colord({ r: 180, g: 180, b: 255 }), // Pale Indigo
colord({ r: 180, g: 235, b: 250 }), // Aqua Pastel
colord({ r: 180, g: 255, b: 215 }), // Pale Mint
colord({ r: 190, g: 140, b: 195 }), // Fuchsia Tint
colord({ r: 190, g: 245, b: 240 }), // Ice Mint
colord({ r: 190, g: 255, b: 225 }), // Mint Water
colord({ r: 195, g: 145, b: 200 }), // Dusky Rose
colord({ r: 200, g: 150, b: 205 }), // Plum Frost
colord({ r: 200, g: 170, b: 255 }), // Lilac Bloom
colord({ r: 200, g: 255, b: 215 }), // Cool Aloe
colord({ r: 200, g: 255, b: 235 }), // Cool Mist
colord({ r: 205, g: 155, b: 210 }), // Berry Foam
colord({ r: 210, g: 160, b: 215 }), // Grape Cloud
colord({ r: 210, g: 255, b: 245 }), // Sea Mist
colord({ r: 215, g: 165, b: 220 }), // Light Bloom
colord({ r: 215, g: 255, b: 200 }), // Fresh Mint
colord({ r: 220, g: 160, b: 255 }), // Violet Mist
colord({ r: 220, g: 170, b: 225 }), // Cherry Blossom
colord({ r: 220, g: 255, b: 255 }), // Pale Aqua
colord({ r: 225, g: 175, b: 230 }), // Faded Rose
colord({ r: 225, g: 255, b: 175 }), // Soft Lime
colord({ r: 230, g: 180, b: 235 }), // Dreamy Mauve
colord({ r: 230, g: 250, b: 255 }), // Sky Haze
colord({ r: 235, g: 150, b: 255 }), // Orchid Glow
colord({ r: 235, g: 185, b: 240 }), // Powder Violet
colord({ r: 240, g: 190, b: 245 }), // Pastel Violet
colord({ r: 240, g: 240, b: 255 }), // Frosted Lilac
colord({ r: 240, g: 250, b: 160 }), // Citrus Wash
colord({ r: 245, g: 160, b: 240 }), // Rose Lilac
colord({ r: 245, g: 195, b: 250 }), // Soft Magenta
colord({ r: 245, g: 245, b: 175 }), // Lemon Mist
colord({ r: 250, g: 200, b: 255 }), // Lilac Cream
colord({ r: 250, g: 230, b: 255 }), // Misty Mauve
colord({ r: 255, g: 170, b: 225 }), // Bubblegum Pink
colord({ r: 255, g: 185, b: 215 }), // Blush Mist
colord({ r: 255, g: 195, b: 235 }), // Faded Fuchsia
colord({ r: 255, g: 200, b: 220 }), // Cotton Rose
colord({ r: 255, g: 205, b: 245 }), // Pastel Orchid
colord({ r: 255, g: 205, b: 255 }), // Violet Bloom
colord({ r: 255, g: 210, b: 230 }), // Pastel Blush
colord({ r: 255, g: 210, b: 250 }), // Lavender Mist
colord({ r: 255, g: 210, b: 255 }), // Orchid Mist
colord({ r: 255, g: 215, b: 195 }), // Apricot Glow
colord({ r: 255, g: 215, b: 245 }), // Rose Whisper
colord({ r: 255, g: 220, b: 235 }), // Pink Mist
colord({ r: 255, g: 220, b: 250 }), // Powder Petal
colord({ r: 255, g: 225, b: 180 }), // Butter Peach
colord({ r: 255, g: 225, b: 255 }), // Petal Mist
colord({ r: 255, g: 230, b: 245 }), // Light Rose
colord({ r: 255, g: 235, b: 200 }), // Cream Peach
colord({ r: 255, g: 235, b: 235 }), // Blushed Petal
colord({ r: 255, g: 240, b: 220 }), // Pastel Sand
colord({ r: 255, g: 245, b: 210 }), // Soft Banana
colord({ b: 0, g: 5, r: 0 }), // Black Mint
colord({ b: 0, g: 15, r: 0 }), // Deep Forest
colord({ b: 0, g: 25, r: 0 }), // Jungle
colord({ b: 0, g: 35, r: 0 }), // Dark Emerald
colord({ b: 0, g: 45, r: 0 }), // Green Moss
colord({ b: 0, g: 55, r: 0 }), // Moss Shadow
colord({ b: 0, g: 65, r: 0 }), // Dark Meadow
colord({ b: 0, g: 75, r: 0 }), // Forest Fern
colord({ b: 0, g: 85, r: 0 }), // Pine Leaf
colord({ b: 0, g: 95, r: 0 }), // Shadow Grass
colord({ b: 0, g: 105, r: 0 }), // Classic Green
colord({ b: 0, g: 115, r: 0 }), // Deep Lime
colord({ b: 0, g: 125, r: 0 }), // Dense Leaf
colord({ b: 0, g: 135, r: 0 }), // Basil Green
colord({ b: 0, g: 145, r: 0 }), // Organic Green
colord({ b: 0, g: 155, r: 0 }), // Bitter Herb
colord({ b: 0, g: 165, r: 0 }), // Raw Spinach
colord({ b: 0, g: 175, r: 0 }), // Woodland
colord({ b: 0, g: 185, r: 0 }), // Spring Weed
colord({ b: 5, g: 195, r: 0 }), // Apple Stem
colord({ b: 10, g: 205, r: 0 }), // Crisp Lettuce
colord({ b: 15, g: 215, r: 0 }), // Vibrant Green
colord({ b: 20, g: 225, r: 0 }), // Bright Herb
colord({ b: 25, g: 235, r: 0 }), // Green Splash
colord({ b: 30, g: 245, r: 0 }), // Mint Leaf
colord({ b: 35, g: 255, r: 0 }), // Fresh Mint
colord({ b: 45, g: 255, r: 10 }), // Neon Grass
colord({ b: 55, g: 255, r: 20 }), // Lemon Balm
colord({ b: 65, g: 255, r: 30 }), // Juicy Green
colord({ b: 75, g: 255, r: 40 }), // Pear Tint
colord({ b: 85, g: 255, r: 50 }), // Avocado Pastel
colord({ b: 95, g: 255, r: 60 }), // Lime Glow
colord({ b: 105, g: 255, r: 70 }), // Light Leaf
colord({ b: 115, g: 255, r: 80 }), // Soft Fern
colord({ b: 125, g: 255, r: 90 }), // Pastel Green
colord({ b: 135, g: 255, r: 100 }), // Green Melon
colord({ b: 145, g: 255, r: 110 }), // Herbal Mist
colord({ b: 155, g: 255, r: 120 }), // Kiwi Foam
colord({ b: 165, g: 255, r: 130 }), // Aloe Fresh
colord({ b: 175, g: 255, r: 140 }), // Light Mint
colord({ b: 255, g: 200, r: 150 }), // Cornflower Mist
colord({ b: 185, g: 255, r: 150 }), // Green Sorbet
colord({ b: 255, g: 215, r: 160 }), // Powder Blue
colord({ b: 195, g: 255, r: 160 }), // Pastel Apple
colord({ b: 255, g: 190, r: 170 }), // Periwinkle Ice
colord({ b: 255, g: 225, r: 170 }), // Baby Sky
colord({ b: 205, g: 255, r: 170 }), // Aloe Breeze
colord({ b: 255, g: 180, r: 180 }), // Pale Indigo
colord({ b: 250, g: 235, r: 180 }), // Aqua Pastel
colord({ b: 215, g: 255, r: 180 }), // Pale Mint
colord({ b: 195, g: 140, r: 190 }), // Fuchsia Tint
colord({ b: 240, g: 245, r: 190 }), // Ice Mint
colord({ b: 225, g: 255, r: 190 }), // Mint Water
colord({ b: 200, g: 145, r: 195 }), // Dusky Rose
colord({ b: 205, g: 150, r: 200 }), // Plum Frost
colord({ b: 255, g: 170, r: 200 }), // Lilac Bloom
colord({ b: 215, g: 255, r: 200 }), // Cool Aloe
colord({ b: 235, g: 255, r: 200 }), // Cool Mist
colord({ b: 210, g: 155, r: 205 }), // Berry Foam
colord({ b: 215, g: 160, r: 210 }), // Grape Cloud
colord({ b: 245, g: 255, r: 210 }), // Sea Mist
colord({ b: 220, g: 165, r: 215 }), // Light Bloom
colord({ b: 200, g: 255, r: 215 }), // Fresh Mint
colord({ b: 255, g: 160, r: 220 }), // Violet Mist
colord({ b: 225, g: 170, r: 220 }), // Cherry Blossom
colord({ b: 255, g: 255, r: 220 }), // Pale Aqua
colord({ b: 230, g: 175, r: 225 }), // Faded Rose
colord({ b: 175, g: 255, r: 225 }), // Soft Lime
colord({ b: 235, g: 180, r: 230 }), // Dreamy Mauve
colord({ b: 255, g: 250, r: 230 }), // Sky Haze
colord({ b: 255, g: 150, r: 235 }), // Orchid Glow
colord({ b: 240, g: 185, r: 235 }), // Powder Violet
colord({ b: 245, g: 190, r: 240 }), // Pastel Violet
colord({ b: 255, g: 240, r: 240 }), // Frosted Lilac
colord({ b: 160, g: 250, r: 240 }), // Citrus Wash
colord({ b: 240, g: 160, r: 245 }), // Rose Lilac
colord({ b: 250, g: 195, r: 245 }), // Soft Magenta
colord({ b: 175, g: 245, r: 245 }), // Lemon Mist
colord({ b: 255, g: 200, r: 250 }), // Lilac Cream
colord({ b: 255, g: 230, r: 250 }), // Misty Mauve
colord({ b: 225, g: 170, r: 255 }), // Bubblegum Pink
colord({ b: 215, g: 185, r: 255 }), // Blush Mist
colord({ b: 235, g: 195, r: 255 }), // Faded Fuchsia
colord({ b: 220, g: 200, r: 255 }), // Cotton Rose
colord({ b: 245, g: 205, r: 255 }), // Pastel Orchid
colord({ b: 255, g: 205, r: 255 }), // Violet Bloom
colord({ b: 230, g: 210, r: 255 }), // Pastel Blush
colord({ b: 250, g: 210, r: 255 }), // Lavender Mist
colord({ b: 255, g: 210, r: 255 }), // Orchid Mist
colord({ b: 195, g: 215, r: 255 }), // Apricot Glow
colord({ b: 245, g: 215, r: 255 }), // Rose Whisper
colord({ b: 235, g: 220, r: 255 }), // Pink Mist
colord({ b: 250, g: 220, r: 255 }), // Powder Petal
colord({ b: 180, g: 225, r: 255 }), // Butter Peach
colord({ b: 255, g: 225, r: 255 }), // Petal Mist
colord({ b: 245, g: 230, r: 255 }), // Light Rose
colord({ b: 200, g: 235, r: 255 }), // Cream Peach
colord({ b: 235, g: 235, r: 255 }), // Blushed Petal
colord({ b: 220, g: 240, r: 255 }), // Pastel Sand
colord({ b: 210, g: 245, r: 255 }), // Soft Banana
];
+13 -15
View File
@@ -376,7 +376,6 @@ export class DefaultConfig implements Config {
return 1_000_000;
}
/* eslint-disable sort-keys */
unitInfo(type: UnitType): UnitInfo {
switch (type) {
case UnitType.TransportShip:
@@ -389,14 +388,14 @@ export class DefaultConfig implements Config {
cost: this.costWrapper(UnitType.Warship, (numUnits: number) =>
Math.min(1_000_000, (numUnits + 1) * 250_000),
),
territoryBound: false,
maxHealth: 1000,
territoryBound: false,
};
case UnitType.Shell:
return {
cost: () => 0n,
territoryBound: false,
damage: 250,
territoryBound: false,
};
case UnitType.SAMMissile:
return {
@@ -405,13 +404,13 @@ export class DefaultConfig implements Config {
};
case UnitType.Port:
return {
canBuildTrainStation: true,
constructionDuration: this.instantBuild() ? 0 : 2 * 10,
cost: this.costWrapper(UnitType.Port, (numUnits: number) =>
Math.min(1_000_000, Math.pow(2, numUnits) * 125_000),
),
territoryBound: true,
constructionDuration: this.instantBuild() ? 0 : 2 * 10,
upgradable: true,
canBuildTrainStation: true,
};
case UnitType.AtomBomb:
return {
@@ -440,47 +439,47 @@ export class DefaultConfig implements Config {
};
case UnitType.MissileSilo:
return {
constructionDuration: this.instantBuild() ? 0 : 10 * 10,
cost: this.costWrapper(UnitType.MissileSilo, () => 1_000_000),
territoryBound: true,
constructionDuration: this.instantBuild() ? 0 : 10 * 10,
upgradable: true,
};
case UnitType.DefensePost:
return {
constructionDuration: this.instantBuild() ? 0 : 5 * 10,
cost: this.costWrapper(UnitType.DefensePost, (numUnits: number) =>
Math.min(250_000, (numUnits + 1) * 50_000),
),
territoryBound: true,
constructionDuration: this.instantBuild() ? 0 : 5 * 10,
};
case UnitType.SAMLauncher:
return {
constructionDuration: this.instantBuild() ? 0 : 30 * 10,
cost: this.costWrapper(UnitType.SAMLauncher, (numUnits: number) =>
Math.min(3_000_000, (numUnits + 1) * 1_500_000),
),
territoryBound: true,
constructionDuration: this.instantBuild() ? 0 : 30 * 10,
upgradable: true,
};
case UnitType.City:
return {
canBuildTrainStation: true,
constructionDuration: this.instantBuild() ? 0 : 2 * 10,
cost: this.costWrapper(UnitType.City, (numUnits: number) =>
Math.min(1_000_000, Math.pow(2, numUnits) * 125_000),
),
territoryBound: true,
constructionDuration: this.instantBuild() ? 0 : 2 * 10,
upgradable: true,
canBuildTrainStation: true,
};
case UnitType.Factory:
return {
canBuildTrainStation: true,
constructionDuration: this.instantBuild() ? 0 : 2 * 10,
cost: this.costWrapper(UnitType.Factory, (numUnits: number) =>
Math.min(1_000_000, Math.pow(2, numUnits) * 125_000),
),
territoryBound: true,
constructionDuration: this.instantBuild() ? 0 : 2 * 10,
canBuildTrainStation: true,
experimental: true,
territoryBound: true,
upgradable: true,
};
case UnitType.Construction:
@@ -491,14 +490,13 @@ export class DefaultConfig implements Config {
case UnitType.Train:
return {
cost: () => 0n,
territoryBound: false,
experimental: true,
territoryBound: false,
};
default:
assertNever(type);
}
}
/* eslint-enable sort-keys */
private costWrapper(
type: UnitType,
+34 -40
View File
@@ -26,26 +26,24 @@ export class PastelTheme implements Theme {
nationColors,
);
/* eslint-disable sort-keys */
private readonly background = colord({ r: 60, g: 60, b: 60 });
private readonly shore = colord({ r: 204, g: 203, b: 158 });
private readonly background = colord({ b: 60, g: 60, r: 60 });
private readonly shore = colord({ b: 158, g: 203, r: 204 });
private readonly falloutColors = [
colord({ r: 120, g: 255, b: 71 }), // Original color
colord({ r: 130, g: 255, b: 85 }), // Slightly lighter
colord({ r: 110, g: 245, b: 65 }), // Slightly darker
colord({ r: 125, g: 255, b: 75 }), // Warmer tint
colord({ r: 115, g: 250, b: 68 }), // Cooler tint
colord({ b: 71, g: 255, r: 120 }), // Original color
colord({ b: 85, g: 255, r: 130 }), // Slightly lighter
colord({ b: 65, g: 245, r: 110 }), // Slightly darker
colord({ b: 75, g: 255, r: 125 }), // Warmer tint
colord({ b: 68, g: 250, r: 115 }), // Cooler tint
];
private readonly water = colord({ r: 70, g: 132, b: 180 });
private readonly shorelineWater = colord({ r: 100, g: 143, b: 255 });
private readonly water = colord({ b: 180, g: 132, r: 70 });
private readonly shorelineWater = colord({ b: 255, g: 143, r: 100 });
private readonly _selfColor = colord({ r: 0, g: 255, b: 0 });
private readonly _allyColor = colord({ r: 255, g: 255, b: 0 });
private readonly _neutralColor = colord({ r: 128, g: 128, b: 128 });
private readonly _enemyColor = colord({ r: 255, g: 0, b: 0 });
private readonly _selfColor = colord({ b: 0, g: 255, r: 0 });
private readonly _allyColor = colord({ b: 0, g: 255, r: 255 });
private readonly _neutralColor = colord({ b: 128, g: 128, r: 128 });
private readonly _enemyColor = colord({ b: 0, g: 0, r: 255 });
private readonly _spawnHighlightColor = colord({ r: 255, g: 213, b: 79 });
/* eslint-enable sort-keys */
private readonly _spawnHighlightColor = colord({ b: 79, g: 213, r: 255 });
teamColor(team: Team): Colord {
return this.teamColorAllocator.assignTeamColor(team);
@@ -71,24 +69,23 @@ export class PastelTheme implements Theme {
specialBuildingColor(player: PlayerView): Colord {
const tc = this.territoryColor(player).rgba;
/* eslint-disable sort-keys */
return colord({
r: Math.max(tc.r - 50, 0),
g: Math.max(tc.g - 50, 0),
b: Math.max(tc.b - 50, 0),
g: Math.max(tc.g - 50, 0),
r: Math.max(tc.r - 50, 0),
});
/* eslint-enable sort-keys */
}
railroadColor(player: PlayerView): Colord {
const tc = this.territoryColor(player).rgba;
/* eslint-disable sort-keys */
const color = colord({
r: Math.max(tc.r - 10, 0),
g: Math.max(tc.g - 10, 0),
b: Math.max(tc.b - 10, 0),
g: Math.max(tc.g - 10, 0),
r: Math.max(tc.r - 10, 0),
});
/* eslint-enable sort-keys */
return color;
}
@@ -97,28 +94,26 @@ export class PastelTheme implements Theme {
if (cached !== undefined) return cached;
const tc = this.territoryColor(player).rgba;
/* eslint-disable sort-keys */
const color = colord({
r: Math.max(tc.r - 40, 0),
g: Math.max(tc.g - 40, 0),
b: Math.max(tc.b - 40, 0),
g: Math.max(tc.g - 40, 0),
r: Math.max(tc.r - 40, 0),
});
/* eslint-enable sort-keys */
this.borderColorCache.set(player.id(), color);
return color;
}
/* eslint-disable sort-keys */
defendedBorderColors(player: PlayerView): { light: Colord; dark: Colord } {
return {
light: this.territoryColor(player).darken(0.2),
dark: this.territoryColor(player).darken(0.4),
light: this.territoryColor(player).darken(0.2),
};
}
focusedBorderColor(): Colord {
return colord({ r: 230, g: 230, b: 230 });
return colord({ b: 230, g: 230, r: 230 });
}
terrainColor(gm: GameMap, tile: TileRef): Colord {
@@ -134,32 +129,31 @@ export class PastelTheme implements Theme {
return this.shorelineWater;
}
return colord({
r: Math.max(w.r - 10 + (11 - Math.min(mag, 10)), 0),
g: Math.max(w.g - 10 + (11 - Math.min(mag, 10)), 0),
b: Math.max(w.b - 10 + (11 - Math.min(mag, 10)), 0),
g: Math.max(w.g - 10 + (11 - Math.min(mag, 10)), 0),
r: Math.max(w.r - 10 + (11 - Math.min(mag, 10)), 0),
});
case TerrainType.Plains:
return colord({
r: 190,
g: 220 - 2 * mag,
b: 138,
g: 220 - 2 * mag,
r: 190,
});
case TerrainType.Highland:
return colord({
r: 200 + 2 * mag,
g: 183 + 2 * mag,
b: 138 + 2 * mag,
g: 183 + 2 * mag,
r: 200 + 2 * mag,
});
case TerrainType.Mountain:
return colord({
r: 230 + mag / 2,
g: 230 + mag / 2,
b: 230 + mag / 2,
g: 230 + mag / 2,
r: 230 + mag / 2,
});
}
}
/* eslint-enable sort-keys */
backgroundColor(): Colord {
return this.background;
+30 -34
View File
@@ -26,26 +26,24 @@ export class PastelThemeDark implements Theme {
nationColors,
);
/* eslint-disable sort-keys */
private readonly background = colord({ r: 0, g: 0, b: 0 });
private readonly shore = colord({ r: 134, g: 133, b: 88 });
private readonly background = colord({ b: 0, g: 0, r: 0 });
private readonly shore = colord({ b: 88, g: 133, r: 134 });
private readonly falloutColors = [
colord({ r: 120, g: 255, b: 71 }), // Original color
colord({ r: 130, g: 255, b: 85 }), // Slightly lighter
colord({ r: 110, g: 245, b: 65 }), // Slightly darker
colord({ r: 125, g: 255, b: 75 }), // Warmer tint
colord({ r: 115, g: 250, b: 68 }), // Cooler tint
colord({ b: 71, g: 255, r: 120 }), // Original color
colord({ b: 85, g: 255, r: 130 }), // Slightly lighter
colord({ b: 65, g: 245, r: 110 }), // Slightly darker
colord({ b: 75, g: 255, r: 125 }), // Warmer tint
colord({ b: 68, g: 250, r: 115 }), // Cooler tint
];
private readonly water = colord({ r: 14, g: 11, b: 30 });
private readonly shorelineWater = colord({ r: 50, g: 50, b: 50 });
private readonly water = colord({ b: 30, g: 11, r: 14 });
private readonly shorelineWater = colord({ b: 50, g: 50, r: 50 });
private readonly _selfColor = colord({ r: 0, g: 255, b: 0 });
private readonly _allyColor = colord({ r: 255, g: 255, b: 0 });
private readonly _neutralColor = colord({ r: 128, g: 128, b: 128 });
private readonly _enemyColor = colord({ r: 255, g: 0, b: 0 });
private readonly _selfColor = colord({ b: 0, g: 255, r: 0 });
private readonly _allyColor = colord({ b: 0, g: 255, r: 255 });
private readonly _neutralColor = colord({ b: 128, g: 128, r: 128 });
private readonly _enemyColor = colord({ b: 0, g: 0, r: 255 });
private readonly _spawnHighlightColor = colord({ r: 255, g: 213, b: 79 });
/* eslint-enable sort-keys */
private readonly _spawnHighlightColor = colord({ b: 79, g: 213, r: 255 });
teamColor(team: Team): Colord {
return this.teamColorAllocator.assignTeamColor(team);
@@ -69,22 +67,21 @@ export class PastelThemeDark implements Theme {
return player.type() === PlayerType.Human ? "#ffffff" : "#e6e6e6";
}
/* eslint-disable sort-keys */
specialBuildingColor(player: PlayerView): Colord {
const tc = this.territoryColor(player).rgba;
return colord({
r: Math.max(tc.r - 50, 0),
g: Math.max(tc.g - 50, 0),
b: Math.max(tc.b - 50, 0),
g: Math.max(tc.g - 50, 0),
r: Math.max(tc.r - 50, 0),
});
}
railroadColor(player: PlayerView): Colord {
const tc = this.territoryColor(player).rgba;
const color = colord({
r: Math.max(tc.r - 10, 0),
g: Math.max(tc.g - 10, 0),
b: Math.max(tc.b - 10, 0),
g: Math.max(tc.g - 10, 0),
r: Math.max(tc.r - 10, 0),
});
return color;
}
@@ -95,9 +92,9 @@ export class PastelThemeDark implements Theme {
const tc = this.territoryColor(player).rgba;
const color = colord({
r: Math.max(tc.r - 40, 0),
g: Math.max(tc.g - 40, 0),
b: Math.max(tc.b - 40, 0),
g: Math.max(tc.g - 40, 0),
r: Math.max(tc.r - 40, 0),
});
this.borderColorCache.set(player.id(), color);
@@ -106,13 +103,13 @@ export class PastelThemeDark implements Theme {
defendedBorderColors(player: PlayerView): { light: Colord; dark: Colord } {
return {
light: this.territoryColor(player).darken(0.2),
dark: this.territoryColor(player).darken(0.4),
light: this.territoryColor(player).darken(0.2),
};
}
focusedBorderColor(): Colord {
return colord({ r: 255, g: 255, b: 255 });
return colord({ b: 255, g: 255, r: 255 });
}
terrainColor(gm: GameMap, tile: TileRef): Colord {
@@ -129,33 +126,32 @@ export class PastelThemeDark implements Theme {
}
if (gm.magnitude(tile) < 10) {
return colord({
r: Math.max(w.r + 9 - mag, 0),
g: Math.max(w.g + 9 - mag, 0),
b: Math.max(w.b + 9 - mag, 0),
g: Math.max(w.g + 9 - mag, 0),
r: Math.max(w.r + 9 - mag, 0),
});
}
return this.water;
case TerrainType.Plains:
return colord({
r: 140,
g: 170 - 2 * mag,
b: 88,
g: 170 - 2 * mag,
r: 140,
});
case TerrainType.Highland:
return colord({
r: 150 + 2 * mag,
g: 133 + 2 * mag,
b: 88 + 2 * mag,
g: 133 + 2 * mag,
r: 150 + 2 * mag,
});
case TerrainType.Mountain:
return colord({
r: 180 + mag / 2,
g: 180 + mag / 2,
b: 180 + mag / 2,
g: 180 + mag / 2,
r: 180 + mag / 2,
});
}
}
/* eslint-enable sort-keys */
backgroundColor(): Colord {
return this.background;
+3 -5
View File
@@ -18,17 +18,16 @@ export class RailroadExecution implements Execution {
return this.active;
}
/* eslint-disable sort-keys */
init(mg: Game, ticks: number): void {
this.mg = mg;
const { tiles } = this.railRoad;
// Inverse direction computation for the first tile
this.railTiles.push({
tile: tiles[0],
railType:
tiles.length > 0
? this.computeExtremityDirection(tiles[0], tiles[1])
: RailType.VERTICAL,
tile: tiles[0],
});
for (let i = 1; i < tiles.length - 1; i++) {
const direction = this.computeDirection(
@@ -36,10 +35,9 @@ export class RailroadExecution implements Execution {
tiles[i],
tiles[i + 1],
);
this.railTiles.push({ tile: tiles[i], railType: direction });
this.railTiles.push({ railType: direction, tile: tiles[i] });
}
this.railTiles.push({
tile: tiles[tiles.length - 1],
railType:
tiles.length > 0
? this.computeExtremityDirection(
@@ -47,9 +45,9 @@ export class RailroadExecution implements Execution {
tiles[tiles.length - 2],
)
: RailType.VERTICAL,
tile: tiles[tiles.length - 1],
});
}
/* eslint-enable sort-keys */
private computeExtremityDirection(tile: TileRef, next: TileRef): RailType {
if (this.mg === undefined) throw new Error("Not initialized");
+1 -2
View File
@@ -97,8 +97,7 @@ class SAMTargetingSystem {
}
const interceptionTile = this.computeInterceptionTile(nuke.unit);
if (interceptionTile !== undefined) {
// eslint-disable-next-line sort-keys
targets.push({ unit: nuke.unit, tile: interceptionTile });
targets.push({ tile: interceptionTile, unit: nuke.unit });
} else {
// Store unreachable nukes in order to prevent useless interception computation
this.storeUnreachableNukes(nuke.unit.id());
+14 -16
View File
@@ -44,18 +44,16 @@ export const Duos = "Duos" as const;
export const Trios = "Trios" as const;
export const Quads = "Quads" as const;
/* eslint-disable sort-keys */
export const ColoredTeams: Record<string, Team> = {
Red: "Red",
Blue: "Blue",
Teal: "Teal",
Purple: "Purple",
Yellow: "Yellow",
Orange: "Orange",
Green: "Green",
Bot: "Bot",
Green: "Green",
Orange: "Orange",
Purple: "Purple",
Red: "Red",
Teal: "Teal",
Yellow: "Yellow",
} as const;
/* eslint-enable sort-keys */
export enum GameMapType {
World = "World",
@@ -103,6 +101,14 @@ export const mapCategories: Record<string, GameMapType[]> = {
GameMapType.Africa,
GameMapType.Oceania,
],
fantasy: [
GameMapType.Pangaea,
GameMapType.Pluto,
GameMapType.MarsRevised,
GameMapType.Mars,
GameMapType.DeglaciatedAntarctica,
],
regional: [
GameMapType.BlackSea,
GameMapType.Britannia,
@@ -120,14 +126,6 @@ export const mapCategories: Record<string, GameMapType[]> = {
GameMapType.Italia,
GameMapType.Yenisei,
],
// eslint-disable-next-line sort-keys
fantasy: [
GameMapType.Pangaea,
GameMapType.Pluto,
GameMapType.MarsRevised,
GameMapType.Mars,
GameMapType.DeglaciatedAntarctica,
],
};
export enum GameType {
+41 -44
View File
@@ -128,58 +128,56 @@ export class PlayerImpl implements Player {
);
const stats = this.mg.stats().getPlayerStats(this);
/* eslint-disable sort-keys */
return {
type: GameUpdateType.Player,
clientID: this.clientID(),
name: this.name(),
displayName: this.displayName(),
id: this.id(),
team: this.team() ?? undefined,
smallID: this.smallID(),
playerType: this.type(),
isAlive: this.isAlive(),
isDisconnected: this.isDisconnected(),
tilesOwned: this.numTilesOwned(),
gold: this._gold,
troops: this.troops(),
allies: this.alliances().map((a) => a.other(this).smallID()),
embargoes: new Set([...this.embargoes.keys()].map((p) => p.toString())),
isTraitor: this.isTraitor(),
targets: this.targets().map((p) => p.smallID()),
outgoingEmojis: this.outgoingEmojis(),
outgoingAttacks: this._outgoingAttacks.map((a) => {
return {
attackerID: a.attacker().smallID(),
targetID: a.target().smallID(),
troops: a.troops(),
id: a.id(),
retreating: a.retreating(),
} satisfies AttackUpdate;
}),
incomingAttacks: this._incomingAttacks.map((a) => {
return {
attackerID: a.attacker().smallID(),
targetID: a.target().smallID(),
troops: a.troops(),
id: a.id(),
retreating: a.retreating(),
} satisfies AttackUpdate;
}),
outgoingAllianceRequests,
alliances: this.alliances().map(
(a) =>
({
id: a.id(),
other: a.other(this).id(),
createdAt: a.createdAt(),
expiresAt: a.expiresAt(),
id: a.id(),
other: a.other(this).id(),
}) satisfies AllianceView,
),
hasSpawned: this.hasSpawned(),
allies: this.alliances().map((a) => a.other(this).smallID()),
betrayals: stats?.betrayals,
clientID: this.clientID(),
displayName: this.displayName(),
embargoes: new Set([...this.embargoes.keys()].map((p) => p.toString())),
gold: this._gold,
hasSpawned: this.hasSpawned(),
id: this.id(),
incomingAttacks: this._incomingAttacks.map((a) => {
return {
attackerID: a.attacker().smallID(),
id: a.id(),
retreating: a.retreating(),
targetID: a.target().smallID(),
troops: a.troops(),
} satisfies AttackUpdate;
}),
isAlive: this.isAlive(),
isDisconnected: this.isDisconnected(),
isTraitor: this.isTraitor(),
name: this.name(),
outgoingAllianceRequests,
outgoingAttacks: this._outgoingAttacks.map((a) => {
return {
attackerID: a.attacker().smallID(),
id: a.id(),
retreating: a.retreating(),
targetID: a.target().smallID(),
troops: a.troops(),
} satisfies AttackUpdate;
}),
outgoingEmojis: this.outgoingEmojis(),
playerType: this.type(),
smallID: this.smallID(),
targets: this.targets().map((p) => p.smallID()),
team: this.team() ?? undefined,
tilesOwned: this.numTilesOwned(),
troops: this.troops(),
type: GameUpdateType.Player,
};
/* eslint-enable sort-keys */
}
smallID(): number {
@@ -512,8 +510,7 @@ export class PlayerImpl implements Player {
}
target(other: Player): void {
// eslint-disable-next-line sort-keys
this.targets_.push({ tick: this.mg.ticks(), target: other });
this.targets_.push({ target: other, tick: this.mg.ticks() });
this.mg.target(this, other);
}
+2 -4
View File
@@ -216,8 +216,7 @@ export class RailNetworkImpl implements RailNetwork {
const visited = new Set<TrainStation>();
const queue: Array<{ station: TrainStation; distance: number }> = [
// eslint-disable-next-line sort-keys
{ station: start, distance: 0 },
{ distance: 0, station: start },
];
let head = 0;
@@ -231,8 +230,7 @@ export class RailNetworkImpl implements RailNetwork {
for (const neighbor of station.neighbors()) {
if (neighbor === dest) return distance + 1;
if (!visited.has(neighbor)) {
// eslint-disable-next-line sort-keys
queue.push({ station: neighbor, distance: distance + 1 });
queue.push({ distance: distance + 1, station: neighbor });
}
}
}
+2 -2
View File
@@ -159,8 +159,8 @@ export class UnitGrid {
if (!unit.isActive()) continue;
const distSquared = this.squaredDistanceFromTile(unit, tile);
if (distSquared > rangeSquared) continue;
// eslint-disable-next-line sort-keys
const value = { unit, distSquared };
const value = { distSquared, unit };
if (predicate !== undefined && !predicate(value)) continue;
nearby.push(value);
}
+14 -16
View File
@@ -114,33 +114,31 @@ export class UnitImpl implements Unit {
return this._id;
}
/* eslint-disable sort-keys */
toUpdate(): UnitUpdate {
return {
type: GameUpdateType.Unit,
unitType: this._type,
constructionType: this._constructionType,
hasTrainStation: this._hasTrainStation,
health: this.hasHealth() ? Number(this._health) : undefined,
id: this._id,
troops: this._troops,
ownerID: this._owner.smallID(),
lastOwnerID: this._lastOwner?.smallID(),
isActive: this._active,
lastOwnerID: this._lastOwner?.smallID(),
lastPos: this._lastTile,
level: this.level(),
loaded: this._loaded,
missileTimerQueue: this._missileTimerQueue,
ownerID: this._owner.smallID(),
pos: this._tile,
reachedTarget: this._reachedTarget,
retreating: this._retreating,
pos: this._tile,
targetable: this._targetable,
lastPos: this._lastTile,
health: this.hasHealth() ? Number(this._health) : undefined,
constructionType: this._constructionType,
targetUnitId: this._targetUnit?.id() ?? undefined,
targetTile: this.targetTile() ?? undefined,
missileTimerQueue: this._missileTimerQueue,
level: this.level(),
hasTrainStation: this._hasTrainStation,
targetUnitId: this._targetUnit?.id() ?? undefined,
trainType: this._trainType,
loaded: this._loaded,
troops: this._troops,
type: GameUpdateType.Unit,
unitType: this._type,
};
}
/* eslint-enable sort-keys */
type(): UnitType {
return this._type;
+3 -4
View File
@@ -148,8 +148,7 @@ export class PathFinder {
}
if (this.game.manhattanDist(curr, dst) < dist) {
// eslint-disable-next-line sort-keys
return { type: PathFindResultType.Completed, node: curr };
return { node: curr, type: PathFindResultType.Completed };
}
if (this.computeFinished) {
@@ -165,8 +164,8 @@ export class PathFinder {
if (tile === undefined) {
throw new Error("missing tile");
}
// eslint-disable-next-line sort-keys
return { type: PathFindResultType.NextTile, node: tile };
return { node: tile, type: PathFindResultType.NextTile };
}
}
+4 -6
View File
@@ -48,18 +48,16 @@ export class SerialAStar<NodeType> implements AStar<NodeType> {
this.sources.forEach((startPoint) => {
this.fwdGScore.set(startPoint, 0);
this.fwdOpenSet.add({
tile: startPoint,
// eslint-disable-next-line sort-keys
fScore: this.heuristic(startPoint, dst),
tile: startPoint,
});
});
// Initialize backward search from destination
this.bwdGScore.set(dst, 0);
this.bwdOpenSet.add({
tile: dst,
// eslint-disable-next-line sort-keys
fScore: this.heuristic(dst, this.findClosestSource(dst)),
tile: dst,
});
}
@@ -151,8 +149,8 @@ export class SerialAStar<NodeType> implements AStar<NodeType> {
const fScore =
totalG +
this.heuristic(neighbor, isForward ? this.dst : this.closestSource);
// eslint-disable-next-line sort-keys
openSet.add({ tile: neighbor, fScore });
openSet.add({ fScore, tile: neighbor });
}
}
}
+1 -2
View File
@@ -50,8 +50,7 @@ export function validateUsername(username: string): {
error?: string;
} {
if (typeof username !== "string") {
// eslint-disable-next-line sort-keys
return { isValid: false, error: translateText("username.not_string") };
return { error: translateText("username.not_string"), isValid: false };
}
if (username.length < MIN_USERNAME_LENGTH) {
+15 -16
View File
@@ -10,14 +10,13 @@ const log = logger.child({ component: "Archive" });
// R2 client configuration
const r2 = new S3({
region: "auto", // R2 ignores region, but it's required by the SDK
/* eslint-disable sort-keys */
endpoint: config.r2Endpoint(),
credentials: {
accessKeyId: config.r2AccessKey(),
secretAccessKey: config.r2SecretKey(),
},
/* eslint-disable sort-keys */
endpoint: config.r2Endpoint(),
region: "auto", // R2 ignores region, but it's required by the SDK
});
const bucket = config.r2Bucket();
@@ -49,8 +48,8 @@ export async function archive(gameRecord: GameRecord) {
const { message, stack, name } = error;
log.error(`${gameRecord.info.gameID}: Final archive error: ${error}`, {
message,
stack,
name,
stack,
...(error && typeof error === "object" ? error : {}),
});
}
@@ -60,11 +59,11 @@ async function archiveAnalyticsToR2(gameRecord: GameRecord) {
// Create analytics data object
const { info, version, gitCommit, subdomain, domain } = gameRecord;
const analyticsData: AnalyticsRecord = {
info,
version,
gitCommit,
subdomain,
domain,
gitCommit,
info,
subdomain,
version,
};
try {
@@ -72,10 +71,10 @@ async function archiveAnalyticsToR2(gameRecord: GameRecord) {
const analyticsKey = `${info.gameID}.json`;
await r2.putObject({
Bucket: bucket,
Key: `${analyticsFolder}/${analyticsKey}`,
Body: JSON.stringify(analyticsData, replacer),
Bucket: bucket,
ContentType: "application/json",
Key: `${analyticsFolder}/${analyticsKey}`,
});
log.info(`${info.gameID}: successfully wrote game analytics to R2`);
@@ -91,8 +90,8 @@ async function archiveAnalyticsToR2(gameRecord: GameRecord) {
const { message, stack, name } = error;
log.error(`${info.gameID}: Error writing game analytics to R2: ${error}`, {
message,
stack,
name,
stack,
...(error && typeof error === "object" ? error : {}),
});
throw error;
@@ -110,10 +109,10 @@ async function archiveFullGameToR2(gameRecord: GameRecord) {
try {
await r2.putObject({
Bucket: bucket,
Key: `${gameFolder}/${recordCopy.info.gameID}`,
Body: JSON.stringify(recordCopy, replacer),
Bucket: bucket,
ContentType: "application/json",
Key: `${gameFolder}/${recordCopy.info.gameID}`,
});
} catch (error) {
log.error(`error saving game ${gameRecord.info.gameID}`);
@@ -148,8 +147,8 @@ export async function readGameRecord(
// Log the error for monitoring purposes
log.error(`${gameId}: Error reading game record from R2: ${error}`, {
message,
stack,
name,
stack,
...(error && typeof error === "object" ? error : {}),
});
@@ -179,8 +178,8 @@ export async function gameRecordExists(gameId: GameID): Promise<boolean> {
}
log.error(`${gameId}: Error checking archive existence: ${error}`, {
message,
stack,
name,
stack,
...(error && typeof error === "object" ? error : {}),
});
return false;
+1 -1
View File
@@ -41,8 +41,8 @@ export class GameManager {
{
bots: 400,
difficulty: Difficulty.Medium,
disableNPCs: false,
disabledUnits: [],
disableNPCs: false,
donateGold: false,
donateTroops: false,
gameMap: GameMapType.World,
+8 -9
View File
@@ -29,9 +29,8 @@ if (config.otelEnabled()) {
// Add OTLP exporter for logs
const logExporter = new OTLPLogExporter({
url: `${config.otelEndpoint()}/v1/logs`,
// eslint-disable-next-line sort-keys
headers,
url: `${config.otelEndpoint()}/v1/logs`,
});
// Add a log processor with the exporter
@@ -57,18 +56,18 @@ const addSeverityFormat = winston.format((info) => {
// Define your base/parent logger
const logger = winston.createLogger({
level: "info",
/* eslint-disable sort-keys */
defaultMeta: {
environment: process.env.GAME_ENV ?? "prod",
service: "openfront",
},
format: winston.format.combine(
winston.format.timestamp(),
addSeverityFormat(),
winston.format.json(),
),
defaultMeta: {
service: "openfront",
environment: process.env.GAME_ENV ?? "prod",
},
/* eslint-enable sort-keys */
level: "info",
transports: [
new winston.transports.Console(),
new OpenTelemetryTransportV3(),
+1 -1
View File
@@ -79,8 +79,8 @@ export class MapPlaylist {
return {
bots: 400,
difficulty: Difficulty.Medium,
disableNPCs: mode === GameMode.Team,
disabledUnits: [],
disableNPCs: mode === GameMode.Team,
donateGold: true,
donateTroops: true,
gameMap: map,
+6 -7
View File
@@ -17,15 +17,14 @@ export function getOtelResource() {
export function getPromLabels() {
return {
"service.instance.id": process.env.HOSTNAME,
/* eslint-disable sort-keys */
"openfront.environment": config.env(),
"openfront.host": process.env.HOST,
"openfront.domain": process.env.DOMAIN,
"openfront.subdomain": process.env.SUBDOMAIN,
"openfront.component": process.env.WORKER_ID
? "Worker " + process.env.WORKER_ID
: "Master",
/* eslint-enable sort-keys */
"openfront.domain": process.env.DOMAIN,
"openfront.environment": config.env(),
"openfront.host": process.env.HOST,
"openfront.subdomain": process.env.SUBDOMAIN,
"service.instance.id": process.env.HOSTNAME,
};
}
+1 -1
View File
@@ -31,8 +31,8 @@ export function initWorkerMetrics(gameManager: GameManager): void {
// Configure the metric reader
const metricReader = new PeriodicExportingMetricReader({
exportIntervalMillis: 15000, // Export metrics every 15 seconds
exporter: metricExporter,
exportIntervalMillis: 15000, // Export metrics every 15 seconds
});
// Create a meter provider
+1 -2
View File
@@ -21,8 +21,7 @@ export async function verifyClientToken(
config: ServerConfig,
): Promise<TokenVerificationResult> {
if (PersistentIdSchema.safeParse(token).success) {
// eslint-disable-next-line sort-keys
return { persistentId: token, claims: null };
return { claims: null, persistentId: token };
}
try {
const issuer = config.jwtIssuer();
+1 -1
View File
@@ -15,8 +15,8 @@ describe("AllianceExtensionExecution", () => {
"ocean_and_land",
{
infiniteGold: true,
instantBuild: true,
infiniteTroops: true,
instantBuild: true,
},
[
playerInfo("player1", PlayerType.Human),
+2 -2
View File
@@ -29,8 +29,8 @@ describe("Attack", () => {
beforeEach(async () => {
game = await setup("ocean_and_land", {
infiniteGold: true,
instantBuild: true,
infiniteTroops: true,
instantBuild: true,
});
const attackerInfo = new PlayerInfo(
"attacker dude",
@@ -130,8 +130,8 @@ describe("Attack race condition with alliance requests", () => {
beforeEach(async () => {
game = await setup("ocean_and_land", {
infiniteGold: true,
instantBuild: true,
infiniteTroops: true,
instantBuild: true,
});
const playerAInfo = new PlayerInfo(
+6 -6
View File
@@ -77,11 +77,11 @@ describe("BotBehavior.handleAllianceRequests", () => {
.mockReturnValue(new Array(alliancesCount));
const mockRequest = {
requestor: () => requestor,
recipient: () => player,
createdAt: () => 0 as unknown as Tick,
accept: jest.fn(),
createdAt: () => 0 as unknown as Tick,
recipient: () => player,
reject: jest.fn(),
requestor: () => requestor,
} as unknown as AllianceRequest;
jest
@@ -120,8 +120,8 @@ describe("BotBehavior.handleAllianceRequests", () => {
test("should accept alliance if requestor is much larger (> 3 times size of recipient) and has too many alliances (>= 3)", () => {
const request = setupAllianceRequest({
numTilesRequestor: 40,
alliancesCount: 4,
numTilesRequestor: 40,
});
botBehavior.handleAllianceRequests();
@@ -132,8 +132,8 @@ describe("BotBehavior.handleAllianceRequests", () => {
test("should accept alliance if requestor is much larger (> 3 times size of recipient) and does not have too many alliances (< 3)", () => {
const request = setupAllianceRequest({
numTilesRequestor: 40,
alliancesCount: 2,
numTilesRequestor: 40,
});
botBehavior.handleAllianceRequests();
@@ -171,8 +171,8 @@ describe("BotBehavior.handleAllianceExtensionRequests", () => {
mockPlayer = {
alliances: jest.fn(() => [mockAlliance]),
relation: jest.fn(),
id: jest.fn(() => "bot_id"),
relation: jest.fn(),
type: jest.fn(() => PlayerType.FakeHuman),
};
+17 -17
View File
@@ -1,6 +1,9 @@
// Mocking the obscenity library to control its behavior in tests.
jest.mock("obscenity", () => {
return {
collapseDuplicatesTransformer: () => ({}),
englishDataset: { build: () => ({}) },
englishRecommendedTransformers: {},
RegExpMatcher: class {
private readonly dummy: string[] = ["foo", "bar", "leet", "code"];
constructor(_opts: any) {}
@@ -16,9 +19,6 @@ jest.mock("obscenity", () => {
return this.dummy.some((token) => decoded.includes(token));
}
},
collapseDuplicatesTransformer: () => ({}),
englishRecommendedTransformers: {},
englishDataset: { build: () => ({}) },
resolveConfusablesTransformer: () => ({}),
resolveLeetSpeakTransformer: () => ({}),
skipNonAlphabeticTransformer: () => ({}),
@@ -53,14 +53,14 @@ describe("username.ts functions", () => {
describe("isProfaneUsername & fixProfaneUsername with leet decoding (mocked)", () => {
test.each([
{ username: "l33t", profane: true }, // decodes to "leet"
{ username: "L33T", profane: true },
{ username: "l33tc0de", profane: true }, // decodes to "leetcode", contains "leet" and "code"
{ username: "L33TC0DE", profane: true },
{ username: "foo123", profane: true }, // contains "foo"
{ username: "b4r", profane: true }, // decodes to "bar"
{ username: "safeName", profane: false },
{ username: "s4f3", profane: false }, // decodes to "safe" but "safe" not in dummy list
{ profane: true, username: "l33t" }, // decodes to "leet"
{ profane: true, username: "L33T" },
{ profane: true, username: "l33tc0de" }, // decodes to "leetcode", contains "leet" and "code"
{ profane: true, username: "L33TC0DE" },
{ profane: true, username: "foo123" }, // contains "foo"
{ profane: true, username: "b4r" }, // decodes to "bar"
{ profane: false, username: "safeName" },
{ profane: false, username: "s4f3" }, // decodes to "safe" but "safe" not in dummy list
])('isProfaneUsername("%s") → %s', ({ username, profane }) => {
expect(isProfaneUsername(username)).toBe(profane);
});
@@ -113,17 +113,17 @@ describe("username.ts functions", () => {
describe("sanitizeUsername", () => {
test.each([
{ input: "GoodName", expected: "GoodName" },
{ input: "a!", expected: "axx" },
{ input: "a$%b", expected: "abx" },
{ expected: "GoodName", input: "GoodName" },
{ expected: "axx", input: "a!" },
{ expected: "abx", input: "a$%b" },
{
input: "abc".repeat(10),
expected: "abc"
.repeat(Math.floor(MAX_USERNAME_LENGTH / 3))
.slice(0, MAX_USERNAME_LENGTH),
input: "abc".repeat(10),
},
{ input: "", expected: "xxx" },
{ input: "Ünicode🐈Test!", expected: "Ünicode🐈Test" },
{ expected: "xxx", input: "" },
{ expected: "Ünicode🐈Test", input: "Ünicode🐈Test!" },
])('sanitizeUsername("%s") → "%s"', ({ input, expected }) => {
const out = sanitizeUsername(input);
expect(out).toBe(expected);
+11 -11
View File
@@ -16,14 +16,14 @@ import {
import { ColoredTeams } from "../src/core/game/Game";
const mockColors: Colord[] = [
colord({ r: 255, g: 0, b: 0 }),
colord({ r: 0, g: 255, b: 0 }),
colord({ r: 0, g: 0, b: 255 }),
colord({ b: 0, g: 0, r: 255 }),
colord({ b: 0, g: 255, r: 0 }),
colord({ b: 255, g: 0, r: 0 }),
];
const fallbackMockColors: Colord[] = [
colord({ r: 0, g: 0, b: 0 }),
colord({ r: 255, g: 255, b: 255 }),
colord({ b: 0, g: 0, r: 0 }),
colord({ b: 255, g: 255, r: 255 }),
];
const fallbackColors = [...fallbackMockColors, ...mockColors];
@@ -148,19 +148,19 @@ describe("ColorAllocator", () => {
describe("selectDistinctColor", () => {
test("returns the most distant color", () => {
const assignedColors = [colord({ r: 255, g: 0, b: 0 })]; // bright red
const assignedColors = [colord({ b: 0, g: 0, r: 255 })]; // bright red
const availableColors = [
colord({ r: 254, g: 1, b: 1 }), // too close
colord({ r: 0, g: 255, b: 0 }), // distinct green
colord({ r: 0, g: 0, b: 255 }), // distinct blue
colord({ b: 1, g: 1, r: 254 }), // too close
colord({ b: 0, g: 255, r: 0 }), // distinct green
colord({ b: 255, g: 0, r: 0 }), // distinct blue
];
const result = selectDistinctColorIndex(availableColors, assignedColors);
expect(result).not.toBeNull();
const rgb = availableColors[result!].toRgb();
expect([
{ r: 0, g: 255, b: 0, a: 1 },
{ r: 0, g: 0, b: 255, a: 1 },
{ a: 1, b: 0, g: 255, r: 0 },
{ a: 1, b: 255, g: 0, r: 0 },
]).toContainEqual(rgb);
});
});
+1 -1
View File
@@ -20,8 +20,8 @@ describe("DeleteUnitExecution Security Tests", () => {
beforeEach(async () => {
game = await setup("plains", {
infiniteGold: true,
instantBuild: true,
infiniteTroops: true,
instantBuild: true,
});
const player1Info = new PlayerInfo(
+4 -4
View File
@@ -7,8 +7,8 @@ import { setup } from "./util/Setup";
describe("Donate troops to an ally", () => {
it("Troops should be successfully donated", async () => {
const game = await setup("ocean_and_land", {
infiniteTroops: false,
donateTroops: true,
infiniteTroops: false,
});
const donorInfo = new PlayerInfo(
@@ -70,8 +70,8 @@ describe("Donate troops to an ally", () => {
describe("Donate gold to an ally", () => {
it("Gold should be successfully donated", async () => {
const game = await setup("ocean_and_land", {
infiniteGold: false,
donateGold: true,
infiniteGold: false,
});
const donorInfo = new PlayerInfo(
@@ -134,8 +134,8 @@ describe("Donate gold to an ally", () => {
describe("Donate troops to a non ally", () => {
it("Troops should not be donated", async () => {
const game = await setup("ocean_and_land", {
infiniteTroops: false,
donateTroops: true,
infiniteTroops: false,
});
const donorInfo = new PlayerInfo(
@@ -194,8 +194,8 @@ describe("Donate troops to a non ally", () => {
describe("Donate Gold to a non ally", () => {
it("Gold should not be donated", async () => {
const game = await setup("ocean_and_land", {
infiniteGold: false,
donateGold: true,
infiniteGold: false,
});
const donorInfo = new PlayerInfo(
@@ -14,8 +14,8 @@ import { TileRef } from "../../../src/core/game/GameMap";
import { GameView, PlayerView } from "../../../src/core/game/GameView";
jest.mock("../../../src/client/Utils", () => ({
translateText: jest.fn((key: string) => key),
renderNumber: jest.fn((num: number) => num.toString()),
translateText: jest.fn((key: string) => key),
}));
jest.mock("../../../src/client/graphics/layers/BuildMenu", () => {
@@ -23,46 +23,46 @@ jest.mock("../../../src/client/graphics/layers/BuildMenu", () => {
return {
flattenedBuildTable: [
{
unitType: UnitType.City,
key: "unit_type.city",
countable: true,
description: "unit_type.city_desc",
icon: "city-icon",
countable: true,
key: "unit_type.city",
unitType: UnitType.City,
},
{
unitType: UnitType.Factory,
key: "unit_type.factory",
countable: true,
description: "unit_type.factory_desc",
icon: "factory-icon",
countable: true,
key: "unit_type.factory",
unitType: UnitType.Factory,
},
{
unitType: UnitType.AtomBomb,
key: "unit_type.atom_bomb",
countable: false,
description: "unit_type.atom_bomb_desc",
icon: "atom-bomb-icon",
countable: false,
key: "unit_type.atom_bomb",
unitType: UnitType.AtomBomb,
},
{
unitType: UnitType.Warship,
key: "unit_type.warship",
countable: true,
description: "unit_type.warship_desc",
icon: "warship-icon",
countable: true,
key: "unit_type.warship",
unitType: UnitType.Warship,
},
{
unitType: UnitType.HydrogenBomb,
key: "unit_type.hydrogen_bomb",
countable: false,
description: "unit_type.hydrogen_bomb_desc",
icon: "hydrogen-bomb-icon",
countable: false,
key: "unit_type.hydrogen_bomb",
unitType: UnitType.HydrogenBomb,
},
{
unitType: UnitType.MIRV,
key: "unit_type.mirv",
countable: false,
description: "unit_type.mirv_desc",
icon: "mirv-icon",
countable: false,
key: "unit_type.mirv",
unitType: UnitType.MIRV,
},
],
};
@@ -95,17 +95,17 @@ describe("RadialMenuElements", () => {
} as unknown as PlayerView;
mockGame = {
inSpawnPhase: jest.fn(() => false),
owner: jest.fn(() => mockPlayer),
isLand: jest.fn(() => true),
config: jest.fn(() => ({
isUnitDisabled: jest.fn(() => false),
theme: () => ({
territoryColor: () => ({
lighten: () => ({ alpha: () => ({ toRgbString: () => "#fff" }) }),
}),
}),
isUnitDisabled: jest.fn(() => false),
})),
inSpawnPhase: jest.fn(() => false),
isLand: jest.fn(() => true),
owner: jest.fn(() => mockPlayer),
} as unknown as GameView;
mockBuildMenu = {
@@ -117,38 +117,38 @@ describe("RadialMenuElements", () => {
mockPlayerActions = {
buildableUnits: [
{ type: UnitType.City, canBuild: true },
{ type: UnitType.Factory, canBuild: true },
{ type: UnitType.AtomBomb, canBuild: true },
{ type: UnitType.Warship, canBuild: true },
{ type: UnitType.HydrogenBomb, canBuild: true },
{ type: UnitType.MIRV, canBuild: true },
{ type: UnitType.TransportShip, canBuild: true },
{ canBuild: true, type: UnitType.City },
{ canBuild: true, type: UnitType.Factory },
{ canBuild: true, type: UnitType.AtomBomb },
{ canBuild: true, type: UnitType.Warship },
{ canBuild: true, type: UnitType.HydrogenBomb },
{ canBuild: true, type: UnitType.MIRV },
{ canBuild: true, type: UnitType.TransportShip },
],
canAttack: true,
interaction: {
canSendAllianceRequest: true,
canBreakAlliance: false,
canDonateTroops: true,
canDonateGold: true,
canDonateTroops: true,
canSendAllianceRequest: true,
},
};
mockTile = {} as TileRef;
mockParams = {
buildMenu: mockBuildMenu,
chatIntegration: {} as any,
closeMenu: jest.fn(),
emojiTable: {} as any,
eventBus: {} as any,
game: mockGame,
myPlayer: mockPlayer,
playerActionHandler: {} as any,
playerActions: mockPlayerActions,
playerPanel: {} as any,
selected: mockPlayer,
tile: mockTile,
playerActions: mockPlayerActions,
game: mockGame,
buildMenu: mockBuildMenu,
emojiTable: {} as any,
playerActionHandler: {} as any,
playerPanel: {} as any,
chatIntegration: {} as any,
eventBus: {} as any,
closeMenu: jest.fn(),
};
});
+25 -25
View File
@@ -11,8 +11,6 @@ describe("UILayer", () => {
beforeEach(() => {
game = {
width: () => 100,
height: () => 100,
config: () => ({
theme: () => ({
territoryColor: () => ({
@@ -20,12 +18,14 @@ describe("UILayer", () => {
}),
}),
}),
x: () => 10,
y: () => 10,
unitInfo: () => ({ maxHealth: 10, constructionDuration: 5 }),
height: () => 100,
myPlayer: () => ({ id: () => 1 }),
ticks: () => 1,
unitInfo: () => ({ constructionDuration: 5, maxHealth: 10 }),
updatesSinceLastTick: () => undefined,
width: () => 100,
x: () => 10,
y: () => 10,
};
eventBus = { on: jest.fn() };
});
@@ -46,10 +46,10 @@ describe("UILayer", () => {
const ui = new UILayer(game, eventBus);
ui.redraw();
const unit = {
type: () => "Warship",
isActive: () => true,
tile: () => ({}),
owner: () => ({}),
tile: () => ({}),
type: () => "Warship",
};
const event = { isSelected: true, unit };
ui.drawSelectionBox = jest.fn();
@@ -61,13 +61,13 @@ describe("UILayer", () => {
const ui = new UILayer(game, eventBus);
ui.redraw();
const unit = {
id: () => 1,
type: () => "Warship",
health: () => 5,
tile: () => ({}),
owner: () => ({}),
isActive: () => true,
createdAt: () => 1,
health: () => 5,
id: () => 1,
isActive: () => true,
owner: () => ({}),
tile: () => ({}),
type: () => "Warship",
} as unknown as UnitView;
ui.drawHealthBar(unit);
expect(ui["allHealthBars"].has(1)).toBe(true);
@@ -90,12 +90,12 @@ describe("UILayer", () => {
const ui = new UILayer(game, eventBus);
ui.redraw();
const unit = {
id: () => 1,
type: () => "Warship",
health: () => 5,
tile: () => ({}),
owner: () => ({}),
id: () => 1,
isActive: () => true,
owner: () => ({}),
tile: () => ({}),
type: () => "Warship",
} as unknown as UnitView;
ui.drawHealthBar(unit);
expect(ui["allHealthBars"].has(1)).toBe(true);
@@ -111,8 +111,8 @@ describe("UILayer", () => {
ui.redraw();
const unit = {
id: () => 2,
tile: () => ({}),
isActive: () => true,
tile: () => ({}),
} as unknown as UnitView;
ui.createLoadingBar(unit);
expect(ui["allProgressBars"].has(2)).toBe(true);
@@ -122,12 +122,12 @@ describe("UILayer", () => {
const ui = new UILayer(game, eventBus);
ui.redraw();
const unit = {
id: () => 2,
type: () => "Construction",
constructionType: () => "City",
id: () => 2,
isActive: () => true,
owner: () => ({ id: () => 1 }),
tile: () => ({}),
isActive: () => true,
type: () => "Construction",
} as unknown as UnitView;
ui.onUnitEvent(unit);
expect(ui["allProgressBars"].has(2)).toBe(true);
@@ -142,13 +142,13 @@ describe("UILayer", () => {
const ui = new UILayer(game, eventBus);
ui.redraw();
const unit = {
id: () => 2,
type: () => "Construction",
constructionType: () => "City",
createdAt: () => 1,
id: () => 2,
isActive: () => true,
owner: () => ({ id: () => 1 }),
tile: () => ({}),
isActive: () => true,
createdAt: () => 1,
type: () => "Construction",
} as unknown as UnitView;
ui.onUnitEvent(unit);
expect(ui["allProgressBars"].has(2)).toBe(true);
@@ -86,9 +86,9 @@ describe("SAM", () => {
const nuke = attacker.buildUnit(UnitType.AtomBomb, game.ref(1, 1), {
targetTile: game.ref(3, 1),
trajectory: [
{ tile: game.ref(1, 1), targetable: true },
{ tile: game.ref(2, 1), targetable: true },
{ tile: game.ref(3, 1), targetable: true },
{ targetable: true, tile: game.ref(1, 1) },
{ targetable: true, tile: game.ref(2, 1) },
{ targetable: true, tile: game.ref(3, 1) },
],
});
executeTicks(game, 3);
@@ -102,17 +102,17 @@ describe("SAM", () => {
attacker.buildUnit(UnitType.AtomBomb, game.ref(2, 1), {
targetTile: game.ref(3, 1),
trajectory: [
{ tile: game.ref(1, 1), targetable: true },
{ tile: game.ref(2, 1), targetable: true },
{ tile: game.ref(3, 1), targetable: true },
{ targetable: true, tile: game.ref(1, 1) },
{ targetable: true, tile: game.ref(2, 1) },
{ targetable: true, tile: game.ref(3, 1) },
],
});
attacker.buildUnit(UnitType.AtomBomb, game.ref(1, 2), {
targetTile: game.ref(1, 3),
trajectory: [
{ tile: game.ref(1, 1), targetable: true },
{ tile: game.ref(1, 2), targetable: true },
{ tile: game.ref(1, 3), targetable: true },
{ targetable: true, tile: game.ref(1, 1) },
{ targetable: true, tile: game.ref(1, 2) },
{ targetable: true, tile: game.ref(1, 3) },
],
});
expect(attacker.units(UnitType.AtomBomb)).toHaveLength(2);
@@ -130,9 +130,9 @@ describe("SAM", () => {
const nuke = attacker.buildUnit(UnitType.AtomBomb, game.ref(1, 1), {
targetTile: game.ref(1, 3),
trajectory: [
{ tile: game.ref(1, 1), targetable: true },
{ tile: game.ref(2, 1), targetable: true },
{ tile: game.ref(3, 1), targetable: true },
{ targetable: true, tile: game.ref(1, 1) },
{ targetable: true, tile: game.ref(2, 1) },
{ targetable: true, tile: game.ref(3, 1) },
],
});
@@ -158,9 +158,9 @@ describe("SAM", () => {
const nuke = attacker.buildUnit(UnitType.AtomBomb, game.ref(1, 1), {
targetTile: game.ref(1, 3),
trajectory: [
{ tile: game.ref(1, 1), targetable: true },
{ tile: game.ref(1, 2), targetable: true },
{ tile: game.ref(1, 3), targetable: true },
{ targetable: true, tile: game.ref(1, 1) },
{ targetable: true, tile: game.ref(1, 2) },
{ targetable: true, tile: game.ref(1, 3) },
],
});
@@ -22,66 +22,66 @@ describe("TradeShipExecution", () => {
});
game.displayMessage = jest.fn();
origOwner = {
canBuild: jest.fn(() => true),
buildUnit: jest.fn((type, spawn, opts) => tradeShip),
displayName: jest.fn(() => "Origin"),
addGold: jest.fn(),
units: jest.fn(() => [dstPort]),
unitCount: jest.fn(() => 1),
id: jest.fn(() => 1),
buildUnit: jest.fn((type, spawn, opts) => tradeShip),
canBuild: jest.fn(() => true),
canTrade: jest.fn(() => true),
displayName: jest.fn(() => "Origin"),
id: jest.fn(() => 1),
unitCount: jest.fn(() => 1),
units: jest.fn(() => [dstPort]),
} as any;
dstOwner = {
id: jest.fn(() => 2),
addGold: jest.fn(),
displayName: jest.fn(() => "Destination"),
units: jest.fn(() => [dstPort]),
unitCount: jest.fn(() => 1),
canTrade: jest.fn(() => true),
displayName: jest.fn(() => "Destination"),
id: jest.fn(() => 2),
unitCount: jest.fn(() => 1),
units: jest.fn(() => [dstPort]),
} as any;
pirate = {
id: jest.fn(() => 3),
addGold: jest.fn(),
displayName: jest.fn(() => "Destination"),
units: jest.fn(() => [piratePort]),
unitCount: jest.fn(() => 1),
canTrade: jest.fn(() => true),
displayName: jest.fn(() => "Destination"),
id: jest.fn(() => 3),
unitCount: jest.fn(() => 1),
units: jest.fn(() => [piratePort]),
} as any;
piratePort = {
tile: jest.fn(() => 40011),
owner: jest.fn(() => pirate),
isActive: jest.fn(() => true),
owner: jest.fn(() => pirate),
tile: jest.fn(() => 40011),
} as any;
srcPort = {
tile: jest.fn(() => 20011),
owner: jest.fn(() => origOwner),
isActive: jest.fn(() => true),
owner: jest.fn(() => origOwner),
tile: jest.fn(() => 20011),
} as any;
dstPort = {
tile: jest.fn(() => 30015), // 15x15
owner: jest.fn(() => dstOwner),
isActive: jest.fn(() => true),
owner: jest.fn(() => dstOwner),
tile: jest.fn(() => 30015), // 15x15
} as any;
tradeShip = {
isActive: jest.fn(() => true),
owner: jest.fn(() => origOwner),
move: jest.fn(),
setTargetUnit: jest.fn(),
setSafeFromPirates: jest.fn(),
delete: jest.fn(),
isActive: jest.fn(() => true),
move: jest.fn(),
owner: jest.fn(() => origOwner),
setSafeFromPirates: jest.fn(),
setTargetUnit: jest.fn(),
tile: jest.fn(() => 2001),
} as any;
tradeShipExecution = new TradeShipExecution(origOwner, srcPort, dstPort);
tradeShipExecution.init(game, 0);
tradeShipExecution["pathFinder"] = {
nextTile: jest.fn(() => ({ type: 0, node: 2001 })),
nextTile: jest.fn(() => ({ node: 2001, type: 0 })),
} as any;
tradeShipExecution["tradeShip"] = tradeShip;
});
@@ -112,7 +112,7 @@ describe("TradeShipExecution", () => {
it("should complete trade and award gold", () => {
tradeShipExecution["pathFinder"] = {
nextTile: jest.fn(() => ({ type: 2, node: 2001 })),
nextTile: jest.fn(() => ({ node: 2001, type: 2 })),
} as any;
tradeShipExecution.tick(1);
expect(tradeShip.delete).toHaveBeenCalledWith(false);
+1 -1
View File
@@ -2,9 +2,9 @@ import { Cluster, TrainStation } from "../../../src/core/game/TrainStation";
const createMockStation = (id: string): jest.Mocked<TrainStation> => {
return {
getCluster: jest.fn(() => null),
id,
setCluster: jest.fn(),
getCluster: jest.fn(() => null),
} as any;
};
+1 -1
View File
@@ -21,8 +21,8 @@ describe("GameImpl", () => {
beforeEach(async () => {
game = await setup("ocean_and_land", {
infiniteGold: true,
instantBuild: true,
infiniteTroops: true,
instantBuild: true,
});
const attackerInfo = new PlayerInfo(
"attacker dude",
+12 -12
View File
@@ -11,17 +11,17 @@ const createMockStation = (unitId: number): any => {
const cluster = new Cluster();
const railroads = new Set<Railroad>();
return {
addRailroad: jest.fn(),
clearRailroads: jest.fn(),
getCluster: jest.fn(() => cluster),
getRailroads: jest.fn(() => railroads),
neighbors: jest.fn(() => []),
setCluster: jest.fn(),
tile: jest.fn(),
unit: {
id: unitId,
setTrainStation: jest.fn(),
},
tile: jest.fn(),
neighbors: jest.fn(() => []),
getCluster: jest.fn(() => cluster),
setCluster: jest.fn(),
addRailroad: jest.fn(),
getRailroads: jest.fn(() => railroads),
clearRailroads: jest.fn(),
};
};
@@ -55,22 +55,22 @@ describe("RailNetworkImpl", () => {
beforeEach(() => {
stationManager = {
addStation: jest.fn(),
removeStation: jest.fn(),
findStation: jest.fn(),
getAll: jest.fn(() => new Set()),
removeStation: jest.fn(),
};
pathService = {
findTilePath: jest.fn(() => [0]),
findStationsPath: jest.fn(() => [0]),
findTilePath: jest.fn(() => [0]),
};
game = {
nearbyUnits: jest.fn(() => []),
addExecution: jest.fn(),
config: () => ({
railroadMaxSize: () => 100,
trainStationMaxRange: () => 80,
trainStationMinRange: () => 10,
railroadMaxSize: () => 100,
}),
nearbyUnits: jest.fn(() => []),
};
network = new RailNetworkImpl(game, stationManager, pathService);
@@ -153,7 +153,7 @@ describe("RailNetworkImpl", () => {
neighborStation.getCluster = jest.fn(() => cluster);
cluster.has = jest.fn(() => false);
const neighborUnit = { unit: neighborStation.unit, distSquared: 20 };
const neighborUnit = { distSquared: 20, unit: neighborStation.unit };
game.nearbyUnits.mockReturnValue([neighborUnit]);
stationManager.findStation.mockReturnValue(neighborStation);
+9 -9
View File
@@ -14,34 +14,34 @@ describe("TrainStation", () => {
beforeEach(() => {
game = {
ticks: jest.fn().mockReturnValue(123),
addExecution: jest.fn(),
addUpdate: jest.fn(),
config: jest.fn().mockReturnValue({
trainGold: (isFriendly: boolean) =>
isFriendly ? BigInt(1000) : BigInt(500),
}),
addUpdate: jest.fn(),
addExecution: jest.fn(),
ticks: jest.fn().mockReturnValue(123),
} as any;
player = {
addGold: jest.fn(),
id: 1,
canTrade: jest.fn().mockReturnValue(true),
id: 1,
isFriendly: jest.fn().mockReturnValue(false),
} as any;
unit = {
owner: jest.fn().mockReturnValue(player),
isActive: jest.fn().mockReturnValue(true),
level: jest.fn().mockReturnValue(1),
owner: jest.fn().mockReturnValue(player),
tile: jest.fn().mockReturnValue({ x: 0, y: 0 }),
type: jest.fn(),
isActive: jest.fn().mockReturnValue(true),
} as any;
trainExecution = {
level: jest.fn(),
loadCargo: jest.fn(),
owner: jest.fn().mockReturnValue(player),
level: jest.fn(),
} as any;
});
@@ -82,7 +82,7 @@ describe("TrainStation", () => {
it("adds and retrieves neighbors", () => {
const stationA = new TrainStation(game, unit);
const stationB = new TrainStation(game, unit);
const railRoad = { from: stationA, to: stationB, tiles: [] } as any;
const railRoad = { from: stationA, tiles: [], to: stationB } as any;
stationA.addRailroad(railRoad);
@@ -96,8 +96,8 @@ describe("TrainStation", () => {
const railRoad = {
from: stationA,
to: stationB,
tiles: [{ x: 1, y: 1 }],
to: stationB,
} as any;
stationA.addRailroad(railRoad);
+7 -7
View File
@@ -7,22 +7,22 @@ describe("PrivilegeChecker.isCustomFlagAllowed (with mock cosmetics)", () => {
};
const mockCosmetics: Cosmetics = {
patterns: {},
flag: {
color: {
a: { color: "#ff0000", flares: ["cosmetic:red"], name: "red" },
b: { color: "#00ff00", name: "green" },
c: { color: "#0000ff", flares: ["cosmetic:blue"], name: "blue" },
},
layers: {
a: {
name: "chocolate",
flares: ["cosmetic:flags"],
name: "chocolate",
},
b: { name: "center_hline" },
c: { name: "admin_layer" },
},
color: {
a: { color: "#ff0000", name: "red", flares: ["cosmetic:red"] },
b: { color: "#00ff00", name: "green" },
c: { color: "#0000ff", name: "blue", flares: ["cosmetic:blue"] },
},
},
patterns: {},
};
const checker = new PrivilegeCheckerImpl(mockCosmetics, dummyPatternDecoder);