mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-03 06:40:33 +00:00
Enable strictNullChecks, eqeqeq (#436)
## Description: Improve type safety and runtime correctness by: 1. Enabling TypeScript's [strictNullChecks](https://www.typescriptlang.org/tsconfig/#strictNullChecks) compiler option. 2. Replacing all loose equality operators (`==` and `!=`) with strict equality operators (`===` and `!==`). 3. Cleaning up of type declarations, null handling logic, and equality expressions throughout the project. Currently, the code allows implicit assumptions that `null` and `undefined` are interchangeable, and relies on type-coercing equality checks that can introduce subtle bugs. These practices make it difficult to reason about when values may be absent and hinder the effectiveness of static analysis. Migrating to strict null checks and enforcing strict equality comparisons will clarify intent, reduce bugs, and make the codebase safer and easier to maintain. Fixes #466 ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced - [x] I understand that submitting code with bugs that could have been caught through manual testing blocks releases and new features for all contributors --------- Co-authored-by: Scott Anderson <662325+scottanderson@users.noreply.github.com> Co-authored-by: evanpelle <openfrontio@gmail.com>
This commit is contained in:
@@ -63,7 +63,7 @@ export function joinLobby(
|
||||
);
|
||||
|
||||
const userSettings: UserSettings = new UserSettings();
|
||||
startGame(lobbyConfig.gameID, lobbyConfig.gameStartInfo?.config);
|
||||
startGame(lobbyConfig.gameID, lobbyConfig.gameStartInfo?.config ?? {});
|
||||
|
||||
const transport = new Transport(lobbyConfig, eventBus);
|
||||
|
||||
@@ -74,12 +74,12 @@ export function joinLobby(
|
||||
let terrainLoad: Promise<TerrainMapData> | null = null;
|
||||
|
||||
const onmessage = (message: ServerMessage) => {
|
||||
if (message.type == "prestart") {
|
||||
if (message.type === "prestart") {
|
||||
consolex.log(`lobby: game prestarting: ${JSON.stringify(message)}`);
|
||||
terrainLoad = loadTerrainMap(message.gameMap);
|
||||
onPrestart();
|
||||
}
|
||||
if (message.type == "start") {
|
||||
if (message.type === "start") {
|
||||
// Trigger prestart for singleplayer games
|
||||
onPrestart();
|
||||
consolex.log(`lobby: game started: ${JSON.stringify(message, null, 2)}`);
|
||||
@@ -109,10 +109,13 @@ export async function createClientGame(
|
||||
userSettings: UserSettings,
|
||||
terrainLoad: Promise<TerrainMapData> | null,
|
||||
): Promise<ClientGameRunner> {
|
||||
if (lobbyConfig.gameStartInfo === undefined) {
|
||||
throw new Error("missing gameStartInfo");
|
||||
}
|
||||
const config = await getConfig(
|
||||
lobbyConfig.gameStartInfo.config,
|
||||
userSettings,
|
||||
lobbyConfig.gameRecord != null,
|
||||
lobbyConfig.gameRecord !== undefined,
|
||||
);
|
||||
let gameMap: TerrainMapData | null = null;
|
||||
|
||||
@@ -160,7 +163,7 @@ export async function createClientGame(
|
||||
}
|
||||
|
||||
export class ClientGameRunner {
|
||||
private myPlayer: PlayerView;
|
||||
private myPlayer: PlayerView | null = null;
|
||||
private isActive = false;
|
||||
|
||||
private turnsSeen = 0;
|
||||
@@ -193,7 +196,7 @@ export class ClientGameRunner {
|
||||
},
|
||||
];
|
||||
let winner: ClientID | Team | null = null;
|
||||
if (update.winnerType == "player") {
|
||||
if (update.winnerType === "player") {
|
||||
winner = this.gameView
|
||||
.playerBySmallID(update.winner as number)
|
||||
.clientID();
|
||||
@@ -201,6 +204,9 @@ export class ClientGameRunner {
|
||||
winner = update.winner as Team;
|
||||
}
|
||||
|
||||
if (this.lobby.gameStartInfo === undefined) {
|
||||
throw new Error("missing gameStartInfo");
|
||||
}
|
||||
const record = createGameRecord(
|
||||
this.lobby.gameStartInfo.gameID,
|
||||
this.lobby.gameStartInfo,
|
||||
@@ -233,10 +239,13 @@ export class ClientGameRunner {
|
||||
this.renderer.initialize();
|
||||
this.input.initialize();
|
||||
this.worker.start((gu: GameUpdateViewData | ErrorUpdate) => {
|
||||
if (this.lobby.gameStartInfo === undefined) {
|
||||
throw new Error("missing gameStartInfo");
|
||||
}
|
||||
if ("errMsg" in gu) {
|
||||
showErrorModal(
|
||||
gu.errMsg,
|
||||
gu.stack,
|
||||
gu.stack ?? "missing",
|
||||
this.lobby.gameStartInfo.gameID,
|
||||
this.lobby.clientID,
|
||||
);
|
||||
@@ -268,7 +277,7 @@ export class ClientGameRunner {
|
||||
};
|
||||
const onmessage = (message: ServerMessage) => {
|
||||
this.lastMessageTime = Date.now();
|
||||
if (message.type == "start") {
|
||||
if (message.type === "start") {
|
||||
this.hasJoined = true;
|
||||
consolex.log("starting game!");
|
||||
for (const turn of message.turns) {
|
||||
@@ -286,7 +295,10 @@ export class ClientGameRunner {
|
||||
this.turnsSeen++;
|
||||
}
|
||||
}
|
||||
if (message.type == "desync") {
|
||||
if (message.type === "desync") {
|
||||
if (this.lobby.gameStartInfo === undefined) {
|
||||
throw new Error("missing gameStartInfo");
|
||||
}
|
||||
showErrorModal(
|
||||
`desync from server: ${JSON.stringify(message)}`,
|
||||
"",
|
||||
@@ -296,12 +308,12 @@ export class ClientGameRunner {
|
||||
"You are desynced from other players. What you see might differ from other players.",
|
||||
);
|
||||
}
|
||||
if (message.type == "turn") {
|
||||
if (message.type === "turn") {
|
||||
if (!this.hasJoined) {
|
||||
this.transport.joinGame(0);
|
||||
return;
|
||||
}
|
||||
if (this.turnsSeen != message.turn.turnNumber) {
|
||||
if (this.turnsSeen !== message.turn.turnNumber) {
|
||||
consolex.error(
|
||||
`got wrong turn have turns ${this.turnsSeen}, received turn ${message.turn.turnNumber}`,
|
||||
);
|
||||
@@ -348,17 +360,17 @@ export class ClientGameRunner {
|
||||
if (this.gameView.inSpawnPhase()) {
|
||||
return;
|
||||
}
|
||||
if (this.myPlayer == null) {
|
||||
this.myPlayer = this.gameView.playerByClientID(this.lobby.clientID);
|
||||
if (this.myPlayer == null) {
|
||||
return;
|
||||
}
|
||||
if (this.myPlayer === null) {
|
||||
const myPlayer = this.gameView.playerByClientID(this.lobby.clientID);
|
||||
if (myPlayer === null) return;
|
||||
this.myPlayer = myPlayer;
|
||||
}
|
||||
this.myPlayer.actions(tile).then((actions) => {
|
||||
if (this.myPlayer === null) return;
|
||||
const bu = actions.buildableUnits.find(
|
||||
(bu) => bu.type == UnitType.TransportShip,
|
||||
(bu) => bu.type === UnitType.TransportShip,
|
||||
);
|
||||
if (bu == null) {
|
||||
if (bu === undefined) {
|
||||
console.warn(`no transport ship buildable units`);
|
||||
return;
|
||||
}
|
||||
@@ -377,7 +389,8 @@ export class ClientGameRunner {
|
||||
this.myPlayer
|
||||
.bestTransportShipSpawn(this.gameView.ref(cell.x, cell.y))
|
||||
.then((spawn: number | false) => {
|
||||
let spawnCell = null;
|
||||
if (this.myPlayer === null) throw new Error("not initialized");
|
||||
let spawnCell: Cell | null = null;
|
||||
if (spawn !== false) {
|
||||
spawnCell = new Cell(
|
||||
this.gameView.x(spawn),
|
||||
|
||||
@@ -26,7 +26,7 @@ export class FlagInput extends LitElement {
|
||||
}
|
||||
|
||||
private setFlag(flag: string) {
|
||||
if (flag == "xx") {
|
||||
if (flag === "xx") {
|
||||
flag = "";
|
||||
}
|
||||
this.flag = flag;
|
||||
|
||||
@@ -87,7 +87,7 @@ export class GoogleAdElement extends LitElement {
|
||||
const isElectron = () => {
|
||||
// Renderer process
|
||||
if (
|
||||
typeof window !== "undefined" &&
|
||||
window !== undefined &&
|
||||
typeof window.process === "object" &&
|
||||
// @ts-expect-error hidden
|
||||
window.process.type === "renderer"
|
||||
@@ -97,7 +97,7 @@ const isElectron = () => {
|
||||
|
||||
// Main process
|
||||
if (
|
||||
typeof process !== "undefined" &&
|
||||
process !== undefined &&
|
||||
typeof process.versions === "object" &&
|
||||
!!process.versions.electron
|
||||
) {
|
||||
|
||||
@@ -31,7 +31,6 @@ export class HostLobbyModal extends LitElement {
|
||||
@state() private disableNPCs = false;
|
||||
@state() private gameMode: GameMode = GameMode.FFA;
|
||||
@state() private teamCount: number | typeof Duos = 2;
|
||||
@state() private disableNukes: boolean = false;
|
||||
@state() private bots: number = 400;
|
||||
@state() private infiniteGold: boolean = false;
|
||||
@state() private infiniteTroops: boolean = false;
|
||||
@@ -40,9 +39,9 @@ export class HostLobbyModal extends LitElement {
|
||||
@state() private copySuccess = false;
|
||||
@state() private players: string[] = [];
|
||||
@state() private useRandomMap: boolean = false;
|
||||
@state() private disabledUnits: string[] = [];
|
||||
@state() private disabledUnits: UnitType[] = [];
|
||||
|
||||
private playersInterval = null;
|
||||
private playersInterval: NodeJS.Timeout | null = null;
|
||||
// Add a new timer for debouncing bot changes
|
||||
private botsUpdateTimer: number | null = null;
|
||||
|
||||
@@ -106,7 +105,7 @@ export class HostLobbyModal extends LitElement {
|
||||
.selected=${!this.useRandomMap &&
|
||||
this.selectedMap === mapValue}
|
||||
.translation=${translateText(
|
||||
`map.${mapKey.toLowerCase()}`,
|
||||
`map.${mapKey?.toLowerCase()}`,
|
||||
)}
|
||||
></map-display>
|
||||
</div>
|
||||
@@ -233,7 +232,7 @@ export class HostLobbyModal extends LitElement {
|
||||
/>
|
||||
<div class="option-card-title">
|
||||
<span>${translateText("host_modal.bots")}</span>${
|
||||
this.bots == 0
|
||||
this.bots === 0
|
||||
? translateText("host_modal.bots_disabled")
|
||||
: this.bots
|
||||
}
|
||||
@@ -326,7 +325,7 @@ export class HostLobbyModal extends LitElement {
|
||||
[UnitType.HydrogenBomb, "unit_type.hydrogen_bomb"],
|
||||
[UnitType.MIRV, "unit_type.mirv"],
|
||||
].map(
|
||||
([unitType, translationKey]) => html`
|
||||
([unitType, translationKey]: [UnitType, string]) => html`
|
||||
<label
|
||||
class="option-card ${this.disabledUnits.includes(
|
||||
unitType,
|
||||
@@ -405,7 +404,7 @@ export class HostLobbyModal extends LitElement {
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</o-modal>
|
||||
`;
|
||||
@@ -503,10 +502,6 @@ export class HostLobbyModal extends LitElement {
|
||||
this.infiniteTroops = Boolean((e.target as HTMLInputElement).checked);
|
||||
this.putGameConfig();
|
||||
}
|
||||
private handleDisableNukesChange(e: Event) {
|
||||
this.disableNukes = Boolean((e.target as HTMLInputElement).checked);
|
||||
this.putGameConfig();
|
||||
}
|
||||
|
||||
private async handleDisableNPCsChange(e: Event) {
|
||||
this.disableNPCs = Boolean((e.target as HTMLInputElement).checked);
|
||||
@@ -537,16 +532,14 @@ export class HostLobbyModal extends LitElement {
|
||||
gameMap: this.selectedMap,
|
||||
difficulty: this.selectedDifficulty,
|
||||
disableNPCs: this.disableNPCs,
|
||||
disableNukes: this.disableNukes,
|
||||
bots: this.bots,
|
||||
infiniteGold: this.infiniteGold,
|
||||
infiniteTroops: this.infiniteTroops,
|
||||
instantBuild: this.instantBuild,
|
||||
gameMode: this.gameMode,
|
||||
numPlayerTeams: this.teamCount,
|
||||
disabledUnits: this.disabledUnits,
|
||||
playerTeams: this.teamCount,
|
||||
} as GameConfig),
|
||||
} satisfies Partial<GameConfig>),
|
||||
},
|
||||
);
|
||||
return response;
|
||||
@@ -607,7 +600,7 @@ export class HostLobbyModal extends LitElement {
|
||||
.then((response) => response.json())
|
||||
.then((data: GameInfo) => {
|
||||
console.log(`got game info response: ${JSON.stringify(data)}`);
|
||||
this.players = data.clients.map((p) => p.username);
|
||||
this.players = data.clients?.map((p) => p.username) ?? [];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ export class InputHandler {
|
||||
|
||||
private alternateView = false;
|
||||
|
||||
private moveInterval: NodeJS.Timeout = null;
|
||||
private moveInterval: NodeJS.Timeout | null = null;
|
||||
private activeKeys = new Set<string>();
|
||||
|
||||
private readonly PAN_SPEED = 5;
|
||||
@@ -300,7 +300,7 @@ export class InputHandler {
|
||||
Math.abs(event.x - this.lastPointerDownX) +
|
||||
Math.abs(event.y - this.lastPointerDownY);
|
||||
if (dist < 10) {
|
||||
if (event.pointerType == "touch") {
|
||||
if (event.pointerType === "touch") {
|
||||
this.eventBus.emit(new ContextMenuEvent(event.clientX, event.clientY));
|
||||
event.preventDefault();
|
||||
return;
|
||||
@@ -385,7 +385,9 @@ export class InputHandler {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
clearInterval(this.moveInterval);
|
||||
if (this.moveInterval !== null) {
|
||||
clearInterval(this.moveInterval);
|
||||
}
|
||||
this.activeKeys.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export class JoinPrivateLobbyModal extends LitElement {
|
||||
@state() private hasJoined = false;
|
||||
@state() private players: string[] = [];
|
||||
|
||||
private playersInterval = null;
|
||||
private playersInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
@@ -98,7 +98,7 @@ export class JoinPrivateLobbyModal extends LitElement {
|
||||
}
|
||||
|
||||
public close() {
|
||||
this.lobbyIdInput.value = null;
|
||||
this.lobbyIdInput.value = "";
|
||||
this.modalEl?.close();
|
||||
if (this.playersInterval) {
|
||||
clearInterval(this.playersInterval);
|
||||
@@ -263,7 +263,7 @@ export class JoinPrivateLobbyModal extends LitElement {
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then((data: GameInfo) => {
|
||||
this.players = data.clients.map((p) => p.username);
|
||||
this.players = data.clients?.map((p) => p.username) ?? [];
|
||||
})
|
||||
.catch((error) => {
|
||||
consolex.error("Error polling players:", error);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { GameConfig, GameID, GameRecord } from "../core/Schemas";
|
||||
|
||||
export interface LocalStatsData {
|
||||
[key: GameID]: {
|
||||
lobby: GameConfig;
|
||||
lobby: Partial<GameConfig>;
|
||||
// Only once the game is over
|
||||
gameRecord?: GameRecord;
|
||||
};
|
||||
@@ -26,8 +26,8 @@ function save(stats: LocalStatsData) {
|
||||
|
||||
// The user can quit the game anytime so better save the lobby as soon as the
|
||||
// game starts.
|
||||
export function startGame(id: GameID, lobby: GameConfig) {
|
||||
if (typeof localStorage === "undefined") {
|
||||
export function startGame(id: GameID, lobby: Partial<GameConfig>) {
|
||||
if (localStorage === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export function startTime() {
|
||||
}
|
||||
|
||||
export function endGame(gameRecord: GameRecord) {
|
||||
if (typeof localStorage === "undefined") {
|
||||
if (localStorage === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ export class LocalServer {
|
||||
|
||||
private paused = false;
|
||||
|
||||
private winner: ClientSendWinnerMessage = null;
|
||||
private winner: ClientSendWinnerMessage | null = null;
|
||||
private allPlayersStats: AllPlayersStats = {};
|
||||
|
||||
private turnsExecuted = 0;
|
||||
@@ -43,7 +43,7 @@ export class LocalServer {
|
||||
|
||||
start() {
|
||||
this.turnCheckInterval = setInterval(() => {
|
||||
if (this.turnsExecuted == this.turns.length) {
|
||||
if (this.turnsExecuted === this.turns.length) {
|
||||
if (
|
||||
this.isReplay ||
|
||||
Date.now() >
|
||||
@@ -62,6 +62,9 @@ export class LocalServer {
|
||||
this.lobbyConfig.gameRecord,
|
||||
).turns;
|
||||
}
|
||||
if (this.lobbyConfig.gameStartInfo === undefined) {
|
||||
throw new Error("missing gameStartInfo");
|
||||
}
|
||||
this.clientMessage(
|
||||
ServerStartGameMessageSchema.parse({
|
||||
type: "start",
|
||||
@@ -84,13 +87,13 @@ export class LocalServer {
|
||||
const clientMsg: ClientMessage = ClientMessageSchema.parse(
|
||||
JSON.parse(message),
|
||||
);
|
||||
if (clientMsg.type == "intent") {
|
||||
if (clientMsg.type === "intent") {
|
||||
if (this.lobbyConfig.gameRecord) {
|
||||
// If we are replaying a game, we don't want to process intents
|
||||
return;
|
||||
}
|
||||
if (this.paused) {
|
||||
if (clientMsg.intent.type == "troop_ratio") {
|
||||
if (clientMsg.intent.type === "troop_ratio") {
|
||||
// Store troop change events because otherwise they are
|
||||
// not registered when game is paused.
|
||||
this.intents.push(clientMsg.intent);
|
||||
@@ -99,7 +102,7 @@ export class LocalServer {
|
||||
}
|
||||
this.intents.push(clientMsg.intent);
|
||||
}
|
||||
if (clientMsg.type == "hash") {
|
||||
if (clientMsg.type === "hash") {
|
||||
if (!this.lobbyConfig.gameRecord) {
|
||||
// If we are playing a singleplayer then store hash.
|
||||
this.turns[clientMsg.turnNumber].hash = clientMsg.hash;
|
||||
@@ -113,7 +116,7 @@ export class LocalServer {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (archivedHash != clientMsg.hash) {
|
||||
if (archivedHash !== clientMsg.hash) {
|
||||
console.error(
|
||||
`desync detected on turn ${clientMsg.turnNumber}, client hash: ${clientMsg.hash}, server hash: ${archivedHash}`,
|
||||
);
|
||||
@@ -131,7 +134,7 @@ export class LocalServer {
|
||||
);
|
||||
}
|
||||
}
|
||||
if (clientMsg.type == "winner") {
|
||||
if (clientMsg.type === "winner") {
|
||||
this.winner = clientMsg;
|
||||
this.allPlayersStats = clientMsg.allPlayersStats;
|
||||
}
|
||||
@@ -172,6 +175,9 @@ export class LocalServer {
|
||||
clientID: this.lobbyConfig.clientID,
|
||||
},
|
||||
];
|
||||
if (this.lobbyConfig.gameStartInfo === undefined) {
|
||||
throw new Error("missing gameStartInfo");
|
||||
}
|
||||
const record = createGameRecord(
|
||||
this.lobbyConfig.gameStartInfo.gameID,
|
||||
this.lobbyConfig.gameStartInfo,
|
||||
@@ -179,8 +185,8 @@ export class LocalServer {
|
||||
this.turns,
|
||||
this.startedAt,
|
||||
Date.now(),
|
||||
this.winner?.winner,
|
||||
this.winner?.winnerType,
|
||||
this.winner?.winner ?? null,
|
||||
this.winner?.winnerType ?? null,
|
||||
this.allPlayersStats,
|
||||
);
|
||||
if (!saveFullGame) {
|
||||
|
||||
+38
-29
@@ -46,7 +46,7 @@ export interface JoinLobbyEvent {
|
||||
}
|
||||
|
||||
class Client {
|
||||
private gameStop: () => void;
|
||||
private gameStop: (() => void) | null = null;
|
||||
|
||||
private usernameInput: UsernameInput | null = null;
|
||||
private flagInput: FlagInput | null = null;
|
||||
@@ -123,7 +123,7 @@ class Client {
|
||||
|
||||
window.addEventListener("beforeunload", () => {
|
||||
consolex.log("Browser is closing");
|
||||
if (this.gameStop != null) {
|
||||
if (this.gameStop !== null) {
|
||||
this.gameStop();
|
||||
}
|
||||
});
|
||||
@@ -136,8 +136,10 @@ class Client {
|
||||
"single-player-modal",
|
||||
) as SinglePlayerModal;
|
||||
spModal instanceof SinglePlayerModal;
|
||||
document.getElementById("single-player").addEventListener("click", () => {
|
||||
if (this.usernameInput.isValid()) {
|
||||
const singlePlayer = document.getElementById("single-player");
|
||||
if (singlePlayer === null) throw new Error("Missing single-player");
|
||||
singlePlayer.addEventListener("click", () => {
|
||||
if (this.usernameInput?.isValid()) {
|
||||
spModal.open();
|
||||
}
|
||||
});
|
||||
@@ -150,7 +152,9 @@ class Client {
|
||||
|
||||
const hlpModal = document.querySelector("help-modal") as HelpModal;
|
||||
hlpModal instanceof HelpModal;
|
||||
document.getElementById("help-button").addEventListener("click", () => {
|
||||
const helpButton = document.getElementById("help-button");
|
||||
if (helpButton === null) throw new Error("Missing help-button");
|
||||
helpButton.addEventListener("click", () => {
|
||||
hlpModal.open();
|
||||
});
|
||||
|
||||
@@ -193,34 +197,39 @@ class Client {
|
||||
"user-setting",
|
||||
) as UserSettingModal;
|
||||
settingsModal instanceof UserSettingModal;
|
||||
document.getElementById("settings-button").addEventListener("click", () => {
|
||||
settingsModal.open();
|
||||
});
|
||||
document
|
||||
.getElementById("settings-button")
|
||||
?.addEventListener("click", () => {
|
||||
settingsModal.open();
|
||||
});
|
||||
|
||||
const hostModal = document.querySelector(
|
||||
"host-lobby-modal",
|
||||
) as HostPrivateLobbyModal;
|
||||
hostModal instanceof HostPrivateLobbyModal;
|
||||
document
|
||||
.getElementById("host-lobby-button")
|
||||
.addEventListener("click", () => {
|
||||
if (this.usernameInput.isValid()) {
|
||||
hostModal.open();
|
||||
this.publicLobby.leaveLobby();
|
||||
}
|
||||
});
|
||||
const hostLobbyButton = document.getElementById("host-lobby-button");
|
||||
if (hostLobbyButton === null) throw new Error("Missing host-lobby-button");
|
||||
hostLobbyButton.addEventListener("click", () => {
|
||||
if (this.usernameInput?.isValid()) {
|
||||
hostModal.open();
|
||||
this.publicLobby.leaveLobby();
|
||||
}
|
||||
});
|
||||
|
||||
this.joinModal = document.querySelector(
|
||||
"join-private-lobby-modal",
|
||||
) as JoinPrivateLobbyModal;
|
||||
this.joinModal instanceof JoinPrivateLobbyModal;
|
||||
document
|
||||
.getElementById("join-private-lobby-button")
|
||||
.addEventListener("click", () => {
|
||||
if (this.usernameInput.isValid()) {
|
||||
this.joinModal.open();
|
||||
}
|
||||
});
|
||||
const joinPrivateLobbyButton = document.getElementById(
|
||||
"join-private-lobby-button",
|
||||
);
|
||||
if (joinPrivateLobbyButton === null)
|
||||
throw new Error("Missing join-private-lobby-button");
|
||||
joinPrivateLobbyButton.addEventListener("click", () => {
|
||||
if (this.usernameInput?.isValid()) {
|
||||
this.joinModal.open();
|
||||
}
|
||||
});
|
||||
|
||||
if (this.userSettings.darkMode()) {
|
||||
document.documentElement.classList.add("dark");
|
||||
@@ -258,7 +267,7 @@ class Client {
|
||||
private async handleJoinLobby(event: CustomEvent) {
|
||||
const lobby = event.detail as JoinLobbyEvent;
|
||||
consolex.log(`joining lobby ${lobby.gameID}`);
|
||||
if (this.gameStop != null) {
|
||||
if (this.gameStop !== null) {
|
||||
consolex.log("joining lobby, stopping existing game");
|
||||
this.gameStop();
|
||||
}
|
||||
@@ -269,10 +278,10 @@ class Client {
|
||||
gameID: lobby.gameID,
|
||||
serverConfig: config,
|
||||
flag:
|
||||
this.flagInput.getCurrentFlag() == "xx"
|
||||
this.flagInput === null || this.flagInput.getCurrentFlag() === "xx"
|
||||
? ""
|
||||
: this.flagInput.getCurrentFlag(),
|
||||
playerName: this.usernameInput.getCurrentUsername(),
|
||||
playerName: this.usernameInput?.getCurrentUsername() ?? "",
|
||||
token: localStorage.getItem("token") ?? getPersistentIDFromCookie(),
|
||||
clientID: lobby.clientID,
|
||||
gameStartInfo: lobby.gameStartInfo ?? lobby.gameRecord?.gameStartInfo,
|
||||
@@ -280,7 +289,7 @@ class Client {
|
||||
},
|
||||
() => {
|
||||
console.log("Closing modals");
|
||||
document.getElementById("settings-button").classList.add("hidden");
|
||||
document.getElementById("settings-button")?.classList.add("hidden");
|
||||
[
|
||||
"single-player-modal",
|
||||
"host-lobby-modal",
|
||||
@@ -319,7 +328,7 @@ class Client {
|
||||
(ad as HTMLElement).style.display = "none";
|
||||
});
|
||||
|
||||
if (event.detail.gameConfig?.gameType != GameType.Singleplayer) {
|
||||
if (event.detail.gameConfig?.gameType !== GameType.Singleplayer) {
|
||||
window.history.pushState({}, "", `/join/${lobby.gameID}`);
|
||||
sessionStorage.setItem("inLobby", "true");
|
||||
}
|
||||
@@ -328,7 +337,7 @@ class Client {
|
||||
}
|
||||
|
||||
private async handleLeaveLobby(/* event: CustomEvent */) {
|
||||
if (this.gameStop == null) {
|
||||
if (this.gameStop === null) {
|
||||
return;
|
||||
}
|
||||
consolex.log("leaving lobby, cancelling game");
|
||||
|
||||
@@ -14,7 +14,7 @@ export class PublicLobby extends LitElement {
|
||||
@state() public isLobbyHighlighted: boolean = false;
|
||||
@state() private isButtonDebounced: boolean = false;
|
||||
private lobbiesInterval: number | null = null;
|
||||
private currLobby: GameInfo = null;
|
||||
private currLobby: GameInfo | null = null;
|
||||
private debounceDelay: number = 750;
|
||||
private lobbyIDToStart = new Map<GameID, number>();
|
||||
|
||||
@@ -46,7 +46,8 @@ export class PublicLobby extends LitElement {
|
||||
// Store the start time on first fetch because endpoint is cached, causing
|
||||
// the time to appear irregular.
|
||||
if (!this.lobbyIDToStart.has(l.gameID)) {
|
||||
this.lobbyIDToStart.set(l.gameID, l.msUntilStart + Date.now());
|
||||
const msUntilStart = l.msUntilStart ?? 0;
|
||||
this.lobbyIDToStart.set(l.gameID, msUntilStart + Date.now());
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -82,17 +83,13 @@ export class PublicLobby extends LitElement {
|
||||
if (!lobby?.gameConfig) {
|
||||
return;
|
||||
}
|
||||
const timeRemaining = Math.max(
|
||||
0,
|
||||
Math.floor((this.lobbyIDToStart.get(lobby.gameID) - Date.now()) / 1000),
|
||||
);
|
||||
const start = this.lobbyIDToStart.get(lobby.gameID) ?? 0;
|
||||
const timeRemaining = Math.max(0, Math.floor((start - Date.now()) / 1000));
|
||||
|
||||
// Format time to show minutes and seconds
|
||||
const minutes = Math.floor(timeRemaining / 60);
|
||||
const seconds = timeRemaining % 60;
|
||||
const timeDisplay = minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
|
||||
const playersRemainingBeforeMax =
|
||||
lobby.gameConfig.maxPlayers - lobby.numClients;
|
||||
|
||||
const teamCount =
|
||||
lobby.gameConfig.gameMode === GameMode.Team
|
||||
@@ -131,8 +128,8 @@ export class PublicLobby extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<div class="text-md font-medium text-blue-100">
|
||||
${lobby.gameConfig.gameMode == GameMode.Team
|
||||
? translateText("public_lobby.teams", { num: teamCount })
|
||||
${lobby.gameConfig.gameMode === GameMode.Team
|
||||
? translateText("public_lobby.teams", { num: teamCount ?? 0 })
|
||||
: translateText("game_mode.ffa")}
|
||||
</div>
|
||||
</div>
|
||||
@@ -168,7 +165,7 @@ export class PublicLobby extends LitElement {
|
||||
this.isButtonDebounced = false;
|
||||
}, this.debounceDelay);
|
||||
|
||||
if (this.currLobby == null) {
|
||||
if (this.currLobby === null) {
|
||||
this.isLobbyHighlighted = true;
|
||||
this.currLobby = lobby;
|
||||
this.dispatchEvent(
|
||||
|
||||
@@ -73,7 +73,7 @@ export class SinglePlayerModal extends LitElement {
|
||||
.selected=${!this.useRandomMap &&
|
||||
this.selectedMap === mapValue}
|
||||
.translation=${translateText(
|
||||
`map.${mapKey.toLowerCase()}`,
|
||||
`map.${mapKey?.toLowerCase()}`,
|
||||
)}
|
||||
></map-display>
|
||||
</div>
|
||||
@@ -204,7 +204,7 @@ export class SinglePlayerModal extends LitElement {
|
||||
/>
|
||||
<div class="option-card-title">
|
||||
<span>${translateText("single_modal.bots")}</span>${this
|
||||
.bots == 0
|
||||
.bots === 0
|
||||
? translateText("single_modal.bots_disabled")
|
||||
: this.bots}
|
||||
</div>
|
||||
@@ -444,7 +444,7 @@ export class SinglePlayerModal extends LitElement {
|
||||
clientID,
|
||||
username: usernameInput.getCurrentUsername(),
|
||||
flag:
|
||||
flagInput.getCurrentFlag() == "xx"
|
||||
flagInput.getCurrentFlag() === "xx"
|
||||
? ""
|
||||
: flagInput.getCurrentFlag(),
|
||||
},
|
||||
|
||||
+29
-20
@@ -60,14 +60,14 @@ export class SendSpawnIntentEvent implements GameEvent {
|
||||
|
||||
export class SendAttackIntentEvent implements GameEvent {
|
||||
constructor(
|
||||
public readonly targetID: PlayerID,
|
||||
public readonly targetID: PlayerID | null,
|
||||
public readonly troops: number,
|
||||
) {}
|
||||
}
|
||||
|
||||
export class SendBoatAttackIntentEvent implements GameEvent {
|
||||
constructor(
|
||||
public readonly targetID: PlayerID,
|
||||
public readonly targetID: PlayerID | null,
|
||||
public readonly dst: Cell,
|
||||
public readonly troops: number,
|
||||
public readonly src: Cell | null = null,
|
||||
@@ -158,7 +158,7 @@ export class MoveWarshipIntentEvent implements GameEvent {
|
||||
}
|
||||
|
||||
export class Transport {
|
||||
private socket: WebSocket;
|
||||
private socket: WebSocket | null = null;
|
||||
|
||||
private localServer: LocalServer;
|
||||
|
||||
@@ -176,8 +176,8 @@ export class Transport {
|
||||
// If gameRecord is not null, we are replaying an archived game.
|
||||
// For multiplayer games, GameConfig is not known until game starts.
|
||||
this.isLocal =
|
||||
lobbyConfig.gameRecord != null ||
|
||||
lobbyConfig.gameStartInfo?.config.gameType == GameType.Singleplayer;
|
||||
lobbyConfig.gameRecord !== undefined ||
|
||||
lobbyConfig.gameStartInfo?.config.gameType === GameType.Singleplayer;
|
||||
|
||||
this.eventBus.on(SendAllianceRequestIntentEvent, (e) =>
|
||||
this.onSendAllianceRequest(e),
|
||||
@@ -228,9 +228,9 @@ export class Transport {
|
||||
|
||||
private startPing() {
|
||||
if (this.isLocal || this.pingInterval) return;
|
||||
if (this.pingInterval == null) {
|
||||
if (this.pingInterval === null) {
|
||||
this.pingInterval = window.setInterval(() => {
|
||||
if (this.socket != null && this.socket.readyState === WebSocket.OPEN) {
|
||||
if (this.socket !== null && this.socket.readyState === WebSocket.OPEN) {
|
||||
this.sendMsg(
|
||||
JSON.stringify({
|
||||
type: "ping",
|
||||
@@ -267,7 +267,7 @@ export class Transport {
|
||||
this.lobbyConfig,
|
||||
onconnect,
|
||||
onmessage,
|
||||
this.lobbyConfig.gameRecord != null,
|
||||
this.lobbyConfig.gameRecord !== undefined,
|
||||
);
|
||||
this.localServer.start();
|
||||
}
|
||||
@@ -290,7 +290,12 @@ export class Transport {
|
||||
console.log("Connected to game server!");
|
||||
while (this.buffer.length > 0) {
|
||||
console.log("sending dropped message");
|
||||
this.sendMsg(this.buffer.pop());
|
||||
const msg = this.buffer.pop();
|
||||
if (msg === undefined) {
|
||||
console.warn("msg is undefined");
|
||||
continue;
|
||||
}
|
||||
this.sendMsg(msg);
|
||||
}
|
||||
onconnect();
|
||||
};
|
||||
@@ -306,13 +311,14 @@ export class Transport {
|
||||
};
|
||||
this.socket.onerror = (err) => {
|
||||
console.error("Socket encountered error: ", err, "Closing socket");
|
||||
if (this.socket === null) return;
|
||||
this.socket.close();
|
||||
};
|
||||
this.socket.onclose = (event: CloseEvent) => {
|
||||
console.log(
|
||||
`WebSocket closed. Code: ${event.code}, Reason: ${event.reason}`,
|
||||
);
|
||||
if (event.code != 1000) {
|
||||
if (event.code !== 1000) {
|
||||
console.log(`reconnecting`);
|
||||
this.reconnect();
|
||||
}
|
||||
@@ -359,6 +365,7 @@ export class Transport {
|
||||
return;
|
||||
}
|
||||
this.stopPing();
|
||||
if (this.socket === null) return;
|
||||
if (this.socket.readyState === WebSocket.OPEN) {
|
||||
console.log("on stop: leaving game");
|
||||
this.socket.close();
|
||||
@@ -426,8 +433,8 @@ export class Transport {
|
||||
troops: event.troops,
|
||||
dstX: event.dst.x,
|
||||
dstY: event.dst.y,
|
||||
srcX: event.src?.x,
|
||||
srcY: event.src?.y,
|
||||
srcX: event.src?.x ?? null,
|
||||
srcY: event.src?.y ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -444,7 +451,7 @@ export class Transport {
|
||||
type: "emoji",
|
||||
clientID: this.lobbyConfig.clientID,
|
||||
recipient:
|
||||
event.recipient == AllPlayers ? AllPlayers : event.recipient.id(),
|
||||
event.recipient === AllPlayers ? AllPlayers : event.recipient.id(),
|
||||
emoji: event.emoji,
|
||||
});
|
||||
}
|
||||
@@ -517,7 +524,7 @@ export class Transport {
|
||||
}
|
||||
|
||||
private onSendWinnerEvent(event: SendWinnerEvent) {
|
||||
if (this.isLocal || this.socket.readyState === WebSocket.OPEN) {
|
||||
if (this.isLocal || this.socket?.readyState === WebSocket.OPEN) {
|
||||
const msg = {
|
||||
type: "winner",
|
||||
winner: event.winner,
|
||||
@@ -528,13 +535,14 @@ export class Transport {
|
||||
} else {
|
||||
console.log(
|
||||
"WebSocket is not open. Current state:",
|
||||
this.socket.readyState,
|
||||
this.socket?.readyState,
|
||||
);
|
||||
console.log("attempting reconnect");
|
||||
}
|
||||
}
|
||||
|
||||
private onSendHashEvent(event: SendHashEvent) {
|
||||
if (this.socket === null) return;
|
||||
if (this.isLocal || this.socket.readyState === WebSocket.OPEN) {
|
||||
this.sendMsg(
|
||||
JSON.stringify({
|
||||
@@ -570,7 +578,7 @@ export class Transport {
|
||||
}
|
||||
|
||||
private sendIntent(intent: Intent) {
|
||||
if (this.isLocal || this.socket.readyState === WebSocket.OPEN) {
|
||||
if (this.isLocal || this.socket?.readyState === WebSocket.OPEN) {
|
||||
const msg = {
|
||||
type: "intent",
|
||||
intent: intent,
|
||||
@@ -579,7 +587,7 @@ export class Transport {
|
||||
} else {
|
||||
console.log(
|
||||
"WebSocket is not open. Current state:",
|
||||
this.socket.readyState,
|
||||
this.socket?.readyState,
|
||||
);
|
||||
console.log("attempting reconnect");
|
||||
}
|
||||
@@ -589,9 +597,10 @@ export class Transport {
|
||||
if (this.isLocal) {
|
||||
this.localServer.onMessage(msg);
|
||||
} else {
|
||||
if (this.socket === null) return;
|
||||
if (
|
||||
this.socket.readyState == WebSocket.CLOSED ||
|
||||
this.socket.readyState == WebSocket.CLOSED
|
||||
this.socket.readyState === WebSocket.CLOSED ||
|
||||
this.socket.readyState === WebSocket.CLOSED
|
||||
) {
|
||||
console.warn("socket not ready, closing and trying later");
|
||||
this.socket.close();
|
||||
@@ -605,7 +614,7 @@ export class Transport {
|
||||
}
|
||||
|
||||
private killExistingSocket(): void {
|
||||
if (this.socket == null) {
|
||||
if (this.socket === null) {
|
||||
return;
|
||||
}
|
||||
// Remove all event listeners
|
||||
|
||||
@@ -281,7 +281,7 @@ export class UserSettingModal extends LitElement {
|
||||
easter="true"
|
||||
@change=${(e: CustomEvent) => {
|
||||
const value = e.detail?.value;
|
||||
if (typeof value !== "undefined") {
|
||||
if (value !== undefined) {
|
||||
console.log("Changed:", value);
|
||||
} else {
|
||||
console.warn("Slider event missing detail.value", e);
|
||||
@@ -300,7 +300,7 @@ export class UserSettingModal extends LitElement {
|
||||
easter="true"
|
||||
@change=${(e: CustomEvent) => {
|
||||
const value = e.detail?.value;
|
||||
if (typeof value !== "undefined") {
|
||||
if (value !== undefined) {
|
||||
console.log("Changed:", value);
|
||||
} else {
|
||||
console.warn("Slider event missing detail.value", e);
|
||||
|
||||
@@ -64,7 +64,7 @@ export class UsernameInput extends LitElement {
|
||||
this.storeUsername(this.username);
|
||||
this.validationError = "";
|
||||
} else {
|
||||
this.validationError = result.error;
|
||||
this.validationError = result.error ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -45,12 +45,12 @@ export function createCanvas(): HTMLCanvasElement {
|
||||
*/
|
||||
export function generateCryptoRandomUUID(): string {
|
||||
// Type guard to check if randomUUID is available
|
||||
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
|
||||
if (crypto !== undefined && "randomUUID" in crypto) {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
|
||||
// Fallback using crypto.getRandomValues
|
||||
if (typeof crypto !== "undefined" && "getRandomValues" in crypto) {
|
||||
if (crypto !== undefined && "getRandomValues" in crypto) {
|
||||
return (([1e7] as any) + -1e3 + -4e3 + -8e3 + -1e11).replace(
|
||||
/[018]/g,
|
||||
(c: number): string =>
|
||||
|
||||
@@ -30,7 +30,7 @@ export class SettingSlider extends LitElement {
|
||||
|
||||
private handleSliderChange(e: Event) {
|
||||
const detail = (e as CustomEvent)?.detail;
|
||||
if (!detail || typeof detail.value === "undefined") {
|
||||
if (!detail || detail.value === undefined) {
|
||||
console.warn("Invalid slider change event", e);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -214,7 +214,9 @@ export class GameRenderer {
|
||||
public uiState: UIState,
|
||||
private layers: Layer[],
|
||||
) {
|
||||
this.context = canvas.getContext("2d");
|
||||
const context = canvas.getContext("2d");
|
||||
if (context === null) throw new Error("2d context not supported");
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
initialize() {
|
||||
|
||||
@@ -90,6 +90,9 @@ export const getColoredSprite = (
|
||||
}
|
||||
|
||||
const sprite = getSpriteForUnit(unit.type());
|
||||
if (sprite === null) {
|
||||
throw new Error(`Failed to load sprite for ${unit.type()}`);
|
||||
}
|
||||
|
||||
const territoryRgb = territoryColor.toRgb();
|
||||
const borderRgb = borderColor.toRgb();
|
||||
|
||||
@@ -9,8 +9,8 @@ export class TransformHandler {
|
||||
private offsetX: number = -350;
|
||||
private offsetY: number = -200;
|
||||
|
||||
private target: Cell;
|
||||
private intervalID = null;
|
||||
private target: Cell | null;
|
||||
private intervalID: NodeJS.Timeout | null = null;
|
||||
private changed = false;
|
||||
|
||||
constructor(
|
||||
@@ -170,6 +170,8 @@ export class TransformHandler {
|
||||
const { screenX, screenY } = this.screenCenter();
|
||||
const screenMapCenter = new Cell(screenX, screenY);
|
||||
|
||||
if (this.target === null) throw new Error("null target");
|
||||
|
||||
if (
|
||||
this.game.manhattanDist(
|
||||
this.game.ref(screenX, screenY),
|
||||
@@ -234,7 +236,7 @@ export class TransformHandler {
|
||||
}
|
||||
|
||||
private clearTarget() {
|
||||
if (this.intervalID != null) {
|
||||
if (this.intervalID !== null) {
|
||||
clearInterval(this.intervalID);
|
||||
this.intervalID = null;
|
||||
}
|
||||
|
||||
@@ -303,13 +303,12 @@ export class BuildMenu extends LitElement implements Layer {
|
||||
private _hidden = true;
|
||||
|
||||
private canBuild(item: BuildItemDisplay): boolean {
|
||||
if (this.game?.myPlayer() == null || this.playerActions == null) {
|
||||
if (this.game?.myPlayer() === null || this.playerActions === null) {
|
||||
return false;
|
||||
}
|
||||
const unit = this.playerActions.buildableUnits.filter(
|
||||
(u) => u.type == item.unitType,
|
||||
);
|
||||
if (!unit) {
|
||||
const buildableUnits = this.playerActions?.buildableUnits ?? [];
|
||||
const unit = buildableUnits.filter((u) => u.type === item.unitType);
|
||||
if (unit.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return unit[0].canBuild !== false;
|
||||
@@ -317,7 +316,7 @@ export class BuildMenu extends LitElement implements Layer {
|
||||
|
||||
private cost(item: BuildItemDisplay): number {
|
||||
for (const bu of this.playerActions?.buildableUnits ?? []) {
|
||||
if (bu.type == item.unitType) {
|
||||
if (bu.type === item.unitType) {
|
||||
return bu.cost;
|
||||
}
|
||||
}
|
||||
@@ -368,9 +367,12 @@ export class BuildMenu extends LitElement implements Layer {
|
||||
width="40"
|
||||
height="40"
|
||||
/>
|
||||
<span class="build-name">${translateText(item.key)}</span>
|
||||
<span class="build-name"
|
||||
>${item.key && translateText(item.key)}</span
|
||||
>
|
||||
<span class="build-description"
|
||||
>${translateText(item.description)}</span
|
||||
>${item.description &&
|
||||
translateText(item.description)}</span
|
||||
>
|
||||
<span class="build-cost" translate="no">
|
||||
${renderNumber(
|
||||
@@ -413,7 +415,7 @@ export class BuildMenu extends LitElement implements Layer {
|
||||
private refresh() {
|
||||
this.game
|
||||
.myPlayer()
|
||||
.actions(this.clickedTile)
|
||||
?.actions(this.clickedTile)
|
||||
.then((actions) => {
|
||||
this.playerActions = actions;
|
||||
this.requestUpdate();
|
||||
|
||||
@@ -63,7 +63,7 @@ export class ChatDisplay extends LitElement implements Layer {
|
||||
if (event.messageType !== MessageType.CHAT) return;
|
||||
const myPlayer = this.game.playerByClientID(this.clientID);
|
||||
if (
|
||||
event.playerID != null &&
|
||||
event.playerID !== null &&
|
||||
(!myPlayer || myPlayer.smallID() !== event.playerID)
|
||||
) {
|
||||
return;
|
||||
@@ -82,6 +82,7 @@ export class ChatDisplay extends LitElement implements Layer {
|
||||
tick() {
|
||||
// this.active = true;
|
||||
const updates = this.game.updatesSinceLastTick();
|
||||
if (updates === null) throw new Error("null updates");
|
||||
const messages = updates[GameUpdateType.DisplayEvent] as
|
||||
| DisplayMessageUpdate[]
|
||||
| undefined;
|
||||
@@ -91,7 +92,7 @@ export class ChatDisplay extends LitElement implements Layer {
|
||||
if (msg.messageType === MessageType.CHAT) {
|
||||
const myPlayer = this.game.playerByClientID(this.clientID);
|
||||
if (
|
||||
msg.playerID != null &&
|
||||
msg.playerID !== null &&
|
||||
(!myPlayer || myPlayer.smallID() !== msg.playerID)
|
||||
) {
|
||||
continue;
|
||||
|
||||
@@ -214,7 +214,8 @@ export class ChatModal extends LitElement {
|
||||
|
||||
private selectPlayer(player: string) {
|
||||
if (this.previewText) {
|
||||
this.previewText = this.selectedPhraseTemplate.replace("[P1]", player);
|
||||
this.previewText =
|
||||
this.selectedPhraseTemplate?.replace("[P1]", player) ?? null;
|
||||
this.selectedPlayer = player;
|
||||
this.requiresPlayerSelection = false;
|
||||
this.requestUpdate();
|
||||
@@ -228,7 +229,9 @@ export class ChatModal extends LitElement {
|
||||
console.log("Key:", this.selectedQuickChatKey);
|
||||
|
||||
if (this.sender && this.recipient && this.selectedQuickChatKey) {
|
||||
const variables = this.selectedPlayer ? { P1: this.selectedPlayer } : {};
|
||||
const variables: Record<string, string> = this.selectedPlayer
|
||||
? { P1: this.selectedPlayer }
|
||||
: {};
|
||||
|
||||
this.eventBus.emit(
|
||||
new SendQuickChatEvent(
|
||||
|
||||
@@ -85,7 +85,7 @@ export class ControlPanel extends LitElement implements Layer {
|
||||
newAttackRatio = 1;
|
||||
}
|
||||
|
||||
if (newAttackRatio == 0.11 && this.attackRatio == 0.01) {
|
||||
if (newAttackRatio === 0.11 && this.attackRatio === 0.01) {
|
||||
// If we're changing the ratio from 1%, then set it to 10% instead of 11% to keep a consistency
|
||||
newAttackRatio = 0.1;
|
||||
}
|
||||
@@ -108,13 +108,13 @@ export class ControlPanel extends LitElement implements Layer {
|
||||
}
|
||||
|
||||
const player = this.game.myPlayer();
|
||||
if (player == null || !player.isAlive()) {
|
||||
if (player === null || !player.isAlive()) {
|
||||
this.setVisibile(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const popIncreaseRate = player.population() - this._population;
|
||||
if (this.game.ticks() % 5 == 0) {
|
||||
if (this.game.ticks() % 5 === 0) {
|
||||
this._popRateIsIncreasing =
|
||||
popIncreaseRate >= this._lastPopulationIncreaseRate;
|
||||
this._lastPopulationIncreaseRate = popIncreaseRate;
|
||||
@@ -275,7 +275,7 @@ export class ControlPanel extends LitElement implements Layer {
|
||||
>${translateText("control_panel.attack_ratio")}:
|
||||
${(this.attackRatio * 100).toFixed(0)}%
|
||||
(${renderTroops(
|
||||
this.game?.myPlayer()?.troops() * this.attackRatio,
|
||||
(this.game?.myPlayer()?.troops() ?? 0) * this.attackRatio,
|
||||
)})</label
|
||||
>
|
||||
<div class="relative h-8">
|
||||
|
||||
@@ -114,7 +114,7 @@ export class EmojiTable extends LitElement {
|
||||
|
||||
this.showTable((emoji) => {
|
||||
const recipient =
|
||||
targetPlayer == this.game.myPlayer()
|
||||
targetPlayer === this.game.myPlayer()
|
||||
? AllPlayers
|
||||
: (targetPlayer as PlayerView);
|
||||
this.eventBus.emit(
|
||||
|
||||
@@ -107,8 +107,10 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
tick() {
|
||||
this.active = true;
|
||||
const updates = this.game.updatesSinceLastTick();
|
||||
for (const [ut, fn] of this.updateMap) {
|
||||
updates[ut]?.forEach((u) => fn(u));
|
||||
if (updates) {
|
||||
for (const [ut, fn] of this.updateMap) {
|
||||
updates[ut]?.forEach(fn);
|
||||
}
|
||||
}
|
||||
|
||||
let remainingEvents = this.events.filter((event) => {
|
||||
@@ -137,16 +139,16 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
// Update attacks
|
||||
this.incomingAttacks = myPlayer.incomingAttacks().filter((a) => {
|
||||
const t = (this.game.playerBySmallID(a.attackerID) as PlayerView).type();
|
||||
return t != PlayerType.Bot;
|
||||
return t !== PlayerType.Bot;
|
||||
});
|
||||
|
||||
this.outgoingAttacks = myPlayer
|
||||
.outgoingAttacks()
|
||||
.filter((a) => a.targetID != 0);
|
||||
.filter((a) => a.targetID !== 0);
|
||||
|
||||
this.outgoingLandAttacks = myPlayer
|
||||
.outgoingAttacks()
|
||||
.filter((a) => a.targetID == 0);
|
||||
.filter((a) => a.targetID === 0);
|
||||
|
||||
this.outgoingBoats = myPlayer
|
||||
.units()
|
||||
@@ -157,7 +159,7 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
|
||||
private addEvent(event: Event) {
|
||||
this.events = [...this.events, event];
|
||||
if (this._hidden == true) {
|
||||
if (this._hidden === true) {
|
||||
this.newEvents++;
|
||||
}
|
||||
this.requestUpdate();
|
||||
@@ -179,7 +181,7 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
onDisplayMessageEvent(event: DisplayMessageUpdate) {
|
||||
const myPlayer = this.game.playerByClientID(this.clientID);
|
||||
if (
|
||||
event.playerID != null &&
|
||||
event.playerID !== null &&
|
||||
(!myPlayer || myPlayer.smallID() !== event.playerID)
|
||||
) {
|
||||
return;
|
||||
@@ -345,6 +347,7 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
: update.player2ID === myPlayer.smallID()
|
||||
? update.player1ID
|
||||
: null;
|
||||
if (otherID === null) return;
|
||||
const other = this.game.playerBySmallID(otherID) as PlayerView;
|
||||
if (!other || !myPlayer.isAlive() || !other.isAlive()) return;
|
||||
|
||||
@@ -394,14 +397,14 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
if (!myPlayer) return;
|
||||
|
||||
const recipient =
|
||||
update.emoji.recipientID == AllPlayers
|
||||
update.emoji.recipientID === AllPlayers
|
||||
? AllPlayers
|
||||
: this.game.playerBySmallID(update.emoji.recipientID);
|
||||
const sender = this.game.playerBySmallID(
|
||||
update.emoji.senderID,
|
||||
) as PlayerView;
|
||||
|
||||
if (recipient == myPlayer) {
|
||||
if (recipient === myPlayer) {
|
||||
this.addEvent({
|
||||
description: `${sender.displayName()}:${update.emoji.message}`,
|
||||
unsafeDescription: true,
|
||||
@@ -427,10 +430,7 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
onUnitIncomingEvent(event: UnitIncomingUpdate) {
|
||||
const myPlayer = this.game.playerByClientID(this.clientID);
|
||||
|
||||
if (
|
||||
event.playerID != null &&
|
||||
(!myPlayer || myPlayer.smallID() !== event.playerID)
|
||||
) {
|
||||
if (!myPlayer || myPlayer.smallID() !== event.playerID) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -482,8 +482,10 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
<button
|
||||
translate="no"
|
||||
class="ml-2"
|
||||
@click=${() =>
|
||||
this.emitGoToPlayerEvent(attack.attackerID)}
|
||||
@click=${() => {
|
||||
attack.attackerID &&
|
||||
this.emitGoToPlayerEvent(attack.attackerID);
|
||||
}}
|
||||
>
|
||||
${renderTroops(attack.troops)}
|
||||
${(
|
||||
@@ -608,7 +610,7 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
this.events.sort((a, b) => {
|
||||
const aPrior = a.priority ?? 100000;
|
||||
const bPrior = b.priority ?? 100000;
|
||||
if (aPrior == bPrior) {
|
||||
if (aPrior === bPrior) {
|
||||
return a.createdAt - b.createdAt;
|
||||
}
|
||||
return bPrior - aPrior;
|
||||
@@ -665,7 +667,8 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
${event.focusID
|
||||
? html`<button
|
||||
@click=${() => {
|
||||
this.emitGoToPlayerEvent(event.focusID);
|
||||
event.focusID &&
|
||||
this.emitGoToPlayerEvent(event.focusID);
|
||||
}}
|
||||
>
|
||||
${this.getEventDescription(event)}
|
||||
@@ -673,7 +676,8 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
: event.unitView
|
||||
? html`<button
|
||||
@click=${() => {
|
||||
this.emitGoToUnitEvent(event.unitView);
|
||||
event.unitView &&
|
||||
this.emitGoToUnitEvent(event.unitView);
|
||||
}}
|
||||
>
|
||||
${this.getEventDescription(event)}
|
||||
|
||||
@@ -28,9 +28,9 @@ export class GoToUnitEvent implements GameEvent {
|
||||
|
||||
@customElement("leader-board")
|
||||
export class Leaderboard extends LitElement implements Layer {
|
||||
public game: GameView;
|
||||
public clientID: ClientID;
|
||||
public eventBus: EventBus;
|
||||
public game: GameView | null = null;
|
||||
public clientID: ClientID | null = null;
|
||||
public eventBus: EventBus | null = null;
|
||||
|
||||
players: Entry[] = [];
|
||||
|
||||
@@ -42,6 +42,7 @@ export class Leaderboard extends LitElement implements Layer {
|
||||
init() {}
|
||||
|
||||
tick() {
|
||||
if (this.game === null) throw new Error("Not initialized");
|
||||
if (!this._shownOnInit && !this.game.inSpawnPhase()) {
|
||||
this._shownOnInit = true;
|
||||
this.showLeaderboard();
|
||||
@@ -51,18 +52,19 @@ export class Leaderboard extends LitElement implements Layer {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.game.ticks() % 10 == 0) {
|
||||
if (this.game.ticks() % 10 === 0) {
|
||||
this.updateLeaderboard();
|
||||
}
|
||||
}
|
||||
|
||||
private updateLeaderboard() {
|
||||
if (this.clientID == null) {
|
||||
if (this.game === null) throw new Error("Not initialized");
|
||||
if (this.clientID === null) {
|
||||
return;
|
||||
}
|
||||
const myPlayer = this.game
|
||||
.playerViews()
|
||||
.find((p) => p.clientID() == this.clientID);
|
||||
const myPlayer =
|
||||
this.game.playerViews().find((p) => p.clientID() === this.clientID) ??
|
||||
null;
|
||||
|
||||
const sorted = this.game
|
||||
.playerViews()
|
||||
@@ -86,16 +88,19 @@ export class Leaderboard extends LitElement implements Layer {
|
||||
),
|
||||
gold: renderNumber(player.gold()),
|
||||
troops: renderNumber(troops),
|
||||
isMyPlayer: player == myPlayer,
|
||||
isMyPlayer: player === myPlayer,
|
||||
player: player,
|
||||
};
|
||||
});
|
||||
|
||||
if (myPlayer != null && this.players.find((p) => p.isMyPlayer) == null) {
|
||||
if (
|
||||
myPlayer !== null &&
|
||||
this.players.find((p) => p.isMyPlayer) === undefined
|
||||
) {
|
||||
let place = 0;
|
||||
for (const p of sorted) {
|
||||
place++;
|
||||
if (p == myPlayer) {
|
||||
if (p === myPlayer) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -122,6 +127,7 @@ export class Leaderboard extends LitElement implements Layer {
|
||||
}
|
||||
|
||||
private handleRowClickPlayer(player: PlayerView) {
|
||||
if (this.eventBus === null) return;
|
||||
this.eventBus.emit(new GoToPlayerEvent(player));
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@ export class MultiTabModal extends LitElement implements Layer {
|
||||
tick() {
|
||||
if (
|
||||
this.game.inSpawnPhase() ||
|
||||
this.game.config().gameConfig().gameType == GameType.Singleplayer ||
|
||||
this.game.config().serverConfig().env() == GameEnv.Dev
|
||||
this.game.config().gameConfig().gameType === GameType.Singleplayer ||
|
||||
this.game.config().serverConfig().env() === GameEnv.Dev
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ class RenderInfo {
|
||||
constructor(
|
||||
public player: PlayerView,
|
||||
public lastRenderCalc: number,
|
||||
public location: Cell,
|
||||
public location: Cell | null,
|
||||
public fontSize: number,
|
||||
public fontColor: string,
|
||||
public element: HTMLElement,
|
||||
@@ -104,7 +104,7 @@ export class NameLayer implements Layer {
|
||||
}
|
||||
|
||||
public tick() {
|
||||
if (this.game.ticks() % 10 != 0) {
|
||||
if (this.game.ticks() % 10 !== 0) {
|
||||
return;
|
||||
}
|
||||
const sorted = this.game
|
||||
@@ -238,7 +238,7 @@ export class NameLayer implements Layer {
|
||||
|
||||
renderPlayerInfo(render: RenderInfo) {
|
||||
if (!render.player.nameLocation() || !render.player.isAlive()) {
|
||||
this.renders = this.renders.filter((r) => r != render);
|
||||
this.renders = this.renders.filter((r) => r !== render);
|
||||
render.element.remove();
|
||||
return;
|
||||
}
|
||||
@@ -355,7 +355,7 @@ export class NameLayer implements Layer {
|
||||
|
||||
// Alliance icon
|
||||
const existingAlliance = iconsDiv.querySelector('[data-icon="alliance"]');
|
||||
if (myPlayer != null && myPlayer.isAlliedWith(render.player)) {
|
||||
if (myPlayer !== null && myPlayer.isAlliedWith(render.player)) {
|
||||
if (!existingAlliance) {
|
||||
iconsDiv.appendChild(
|
||||
this.createIconElement(
|
||||
@@ -372,7 +372,7 @@ export class NameLayer implements Layer {
|
||||
// Alliance request icon
|
||||
const data = '[data-icon="alliance-request"]';
|
||||
const existingRequestAlliance = iconsDiv.querySelector(data);
|
||||
if (myPlayer != null && render.player.isRequestingAllianceWith(myPlayer)) {
|
||||
if (myPlayer !== null && render.player.isRequestingAllianceWith(myPlayer)) {
|
||||
if (!existingRequestAlliance) {
|
||||
iconsDiv.appendChild(
|
||||
this.createIconElement(
|
||||
@@ -389,7 +389,7 @@ export class NameLayer implements Layer {
|
||||
// Target icon
|
||||
const existingTarget = iconsDiv.querySelector('[data-icon="target"]');
|
||||
if (
|
||||
myPlayer != null &&
|
||||
myPlayer !== null &&
|
||||
new Set(myPlayer.transitiveTargets()).has(render.player)
|
||||
) {
|
||||
if (!existingTarget) {
|
||||
@@ -412,11 +412,11 @@ export class NameLayer implements Layer {
|
||||
.outgoingEmojis()
|
||||
.filter(
|
||||
(emoji) =>
|
||||
emoji.recipientID == AllPlayers ||
|
||||
emoji.recipientID == myPlayer?.smallID(),
|
||||
emoji.recipientID === AllPlayers ||
|
||||
emoji.recipientID === myPlayer?.smallID(),
|
||||
);
|
||||
|
||||
if (this.game.config().userSettings().emojis() && emojis.length > 0) {
|
||||
if (this.game.config().userSettings()?.emojis() && emojis.length > 0) {
|
||||
if (!existingEmoji) {
|
||||
const emojiDiv = document.createElement("div");
|
||||
emojiDiv.setAttribute("data-icon", "emoji");
|
||||
@@ -451,8 +451,8 @@ export class NameLayer implements Layer {
|
||||
}
|
||||
|
||||
const nukesSentByOtherPlayer = this.game.units().filter((unit) => {
|
||||
const isSendingNuke = render.player.id() == unit.owner().id();
|
||||
const notMyPlayer = !myPlayer || unit.owner().id() != myPlayer.id();
|
||||
const isSendingNuke = render.player.id() === unit.owner().id();
|
||||
const notMyPlayer = !myPlayer || unit.owner().id() !== myPlayer.id();
|
||||
return (
|
||||
nukeTypes.includes(unit.type()) &&
|
||||
isSendingNuke &&
|
||||
@@ -462,24 +462,25 @@ export class NameLayer implements Layer {
|
||||
});
|
||||
const isMyPlayerTarget = nukesSentByOtherPlayer.find((unit) => {
|
||||
const detonationDst = unit.detonationDst();
|
||||
if (detonationDst === undefined) return false;
|
||||
const targetId = this.game.owner(detonationDst).id();
|
||||
return myPlayer && targetId == this.myPlayer.id();
|
||||
return myPlayer && targetId === myPlayer.id();
|
||||
});
|
||||
const existingNuke = iconsDiv.querySelector(
|
||||
'[data-icon="nuke"]',
|
||||
) as HTMLImageElement;
|
||||
|
||||
if (existingNuke) {
|
||||
if (nukesSentByOtherPlayer.length == 0) {
|
||||
if (nukesSentByOtherPlayer.length === 0) {
|
||||
existingNuke.remove();
|
||||
} else if (
|
||||
isMyPlayerTarget &&
|
||||
existingNuke.src != this.nukeRedIconImage.src
|
||||
existingNuke.src !== this.nukeRedIconImage.src
|
||||
) {
|
||||
existingNuke.src = this.nukeRedIconImage.src;
|
||||
} else if (
|
||||
!isMyPlayerTarget &&
|
||||
existingNuke.src != this.nukeWhiteIconImage.src
|
||||
existingNuke.src !== this.nukeWhiteIconImage.src
|
||||
) {
|
||||
existingNuke.src = this.nukeWhiteIconImage.src;
|
||||
}
|
||||
@@ -499,7 +500,7 @@ export class NameLayer implements Layer {
|
||||
}
|
||||
|
||||
// Position element with scale
|
||||
if (render.location && render.location != oldLocation) {
|
||||
if (render.location && render.location !== oldLocation) {
|
||||
const scale = Math.min(baseSize * 0.25, 3);
|
||||
render.element.style.transform = `translate(${render.location.x}px, ${render.location.y}px) translate(-50%, -50%) scale(${scale})`;
|
||||
}
|
||||
@@ -525,12 +526,12 @@ export class NameLayer implements Layer {
|
||||
}
|
||||
|
||||
private getPlayer(): PlayerView | null {
|
||||
if (this.myPlayer != null) {
|
||||
if (this.myPlayer !== null) {
|
||||
return this.myPlayer;
|
||||
}
|
||||
this.myPlayer = this.game
|
||||
.playerViews()
|
||||
.find((p) => p.clientID() == this.clientID);
|
||||
this.myPlayer =
|
||||
this.game.playerViews().find((p) => p.clientID() === this.clientID) ??
|
||||
null;
|
||||
return this.myPlayer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,19 +122,20 @@ export class OptionsMenu extends LitElement implements Layer {
|
||||
init() {
|
||||
console.log("init called from OptionsMenu");
|
||||
this.showPauseButton =
|
||||
this.game.config().gameConfig().gameType == GameType.Singleplayer ||
|
||||
this.game.config().gameConfig().gameType === GameType.Singleplayer ||
|
||||
this.game.config().isReplay();
|
||||
this.isVisible = true;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
tick() {
|
||||
this.hasWinner =
|
||||
this.hasWinner ||
|
||||
this.game.updatesSinceLastTick()[GameUpdateType.Win].length > 0;
|
||||
const updates = this.game.updatesSinceLastTick();
|
||||
if (updates) {
|
||||
this.hasWinner = this.hasWinner || updates[GameUpdateType.Win].length > 0;
|
||||
}
|
||||
if (this.game.inSpawnPhase()) {
|
||||
this.timer = 0;
|
||||
} else if (!this.hasWinner && this.game.ticks() % 10 == 0) {
|
||||
} else if (!this.hasWinner && this.game.ticks() % 10 === 0) {
|
||||
this.timer++;
|
||||
}
|
||||
this.isVisible = true;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { LitElement, TemplateResult, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { translateText } from "../../../client/Utils";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
@@ -177,13 +177,13 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||
private renderPlayerInfo(player: PlayerView) {
|
||||
const myPlayer = this.myPlayer();
|
||||
const isFriendly = myPlayer?.isFriendly(player);
|
||||
let relationHtml = null;
|
||||
let relationHtml: TemplateResult | null = null;
|
||||
const attackingTroops = player
|
||||
.outgoingAttacks()
|
||||
.map((a) => a.troops)
|
||||
.reduce((a, b) => a + b, 0);
|
||||
|
||||
if (player.type() == PlayerType.FakeHuman && myPlayer != null) {
|
||||
if (player.type() === PlayerType.FakeHuman && myPlayer !== null) {
|
||||
const relation =
|
||||
this.playerProfile?.relations[myPlayer.smallID()] ?? Relation.Neutral;
|
||||
const relationClass = this.getRelationClass(relation);
|
||||
@@ -224,7 +224,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||
: ""}
|
||||
${player.name()}
|
||||
</div>
|
||||
${player.team() != null
|
||||
${player.team() !== null
|
||||
? html`<div class="text-sm opacity-80">
|
||||
${translateText("player_info_overlay.team")}: ${player.team()}
|
||||
</div>`
|
||||
@@ -271,7 +271,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||
|
||||
private renderUnitInfo(unit: UnitView) {
|
||||
const isAlly =
|
||||
(unit.owner() == this.myPlayer() ||
|
||||
(unit.owner() === this.myPlayer() ||
|
||||
this.myPlayer()?.isFriendly(unit.owner())) ??
|
||||
false;
|
||||
|
||||
@@ -312,8 +312,8 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||
<div
|
||||
class="bg-opacity-60 bg-gray-900 rounded-lg shadow-lg backdrop-blur-sm transition-all duration-300 text-white text-lg md:text-base ${containerClasses}"
|
||||
>
|
||||
${this.player != null ? this.renderPlayerInfo(this.player) : ""}
|
||||
${this.unit != null ? this.renderUnitInfo(this.unit) : ""}
|
||||
${this.player !== null ? this.renderPlayerInfo(this.player) : ""}
|
||||
${this.unit !== null ? this.renderUnitInfo(this.unit) : ""}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -39,8 +39,8 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
public eventBus: EventBus;
|
||||
public emojiTable: EmojiTable;
|
||||
|
||||
private actions: PlayerActions = null;
|
||||
private tile: TileRef = null;
|
||||
private actions: PlayerActions | null = null;
|
||||
private tile: TileRef | null = null;
|
||||
|
||||
@state()
|
||||
private isVisible: boolean = false;
|
||||
@@ -125,7 +125,7 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
private handleEmojiClick(e: Event, myPlayer: PlayerView, other: PlayerView) {
|
||||
e.stopPropagation();
|
||||
this.emojiTable.showTable((emoji: string) => {
|
||||
if (myPlayer == other) {
|
||||
if (myPlayer === other) {
|
||||
this.eventBus.emit(
|
||||
new SendEmojiIntentEvent(
|
||||
AllPlayers,
|
||||
@@ -181,12 +181,16 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
return 0;
|
||||
}
|
||||
let sum = 0;
|
||||
const nukes = stats.sentNukes[this.g.myPlayer().id()];
|
||||
const player = this.g.myPlayer();
|
||||
if (player === null) {
|
||||
return 0;
|
||||
}
|
||||
const nukes = stats.sentNukes[player.id()];
|
||||
if (!nukes) {
|
||||
return 0;
|
||||
}
|
||||
for (const nukeType in nukes) {
|
||||
if (nukeType != UnitType.MIRVWarhead) {
|
||||
if (nukeType !== UnitType.MIRVWarhead) {
|
||||
sum += nukes[nukeType];
|
||||
}
|
||||
}
|
||||
@@ -198,10 +202,8 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
return html``;
|
||||
}
|
||||
const myPlayer = this.g.myPlayer();
|
||||
if (myPlayer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (myPlayer === null) return;
|
||||
if (this.tile === null) return;
|
||||
let other = this.g.owner(this.tile);
|
||||
if (!other.isPlayer()) {
|
||||
this.hide();
|
||||
@@ -210,16 +212,16 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
}
|
||||
other = other as PlayerView;
|
||||
|
||||
const canDonate = this.actions.interaction?.canDonate;
|
||||
const canDonate = this.actions?.interaction?.canDonate;
|
||||
const canSendAllianceRequest =
|
||||
this.actions.interaction?.canSendAllianceRequest;
|
||||
this.actions?.interaction?.canSendAllianceRequest;
|
||||
const canSendEmoji =
|
||||
other == myPlayer
|
||||
? this.actions.canSendEmojiAllPlayers
|
||||
: this.actions.interaction?.canSendEmoji;
|
||||
const canBreakAlliance = this.actions.interaction?.canBreakAlliance;
|
||||
const canTarget = this.actions.interaction?.canTarget;
|
||||
const canEmbargo = this.actions.interaction?.canEmbargo;
|
||||
other === myPlayer
|
||||
? this.actions?.canSendEmojiAllPlayers
|
||||
: this.actions?.interaction?.canSendEmoji;
|
||||
const canBreakAlliance = this.actions?.interaction?.canBreakAlliance;
|
||||
const canTarget = this.actions?.interaction?.canTarget;
|
||||
const canEmbargo = this.actions?.interaction?.canEmbargo;
|
||||
|
||||
return html`
|
||||
<div
|
||||
@@ -392,7 +394,7 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
</button>`
|
||||
: ""}
|
||||
</div>
|
||||
${canEmbargo && other != myPlayer
|
||||
${canEmbargo && other !== myPlayer
|
||||
? html`<button
|
||||
@click=${(e) => this.handleEmbargoClick(e, myPlayer, other)}
|
||||
class="w-100 h-10 flex items-center justify-center
|
||||
@@ -402,7 +404,7 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
${translateText("player_panel.stop_trade")}
|
||||
</button>`
|
||||
: ""}
|
||||
${!canEmbargo && other != myPlayer
|
||||
${!canEmbargo && other !== myPlayer
|
||||
? html`<button
|
||||
@click=${(e) =>
|
||||
this.handleStopEmbargoClick(e, myPlayer, other)}
|
||||
|
||||
@@ -52,7 +52,16 @@ export class RadialMenu implements Layer {
|
||||
private originalTileOwner: PlayerView | TerraNullius;
|
||||
private menuElement: d3.Selection<HTMLDivElement, unknown, null, undefined>;
|
||||
private isVisible: boolean = false;
|
||||
private readonly menuItems = new Map([
|
||||
private readonly menuItems: Map<
|
||||
Slot,
|
||||
{
|
||||
name: string;
|
||||
disabled: boolean;
|
||||
action: () => void;
|
||||
color?: string | null;
|
||||
icon?: string | null;
|
||||
}
|
||||
> = new Map([
|
||||
[
|
||||
Slot.Boat,
|
||||
{
|
||||
@@ -105,7 +114,7 @@ export class RadialMenu implements Layer {
|
||||
e.x,
|
||||
e.y,
|
||||
);
|
||||
if (clickedCell == null) {
|
||||
if (clickedCell === null) {
|
||||
return;
|
||||
}
|
||||
if (!this.g.isValidCoord(clickedCell.x, clickedCell.y)) {
|
||||
@@ -113,7 +122,7 @@ export class RadialMenu implements Layer {
|
||||
}
|
||||
const tile = this.g.ref(clickedCell.x, clickedCell.y);
|
||||
const p = this.g.playerByClientID(this.clientID);
|
||||
if (p == null) {
|
||||
if (p === null) {
|
||||
return;
|
||||
}
|
||||
this.buildMenu.showMenu(tile);
|
||||
@@ -280,12 +289,12 @@ export class RadialMenu implements Layer {
|
||||
if (myPlayer === null || !myPlayer.isAlive()) return;
|
||||
const tile = this.g.ref(this.clickedCell.x, this.clickedCell.y);
|
||||
if (this.originalTileOwner.isPlayer()) {
|
||||
if (this.g.owner(tile) != this.originalTileOwner) {
|
||||
if (this.g.owner(tile) !== this.originalTileOwner) {
|
||||
this.closeMenu();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (this.g.owner(tile).isPlayer() || this.g.owner(tile) == myPlayer) {
|
||||
if (this.g.owner(tile).isPlayer() || this.g.owner(tile) === myPlayer) {
|
||||
this.closeMenu();
|
||||
return;
|
||||
}
|
||||
@@ -383,7 +392,7 @@ export class RadialMenu implements Layer {
|
||||
});
|
||||
}
|
||||
if (
|
||||
actions.buildableUnits.find((bu) => bu.type == UnitType.TransportShip)
|
||||
actions.buildableUnits.find((bu) => bu.type === UnitType.TransportShip)
|
||||
?.canBuild
|
||||
) {
|
||||
this.activateMenuElement(Slot.Boat, "#3f6ab1", boatIcon, () => {
|
||||
@@ -395,6 +404,7 @@ export class RadialMenu implements Layer {
|
||||
spawnTile = new Cell(this.g.x(spawn), this.g.y(spawn));
|
||||
}
|
||||
|
||||
if (this.clickedCell === null) return;
|
||||
this.eventBus.emit(
|
||||
new SendBoatAttackIntentEvent(
|
||||
this.g.owner(tile).id(),
|
||||
@@ -446,12 +456,13 @@ export class RadialMenu implements Layer {
|
||||
return;
|
||||
}
|
||||
consolex.log("Center button clicked");
|
||||
if (this.clickedCell === null) return;
|
||||
const clicked = this.g.ref(this.clickedCell.x, this.clickedCell.y);
|
||||
if (this.g.inSpawnPhase()) {
|
||||
this.eventBus.emit(new SendSpawnIntentEvent(this.clickedCell));
|
||||
} else {
|
||||
const myPlayer = this.g.myPlayer();
|
||||
if (myPlayer != null && this.g.owner(clicked) != myPlayer) {
|
||||
if (myPlayer !== null && this.g.owner(clicked) !== myPlayer) {
|
||||
this.eventBus.emit(
|
||||
new SendAttackIntentEvent(
|
||||
this.g.owner(clicked).id(),
|
||||
@@ -478,6 +489,7 @@ export class RadialMenu implements Layer {
|
||||
action: () => void,
|
||||
) {
|
||||
const menuItem = this.menuItems.get(slot);
|
||||
if (menuItem === undefined) return;
|
||||
menuItem.action = action;
|
||||
menuItem.disabled = false;
|
||||
menuItem.color = color;
|
||||
|
||||
@@ -24,13 +24,14 @@ export class SpawnTimer implements Layer {
|
||||
this.ratios = [];
|
||||
this.colors = [];
|
||||
|
||||
if (this.game.config().gameConfig().gameMode != GameMode.Team) {
|
||||
if (this.game.config().gameConfig().gameMode !== GameMode.Team) {
|
||||
return;
|
||||
}
|
||||
|
||||
const teamTiles: Map<Team, number> = new Map();
|
||||
for (const player of this.game.players()) {
|
||||
const team = player.team();
|
||||
if (team === null) throw new Error("Team is null");
|
||||
const tiles = teamTiles.get(team) ?? 0;
|
||||
const sum = tiles + player.numTilesOwned();
|
||||
teamTiles.set(team, sum);
|
||||
|
||||
@@ -43,7 +43,7 @@ export class StructureLayer implements Layer {
|
||||
private canvas: HTMLCanvasElement;
|
||||
private context: CanvasRenderingContext2D;
|
||||
private unitIcons: Map<string, ImageData> = new Map();
|
||||
private theme: Theme = null;
|
||||
private theme: Theme;
|
||||
|
||||
// Configuration for supported unit types only
|
||||
private readonly unitConfigs: Partial<Record<UnitType, UnitRenderConfig>> = {
|
||||
@@ -106,6 +106,7 @@ export class StructureLayer implements Layer {
|
||||
// Create temporary canvas for icon processing
|
||||
const tempCanvas = document.createElement("canvas");
|
||||
const tempContext = tempCanvas.getContext("2d");
|
||||
if (tempContext === null) throw new Error("2d context not supported");
|
||||
tempCanvas.width = image.width;
|
||||
tempCanvas.height = image.height;
|
||||
|
||||
@@ -135,11 +136,13 @@ export class StructureLayer implements Layer {
|
||||
}
|
||||
|
||||
tick() {
|
||||
this.game
|
||||
.updatesSinceLastTick()
|
||||
[
|
||||
GameUpdateType.Unit
|
||||
].forEach((u) => this.handleUnitRendering(this.game.unit(u.id)));
|
||||
const updates = this.game.updatesSinceLastTick();
|
||||
const unitUpdates = updates !== null ? updates[GameUpdateType.Unit] : [];
|
||||
for (const u of unitUpdates) {
|
||||
const unit = this.game.unit(u.id);
|
||||
if (unit === undefined) continue;
|
||||
this.handleUnitRendering(unit);
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
@@ -149,7 +152,9 @@ export class StructureLayer implements Layer {
|
||||
redraw() {
|
||||
console.log("structure layer redrawing");
|
||||
this.canvas = document.createElement("canvas");
|
||||
this.context = this.canvas.getContext("2d", { alpha: true });
|
||||
const context = this.canvas.getContext("2d", { alpha: true });
|
||||
if (context === null) throw new Error("2d context not supported");
|
||||
this.context = context;
|
||||
this.canvas.width = this.game.width();
|
||||
this.canvas.height = this.game.height();
|
||||
this.game.units().forEach((u) => this.handleUnitRendering(u));
|
||||
@@ -193,7 +198,7 @@ export class StructureLayer implements Layer {
|
||||
)) {
|
||||
this.paintCell(
|
||||
new Cell(this.game.x(tile), this.game.y(tile)),
|
||||
unit.type() == UnitType.Construction
|
||||
unit.type() === UnitType.Construction
|
||||
? underConstructionColor
|
||||
: this.theme.territoryColor(unit.owner()),
|
||||
130,
|
||||
@@ -220,15 +225,15 @@ export class StructureLayer implements Layer {
|
||||
if (!this.isUnitTypeSupported(unitType)) return;
|
||||
|
||||
const config = this.unitConfigs[unitType];
|
||||
let icon: ImageData;
|
||||
let icon: ImageData | undefined;
|
||||
|
||||
if (unitType == UnitType.SAMLauncher && unit.isCooldown()) {
|
||||
if (unitType === UnitType.SAMLauncher && unit.isCooldown()) {
|
||||
icon = this.unitIcons.get("reloadingSam");
|
||||
} else {
|
||||
icon = this.unitIcons.get(iconType);
|
||||
}
|
||||
|
||||
if (unitType == UnitType.MissileSilo && unit.isCooldown()) {
|
||||
if (unitType === UnitType.MissileSilo && unit.isCooldown()) {
|
||||
icon = this.unitIcons.get("reloadingSilo");
|
||||
} else {
|
||||
icon = this.unitIcons.get(iconType);
|
||||
@@ -248,15 +253,15 @@ export class StructureLayer implements Layer {
|
||||
if (!unit.isActive()) return;
|
||||
|
||||
let borderColor = this.theme.borderColor(unit.owner());
|
||||
if (unitType == UnitType.SAMLauncher && unit.isCooldown()) {
|
||||
if (unitType === UnitType.SAMLauncher && unit.isCooldown()) {
|
||||
borderColor = reloadingColor;
|
||||
} else if (unit.type() == UnitType.Construction) {
|
||||
} else if (unit.type() === UnitType.Construction) {
|
||||
borderColor = underConstructionColor;
|
||||
}
|
||||
|
||||
if (unitType == UnitType.MissileSilo && unit.isCooldown()) {
|
||||
if (unitType === UnitType.MissileSilo && unit.isCooldown()) {
|
||||
borderColor = reloadingColor;
|
||||
} else if (unit.type() == UnitType.Construction) {
|
||||
} else if (unit.type() === UnitType.Construction) {
|
||||
borderColor = underConstructionColor;
|
||||
}
|
||||
|
||||
@@ -277,7 +282,7 @@ export class StructureLayer implements Layer {
|
||||
unit: UnitView,
|
||||
) {
|
||||
let color = this.theme.borderColor(unit.owner());
|
||||
if (unit.type() == UnitType.Construction) {
|
||||
if (unit.type() === UnitType.Construction) {
|
||||
color = underConstructionColor;
|
||||
}
|
||||
for (let y = 0; y < height; y++) {
|
||||
|
||||
@@ -30,7 +30,7 @@ export class TeamStats extends LitElement implements Layer {
|
||||
init() {}
|
||||
|
||||
tick() {
|
||||
if (this.game.config().gameConfig().gameMode != GameMode.Team) {
|
||||
if (this.game.config().gameConfig().gameMode !== GameMode.Team) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ export class TeamStats extends LitElement implements Layer {
|
||||
const grouped: Record<number, PlayerView[]> = {};
|
||||
for (const player of players) {
|
||||
const team = player.team();
|
||||
if (team === null) continue;
|
||||
if (!grouped[team]) grouped[team] = [];
|
||||
grouped[team].push(player);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,9 @@ export class TerrainLayer implements Layer {
|
||||
|
||||
redraw(): void {
|
||||
this.canvas = document.createElement("canvas");
|
||||
this.context = this.canvas.getContext("2d");
|
||||
const context = this.canvas.getContext("2d");
|
||||
if (context === null) throw new Error("2d context not supported");
|
||||
this.context = context;
|
||||
|
||||
this.imageData = this.context.getImageData(
|
||||
0,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Theme } from "../../../core/configuration/Config";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { Cell, PlayerType, UnitType } from "../../../core/game/Game";
|
||||
import { euclDistFN, TileRef } from "../../../core/game/GameMap";
|
||||
import { GameUpdateType, UnitUpdate } from "../../../core/game/GameUpdates";
|
||||
import { GameUpdateType } from "../../../core/game/GameUpdates";
|
||||
import { GameView, PlayerView } from "../../../core/game/GameView";
|
||||
import { PseudoRandom } from "../../../core/PseudoRandom";
|
||||
import { AlternateViewEvent, DragEvent } from "../../InputHandler";
|
||||
@@ -22,7 +22,7 @@ export class TerritoryLayer implements Layer {
|
||||
return a.lastUpdate - b.lastUpdate;
|
||||
});
|
||||
private random = new PseudoRandom(123);
|
||||
private theme: Theme = null;
|
||||
private theme: Theme;
|
||||
|
||||
// Used for spawn highlighting
|
||||
private highlightCanvas: HTMLCanvasElement;
|
||||
@@ -58,17 +58,18 @@ export class TerritoryLayer implements Layer {
|
||||
|
||||
tick() {
|
||||
this.game.recentlyUpdatedTiles().forEach((t) => this.enqueueTile(t));
|
||||
this.game.updatesSinceLastTick()[GameUpdateType.Unit].forEach((u) => {
|
||||
const update = u as UnitUpdate;
|
||||
if (update.unitType == UnitType.DefensePost) {
|
||||
const updates = this.game.updatesSinceLastTick();
|
||||
const unitUpdates = updates !== null ? updates[GameUpdateType.Unit] : [];
|
||||
unitUpdates.forEach((update) => {
|
||||
if (update.unitType === UnitType.DefensePost) {
|
||||
const tile = update.pos;
|
||||
this.game
|
||||
.bfs(tile, euclDistFN(tile, this.game.config().defensePostRange()))
|
||||
.forEach((t) => {
|
||||
if (
|
||||
this.game.isBorder(t) &&
|
||||
(this.game.ownerID(t) == update.ownerID ||
|
||||
this.game.ownerID(t) == update.lastOwnerID)
|
||||
(this.game.ownerID(t) === update.ownerID ||
|
||||
this.game.ownerID(t) === update.lastOwnerID)
|
||||
) {
|
||||
this.enqueueTile(t);
|
||||
}
|
||||
@@ -90,7 +91,7 @@ export class TerritoryLayer implements Layer {
|
||||
if (!this.game.inSpawnPhase()) {
|
||||
return;
|
||||
}
|
||||
if (this.game.ticks() % 5 == 0) {
|
||||
if (this.game.ticks() % 5 === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -102,7 +103,7 @@ export class TerritoryLayer implements Layer {
|
||||
);
|
||||
const humans = this.game
|
||||
.playerViews()
|
||||
.filter((p) => p.type() == PlayerType.Human);
|
||||
.filter((p) => p.type() === PlayerType.Human);
|
||||
|
||||
for (const human of humans) {
|
||||
const center = human.nameLocation();
|
||||
@@ -114,10 +115,11 @@ export class TerritoryLayer implements Layer {
|
||||
continue;
|
||||
}
|
||||
let color = this.theme.spawnHighlightColor();
|
||||
const myPlayer = this.game.myPlayer();
|
||||
if (
|
||||
this.game.myPlayer() != null &&
|
||||
this.game.myPlayer() != human &&
|
||||
this.game.myPlayer().isFriendly(human)
|
||||
myPlayer !== null &&
|
||||
myPlayer !== human &&
|
||||
myPlayer.isFriendly(human)
|
||||
) {
|
||||
color = this.theme.selfColor();
|
||||
}
|
||||
@@ -150,7 +152,9 @@ export class TerritoryLayer implements Layer {
|
||||
redraw() {
|
||||
console.log("redrew territory layer");
|
||||
this.canvas = document.createElement("canvas");
|
||||
this.context = this.canvas.getContext("2d");
|
||||
const context = this.canvas.getContext("2d");
|
||||
if (context === null) throw new Error("2d context not supported");
|
||||
this.context = context;
|
||||
|
||||
this.imageData = this.context.getImageData(
|
||||
0,
|
||||
@@ -165,9 +169,11 @@ export class TerritoryLayer implements Layer {
|
||||
|
||||
// Add a second canvas for highlights
|
||||
this.highlightCanvas = document.createElement("canvas");
|
||||
this.highlightContext = this.highlightCanvas.getContext("2d", {
|
||||
const highlightContext = this.highlightCanvas.getContext("2d", {
|
||||
alpha: true,
|
||||
});
|
||||
if (highlightContext === null) throw new Error("2d context not supported");
|
||||
this.highlightContext = highlightContext;
|
||||
this.highlightCanvas.width = this.game.width();
|
||||
this.highlightCanvas.height = this.game.height();
|
||||
|
||||
@@ -219,7 +225,7 @@ export class TerritoryLayer implements Layer {
|
||||
|
||||
renderTerritory() {
|
||||
let numToRender = Math.floor(this.tileToRenderQueue.size() / 10);
|
||||
if (numToRender == 0 || this.game.inSpawnPhase()) {
|
||||
if (numToRender === 0 || this.game.inSpawnPhase()) {
|
||||
numToRender = this.tileToRenderQueue.size();
|
||||
}
|
||||
|
||||
@@ -252,7 +258,7 @@ export class TerritoryLayer implements Layer {
|
||||
}
|
||||
const owner = this.game.owner(tile) as PlayerView;
|
||||
if (this.game.isBorder(tile)) {
|
||||
const playerIsFocused = owner && this.game.focusedPlayer() == owner;
|
||||
const playerIsFocused = owner && this.game.focusedPlayer() === owner;
|
||||
if (
|
||||
this.game.hasUnitNearby(
|
||||
tile,
|
||||
@@ -265,7 +271,7 @@ export class TerritoryLayer implements Layer {
|
||||
const x = this.game.x(tile);
|
||||
const y = this.game.y(tile);
|
||||
const lightTile =
|
||||
(x % 2 == 0 && y % 2 == 0) || (y % 2 == 1 && x % 2 == 1);
|
||||
(x % 2 === 0 && y % 2 === 0) || (y % 2 === 1 && x % 2 === 1);
|
||||
const borderColor = lightTile ? borderColors.light : borderColors.dark;
|
||||
this.paintCell(x, y, borderColor, 255);
|
||||
} else {
|
||||
|
||||
@@ -23,18 +23,21 @@ export class TopBar extends LitElement implements Layer {
|
||||
}
|
||||
|
||||
tick() {
|
||||
if (this.game?.myPlayer() !== null) {
|
||||
const popIncreaseRate =
|
||||
this.game.myPlayer().population() - this._population;
|
||||
if (this.game.ticks() % 5 == 0) {
|
||||
this._popRateIsIncreasing =
|
||||
popIncreaseRate >= this._lastPopulationIncreaseRate;
|
||||
this._lastPopulationIncreaseRate = popIncreaseRate;
|
||||
}
|
||||
}
|
||||
this.updatePopulationIncrease();
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private updatePopulationIncrease() {
|
||||
const player = this.game?.myPlayer();
|
||||
if (player === null) return;
|
||||
const popIncreaseRate = player.population() - this._population;
|
||||
if (this.game.ticks() % 5 === 0) {
|
||||
this._popRateIsIncreasing =
|
||||
popIncreaseRate >= this._lastPopulationIncreaseRate;
|
||||
this._lastPopulationIncreaseRate = popIncreaseRate;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.isVisible) {
|
||||
return html``;
|
||||
|
||||
@@ -14,9 +14,9 @@ import { Layer } from "./Layer";
|
||||
*/
|
||||
export class UILayer implements Layer {
|
||||
private canvas: HTMLCanvasElement;
|
||||
private context: CanvasRenderingContext2D;
|
||||
private context: CanvasRenderingContext2D | null;
|
||||
|
||||
private theme: Theme = null;
|
||||
private theme: Theme | null = null;
|
||||
private selectionAnimTime = 0;
|
||||
|
||||
// Keep track of currently selected unit
|
||||
@@ -136,6 +136,7 @@ export class UILayer implements Layer {
|
||||
baseOpacity + Math.sin(this.selectionAnimTime * 0.1) * pulseAmount;
|
||||
|
||||
// Get the unit's owner color for the box
|
||||
if (this.theme === null) throw new Error("missing theme");
|
||||
const ownerColor = this.theme.territoryColor(unit.owner());
|
||||
|
||||
// Create a brighter version of the owner color for the selection
|
||||
@@ -196,12 +197,14 @@ export class UILayer implements Layer {
|
||||
}
|
||||
|
||||
paintCell(x: number, y: number, color: Colord, alpha: number) {
|
||||
if (this.context === null) throw new Error("null context");
|
||||
this.clearCell(x, y);
|
||||
this.context.fillStyle = color.alpha(alpha / 255).toRgbString();
|
||||
this.context.fillRect(x, y, 1, 1);
|
||||
}
|
||||
|
||||
clearCell(x: number, y: number) {
|
||||
if (this.context === null) throw new Error("null context");
|
||||
this.context.clearRect(x, y, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export class UnitLayer implements Layer {
|
||||
|
||||
private unitToTrail = new Map<UnitView, TileRef[]>();
|
||||
|
||||
private theme: Theme = null;
|
||||
private theme: Theme;
|
||||
|
||||
private alternateView = false;
|
||||
|
||||
@@ -67,7 +67,7 @@ export class UnitLayer implements Layer {
|
||||
}
|
||||
|
||||
tick() {
|
||||
if (this.myPlayer == null) {
|
||||
if (this.myPlayer === null) {
|
||||
this.myPlayer = this.game.playerByClientID(this.clientID);
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ export class UnitLayer implements Layer {
|
||||
const clickRef = this.game.ref(cell.x, cell.y);
|
||||
|
||||
// Make sure we have the current player
|
||||
if (this.myPlayer == null) {
|
||||
if (this.myPlayer === null) {
|
||||
this.myPlayer = this.game.playerByClientID(this.clientID);
|
||||
}
|
||||
|
||||
@@ -189,9 +189,13 @@ export class UnitLayer implements Layer {
|
||||
|
||||
redraw() {
|
||||
this.canvas = document.createElement("canvas");
|
||||
this.context = this.canvas.getContext("2d");
|
||||
const context = this.canvas.getContext("2d");
|
||||
if (context === null) throw new Error("2d context not supported");
|
||||
this.context = context;
|
||||
this.transportShipTrailCanvas = document.createElement("canvas");
|
||||
this.unitTrailContext = this.transportShipTrailCanvas.getContext("2d");
|
||||
const trailContext = this.transportShipTrailCanvas.getContext("2d");
|
||||
if (trailContext === null) throw new Error("2d context not supported");
|
||||
this.unitTrailContext = trailContext;
|
||||
|
||||
this.canvas.width = this.game.width();
|
||||
this.canvas.height = this.game.height();
|
||||
@@ -217,15 +221,13 @@ export class UnitLayer implements Layer {
|
||||
private updateUnitsSprites() {
|
||||
const unitsToUpdate = this.game
|
||||
.updatesSinceLastTick()
|
||||
?.[GameUpdateType.Unit]?.map((unit) => this.game.unit(unit.id));
|
||||
unitsToUpdate
|
||||
?.filter((UnitView) => isSpriteReady(UnitView.type()))
|
||||
.forEach((unitView) => {
|
||||
this.clearUnitCells(unitView);
|
||||
?.[GameUpdateType.Unit]?.map((unit) => this.game.unit(unit.id))
|
||||
?.forEach((unitView) => {
|
||||
if (unitView === undefined) return;
|
||||
const ready = isSpriteReady(unitView.type());
|
||||
if (ready) this.clearUnitCells(unitView);
|
||||
this.onUnitEvent(unitView);
|
||||
});
|
||||
unitsToUpdate?.forEach((unitView) => {
|
||||
this.onUnitEvent(unitView);
|
||||
});
|
||||
}
|
||||
|
||||
private clearUnitCells(unit: UnitView) {
|
||||
@@ -243,10 +245,10 @@ export class UnitLayer implements Layer {
|
||||
}
|
||||
|
||||
private relationship(unit: UnitView): Relationship {
|
||||
if (this.myPlayer == null) {
|
||||
if (this.myPlayer === null) {
|
||||
return Relationship.Enemy;
|
||||
}
|
||||
if (this.myPlayer == unit.owner()) {
|
||||
if (this.myPlayer === unit.owner()) {
|
||||
return Relationship.Self;
|
||||
}
|
||||
if (this.myPlayer.isFriendly(unit.owner())) {
|
||||
@@ -301,8 +303,8 @@ export class UnitLayer implements Layer {
|
||||
|
||||
// Clear current and previous positions
|
||||
this.clearCell(this.game.x(unit.lastTile()), this.game.y(unit.lastTile()));
|
||||
if (this.oldShellTile.has(unit)) {
|
||||
const oldTile = this.oldShellTile.get(unit);
|
||||
const oldTile = this.oldShellTile.get(unit);
|
||||
if (oldTile !== undefined) {
|
||||
this.clearCell(this.game.x(oldTile), this.game.y(oldTile));
|
||||
}
|
||||
|
||||
@@ -348,7 +350,7 @@ export class UnitLayer implements Layer {
|
||||
}
|
||||
|
||||
private clearTrail(unit: UnitView) {
|
||||
const trail = this.unitToTrail.get(unit);
|
||||
const trail = this.unitToTrail.get(unit) ?? [];
|
||||
const rel = this.relationship(unit);
|
||||
for (const t of trail) {
|
||||
this.clearCell(this.game.x(t), this.game.y(t), this.unitTrailContext);
|
||||
@@ -381,7 +383,7 @@ export class UnitLayer implements Layer {
|
||||
}
|
||||
|
||||
let newTrailSize = 1;
|
||||
const trail = this.unitToTrail.get(unit);
|
||||
const trail = this.unitToTrail.get(unit) ?? [];
|
||||
// It can move faster than 1 pixel, draw a line for the trail or else it will be dotted
|
||||
if (trail.length >= 1) {
|
||||
const cur = {
|
||||
@@ -441,7 +443,7 @@ export class UnitLayer implements Layer {
|
||||
if (!this.unitToTrail.has(unit)) {
|
||||
this.unitToTrail.set(unit, []);
|
||||
}
|
||||
const trail = this.unitToTrail.get(unit);
|
||||
const trail = this.unitToTrail.get(unit) ?? [];
|
||||
trail.push(unit.lastTile());
|
||||
|
||||
// Paint trail
|
||||
@@ -496,15 +498,16 @@ export class UnitLayer implements Layer {
|
||||
const x = this.game.x(unit.tile());
|
||||
const y = this.game.y(unit.tile());
|
||||
|
||||
let alternateViewColor = null;
|
||||
let alternateViewColor: Colord | null = null;
|
||||
|
||||
if (this.alternateView) {
|
||||
let rel = this.relationship(unit);
|
||||
if (unit.type() == UnitType.TradeShip && unit.dstPortId() != null) {
|
||||
const target = this.game.unit(unit.dstPortId())?.owner();
|
||||
const dstPortId = unit.dstPortId();
|
||||
if (unit.type() === UnitType.TradeShip && dstPortId !== undefined) {
|
||||
const target = this.game.unit(dstPortId)?.owner();
|
||||
const myPlayer = this.game.myPlayer();
|
||||
if (myPlayer != null && target != null) {
|
||||
if (myPlayer == target) {
|
||||
if (myPlayer !== null && target !== undefined) {
|
||||
if (myPlayer === target) {
|
||||
rel = Relationship.Self;
|
||||
} else if (myPlayer.isFriendly(target)) {
|
||||
rel = Relationship.Ally;
|
||||
@@ -528,7 +531,7 @@ export class UnitLayer implements Layer {
|
||||
unit,
|
||||
this.theme,
|
||||
alternateViewColor ?? customTerritoryColor,
|
||||
alternateViewColor,
|
||||
alternateViewColor ?? undefined,
|
||||
);
|
||||
|
||||
if (unit.isActive()) {
|
||||
|
||||
@@ -182,12 +182,14 @@ export class WinModal extends LitElement implements Layer {
|
||||
this._title = translateText("win_modal.died");
|
||||
this.show();
|
||||
}
|
||||
this.game.updatesSinceLastTick()[GameUpdateType.Win].forEach((wu) => {
|
||||
const updates = this.game.updatesSinceLastTick();
|
||||
const winUpdates = updates !== null ? updates[GameUpdateType.Win] : [];
|
||||
winUpdates.forEach((wu) => {
|
||||
if (wu.winnerType === "team") {
|
||||
this.eventBus.emit(
|
||||
new SendWinnerEvent(wu.winner as Team, wu.allPlayersStats, "team"),
|
||||
);
|
||||
if (wu.winner == this.game.myPlayer()?.team()) {
|
||||
if (wu.winner === this.game.myPlayer()?.team()) {
|
||||
this._title = translateText("win_modal.your_team");
|
||||
} else {
|
||||
this._title = translateText("win_modal.other_team", {
|
||||
@@ -199,10 +201,13 @@ export class WinModal extends LitElement implements Layer {
|
||||
const winner = this.game.playerBySmallID(
|
||||
wu.winner as number,
|
||||
) as PlayerView;
|
||||
this.eventBus.emit(
|
||||
new SendWinnerEvent(winner.clientID(), wu.allPlayersStats, "player"),
|
||||
);
|
||||
if (winner == this.game.myPlayer()) {
|
||||
const winnerClient = winner.clientID();
|
||||
if (winnerClient !== null) {
|
||||
this.eventBus.emit(
|
||||
new SendWinnerEvent(winnerClient, wu.allPlayersStats, "player"),
|
||||
);
|
||||
}
|
||||
if (winner === this.game.myPlayer()) {
|
||||
this._title = translateText("win_modal.you_won");
|
||||
} else {
|
||||
this._title = translateText("win_modal.you_won", {
|
||||
|
||||
Reference in New Issue
Block a user