mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-19 08:55:03 +00:00
Merge branch 'main' into features/footer
This commit is contained in:
@@ -20,7 +20,7 @@ export class PublicLobby extends LitElement {
|
||||
this.fetchAndUpdateLobbies();
|
||||
this.lobbiesInterval = window.setInterval(
|
||||
() => this.fetchAndUpdateLobbies(),
|
||||
1000,
|
||||
1000
|
||||
);
|
||||
}
|
||||
|
||||
@@ -60,6 +60,11 @@ export class PublicLobby extends LitElement {
|
||||
const lobby = this.lobbies[0];
|
||||
const timeRemaining = Math.max(0, Math.floor(lobby.msUntilStart / 1000));
|
||||
|
||||
// Format time to show minutes and seconds
|
||||
const minutes = Math.floor(timeRemaining / 60);
|
||||
const seconds = timeRemaining % 60;
|
||||
const timeDisplay = minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
|
||||
|
||||
return html`
|
||||
<button
|
||||
@click=${() => this.lobbyClicked(lobby)}
|
||||
@@ -68,12 +73,25 @@ export class PublicLobby extends LitElement {
|
||||
: "bg-gradient-to-r from-blue-600 to-blue-500"} text-white font-medium rounded-xl transition-opacity duration-200 hover:opacity-90"
|
||||
>
|
||||
<div class="text-lg md:text-2xl font-semibold mb-2">Next Game</div>
|
||||
<div
|
||||
class="flex flex-col gap-1 md:gap-2 text-blue-100 text-s md:text-lg"
|
||||
>
|
||||
<div>Starts in: ${timeRemaining}s</div>
|
||||
<div>Players: ${lobby.numClients}</div>
|
||||
<div>ID: ${lobby.id}</div>
|
||||
<div class="flex items-center justify-center gap-4">
|
||||
<div class="flex flex-col items-start">
|
||||
<div class="text-md font-medium text-blue-100">
|
||||
${lobby.gameConfig.gameMap}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-start">
|
||||
<div class="text-md font-medium text-blue-100">
|
||||
${lobby.numClients}
|
||||
${lobby.numClients === 1 ? "Player" : "Players"} waiting
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
class="min-w-20 text-sm font-medium px-2 py-1 bg-white/10 rounded-xl text-blue-100 text-center"
|
||||
>
|
||||
${timeDisplay}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
`;
|
||||
@@ -93,7 +111,7 @@ export class PublicLobby extends LitElement {
|
||||
},
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}),
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.dispatchEvent(
|
||||
@@ -101,7 +119,7 @@ export class PublicLobby extends LitElement {
|
||||
detail: { lobby: this.currLobby },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}),
|
||||
})
|
||||
);
|
||||
this.currLobby = null;
|
||||
}
|
||||
|
||||
@@ -135,62 +135,80 @@ export class ControlPanel extends LitElement implements Layer {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative mb-4 lg:mb-4 h-6 lg:h-6">
|
||||
<div class="relative mb-4 lg:mb-4">
|
||||
<label class="block text-white mb-1"
|
||||
>Troops: ${renderTroops(this._troops)} | Workers:
|
||||
${renderTroops(this._workers)}</label
|
||||
>
|
||||
<div
|
||||
class="absolute h-2 bg-blue-500/60 rounded top-6 transition-all duration-300"
|
||||
style="width: ${this.currentTroopRatio * 100}%"
|
||||
></div>
|
||||
<div
|
||||
class="absolute w-4 h-4 bg-white border-2 border-blue-500 rounded-full top-5 -ml-2 cursor-pointer hover:scale-110 transition-transform"
|
||||
style="left: ${this.targetTroopRatio * 100}%"
|
||||
></div>
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="100"
|
||||
.value=${this.targetTroopRatio * 100}
|
||||
@input=${(e: Event) => {
|
||||
this.targetTroopRatio =
|
||||
parseInt((e.target as HTMLInputElement).value) / 100;
|
||||
this.onTroopChange(this.targetTroopRatio);
|
||||
}}
|
||||
class="absolute w-full top-3 m-0 opacity-0 cursor-pointer"
|
||||
/>
|
||||
<div class="relative h-8">
|
||||
<!-- Background track -->
|
||||
<div
|
||||
class="absolute left-0 right-0 top-3 h-2 bg-white/20 rounded"
|
||||
></div>
|
||||
<!-- Fill track -->
|
||||
<div
|
||||
class="absolute left-0 top-3 h-2 bg-blue-500/60 rounded transition-all duration-300"
|
||||
style="width: ${this.currentTroopRatio * 100}%"
|
||||
></div>
|
||||
<!-- Range input - exactly overlaying the visual elements -->
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="100"
|
||||
.value=${this.targetTroopRatio * 100}
|
||||
@input=${(e: Event) => {
|
||||
this.targetTroopRatio =
|
||||
parseInt((e.target as HTMLInputElement).value) / 100;
|
||||
this.onTroopChange(this.targetTroopRatio);
|
||||
}}
|
||||
class="absolute left-0 right-0 top-2 m-0 h-4 opacity-0 cursor-pointer"
|
||||
/>
|
||||
<!-- Handle -->
|
||||
<div
|
||||
class="absolute top-2 w-4 h-4 bg-white border-2 border-blue-500 rounded-full -ml-2 cursor-pointer hover:scale-110 transition-transform pointer-events-none"
|
||||
style="left: ${this.targetTroopRatio * 100}%"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative mb:0 lg:mb-4 h-10 lg:h-12">
|
||||
<div class="relative mb-0 lg:mb-4">
|
||||
<label class="block text-white mb-1"
|
||||
>Attack Ratio: ${(this.attackRatio * 100).toFixed(0)}%</label
|
||||
>
|
||||
<div class="absolute w-full h-2 bg-white/20 rounded top-6"></div>
|
||||
<div
|
||||
class="absolute h-2 bg-red-500/60 rounded top-6 transition-all duration-300"
|
||||
style="width: ${this.attackRatio * 100}%"
|
||||
></div>
|
||||
<div
|
||||
class="absolute w-4 h-4 bg-white border-2 border-red-500 rounded-full top-5 -ml-2 cursor-pointer hover:scale-110 transition-transform"
|
||||
style="left: ${this.attackRatio * 100}%"
|
||||
></div>
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="100"
|
||||
.value=${this.attackRatio * 100}
|
||||
@input=${(e: Event) => {
|
||||
this.attackRatio =
|
||||
parseInt((e.target as HTMLInputElement).value) / 100;
|
||||
this.onAttackRatioChange(this.attackRatio);
|
||||
}}
|
||||
class="absolute w-full top-3 m-0 opacity-0 cursor-pointer"
|
||||
/>
|
||||
<div class="relative h-8">
|
||||
<!-- Background track -->
|
||||
<div
|
||||
class="absolute left-0 right-0 top-3 h-2 bg-white/20 rounded"
|
||||
></div>
|
||||
<!-- Fill track -->
|
||||
<div
|
||||
class="absolute left-0 top-3 h-2 bg-red-500/60 rounded transition-all duration-300"
|
||||
style="width: ${this.attackRatio * 100}%"
|
||||
></div>
|
||||
<!-- Range input - exactly overlaying the visual elements -->
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="100"
|
||||
.value=${this.attackRatio * 100}
|
||||
@input=${(e: Event) => {
|
||||
this.attackRatio =
|
||||
parseInt((e.target as HTMLInputElement).value) / 100;
|
||||
this.onAttackRatioChange(this.attackRatio);
|
||||
}}
|
||||
class="absolute left-0 right-0 top-2 m-0 h-4 opacity-0 cursor-pointer"
|
||||
/>
|
||||
<!-- Handle -->
|
||||
<div
|
||||
class="absolute top-2 w-4 h-4 bg-white border-2 border-red-500 rounded-full -ml-2 cursor-pointer hover:scale-110 transition-transform pointer-events-none"
|
||||
style="left: ${this.attackRatio * 100}%"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
createRenderRoot() {
|
||||
return this; // Disable shadow DOM to allow Tailwind styles
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ class RenderInfo {
|
||||
public lastRenderCalc: number,
|
||||
public location: Cell,
|
||||
public fontSize: number,
|
||||
public element: HTMLElement,
|
||||
public element: HTMLElement
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ export class NameLayer implements Layer {
|
||||
private game: GameView,
|
||||
private theme: Theme,
|
||||
private transformHandler: TransformHandler,
|
||||
private clientID: ClientID,
|
||||
private clientID: ClientID
|
||||
) {
|
||||
this.traitorIconImage = new Image();
|
||||
this.traitorIconImage.src = traitorIcon;
|
||||
@@ -100,13 +100,7 @@ export class NameLayer implements Layer {
|
||||
if (!this.seenPlayers.has(player)) {
|
||||
this.seenPlayers.add(player);
|
||||
this.renders.push(
|
||||
new RenderInfo(
|
||||
player,
|
||||
0,
|
||||
null,
|
||||
0,
|
||||
this.createPlayerElement(player),
|
||||
),
|
||||
new RenderInfo(player, 0, null, 0, this.createPlayerElement(player))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -115,11 +109,11 @@ export class NameLayer implements Layer {
|
||||
|
||||
public renderLayer(mainContex: CanvasRenderingContext2D) {
|
||||
const screenPosOld = this.transformHandler.worldToScreenCoordinates(
|
||||
new Cell(0, 0),
|
||||
new Cell(0, 0)
|
||||
);
|
||||
const screenPos = new Cell(
|
||||
screenPosOld.x - window.innerWidth / 2,
|
||||
screenPosOld.y - window.innerHeight / 2,
|
||||
screenPosOld.y - window.innerHeight / 2
|
||||
);
|
||||
this.container.style.transform = `translate(${screenPos.x}px, ${screenPos.y}px) scale(${this.transformHandler.scale})`;
|
||||
|
||||
@@ -136,7 +130,7 @@ export class NameLayer implements Layer {
|
||||
0,
|
||||
0,
|
||||
mainContex.canvas.width,
|
||||
mainContex.canvas.height,
|
||||
mainContex.canvas.height
|
||||
);
|
||||
}
|
||||
|
||||
@@ -191,7 +185,7 @@ export class NameLayer implements Layer {
|
||||
const oldLocation = render.location;
|
||||
render.location = new Cell(
|
||||
render.player.nameLocation().x,
|
||||
render.player.nameLocation().y,
|
||||
render.player.nameLocation().y
|
||||
);
|
||||
|
||||
// Calculate base size and scale
|
||||
@@ -230,7 +224,7 @@ export class NameLayer implements Layer {
|
||||
if (render.player === this.firstPlace) {
|
||||
if (!existingCrown) {
|
||||
iconsDiv.appendChild(
|
||||
this.createIconElement(this.crownIconImage.src, iconSize, "crown"),
|
||||
this.createIconElement(this.crownIconImage.src, iconSize, "crown")
|
||||
);
|
||||
}
|
||||
} else if (existingCrown) {
|
||||
@@ -242,11 +236,7 @@ export class NameLayer implements Layer {
|
||||
if (render.player.isTraitor()) {
|
||||
if (!existingTraitor) {
|
||||
iconsDiv.appendChild(
|
||||
this.createIconElement(
|
||||
this.traitorIconImage.src,
|
||||
iconSize,
|
||||
"traitor",
|
||||
),
|
||||
this.createIconElement(this.traitorIconImage.src, iconSize, "traitor")
|
||||
);
|
||||
}
|
||||
} else if (existingTraitor) {
|
||||
@@ -261,8 +251,8 @@ export class NameLayer implements Layer {
|
||||
this.createIconElement(
|
||||
this.allianceIconImage.src,
|
||||
iconSize,
|
||||
"alliance",
|
||||
),
|
||||
"alliance"
|
||||
)
|
||||
);
|
||||
}
|
||||
} else if (existingAlliance) {
|
||||
@@ -277,7 +267,7 @@ export class NameLayer implements Layer {
|
||||
) {
|
||||
if (!existingTarget) {
|
||||
iconsDiv.appendChild(
|
||||
this.createIconElement(this.targetIconImage.src, iconSize, "target"),
|
||||
this.createIconElement(this.targetIconImage.src, iconSize, "target")
|
||||
);
|
||||
}
|
||||
} else if (existingTarget) {
|
||||
@@ -291,7 +281,7 @@ export class NameLayer implements Layer {
|
||||
.filter(
|
||||
(emoji) =>
|
||||
emoji.recipientID == AllPlayers ||
|
||||
emoji.recipientID == myPlayer?.smallID(),
|
||||
emoji.recipientID == myPlayer?.smallID()
|
||||
);
|
||||
|
||||
if (emojis.length > 0) {
|
||||
@@ -324,7 +314,7 @@ export class NameLayer implements Layer {
|
||||
private createIconElement(
|
||||
src: string,
|
||||
size: number,
|
||||
id: string,
|
||||
id: string
|
||||
): HTMLImageElement {
|
||||
const icon = document.createElement("img");
|
||||
icon.src = src;
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
manhattanDistFN,
|
||||
TileRef,
|
||||
} from "../../../core/game/GameMap";
|
||||
import { GameUpdateType } from "../../../core/game/GameUpdates";
|
||||
|
||||
enum Relationship {
|
||||
Self,
|
||||
@@ -48,9 +49,9 @@ export class UnitLayer implements Layer {
|
||||
if (this.myPlayer == null) {
|
||||
this.myPlayer = this.game.playerByClientID(this.clientID);
|
||||
}
|
||||
for (const unit of this.game.units()) {
|
||||
if (unit.wasUpdated()) this.onUnitEvent(unit);
|
||||
}
|
||||
this.game.updatesSinceLastTick()?.[GameUpdateType.Unit]?.forEach((unit) => {
|
||||
this.onUnitEvent(this.game.unit(unit.id));
|
||||
});
|
||||
}
|
||||
|
||||
init() {
|
||||
@@ -79,9 +80,11 @@ export class UnitLayer implements Layer {
|
||||
|
||||
this.canvas.width = this.game.width();
|
||||
this.canvas.height = this.game.height();
|
||||
for (const unit of this.game.units()) {
|
||||
this.onUnitEvent(unit);
|
||||
}
|
||||
this.game
|
||||
?.updatesSinceLastTick()
|
||||
?.[GameUpdateType.Unit]?.forEach((unit) => {
|
||||
this.onUnitEvent(this.game.unit(unit.id));
|
||||
});
|
||||
}
|
||||
|
||||
private relationship(unit: UnitView): Relationship {
|
||||
|
||||
+21
-17
@@ -74,6 +74,11 @@
|
||||
gtag("js", new Date());
|
||||
gtag("config", "AW-16702609763");
|
||||
</script>
|
||||
<script
|
||||
async
|
||||
src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-7035513310742290"
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
</head>
|
||||
|
||||
<body
|
||||
@@ -81,19 +86,18 @@
|
||||
>
|
||||
<!-- Main container with responsive padding -->
|
||||
<!-- Logo section remains the same -->
|
||||
<div
|
||||
class="container mx-auto px-4 sm:px-6 lg:px-8 py-4 sm:py-6 lg:py-8 flex-grow"
|
||||
>
|
||||
<img
|
||||
src="../../resources/images/OpenFrontLogo.svg"
|
||||
alt="OpenFront.io"
|
||||
class="pt-6 md:pt-12 h-auto w-3/4 md:w-1/2 lg:w-1/3 mx-auto transform sm:scale-125 md:scale-150 lg:scale-175"
|
||||
/>
|
||||
<h3
|
||||
class="font-sans text-center text-black pb-6 md:pb-12 text-sm sm:text-base lg:text-lg"
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 py-4 sm:py-6 lg:py-8 flex-grow">
|
||||
<div class="flex justify-center">
|
||||
<img
|
||||
src="../../resources/images/OpenFrontLogo.png"
|
||||
alt="OpenFront.io"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="flex justify-center text-sm font-bold mt-[-10px] pb-6 md:pb-12"
|
||||
>
|
||||
(v0.15.0)
|
||||
</h3>
|
||||
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"
|
||||
@@ -161,15 +165,15 @@
|
||||
class="bottom-0 w-full flex-col-reverse sm:flex-row z-50"
|
||||
style="position: fixed; pointer-events: none"
|
||||
>
|
||||
<div class="" style="pointer-events: auto; max-width: max-content;">
|
||||
<control-panel></control-panel>
|
||||
</div>
|
||||
<div
|
||||
class="sm:fixed sm:right-0 sm:bottom-0 sm:flex justify-end"
|
||||
style="pointer-events: auto; max-width: max-content;"
|
||||
class="w-full sm:w-2/3 sm:fixed sm:right-0 sm:bottom-0 sm:flex justify-end"
|
||||
style="pointer-events: auto"
|
||||
>
|
||||
<events-display></events-display>
|
||||
</div>
|
||||
<div class="w-full sm:w-1/3" style="pointer-events: auto">
|
||||
<control-panel></control-panel>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer section -->
|
||||
|
||||
Reference in New Issue
Block a user