mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-13 20:19:59 +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:
@@ -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