mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-20 02:40:00 +00:00
Merge branch 'main' into ui/modal-overlay-improvements
This commit is contained in:
@@ -48,6 +48,7 @@ import { GameView, PlayerView } from "../core/game/GameView";
|
||||
import { GameUpdateViewData } from "../core/game/GameUpdates";
|
||||
|
||||
export interface LobbyConfig {
|
||||
flag: () => string;
|
||||
playerName: () => string;
|
||||
clientID: ClientID;
|
||||
playerID: PlayerID;
|
||||
@@ -56,6 +57,9 @@ export interface LobbyConfig {
|
||||
gameID: GameID;
|
||||
map: GameMapType | null;
|
||||
difficulty: Difficulty | null;
|
||||
disableBots: boolean | null;
|
||||
disableNPCs: boolean | null;
|
||||
creativeMode: boolean | null;
|
||||
}
|
||||
|
||||
export function joinLobby(
|
||||
@@ -77,6 +81,9 @@ export function joinLobby(
|
||||
gameType: GameType.Singleplayer,
|
||||
gameMap: lobbyConfig.map,
|
||||
difficulty: lobbyConfig.difficulty,
|
||||
disableBots: lobbyConfig.disableBots,
|
||||
disableNPCs: lobbyConfig.disableNPCs,
|
||||
creativeMode: lobbyConfig.creativeMode,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -475,6 +475,24 @@ export class HostLobbyModal extends LitElement {
|
||||
this.putGameConfig();
|
||||
}
|
||||
|
||||
private async handleDisableBotsChange(e: Event) {
|
||||
this.disableBots = Boolean((e.target as HTMLInputElement).checked);
|
||||
consolex.log(`updating disable bots to ${this.disableBots}`);
|
||||
this.putGameConfig();
|
||||
}
|
||||
|
||||
private async handleDisableNPCsChange(e: Event) {
|
||||
this.disableNPCs = Boolean((e.target as HTMLInputElement).checked);
|
||||
consolex.log(`updating disable npcs to ${this.disableNPCs}`);
|
||||
this.putGameConfig();
|
||||
}
|
||||
|
||||
private async handleCreativeModeChange(e: Event) {
|
||||
this.creativeMode = Boolean((e.target as HTMLInputElement).checked);
|
||||
consolex.log(`updating creative mode to ${this.creativeMode}`);
|
||||
this.putGameConfig();
|
||||
}
|
||||
|
||||
private async putGameConfig() {
|
||||
const response = await fetch(`/private_lobby/${this.lobbyId}`, {
|
||||
method: "PUT",
|
||||
|
||||
+92
-32
@@ -2,46 +2,31 @@ import { EventBus, GameEvent } from "../core/EventBus";
|
||||
import { Game } from "../core/game/Game";
|
||||
|
||||
export class MouseUpEvent implements GameEvent {
|
||||
constructor(
|
||||
public readonly x: number,
|
||||
public readonly y: number,
|
||||
) {}
|
||||
constructor(public readonly x: number, public readonly y: number) {}
|
||||
}
|
||||
|
||||
export class MouseDownEvent implements GameEvent {
|
||||
constructor(
|
||||
public readonly x: number,
|
||||
public readonly y: number,
|
||||
) {}
|
||||
constructor(public readonly x: number, public readonly y: number) {}
|
||||
}
|
||||
|
||||
export class MouseMoveEvent implements GameEvent {
|
||||
constructor(
|
||||
public readonly x: number,
|
||||
public readonly y: number,
|
||||
) {}
|
||||
constructor(public readonly x: number, public readonly y: number) {}
|
||||
}
|
||||
|
||||
export class ContextMenuEvent implements GameEvent {
|
||||
constructor(
|
||||
public readonly x: number,
|
||||
public readonly y: number,
|
||||
) {}
|
||||
constructor(public readonly x: number, public readonly y: number) {}
|
||||
}
|
||||
|
||||
export class ZoomEvent implements GameEvent {
|
||||
constructor(
|
||||
public readonly x: number,
|
||||
public readonly y: number,
|
||||
public readonly delta: number,
|
||||
public readonly delta: number
|
||||
) {}
|
||||
}
|
||||
|
||||
export class DragEvent implements GameEvent {
|
||||
constructor(
|
||||
public readonly deltaX: number,
|
||||
public readonly deltaY: number,
|
||||
) {}
|
||||
constructor(public readonly deltaX: number, public readonly deltaY: number) {}
|
||||
}
|
||||
|
||||
export class AlternateViewEvent implements GameEvent {
|
||||
@@ -51,10 +36,7 @@ export class AlternateViewEvent implements GameEvent {
|
||||
export class RefreshGraphicsEvent implements GameEvent {}
|
||||
|
||||
export class ShowBuildMenuEvent implements GameEvent {
|
||||
constructor(
|
||||
public readonly x: number,
|
||||
public readonly y: number,
|
||||
) {}
|
||||
constructor(public readonly x: number, public readonly y: number) {}
|
||||
}
|
||||
|
||||
export class InputHandler {
|
||||
@@ -72,10 +54,13 @@ export class InputHandler {
|
||||
|
||||
private alternateView = false;
|
||||
|
||||
constructor(
|
||||
private canvas: HTMLCanvasElement,
|
||||
private eventBus: EventBus,
|
||||
) {}
|
||||
private moveInterval: any = null;
|
||||
private activeKeys = new Set<string>();
|
||||
|
||||
private readonly PAN_SPEED = 5;
|
||||
private readonly ZOOM_SPEED = 10;
|
||||
|
||||
constructor(private canvas: HTMLCanvasElement, private eventBus: EventBus) {}
|
||||
|
||||
initialize() {
|
||||
this.canvas.addEventListener("pointerdown", (e) => this.onPointerDown(e));
|
||||
@@ -95,14 +80,67 @@ export class InputHandler {
|
||||
});
|
||||
this.pointers.clear();
|
||||
|
||||
// Initialize the combined movement interval
|
||||
this.moveInterval = setInterval(() => {
|
||||
let deltaX = 0;
|
||||
let deltaY = 0;
|
||||
|
||||
// Handle both WASD and arrow keys
|
||||
if (this.activeKeys.has("KeyW") || this.activeKeys.has("ArrowUp"))
|
||||
deltaY += this.PAN_SPEED;
|
||||
if (this.activeKeys.has("KeyS") || this.activeKeys.has("ArrowDown"))
|
||||
deltaY -= this.PAN_SPEED;
|
||||
if (this.activeKeys.has("KeyA") || this.activeKeys.has("ArrowLeft"))
|
||||
deltaX += this.PAN_SPEED;
|
||||
if (this.activeKeys.has("KeyD") || this.activeKeys.has("ArrowRight"))
|
||||
deltaX -= this.PAN_SPEED;
|
||||
|
||||
if (deltaX !== 0 || deltaY !== 0) {
|
||||
this.eventBus.emit(new DragEvent(deltaX, deltaY));
|
||||
}
|
||||
|
||||
// Handle zooming
|
||||
const screenCenterX = window.innerWidth / 2;
|
||||
const screenCenterY = window.innerHeight / 2;
|
||||
|
||||
if (this.activeKeys.has("Minus")) {
|
||||
this.eventBus.emit(
|
||||
new ZoomEvent(screenCenterX, screenCenterY, this.ZOOM_SPEED)
|
||||
);
|
||||
}
|
||||
if (this.activeKeys.has("Equal")) {
|
||||
this.eventBus.emit(
|
||||
new ZoomEvent(screenCenterX, screenCenterY, -this.ZOOM_SPEED)
|
||||
);
|
||||
}
|
||||
}, 1);
|
||||
|
||||
window.addEventListener("keydown", (e) => {
|
||||
if (e.code === "Space") {
|
||||
e.preventDefault(); // Prevent page scrolling
|
||||
e.preventDefault();
|
||||
if (!this.alternateView) {
|
||||
this.alternateView = true;
|
||||
this.eventBus.emit(new AlternateViewEvent(true));
|
||||
}
|
||||
}
|
||||
|
||||
// Add all movement keys to activeKeys
|
||||
if (
|
||||
[
|
||||
"KeyW",
|
||||
"KeyA",
|
||||
"KeyS",
|
||||
"KeyD",
|
||||
"ArrowUp",
|
||||
"ArrowLeft",
|
||||
"ArrowDown",
|
||||
"ArrowRight",
|
||||
"Minus",
|
||||
"Equal",
|
||||
].includes(e.code)
|
||||
) {
|
||||
this.activeKeys.add(e.code);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("keyup", (e) => {
|
||||
@@ -115,6 +153,24 @@ export class InputHandler {
|
||||
e.preventDefault();
|
||||
this.eventBus.emit(new RefreshGraphicsEvent());
|
||||
}
|
||||
|
||||
// Remove all movement keys from activeKeys
|
||||
if (
|
||||
[
|
||||
"KeyW",
|
||||
"KeyA",
|
||||
"KeyS",
|
||||
"KeyD",
|
||||
"ArrowUp",
|
||||
"ArrowLeft",
|
||||
"ArrowDown",
|
||||
"ArrowRight",
|
||||
"Minus",
|
||||
"Equal",
|
||||
].includes(e.code)
|
||||
) {
|
||||
this.activeKeys.delete(e.code);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -193,10 +249,9 @@ export class InputHandler {
|
||||
const pinchDelta = currentPinchDistance - this.lastPinchDistance;
|
||||
|
||||
if (Math.abs(pinchDelta) > 1) {
|
||||
// Threshold to avoid tiny zoom adjustments
|
||||
const zoomCenter = this.getPinchCenter();
|
||||
this.eventBus.emit(
|
||||
new ZoomEvent(zoomCenter.x, zoomCenter.y, -pinchDelta * 2),
|
||||
new ZoomEvent(zoomCenter.x, zoomCenter.y, -pinchDelta * 2)
|
||||
);
|
||||
this.lastPinchDistance = currentPinchDistance;
|
||||
}
|
||||
@@ -222,4 +277,9 @@ export class InputHandler {
|
||||
y: (pointerEvents[0].clientY + pointerEvents[1].clientY) / 2,
|
||||
};
|
||||
}
|
||||
|
||||
destroy() {
|
||||
clearInterval(this.moveInterval);
|
||||
this.activeKeys.clear();
|
||||
}
|
||||
}
|
||||
|
||||
+150
-127
@@ -1,156 +1,179 @@
|
||||
import { ClientGameRunner, joinLobby } from "./ClientGameRunner";
|
||||
import backgroundImage from "../../resources/images/EuropeBackground.svg";
|
||||
import favicon from "../../resources/images/Favicon.svg";
|
||||
import { ClientGameRunner, joinLobby } from './ClientGameRunner';
|
||||
import backgroundImage from '../../resources/images/EuropeBackground.svg';
|
||||
import favicon from '../../resources/images/Favicon.svg';
|
||||
|
||||
import "./PublicLobby";
|
||||
import "./UsernameInput";
|
||||
import "./styles.css";
|
||||
import { UsernameInput } from "./UsernameInput";
|
||||
import { SinglePlayerModal } from "./SinglePlayerModal";
|
||||
import { HostLobbyModal as HostPrivateLobbyModal } from "./HostLobbyModal";
|
||||
import { JoinPrivateLobbyModal } from "./JoinPrivateLobbyModal";
|
||||
import { generateID } from "../core/Util";
|
||||
import { generateCryptoRandomUUID } from "./Utils";
|
||||
import { consolex } from "../core/Consolex";
|
||||
import './PublicLobby';
|
||||
import './UsernameInput';
|
||||
import './styles.css';
|
||||
import { UsernameInput } from './UsernameInput';
|
||||
import { SinglePlayerModal } from './SinglePlayerModal';
|
||||
import { HostLobbyModal as HostPrivateLobbyModal } from './HostLobbyModal';
|
||||
import { JoinPrivateLobbyModal } from './JoinPrivateLobbyModal';
|
||||
import { generateID } from '../core/Util';
|
||||
import { generateCryptoRandomUUID } from './Utils';
|
||||
import { consolex } from '../core/Consolex';
|
||||
import './components/FlagInput';
|
||||
import { FlagInput } from './components/FlagInput';
|
||||
|
||||
class Client {
|
||||
private gameStop: () => void;
|
||||
private gameStop: () => void;
|
||||
|
||||
private usernameInput: UsernameInput | null = null;
|
||||
private usernameInput: UsernameInput | null = null;
|
||||
private flagInput: FlagInput | null = null;
|
||||
|
||||
private joinModal: JoinPrivateLobbyModal;
|
||||
constructor() {}
|
||||
private joinModal: JoinPrivateLobbyModal;
|
||||
constructor() {}
|
||||
|
||||
initialize(): void {
|
||||
this.usernameInput = document.querySelector(
|
||||
"username-input"
|
||||
) as UsernameInput;
|
||||
if (!this.usernameInput) {
|
||||
consolex.warn("Username input element not found");
|
||||
}
|
||||
window.addEventListener("beforeunload", (event) => {
|
||||
consolex.log("Browser is closing");
|
||||
if (this.gameStop != null) {
|
||||
this.gameStop();
|
||||
}
|
||||
});
|
||||
initialize(): void {
|
||||
this.flagInput = document.querySelector('flag-input') as FlagInput;
|
||||
if (!this.flagInput) {
|
||||
consolex.warn('Flag input element not found');
|
||||
}
|
||||
|
||||
setFavicon();
|
||||
document.addEventListener("join-lobby", this.handleJoinLobby.bind(this));
|
||||
document.addEventListener("leave-lobby", this.handleLeaveLobby.bind(this));
|
||||
document.addEventListener(
|
||||
"single-player",
|
||||
this.handleSinglePlayer.bind(this)
|
||||
);
|
||||
this.usernameInput = document.querySelector(
|
||||
'username-input'
|
||||
) as UsernameInput;
|
||||
if (!this.usernameInput) {
|
||||
consolex.warn('Username input element not found');
|
||||
}
|
||||
window.addEventListener('beforeunload', (event) => {
|
||||
consolex.log('Browser is closing');
|
||||
if (this.gameStop != null) {
|
||||
this.gameStop();
|
||||
}
|
||||
});
|
||||
|
||||
const spModal = document.querySelector(
|
||||
"single-player-modal"
|
||||
) as SinglePlayerModal;
|
||||
spModal instanceof SinglePlayerModal;
|
||||
document.getElementById("single-player").addEventListener("click", () => {
|
||||
if (this.usernameInput.isValid()) {
|
||||
spModal.open();
|
||||
}
|
||||
});
|
||||
setFavicon();
|
||||
document.addEventListener(
|
||||
'join-lobby',
|
||||
this.handleJoinLobby.bind(this)
|
||||
);
|
||||
document.addEventListener(
|
||||
'leave-lobby',
|
||||
this.handleLeaveLobby.bind(this)
|
||||
);
|
||||
document.addEventListener(
|
||||
'single-player',
|
||||
this.handleSinglePlayer.bind(this)
|
||||
);
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
const spModal = document.querySelector(
|
||||
'single-player-modal'
|
||||
) as SinglePlayerModal;
|
||||
spModal instanceof SinglePlayerModal;
|
||||
document
|
||||
.getElementById('single-player')
|
||||
.addEventListener('click', () => {
|
||||
if (this.usernameInput.isValid()) {
|
||||
spModal.open();
|
||||
}
|
||||
});
|
||||
|
||||
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 hostModal = document.querySelector(
|
||||
'host-lobby-modal'
|
||||
) as HostPrivateLobbyModal;
|
||||
hostModal instanceof HostPrivateLobbyModal;
|
||||
document
|
||||
.getElementById('host-lobby-button')
|
||||
.addEventListener('click', () => {
|
||||
if (this.usernameInput.isValid()) {
|
||||
hostModal.open();
|
||||
}
|
||||
});
|
||||
|
||||
private async handleJoinLobby(event: CustomEvent) {
|
||||
const lobby = event.detail.lobby;
|
||||
consolex.log(`joining lobby ${lobby.id}`);
|
||||
if (this.gameStop != null) {
|
||||
consolex.log("joining lobby, stopping existing game");
|
||||
this.gameStop();
|
||||
}
|
||||
this.gameStop = joinLobby(
|
||||
{
|
||||
gameType: event.detail.gameType,
|
||||
playerName: (): string => this.usernameInput.getCurrentUsername(),
|
||||
gameID: lobby.id,
|
||||
persistentID: getPersistentIDFromCookie(),
|
||||
playerID: generateID(),
|
||||
clientID: generateID(),
|
||||
map: event.detail.map,
|
||||
difficulty: event.detail.difficulty,
|
||||
},
|
||||
() => this.joinModal.close()
|
||||
);
|
||||
}
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async handleLeaveLobby(event: CustomEvent) {
|
||||
if (this.gameStop == null) {
|
||||
return;
|
||||
}
|
||||
consolex.log("leaving lobby, cancelling game");
|
||||
this.gameStop();
|
||||
this.gameStop = null;
|
||||
}
|
||||
private async handleJoinLobby(event: CustomEvent) {
|
||||
const lobby = event.detail.lobby;
|
||||
consolex.log(`joining lobby ${lobby.id}`);
|
||||
if (this.gameStop != null) {
|
||||
consolex.log('joining lobby, stopping existing game');
|
||||
this.gameStop();
|
||||
}
|
||||
this.gameStop = joinLobby(
|
||||
{
|
||||
gameType: event.detail.gameType,
|
||||
flag: (): string => this.flagInput.getCurrentFlag(),
|
||||
playerName: (): string =>
|
||||
this.usernameInput.getCurrentUsername(),
|
||||
gameID: lobby.id,
|
||||
persistentID: getPersistentIDFromCookie(),
|
||||
playerID: generateID(),
|
||||
clientID: generateID(),
|
||||
map: event.detail.map,
|
||||
difficulty: event.detail.difficulty,
|
||||
disableBots: event.detail.disableBots,
|
||||
disableNPCs: event.detail.disableNPCs,
|
||||
creativeMode: event.detail.creativeMode,
|
||||
},
|
||||
() => this.joinModal.close()
|
||||
);
|
||||
}
|
||||
|
||||
private async handleSinglePlayer(event: CustomEvent) {
|
||||
alert("coming soon");
|
||||
}
|
||||
private async handleLeaveLobby(event: CustomEvent) {
|
||||
if (this.gameStop == null) {
|
||||
return;
|
||||
}
|
||||
consolex.log('leaving lobby, cancelling game');
|
||||
this.gameStop();
|
||||
this.gameStop = null;
|
||||
}
|
||||
|
||||
private async handleSinglePlayer(event: CustomEvent) {
|
||||
alert('coming soon');
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the client when the DOM is loaded
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
new Client().initialize();
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new Client().initialize();
|
||||
});
|
||||
|
||||
document.body.style.backgroundImage = `url(${backgroundImage})`;
|
||||
|
||||
function setFavicon(): void {
|
||||
const link = document.createElement("link");
|
||||
link.type = "image/x-icon";
|
||||
link.rel = "shortcut icon";
|
||||
link.href = favicon;
|
||||
document.head.appendChild(link);
|
||||
const link = document.createElement('link');
|
||||
link.type = 'image/x-icon';
|
||||
link.rel = 'shortcut icon';
|
||||
link.href = favicon;
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
|
||||
// WARNING: DO NOT EXPOSE THIS ID
|
||||
export function getPersistentIDFromCookie(): string {
|
||||
const COOKIE_NAME = "player_persistent_id";
|
||||
const COOKIE_NAME = 'player_persistent_id';
|
||||
|
||||
// Try to get existing cookie
|
||||
const cookies = document.cookie.split(";");
|
||||
for (let cookie of cookies) {
|
||||
const [cookieName, cookieValue] = cookie.split("=").map((c) => c.trim());
|
||||
if (cookieName === COOKIE_NAME) {
|
||||
return cookieValue;
|
||||
}
|
||||
}
|
||||
// Try to get existing cookie
|
||||
const cookies = document.cookie.split(';');
|
||||
for (let cookie of cookies) {
|
||||
const [cookieName, cookieValue] = cookie
|
||||
.split('=')
|
||||
.map((c) => c.trim());
|
||||
if (cookieName === COOKIE_NAME) {
|
||||
return cookieValue;
|
||||
}
|
||||
}
|
||||
|
||||
// If no cookie exists, create new ID and set cookie
|
||||
const newID = generateCryptoRandomUUID();
|
||||
document.cookie = [
|
||||
`${COOKIE_NAME}=${newID}`,
|
||||
`max-age=${5 * 365 * 24 * 60 * 60}`, // 5 years
|
||||
"path=/",
|
||||
"SameSite=Strict",
|
||||
"Secure",
|
||||
].join(";");
|
||||
// If no cookie exists, create new ID and set cookie
|
||||
const newID = generateCryptoRandomUUID();
|
||||
document.cookie = [
|
||||
`${COOKIE_NAME}=${newID}`,
|
||||
`max-age=${5 * 365 * 24 * 60 * 60}`, // 5 years
|
||||
'path=/',
|
||||
'SameSite=Strict',
|
||||
'Secure',
|
||||
].join(';');
|
||||
|
||||
return newID;
|
||||
return newID;
|
||||
}
|
||||
|
||||
@@ -268,6 +268,31 @@ export class SinglePlayerModal extends LitElement {
|
||||
/>
|
||||
<label for="disable-npcs">Disable NPCs</label>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="disable-bots"
|
||||
@change=${this.handleDisableBotsChange}
|
||||
/>
|
||||
<label for="disable-bots">Disable Bots</label>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="disable-npcs"
|
||||
@change=${this.handleDisableNPCsChange}
|
||||
/>
|
||||
<label for="disable-npcs">Disable NPCs</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="creative-mode"
|
||||
@change=${this.handleCreativeModeChange}
|
||||
/>
|
||||
<label for="creative-mode">Creative mode</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input
|
||||
@@ -308,6 +333,15 @@ export class SinglePlayerModal extends LitElement {
|
||||
private handleCreativeModeChange(e: Event) {
|
||||
this.creativeMode = Boolean((e.target as HTMLInputElement).checked);
|
||||
}
|
||||
private handleDisableBotsChange(e: Event) {
|
||||
this.disableBots = Boolean((e.target as HTMLInputElement).checked);
|
||||
}
|
||||
private handleDisableNPCsChange(e: Event) {
|
||||
this.disableNPCs = Boolean((e.target as HTMLInputElement).checked);
|
||||
}
|
||||
private handleCreativeModeChange(e: Event) {
|
||||
this.creativeMode = Boolean((e.target as HTMLInputElement).checked);
|
||||
}
|
||||
private startGame() {
|
||||
consolex.log(
|
||||
`Starting single player game with map: ${GameMapType[this.selectedMap]}`
|
||||
|
||||
@@ -340,6 +340,7 @@ export class Transport {
|
||||
type: "spawn",
|
||||
clientID: this.lobbyConfig.clientID,
|
||||
playerID: this.lobbyConfig.playerID,
|
||||
flag: this.lobbyConfig.flag(),
|
||||
name: this.lobbyConfig.playerName(),
|
||||
playerType: PlayerType.Human,
|
||||
x: event.cell.x,
|
||||
|
||||
+3
-2
@@ -3,6 +3,7 @@ export function renderTroops(troops: number): string {
|
||||
}
|
||||
|
||||
export function renderNumber(num: number) {
|
||||
num = Math.max(num, 0);
|
||||
let numStr = "";
|
||||
if (num >= 10_000_000) {
|
||||
numStr = (num / 1000000).toFixed(1) + "M";
|
||||
@@ -51,7 +52,7 @@ export function generateCryptoRandomUUID(): string {
|
||||
(
|
||||
c ^
|
||||
(crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
|
||||
).toString(16),
|
||||
).toString(16)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,6 +64,6 @@ export function generateCryptoRandomUUID(): string {
|
||||
const r: number = (Math.random() * 16) | 0;
|
||||
const v: number = c === "x" ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import Countries from "../data/countries.json";
|
||||
|
||||
const flagKey: string = "flag";
|
||||
|
||||
@customElement("flag-input")
|
||||
export class FlagInput extends LitElement {
|
||||
@state() private flag: string = "";
|
||||
@state() private search: string = "";
|
||||
@state() private showModal: boolean = false;
|
||||
|
||||
static styles = css`
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.flag-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.no-selected-flag {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
top: 8px;
|
||||
height: 50px;
|
||||
border-radius: 0.75rem;
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selected-flag {
|
||||
width: 48px;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
left: 24px;
|
||||
top: 14px;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.flag-modal {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
position: absolute;
|
||||
top: 60px;
|
||||
left: 0;
|
||||
width: 560px;
|
||||
height: 500px;
|
||||
background-color: rgb(35 35 35 / 0.8);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
backdrop-filter: blur(12px);
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.flag-search {
|
||||
height: 2rem;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
text-align: center;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.flag-dropdown {
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
opacity: 0.7;
|
||||
width: calc(100% / 4 - 15px);
|
||||
text-align: center;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.country-flag {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.flag-modal {
|
||||
left: 0px;
|
||||
width: calc(100% - 16px);
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
width: calc(100% / 3 - 15px);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
private handleSearch(e: Event) {
|
||||
this.search = String((e.target as HTMLInputElement).value);
|
||||
}
|
||||
|
||||
private setFlag(flag: string) {
|
||||
this.flag = flag;
|
||||
this.showModal = false;
|
||||
this.storeFlag(flag);
|
||||
}
|
||||
|
||||
public getCurrentFlag(): string {
|
||||
return this.flag;
|
||||
}
|
||||
|
||||
private getStoredFlag(): string {
|
||||
const storedFlag = localStorage.getItem(flagKey);
|
||||
if (storedFlag) {
|
||||
return storedFlag;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private storeFlag(flag: string) {
|
||||
if (flag) {
|
||||
localStorage.setItem(flagKey, flag);
|
||||
} else if (flag === "") {
|
||||
localStorage.removeItem(flagKey);
|
||||
}
|
||||
}
|
||||
|
||||
private dispatchFlagEvent() {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("flag-change", {
|
||||
detail: { flag: this.flag },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.flag = this.getStoredFlag();
|
||||
this.dispatchFlagEvent();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="flag-container">
|
||||
${this.flag === ""
|
||||
? html` <button
|
||||
class="no-selected-flag"
|
||||
@click=${() => (this.showModal = true)}
|
||||
>
|
||||
Flags
|
||||
</button>`
|
||||
: html`<img
|
||||
class="selected-flag"
|
||||
src="flags/${this.flag}.svg"
|
||||
@click=${() => (this.showModal = true)}
|
||||
/>`}
|
||||
${this.showModal
|
||||
? html`
|
||||
<div
|
||||
class="flag-modal ${this.showModal
|
||||
? ""
|
||||
: "hidden"}"
|
||||
>
|
||||
<input
|
||||
class="flag-search"
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
@change=${this.handleSearch}
|
||||
@keyup=${this.handleSearch}
|
||||
/>
|
||||
<div class="flag-dropdown">
|
||||
<!-- Show each flag as button -->
|
||||
<button
|
||||
@click=${() => this.setFlag("")}
|
||||
class="dropdown-item"
|
||||
>
|
||||
<img
|
||||
class="country-flag"
|
||||
src="flags/none.svg"
|
||||
/>
|
||||
<span class="country-name">None</span>
|
||||
</button>
|
||||
${Countries.filter(
|
||||
(country) =>
|
||||
country.name
|
||||
.toLowerCase()
|
||||
.includes(
|
||||
this.search.toLowerCase()
|
||||
) ||
|
||||
country.code
|
||||
.toLowerCase()
|
||||
.includes(
|
||||
this.search.toLowerCase()
|
||||
)
|
||||
).map(
|
||||
(country) => html`
|
||||
<button
|
||||
@click=${() =>
|
||||
this.setFlag(country.code)}
|
||||
class="dropdown-item"
|
||||
>
|
||||
<img
|
||||
class="country-flag"
|
||||
src="flags/${country.code}.svg"
|
||||
/>
|
||||
<span class="country-name"
|
||||
>${country.name}</span
|
||||
>
|
||||
</button>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,974 @@
|
||||
[
|
||||
{
|
||||
"name": "Afghanistan",
|
||||
"code": "af"
|
||||
},
|
||||
{
|
||||
"name": "Åland Islands",
|
||||
"code": "ax"
|
||||
},
|
||||
{
|
||||
"name": "Albania",
|
||||
"code": "al"
|
||||
},
|
||||
{
|
||||
"name": "Algeria",
|
||||
"code": "dz"
|
||||
},
|
||||
{
|
||||
"name": "American Samoa",
|
||||
"code": "as"
|
||||
},
|
||||
{
|
||||
"name": "AndorrA",
|
||||
"code": "ad"
|
||||
},
|
||||
{
|
||||
"name": "Angola",
|
||||
"code": "ao"
|
||||
},
|
||||
{
|
||||
"name": "Anguilla",
|
||||
"code": "ai"
|
||||
},
|
||||
{
|
||||
"name": "Antarctica",
|
||||
"code": "aq"
|
||||
},
|
||||
{
|
||||
"name": "Antigua and Barbuda",
|
||||
"code": "ag"
|
||||
},
|
||||
{
|
||||
"name": "Argentina",
|
||||
"code": "ar"
|
||||
},
|
||||
{
|
||||
"name": "Armenia",
|
||||
"code": "am"
|
||||
},
|
||||
{
|
||||
"name": "Aruba",
|
||||
"code": "aw"
|
||||
},
|
||||
{
|
||||
"name": "Australia",
|
||||
"code": "au"
|
||||
},
|
||||
{
|
||||
"name": "Austria",
|
||||
"code": "at"
|
||||
},
|
||||
{
|
||||
"name": "Azerbaijan",
|
||||
"code": "az"
|
||||
},
|
||||
{
|
||||
"name": "Bahamas",
|
||||
"code": "bs"
|
||||
},
|
||||
{
|
||||
"name": "Bahrain",
|
||||
"code": "bh"
|
||||
},
|
||||
{
|
||||
"name": "Bangladesh",
|
||||
"code": "bd"
|
||||
},
|
||||
{
|
||||
"name": "Barbados",
|
||||
"code": "bb"
|
||||
},
|
||||
{
|
||||
"name": "Belarus",
|
||||
"code": "by"
|
||||
},
|
||||
{
|
||||
"name": "Belgium",
|
||||
"code": "be"
|
||||
},
|
||||
{
|
||||
"name": "Belize",
|
||||
"code": "bz"
|
||||
},
|
||||
{
|
||||
"name": "Benin",
|
||||
"code": "bj"
|
||||
},
|
||||
{
|
||||
"name": "Bermuda",
|
||||
"code": "bm"
|
||||
},
|
||||
{
|
||||
"name": "Bhutan",
|
||||
"code": "bt"
|
||||
},
|
||||
{
|
||||
"name": "Bolivia",
|
||||
"code": "bo"
|
||||
},
|
||||
{
|
||||
"name": "Bosnia and Herzegovina",
|
||||
"code": "ba"
|
||||
},
|
||||
{
|
||||
"name": "Botswana",
|
||||
"code": "bw"
|
||||
},
|
||||
{
|
||||
"name": "Bouvet Island",
|
||||
"code": "bv"
|
||||
},
|
||||
{
|
||||
"name": "Brazil",
|
||||
"code": "br"
|
||||
},
|
||||
{
|
||||
"name": "British Indian Ocean Territory",
|
||||
"code": "io"
|
||||
},
|
||||
{
|
||||
"name": "Brunei Darussalam",
|
||||
"code": "bn"
|
||||
},
|
||||
{
|
||||
"name": "Bulgaria",
|
||||
"code": "bg"
|
||||
},
|
||||
{
|
||||
"name": "Burkina Faso",
|
||||
"code": "bf"
|
||||
},
|
||||
{
|
||||
"name": "Burundi",
|
||||
"code": "bi"
|
||||
},
|
||||
{
|
||||
"name": "Cambodia",
|
||||
"code": "kh"
|
||||
},
|
||||
{
|
||||
"name": "Cameroon",
|
||||
"code": "cm"
|
||||
},
|
||||
{
|
||||
"name": "Canada",
|
||||
"code": "ca"
|
||||
},
|
||||
{
|
||||
"name": "Cape Verde",
|
||||
"code": "cv"
|
||||
},
|
||||
{
|
||||
"name": "Cayman Islands",
|
||||
"code": "ky"
|
||||
},
|
||||
{
|
||||
"name": "Central African Republic",
|
||||
"code": "cf"
|
||||
},
|
||||
{
|
||||
"name": "Chad",
|
||||
"code": "td"
|
||||
},
|
||||
{
|
||||
"name": "Chile",
|
||||
"code": "cl"
|
||||
},
|
||||
{
|
||||
"name": "China",
|
||||
"code": "cn"
|
||||
},
|
||||
{
|
||||
"name": "Christmas Island",
|
||||
"code": "cx"
|
||||
},
|
||||
{
|
||||
"name": "Cocos (Keeling) Islands",
|
||||
"code": "cc"
|
||||
},
|
||||
{
|
||||
"name": "Colombia",
|
||||
"code": "co"
|
||||
},
|
||||
{
|
||||
"name": "Comoros",
|
||||
"code": "km"
|
||||
},
|
||||
{
|
||||
"name": "Congo",
|
||||
"code": "cg"
|
||||
},
|
||||
{
|
||||
"name": "Congo, The Democratic Republic of the",
|
||||
"code": "cd"
|
||||
},
|
||||
{
|
||||
"name": "Cook Islands",
|
||||
"code": "ck"
|
||||
},
|
||||
{
|
||||
"name": "Costa Rica",
|
||||
"code": "cr"
|
||||
},
|
||||
{
|
||||
"name": "Cote D'Ivoire",
|
||||
"code": "ci"
|
||||
},
|
||||
{
|
||||
"name": "Croatia",
|
||||
"code": "hr"
|
||||
},
|
||||
{
|
||||
"name": "Cuba",
|
||||
"code": "cu"
|
||||
},
|
||||
{
|
||||
"name": "Cyprus",
|
||||
"code": "cy"
|
||||
},
|
||||
{
|
||||
"name": "Czech Republic",
|
||||
"code": "cz"
|
||||
},
|
||||
{
|
||||
"name": "Denmark",
|
||||
"code": "dk"
|
||||
},
|
||||
{
|
||||
"name": "Djibouti",
|
||||
"code": "dj"
|
||||
},
|
||||
{
|
||||
"name": "Dominica",
|
||||
"code": "dm"
|
||||
},
|
||||
{
|
||||
"name": "Dominican Republic",
|
||||
"code": "do"
|
||||
},
|
||||
{
|
||||
"name": "Ecuador",
|
||||
"code": "ec"
|
||||
},
|
||||
{
|
||||
"name": "Egypt",
|
||||
"code": "eg"
|
||||
},
|
||||
{
|
||||
"name": "El Salvador",
|
||||
"code": "sv"
|
||||
},
|
||||
{
|
||||
"name": "Equatorial Guinea",
|
||||
"code": "gq"
|
||||
},
|
||||
{
|
||||
"name": "Eritrea",
|
||||
"code": "er"
|
||||
},
|
||||
{
|
||||
"name": "Estonia",
|
||||
"code": "ee"
|
||||
},
|
||||
{
|
||||
"name": "Ethiopia",
|
||||
"code": "et"
|
||||
},
|
||||
{
|
||||
"name": "Falkland Islands (Malvinas)",
|
||||
"code": "fk"
|
||||
},
|
||||
{
|
||||
"name": "Faroe Islands",
|
||||
"code": "fo"
|
||||
},
|
||||
{
|
||||
"name": "Fiji",
|
||||
"code": "fj"
|
||||
},
|
||||
{
|
||||
"name": "Finland",
|
||||
"code": "fi"
|
||||
},
|
||||
{
|
||||
"name": "France",
|
||||
"code": "fr"
|
||||
},
|
||||
{
|
||||
"name": "French Guiana",
|
||||
"code": "gf"
|
||||
},
|
||||
{
|
||||
"name": "French Polynesia",
|
||||
"code": "pf"
|
||||
},
|
||||
{
|
||||
"name": "French Southern Territories",
|
||||
"code": "tf"
|
||||
},
|
||||
{
|
||||
"name": "Gabon",
|
||||
"code": "ga"
|
||||
},
|
||||
{
|
||||
"name": "Gambia",
|
||||
"code": "gm"
|
||||
},
|
||||
{
|
||||
"name": "Georgia",
|
||||
"code": "ge"
|
||||
},
|
||||
{
|
||||
"name": "Germany",
|
||||
"code": "de"
|
||||
},
|
||||
{
|
||||
"name": "Ghana",
|
||||
"code": "gh"
|
||||
},
|
||||
{
|
||||
"name": "Gibraltar",
|
||||
"code": "gi"
|
||||
},
|
||||
{
|
||||
"name": "Greece",
|
||||
"code": "gr"
|
||||
},
|
||||
{
|
||||
"name": "Greenland",
|
||||
"code": "gl"
|
||||
},
|
||||
{
|
||||
"name": "Grenada",
|
||||
"code": "gd"
|
||||
},
|
||||
{
|
||||
"name": "Guadeloupe",
|
||||
"code": "gp"
|
||||
},
|
||||
{
|
||||
"name": "Guam",
|
||||
"code": "gu"
|
||||
},
|
||||
{
|
||||
"name": "Guatemala",
|
||||
"code": "gt"
|
||||
},
|
||||
{
|
||||
"name": "Guernsey",
|
||||
"code": "gg"
|
||||
},
|
||||
{
|
||||
"name": "Guinea",
|
||||
"code": "gn"
|
||||
},
|
||||
{
|
||||
"name": "Guinea-Bissau",
|
||||
"code": "gw"
|
||||
},
|
||||
{
|
||||
"name": "Guyana",
|
||||
"code": "gy"
|
||||
},
|
||||
{
|
||||
"name": "Haiti",
|
||||
"code": "ht"
|
||||
},
|
||||
{
|
||||
"name": "Heard Island and Mcdonald Islands",
|
||||
"code": "hm"
|
||||
},
|
||||
{
|
||||
"name": "Holy See (Vatican City State)",
|
||||
"code": "va"
|
||||
},
|
||||
{
|
||||
"name": "Honduras",
|
||||
"code": "hn"
|
||||
},
|
||||
{
|
||||
"name": "Hong Kong",
|
||||
"code": "hk"
|
||||
},
|
||||
{
|
||||
"name": "Hungary",
|
||||
"code": "hu"
|
||||
},
|
||||
{
|
||||
"name": "Iceland",
|
||||
"code": "is"
|
||||
},
|
||||
{
|
||||
"name": "India",
|
||||
"code": "in"
|
||||
},
|
||||
{
|
||||
"name": "Indonesia",
|
||||
"code": "id"
|
||||
},
|
||||
{
|
||||
"name": "Iran, Islamic Republic Of",
|
||||
"code": "ir"
|
||||
},
|
||||
{
|
||||
"name": "Iraq",
|
||||
"code": "iq"
|
||||
},
|
||||
{
|
||||
"name": "Ireland",
|
||||
"code": "ie"
|
||||
},
|
||||
{
|
||||
"name": "Isle of Man",
|
||||
"code": "im"
|
||||
},
|
||||
{
|
||||
"name": "Israel",
|
||||
"code": "il"
|
||||
},
|
||||
{
|
||||
"name": "Italy",
|
||||
"code": "it"
|
||||
},
|
||||
{
|
||||
"name": "Jamaica",
|
||||
"code": "jm"
|
||||
},
|
||||
{
|
||||
"name": "Japan",
|
||||
"code": "jp"
|
||||
},
|
||||
{
|
||||
"name": "Jersey",
|
||||
"code": "je"
|
||||
},
|
||||
{
|
||||
"name": "Jordan",
|
||||
"code": "jo"
|
||||
},
|
||||
{
|
||||
"name": "Kazakhstan",
|
||||
"code": "kz"
|
||||
},
|
||||
{
|
||||
"name": "Kenya",
|
||||
"code": "ke"
|
||||
},
|
||||
{
|
||||
"name": "Kiribati",
|
||||
"code": "ki"
|
||||
},
|
||||
{
|
||||
"name": "Korea, Democratic People'S Republic of",
|
||||
"code": "kp"
|
||||
},
|
||||
{
|
||||
"name": "Korea, Republic of",
|
||||
"code": "kr"
|
||||
},
|
||||
{
|
||||
"name": "Kuwait",
|
||||
"code": "kw"
|
||||
},
|
||||
{
|
||||
"name": "Kyrgyzstan",
|
||||
"code": "kg"
|
||||
},
|
||||
{
|
||||
"name": "Lao People'S Democratic Republic",
|
||||
"code": "la"
|
||||
},
|
||||
{
|
||||
"name": "Latvia",
|
||||
"code": "lv"
|
||||
},
|
||||
{
|
||||
"name": "Lebanon",
|
||||
"code": "lb"
|
||||
},
|
||||
{
|
||||
"name": "Lesotho",
|
||||
"code": "ls"
|
||||
},
|
||||
{
|
||||
"name": "Liberia",
|
||||
"code": "lr"
|
||||
},
|
||||
{
|
||||
"name": "Libyan Arab Jamahiriya",
|
||||
"code": "ly"
|
||||
},
|
||||
{
|
||||
"name": "Liechtenstein",
|
||||
"code": "li"
|
||||
},
|
||||
{
|
||||
"name": "Lithuania",
|
||||
"code": "lt"
|
||||
},
|
||||
{
|
||||
"name": "Luxembourg",
|
||||
"code": "lu"
|
||||
},
|
||||
{
|
||||
"name": "Macao",
|
||||
"code": "mo"
|
||||
},
|
||||
{
|
||||
"name": "Macedonia, The Former Yugoslav Republic of",
|
||||
"code": "mk"
|
||||
},
|
||||
{
|
||||
"name": "Madagascar",
|
||||
"code": "mg"
|
||||
},
|
||||
{
|
||||
"name": "Malawi",
|
||||
"code": "mw"
|
||||
},
|
||||
{
|
||||
"name": "Malaysia",
|
||||
"code": "my"
|
||||
},
|
||||
{
|
||||
"name": "Maldives",
|
||||
"code": "mv"
|
||||
},
|
||||
{
|
||||
"name": "Mali",
|
||||
"code": "ml"
|
||||
},
|
||||
{
|
||||
"name": "Malta",
|
||||
"code": "mt"
|
||||
},
|
||||
{
|
||||
"name": "Marshall Islands",
|
||||
"code": "mh"
|
||||
},
|
||||
{
|
||||
"name": "Martinique",
|
||||
"code": "mq"
|
||||
},
|
||||
{
|
||||
"name": "Mauritania",
|
||||
"code": "mr"
|
||||
},
|
||||
{
|
||||
"name": "Mauritius",
|
||||
"code": "mu"
|
||||
},
|
||||
{
|
||||
"name": "Mayotte",
|
||||
"code": "yt"
|
||||
},
|
||||
{
|
||||
"name": "Mexico",
|
||||
"code": "mx"
|
||||
},
|
||||
{
|
||||
"name": "Micronesia, Federated States of",
|
||||
"code": "fm"
|
||||
},
|
||||
{
|
||||
"name": "Moldova, Republic of",
|
||||
"code": "md"
|
||||
},
|
||||
{
|
||||
"name": "Monaco",
|
||||
"code": "mc"
|
||||
},
|
||||
{
|
||||
"name": "Mongolia",
|
||||
"code": "mn"
|
||||
},
|
||||
{
|
||||
"name": "Montserrat",
|
||||
"code": "ms"
|
||||
},
|
||||
{
|
||||
"name": "Morocco",
|
||||
"code": "ma"
|
||||
},
|
||||
{
|
||||
"name": "Mozambique",
|
||||
"code": "mz"
|
||||
},
|
||||
{
|
||||
"name": "Myanmar",
|
||||
"code": "mm"
|
||||
},
|
||||
{
|
||||
"name": "Namibia",
|
||||
"code": "na"
|
||||
},
|
||||
{
|
||||
"name": "Nauru",
|
||||
"code": "nr"
|
||||
},
|
||||
{
|
||||
"name": "Nepal",
|
||||
"code": "np"
|
||||
},
|
||||
{
|
||||
"name": "Netherlands",
|
||||
"code": "nl"
|
||||
},
|
||||
{
|
||||
"name": "Netherlands Antilles",
|
||||
"code": "an"
|
||||
},
|
||||
{
|
||||
"name": "New Caledonia",
|
||||
"code": "nc"
|
||||
},
|
||||
{
|
||||
"name": "New Zealand",
|
||||
"code": "nz"
|
||||
},
|
||||
{
|
||||
"name": "Nicaragua",
|
||||
"code": "ni"
|
||||
},
|
||||
{
|
||||
"name": "Niger",
|
||||
"code": "ne"
|
||||
},
|
||||
{
|
||||
"name": "Nigeria",
|
||||
"code": "ng"
|
||||
},
|
||||
{
|
||||
"name": "Niue",
|
||||
"code": "nu"
|
||||
},
|
||||
{
|
||||
"name": "Norfolk Island",
|
||||
"code": "nf"
|
||||
},
|
||||
{
|
||||
"name": "Northern Mariana Islands",
|
||||
"code": "mp"
|
||||
},
|
||||
{
|
||||
"name": "Norway",
|
||||
"code": "no"
|
||||
},
|
||||
{
|
||||
"name": "Oman",
|
||||
"code": "om"
|
||||
},
|
||||
{
|
||||
"name": "Pakistan",
|
||||
"code": "pk"
|
||||
},
|
||||
{
|
||||
"name": "Palau",
|
||||
"code": "pw"
|
||||
},
|
||||
{
|
||||
"name": "Palestinian Territory, Occupied",
|
||||
"code": "ps"
|
||||
},
|
||||
{
|
||||
"name": "Panama",
|
||||
"code": "pa"
|
||||
},
|
||||
{
|
||||
"name": "Papua New Guinea",
|
||||
"code": "pg"
|
||||
},
|
||||
{
|
||||
"name": "Paraguay",
|
||||
"code": "py"
|
||||
},
|
||||
{
|
||||
"name": "Peru",
|
||||
"code": "pe"
|
||||
},
|
||||
{
|
||||
"name": "Philippines",
|
||||
"code": "ph"
|
||||
},
|
||||
{
|
||||
"name": "Pitcairn",
|
||||
"code": "pn"
|
||||
},
|
||||
{
|
||||
"name": "Poland",
|
||||
"code": "pl"
|
||||
},
|
||||
{
|
||||
"name": "Portugal",
|
||||
"code": "pt"
|
||||
},
|
||||
{
|
||||
"name": "Puerto Rico",
|
||||
"code": "pr"
|
||||
},
|
||||
{
|
||||
"name": "Qatar",
|
||||
"code": "qa"
|
||||
},
|
||||
{
|
||||
"name": "Reunion",
|
||||
"code": "re"
|
||||
},
|
||||
{
|
||||
"name": "Romania",
|
||||
"code": "ro"
|
||||
},
|
||||
{
|
||||
"name": "Russian Federation",
|
||||
"code": "ru"
|
||||
},
|
||||
{
|
||||
"name": "RWANDA",
|
||||
"code": "rw"
|
||||
},
|
||||
{
|
||||
"name": "Saint Helena",
|
||||
"code": "sh"
|
||||
},
|
||||
{
|
||||
"name": "Saint Kitts and Nevis",
|
||||
"code": "kn"
|
||||
},
|
||||
{
|
||||
"name": "Saint Lucia",
|
||||
"code": "lc"
|
||||
},
|
||||
{
|
||||
"name": "Saint Pierre and Miquelon",
|
||||
"code": "pm"
|
||||
},
|
||||
{
|
||||
"name": "Saint Vincent and the Grenadines",
|
||||
"code": "vc"
|
||||
},
|
||||
{
|
||||
"name": "Samoa",
|
||||
"code": "ws"
|
||||
},
|
||||
{
|
||||
"name": "San Marino",
|
||||
"code": "sm"
|
||||
},
|
||||
{
|
||||
"name": "Sao Tome and Principe",
|
||||
"code": "st"
|
||||
},
|
||||
{
|
||||
"name": "Saudi Arabia",
|
||||
"code": "sa"
|
||||
},
|
||||
{
|
||||
"name": "Senegal",
|
||||
"code": "sn"
|
||||
},
|
||||
{
|
||||
"name": "Serbia and Montenegro",
|
||||
"code": "cs"
|
||||
},
|
||||
{
|
||||
"name": "Seychelles",
|
||||
"code": "sc"
|
||||
},
|
||||
{
|
||||
"name": "Sierra Leone",
|
||||
"code": "sl"
|
||||
},
|
||||
{
|
||||
"name": "Singapore",
|
||||
"code": "sg"
|
||||
},
|
||||
{
|
||||
"name": "Slovakia",
|
||||
"code": "sk"
|
||||
},
|
||||
{
|
||||
"name": "Slovenia",
|
||||
"code": "si"
|
||||
},
|
||||
{
|
||||
"name": "Solomon Islands",
|
||||
"code": "sb"
|
||||
},
|
||||
{
|
||||
"name": "Somalia",
|
||||
"code": "so"
|
||||
},
|
||||
{
|
||||
"name": "South Africa",
|
||||
"code": "za"
|
||||
},
|
||||
{
|
||||
"name": "South Georgia and the South Sandwich Islands",
|
||||
"code": "gs"
|
||||
},
|
||||
{
|
||||
"name": "Spain",
|
||||
"code": "es"
|
||||
},
|
||||
{
|
||||
"name": "Sri Lanka",
|
||||
"code": "lk"
|
||||
},
|
||||
{
|
||||
"name": "Sudan",
|
||||
"code": "sd"
|
||||
},
|
||||
{
|
||||
"name": "Suriname",
|
||||
"code": "sr"
|
||||
},
|
||||
{
|
||||
"name": "Svalbard and Jan Mayen",
|
||||
"code": "sj"
|
||||
},
|
||||
{
|
||||
"name": "Swaziland",
|
||||
"code": "sz"
|
||||
},
|
||||
{
|
||||
"name": "Sweden",
|
||||
"code": "se"
|
||||
},
|
||||
{
|
||||
"name": "Switzerland",
|
||||
"code": "ch"
|
||||
},
|
||||
{
|
||||
"name": "Syrian Arab Republic",
|
||||
"code": "sy"
|
||||
},
|
||||
{
|
||||
"name": "Taiwan, Province of China",
|
||||
"code": "tw"
|
||||
},
|
||||
{
|
||||
"name": "Tajikistan",
|
||||
"code": "tj"
|
||||
},
|
||||
{
|
||||
"name": "Tanzania, United Republic of",
|
||||
"code": "tz"
|
||||
},
|
||||
{
|
||||
"name": "Thailand",
|
||||
"code": "th"
|
||||
},
|
||||
{
|
||||
"name": "Timor-Leste",
|
||||
"code": "tl"
|
||||
},
|
||||
{
|
||||
"name": "Togo",
|
||||
"code": "tg"
|
||||
},
|
||||
{
|
||||
"name": "Tokelau",
|
||||
"code": "tk"
|
||||
},
|
||||
{
|
||||
"name": "Tonga",
|
||||
"code": "to"
|
||||
},
|
||||
{
|
||||
"name": "Trinidad and Tobago",
|
||||
"code": "tt"
|
||||
},
|
||||
{
|
||||
"name": "Tunisia",
|
||||
"code": "tn"
|
||||
},
|
||||
{
|
||||
"name": "Turkey",
|
||||
"code": "tr"
|
||||
},
|
||||
{
|
||||
"name": "Turkmenistan",
|
||||
"code": "tm"
|
||||
},
|
||||
{
|
||||
"name": "Turks and Caicos Islands",
|
||||
"code": "tc"
|
||||
},
|
||||
{
|
||||
"name": "Tuvalu",
|
||||
"code": "tv"
|
||||
},
|
||||
{
|
||||
"name": "Uganda",
|
||||
"code": "ug"
|
||||
},
|
||||
{
|
||||
"name": "Ukraine",
|
||||
"code": "ua"
|
||||
},
|
||||
{
|
||||
"name": "United Arab Emirates",
|
||||
"code": "ae"
|
||||
},
|
||||
{
|
||||
"name": "United Kingdom",
|
||||
"code": "gb"
|
||||
},
|
||||
{
|
||||
"name": "United States",
|
||||
"code": "us"
|
||||
},
|
||||
{
|
||||
"name": "United States Minor Outlying Islands",
|
||||
"code": "um"
|
||||
},
|
||||
{
|
||||
"name": "Uruguay",
|
||||
"code": "uy"
|
||||
},
|
||||
{
|
||||
"name": "Uzbekistan",
|
||||
"code": "uz"
|
||||
},
|
||||
{
|
||||
"name": "Vanuatu",
|
||||
"code": "vu"
|
||||
},
|
||||
{
|
||||
"name": "Venezuela",
|
||||
"code": "ve"
|
||||
},
|
||||
{
|
||||
"name": "Viet Nam",
|
||||
"code": "vn"
|
||||
},
|
||||
{
|
||||
"name": "Virgin Islands, British",
|
||||
"code": "vg"
|
||||
},
|
||||
{
|
||||
"name": "Virgin Islands, U.S.",
|
||||
"code": "vi"
|
||||
},
|
||||
{
|
||||
"name": "Wallis and Futuna",
|
||||
"code": "wf"
|
||||
},
|
||||
{
|
||||
"name": "Western Sahara",
|
||||
"code": "eh"
|
||||
},
|
||||
{
|
||||
"name": "Yemen",
|
||||
"code": "ye"
|
||||
},
|
||||
{
|
||||
"name": "Zambia",
|
||||
"code": "zm"
|
||||
},
|
||||
{
|
||||
"name": "Zimbabwe",
|
||||
"code": "zw"
|
||||
}
|
||||
]
|
||||
@@ -15,6 +15,7 @@ import warshipIcon from "../../../../resources/images/BattleshipIconWhite.svg";
|
||||
import missileSiloIcon from "../../../../resources/images/MissileSiloIconWhite.svg";
|
||||
import goldCoinIcon from "../../../../resources/images/GoldCoinIcon.svg";
|
||||
import portIcon from "../../../../resources/images/PortIcon.svg";
|
||||
import mirvIcon from "../../../../resources/images/MIRVIcon.svg";
|
||||
import cityIcon from "../../../../resources/images/CityIconWhite.svg";
|
||||
import shieldIcon from "../../../../resources/images/ShieldIconWhite.svg";
|
||||
import { renderNumber } from "../../Utils";
|
||||
@@ -28,7 +29,8 @@ interface BuildItemDisplay {
|
||||
const buildTable: BuildItemDisplay[][] = [
|
||||
[
|
||||
{ unitType: UnitType.AtomBomb, icon: atomBombIcon },
|
||||
{ unitType: UnitType.MIRV, icon: hydrogenBombIcon },
|
||||
{ unitType: UnitType.MIRV, icon: mirvIcon },
|
||||
{ unitType: UnitType.HydrogenBomb, icon: hydrogenBombIcon },
|
||||
{ unitType: UnitType.Warship, icon: warshipIcon },
|
||||
{ unitType: UnitType.Port, icon: portIcon },
|
||||
{ unitType: UnitType.MissileSilo, icon: missileSiloIcon },
|
||||
|
||||
@@ -195,7 +195,7 @@ export class ControlPanel extends LitElement implements Layer {
|
||||
type="range"
|
||||
min="1"
|
||||
max="100"
|
||||
.value=${this.targetTroopRatio * 100}
|
||||
.value=${(this.targetTroopRatio * 100).toString()}
|
||||
@input=${(e: Event) => {
|
||||
this.targetTroopRatio =
|
||||
parseInt((e.target as HTMLInputElement).value) / 100;
|
||||
@@ -225,7 +225,7 @@ export class ControlPanel extends LitElement implements Layer {
|
||||
type="range"
|
||||
min="1"
|
||||
max="100"
|
||||
.value=${this.attackRatio * 100}
|
||||
.value=${(this.attackRatio * 100).toString()}
|
||||
@input=${(e: Event) => {
|
||||
this.attackRatio =
|
||||
parseInt((e.target as HTMLInputElement).value) / 100;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { AllPlayers, MessageType } from "../../../core/game/Game";
|
||||
import { DisplayMessageUpdate } from "../../../core/game/GameUpdates";
|
||||
import { AllPlayers, MessageType, PlayerType } from "../../../core/game/Game";
|
||||
import {
|
||||
AttackUpdate,
|
||||
DisplayMessageUpdate,
|
||||
} from "../../../core/game/GameUpdates";
|
||||
import { EmojiUpdate } from "../../../core/game/GameUpdates";
|
||||
import { TargetPlayerUpdate } from "../../../core/game/GameUpdates";
|
||||
import { AllianceExpiredUpdate } from "../../../core/game/GameUpdates";
|
||||
@@ -16,6 +19,7 @@ import { SendAllianceReplyIntentEvent } from "../../Transport";
|
||||
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
||||
import { onlyImages, sanitize } from "../../../core/Util";
|
||||
import { GameView, PlayerView } from "../../../core/game/GameView";
|
||||
import { renderTroops } from "../../Utils";
|
||||
|
||||
interface Event {
|
||||
description: string;
|
||||
@@ -38,6 +42,8 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
public clientID: ClientID;
|
||||
|
||||
private events: Event[] = [];
|
||||
@state() private incomingAttacks: AttackUpdate[] = [];
|
||||
@state() private outgoingAttacks: AttackUpdate[] = [];
|
||||
|
||||
private updateMap = new Map([
|
||||
[GameUpdateType.DisplayEvent, (u) => this.onDisplayMessageEvent(u)],
|
||||
@@ -54,6 +60,8 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
constructor() {
|
||||
super();
|
||||
this.events = [];
|
||||
this.incomingAttacks = [];
|
||||
this.outgoingAttacks = [];
|
||||
}
|
||||
|
||||
init() {}
|
||||
@@ -80,6 +88,31 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
this.events = remainingEvents;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
const myPlayer = this.game.myPlayer();
|
||||
if (!myPlayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
myPlayer.incomingAttacks().forEach((a) => {
|
||||
console.log(
|
||||
`got type: ${(
|
||||
this.game.playerBySmallID(a.attackerID) as PlayerView
|
||||
).type()}`
|
||||
);
|
||||
});
|
||||
|
||||
// Update attacks
|
||||
this.incomingAttacks = myPlayer.incomingAttacks().filter((a) => {
|
||||
const t = (this.game.playerBySmallID(a.attackerID) as PlayerView).type();
|
||||
return t != PlayerType.Bot;
|
||||
});
|
||||
|
||||
this.outgoingAttacks = myPlayer
|
||||
.outgoingAttacks()
|
||||
.filter((a) => a.targetID != 0);
|
||||
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private addEvent(event: Event) {
|
||||
@@ -125,10 +158,10 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
}
|
||||
|
||||
const requestor = this.game.playerBySmallID(
|
||||
update.requestorID,
|
||||
update.requestorID
|
||||
) as PlayerView;
|
||||
const recipient = this.game.playerBySmallID(
|
||||
update.recipientID,
|
||||
update.recipientID
|
||||
) as PlayerView;
|
||||
|
||||
this.addEvent({
|
||||
@@ -139,7 +172,7 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
className: "btn",
|
||||
action: () =>
|
||||
this.eventBus.emit(
|
||||
new SendAllianceReplyIntentEvent(requestor, recipient, true),
|
||||
new SendAllianceReplyIntentEvent(requestor, recipient, true)
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -147,7 +180,7 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
className: "btn-info",
|
||||
action: () =>
|
||||
this.eventBus.emit(
|
||||
new SendAllianceReplyIntentEvent(requestor, recipient, false),
|
||||
new SendAllianceReplyIntentEvent(requestor, recipient, false)
|
||||
),
|
||||
},
|
||||
],
|
||||
@@ -156,7 +189,7 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
createdAt: this.game.ticks(),
|
||||
onDelete: () =>
|
||||
this.eventBus.emit(
|
||||
new SendAllianceReplyIntentEvent(requestor, recipient, false),
|
||||
new SendAllianceReplyIntentEvent(requestor, recipient, false)
|
||||
),
|
||||
});
|
||||
}
|
||||
@@ -168,7 +201,7 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
}
|
||||
|
||||
const recipient = this.game.playerBySmallID(
|
||||
update.request.recipientID,
|
||||
update.request.recipientID
|
||||
) as PlayerView;
|
||||
|
||||
this.addEvent({
|
||||
@@ -213,8 +246,8 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
update.player1ID === myPlayer.smallID()
|
||||
? update.player2ID
|
||||
: update.player2ID === myPlayer.smallID()
|
||||
? update.player1ID
|
||||
: null;
|
||||
? update.player1ID
|
||||
: null;
|
||||
const other = this.game.playerBySmallID(otherID) as PlayerView;
|
||||
if (!other || !myPlayer.isAlive() || !other.isAlive()) return;
|
||||
|
||||
@@ -250,7 +283,7 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
? AllPlayers
|
||||
: this.game.playerBySmallID(update.emoji.recipientID);
|
||||
const sender = this.game.playerBySmallID(
|
||||
update.emoji.senderID,
|
||||
update.emoji.senderID
|
||||
) as PlayerView;
|
||||
|
||||
if (recipient == myPlayer) {
|
||||
@@ -289,8 +322,62 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
}
|
||||
}
|
||||
|
||||
private renderAttacks() {
|
||||
if (
|
||||
this.incomingAttacks.length === 0 &&
|
||||
this.outgoingAttacks.length === 0
|
||||
) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.incomingAttacks.length > 0
|
||||
? html`
|
||||
<tr class="border-t border-gray-700">
|
||||
<td class="lg:p-3 p-1 text-left text-red-400">
|
||||
${this.incomingAttacks.map(
|
||||
(attack) => html`
|
||||
<div class="ml-2">
|
||||
${renderTroops(attack.troops)}
|
||||
${(
|
||||
this.game.playerBySmallID(
|
||||
attack.attackerID
|
||||
) as PlayerView
|
||||
)?.name()}
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
`
|
||||
: ""}
|
||||
${this.outgoingAttacks.length > 0
|
||||
? html`
|
||||
<tr class="border-t border-gray-700">
|
||||
<td class="lg:p-3 p-1 text-left text-blue-400">
|
||||
${this.outgoingAttacks.map(
|
||||
(attack) => html`
|
||||
<div class="ml-2">
|
||||
${renderTroops(attack.troops)}
|
||||
${(
|
||||
this.game.playerBySmallID(attack.targetID) as PlayerView
|
||||
)?.name()}
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.events.length === 0) {
|
||||
if (
|
||||
this.events.length === 0 &&
|
||||
this.incomingAttacks.length === 0 &&
|
||||
this.outgoingAttacks.length === 0
|
||||
) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
@@ -306,7 +393,7 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
(event, index) => html`
|
||||
<tr
|
||||
class="border-b border-opacity-0 ${this.getMessageTypeClasses(
|
||||
event.type,
|
||||
event.type
|
||||
)}"
|
||||
>
|
||||
<td class="lg:p-3 p-1 text-left">
|
||||
@@ -331,15 +418,16 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
>
|
||||
${btn.text}
|
||||
</button>
|
||||
`,
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</td>
|
||||
</tr>
|
||||
`,
|
||||
`
|
||||
)}
|
||||
${this.renderAttacks()}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -347,6 +435,6 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
}
|
||||
|
||||
createRenderRoot() {
|
||||
return this; // Required for Tailwind classes to work with Lit
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import targetIcon from "../../../../resources/images/TargetIcon.svg";
|
||||
import { ClientID } from "../../../core/Schemas";
|
||||
import { GameView, PlayerView } from "../../../core/game/GameView";
|
||||
import { createCanvas, renderTroops } from "../../Utils";
|
||||
import { sanitize } from "../../../core/Util";
|
||||
|
||||
class RenderInfo {
|
||||
public icons: Map<string, HTMLImageElement> = new Map(); // Track icon elements
|
||||
@@ -142,33 +143,50 @@ export class NameLayer implements Layer {
|
||||
element.style.alignItems = "center";
|
||||
element.style.gap = "0px";
|
||||
|
||||
|
||||
if (player.flag()) {
|
||||
const flagImg = document.createElement("img");
|
||||
flagImg.classList.add('player-flag');
|
||||
flagImg.style.marginBottom = "-5%";
|
||||
flagImg.style.opacity = '0.8';
|
||||
flagImg.src = 'flags/' + sanitize(player.flag()) + '.svg';
|
||||
flagImg.style.zIndex = "1";
|
||||
flagImg.style.width = "40%";
|
||||
flagImg.style.aspectRatio = "3/4";
|
||||
element.appendChild(flagImg);
|
||||
}
|
||||
|
||||
const nameDiv = document.createElement("div");
|
||||
nameDiv.innerHTML = player.name();
|
||||
nameDiv.classList.add('player-name');
|
||||
nameDiv.innerHTML = (player.type() !== PlayerType.Human ? "🤖 " : '') + player.name();
|
||||
nameDiv.style.color = this.theme.playerInfoColor(player.id()).toHex();
|
||||
nameDiv.style.fontFamily = this.theme.font();
|
||||
nameDiv.style.whiteSpace = "nowrap";
|
||||
nameDiv.style.overflow = "hidden";
|
||||
nameDiv.style.textOverflow = "ellipsis";
|
||||
nameDiv.style.zIndex = "2";
|
||||
nameDiv.style.zIndex = "3";
|
||||
element.appendChild(nameDiv);
|
||||
|
||||
|
||||
const troopsDiv = document.createElement("div");
|
||||
troopsDiv.classList.add('player-troops');
|
||||
troopsDiv.textContent = renderTroops(player.troops());
|
||||
troopsDiv.style.color = this.theme.playerInfoColor(player.id()).toHex();
|
||||
troopsDiv.style.fontFamily = this.theme.font();
|
||||
troopsDiv.style.fontWeight = "bold";
|
||||
troopsDiv.style.zIndex = "2";
|
||||
troopsDiv.style.zIndex = "3";
|
||||
element.appendChild(troopsDiv);
|
||||
|
||||
const iconsDiv = document.createElement("div");
|
||||
iconsDiv.classList.add('player-icons');
|
||||
iconsDiv.style.display = "flex";
|
||||
iconsDiv.style.gap = "4px";
|
||||
iconsDiv.style.justifyContent = "center";
|
||||
iconsDiv.style.alignItems = "center";
|
||||
iconsDiv.style.position = "absolute"; // Add this
|
||||
iconsDiv.style.zIndex = "1"; // Add this
|
||||
iconsDiv.style.width = "100%"; // Add this
|
||||
iconsDiv.style.height = "100%"; // Add this
|
||||
iconsDiv.style.position = "absolute";
|
||||
iconsDiv.style.zIndex = "2";
|
||||
iconsDiv.style.width = "100%";
|
||||
iconsDiv.style.height = "100%";
|
||||
element.appendChild(iconsDiv);
|
||||
|
||||
this.container.appendChild(element);
|
||||
@@ -208,14 +226,14 @@ export class NameLayer implements Layer {
|
||||
render.lastRenderCalc = now + this.rand.nextInt(0, 100);
|
||||
|
||||
// Update text sizes
|
||||
const nameDiv = render.element.children[0] as HTMLDivElement;
|
||||
const troopsDiv = render.element.children[1] as HTMLDivElement;
|
||||
const nameDiv = render.element.querySelector(".player-name") as HTMLDivElement;
|
||||
const troopsDiv = render.element.querySelector(".player-troops") as HTMLDivElement;
|
||||
nameDiv.style.fontSize = `${render.fontSize}px`;
|
||||
troopsDiv.style.fontSize = `${render.fontSize}px`;
|
||||
troopsDiv.textContent = renderTroops(render.player.troops());
|
||||
|
||||
// Handle icons
|
||||
const iconsDiv = render.element.children[2] as HTMLDivElement;
|
||||
const iconsDiv = render.element.querySelector(".player-icons") as HTMLDivElement;
|
||||
const iconSize = Math.min(render.fontSize * 1.5, 48);
|
||||
const myPlayer = this.getPlayer();
|
||||
|
||||
|
||||
@@ -26,16 +26,19 @@ export class TopBar extends LitElement implements Layer {
|
||||
if (!this.isVisible) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const myPlayer = this.game?.myPlayer();
|
||||
if (!myPlayer?.isAlive() || this.game?.inSpawnPhase()) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const popRate = this.game.config().populationIncreaseRate(myPlayer) * 10;
|
||||
const maxPop = this.game.config().maxPopulation(myPlayer);
|
||||
const goldPerSecond = this.game.config().goldAdditionRate(myPlayer) * 10;
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="fixed top-0 z-50 bg-black/90 text-white text-sm p-1 rounded grid grid-cols-1 sm:grid-cols-2 w-1/2 sm:w-2/3 md:w-1/2 lg:hidden"
|
||||
class="fixed top-0 z-50 bg-gray-800/70 text-white text-sm p-1 rounded grid grid-cols-1 sm:grid-cols-2 w-1/2 sm:w-2/3 md:w-1/2 lg:hidden backdrop-blur"
|
||||
>
|
||||
<!-- Pop section (takes 2 columns on desktop) -->
|
||||
<div
|
||||
@@ -48,7 +51,6 @@ export class TopBar extends LitElement implements Layer {
|
||||
>
|
||||
<span>(+${renderTroops(popRate)})</span>
|
||||
</div>
|
||||
|
||||
<!-- Gold section (takes 1 column on desktop) -->
|
||||
<div
|
||||
class="flex items-center space-x-2 overflow-x-auto whitespace-nowrap"
|
||||
|
||||
@@ -80,7 +80,8 @@
|
||||
v0.15.0
|
||||
</div>
|
||||
|
||||
<div class="max-w-sm sm:max-w-md lg:max-w-lg xl:max-w-xl mx-auto p-2 pb-4">
|
||||
<div class="relative items-center max-w-sm sm:max-w-md lg:max-w-lg xl:max-w-xl mx-auto p-2 pb-4">
|
||||
<flag-input></flag-input>
|
||||
<username-input class="w-full"></username-input>
|
||||
</div>
|
||||
|
||||
@@ -146,8 +147,10 @@
|
||||
<div class="flex sm:flex-row flex-col sm:gap-8 gap-2">
|
||||
<a href="https://youtu.be/jvHEvbko3uw?si=znspkP84P76B1w5I"
|
||||
class="text-white/70 hover:text-white transition-colors duration-300" target="_blank">How to Play</a>
|
||||
<a href="https://discord.gg/k22YrnAzGp" class="text-white/70 hover:text-white transition-colors duration-300"
|
||||
target="_blank">Discord</a>
|
||||
<a href="https://discord.gg/k22YrnAzGp" class="text-white/70 hover:text-white transition-colors duration-300"
|
||||
target="_blank">Discord</a>
|
||||
<a href="https://openfront.fandom.com/wiki/Openfront_Wiki" class="text-white/70 hover:text-white transition-colors duration-300"
|
||||
target="_blank">Wiki</a>
|
||||
</div>
|
||||
<div class="text-white/70">
|
||||
© 2025
|
||||
|
||||
Reference in New Issue
Block a user