mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-18 17:53:29 +00:00
can leave lobby
This commit is contained in:
@@ -54,3 +54,4 @@
|
|||||||
* BUG: fix hotreload (priority queue breaks it)
|
* BUG: fix hotreload (priority queue breaks it)
|
||||||
* PERF: use hierarchical a* search for boats
|
* PERF: use hierarchical a* search for boats
|
||||||
* Add terrain elevation to map
|
* Add terrain elevation to map
|
||||||
|
* boats can go around the world
|
||||||
+32
-44
@@ -24,6 +24,7 @@ class Client {
|
|||||||
|
|
||||||
private lobbiesContainer: HTMLElement | null;
|
private lobbiesContainer: HTMLElement | null;
|
||||||
private lobbiesInterval: NodeJS.Timeout | null = null;
|
private lobbiesInterval: NodeJS.Timeout | null = null;
|
||||||
|
private isLobbyHighlighted: boolean = false;
|
||||||
|
|
||||||
private random = new PseudoRandom(1234)
|
private random = new PseudoRandom(1234)
|
||||||
|
|
||||||
@@ -52,34 +53,31 @@ class Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateLobbiesDisplay(lobbies: Lobby[]): void {
|
private updateLobbiesDisplay(lobbies: Lobby[]): void {
|
||||||
if (!this.lobbiesContainer) return;
|
if (lobbies.length === 0) {
|
||||||
|
document.getElementById('lobby-button').style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.lobbiesContainer.innerHTML = ''; // Clear existing lobbies
|
const lobby = lobbies[0];
|
||||||
|
const lobbyButton = document.getElementById('lobby-button');
|
||||||
|
const nameElement = document.getElementById('lobby-name');
|
||||||
|
const timerElement = document.getElementById('lobby-timer');
|
||||||
|
const playerCountElement = document.getElementById('player-count');
|
||||||
|
|
||||||
lobbies.forEach(lobby => {
|
if (lobbyButton) {
|
||||||
const button = document.createElement('button');
|
lobbyButton.style.display = 'flex';
|
||||||
button.className = 'lobby-button';
|
lobbyButton.onclick = () => this.joinLobby(lobby);
|
||||||
|
|
||||||
const nameElement = document.createElement('div');
|
// Preserve the highlighted state
|
||||||
nameElement.className = 'lobby-name';
|
lobbyButton.classList.toggle('highlighted', this.isLobbyHighlighted);
|
||||||
nameElement.textContent = `Lobby ${lobby.id}`;
|
}
|
||||||
|
|
||||||
const timerElement = document.createElement('div');
|
if (nameElement) nameElement.textContent = `Lobby ${lobby.id}`;
|
||||||
timerElement.className = 'lobby-timer';
|
if (timerElement) {
|
||||||
const timeRemaining = Math.max(0, Math.floor((lobby.startTime - Date.now()) / 1000));
|
const timeRemaining = Math.max(0, Math.floor((lobby.startTime - Date.now()) / 1000));
|
||||||
timerElement.textContent = `Starts in: ${timeRemaining}s`;
|
timerElement.textContent = `Starts in: ${timeRemaining}s`;
|
||||||
|
}
|
||||||
const playerCountElement = document.createElement('div');
|
if (playerCountElement) playerCountElement.textContent = `Players: ${lobby.numClients}`;
|
||||||
playerCountElement.className = 'player-count';
|
|
||||||
playerCountElement.textContent = `Players: ${lobby.numClients}`
|
|
||||||
|
|
||||||
button.appendChild(nameElement);
|
|
||||||
button.appendChild(timerElement);
|
|
||||||
button.appendChild(playerCountElement);
|
|
||||||
|
|
||||||
button.onclick = () => this.joinLobby(lobby);
|
|
||||||
this.lobbiesContainer.appendChild(button);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchLobbies(): Promise<Lobby[]> {
|
async fetchLobbies(): Promise<Lobby[]> {
|
||||||
@@ -97,25 +95,16 @@ class Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async joinLobby(lobby: Lobby) {
|
private joinLobby(lobby: Lobby) {
|
||||||
clearInterval(this.lobbiesInterval);
|
const lobbyButton = document.getElementById('lobby-button');
|
||||||
|
if (lobbyButton) {
|
||||||
if (this.lobbiesContainer) {
|
this.isLobbyHighlighted = !this.isLobbyHighlighted;
|
||||||
// Clear existing content
|
lobbyButton.classList.toggle('highlighted', this.isLobbyHighlighted);
|
||||||
this.lobbiesContainer.innerHTML = '';
|
}
|
||||||
|
if (!this.isLobbyHighlighted) {
|
||||||
// Ensure the container takes up the full height of the viewport
|
this.game.stop()
|
||||||
this.lobbiesContainer.style.display = 'flex';
|
this.game = null
|
||||||
this.lobbiesContainer.style.justifyContent = 'center';
|
return
|
||||||
this.lobbiesContainer.style.alignItems = 'center';
|
|
||||||
this.lobbiesContainer.style.minHeight = '100vh';
|
|
||||||
|
|
||||||
// Create and add the joining message
|
|
||||||
const joiningMessage = document.createElement('div');
|
|
||||||
joiningMessage.textContent = `Joining: ${lobby.id}`;
|
|
||||||
joiningMessage.className = 'joining-message';
|
|
||||||
|
|
||||||
this.lobbiesContainer.appendChild(joiningMessage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.game != null) {
|
if (this.game != null) {
|
||||||
@@ -123,11 +112,10 @@ class Client {
|
|||||||
}
|
}
|
||||||
this.game = createClientGame(getUsername(), new PseudoRandom(Date.now()).nextID(), lobby.id, getConfig(), this.terrainMap);
|
this.game = createClientGame(getUsername(), new PseudoRandom(Date.now()).nextID(), lobby.id, getConfig(), this.terrainMap);
|
||||||
this.game.join();
|
this.game.join();
|
||||||
const g = this.game
|
const g = this.game;
|
||||||
window.addEventListener('beforeunload', function (event) {
|
window.addEventListener('beforeunload', function (event) {
|
||||||
// Your function logic here
|
|
||||||
console.log('Browser is closing');
|
console.log('Browser is closing');
|
||||||
g.stop()
|
g.stop();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,9 @@ export class ClientGame {
|
|||||||
};
|
};
|
||||||
this.socket.onclose = (event: CloseEvent) => {
|
this.socket.onclose = (event: CloseEvent) => {
|
||||||
console.log(`WebSocket closed. Code: ${event.code}, Reason: ${event.reason}`);
|
console.log(`WebSocket closed. Code: ${event.code}, Reason: ${event.reason}`);
|
||||||
|
if (!this.isActive) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (event.code != 1000) {
|
if (event.code != 1000) {
|
||||||
this.join()
|
this.join()
|
||||||
}
|
}
|
||||||
@@ -126,6 +129,7 @@ export class ClientGame {
|
|||||||
clearInterval(this.intervalID)
|
clearInterval(this.intervalID)
|
||||||
this.isActive = false
|
this.isActive = false
|
||||||
if (this.socket.readyState === WebSocket.OPEN) {
|
if (this.socket.readyState === WebSocket.OPEN) {
|
||||||
|
console.log('on stop: leaving game')
|
||||||
const msg = ClientLeaveMessageSchema.parse({
|
const msg = ClientLeaveMessageSchema.parse({
|
||||||
type: "leave",
|
type: "leave",
|
||||||
clientID: this.id,
|
clientID: this.id,
|
||||||
|
|||||||
+17
-30
@@ -5,7 +5,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>OpenFront (ALPHA)</title>
|
<title>OpenFront (ALPHA)</title>
|
||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" type="text/css" href="styles.css">
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background-color: #15151500;
|
background-color: #15151500;
|
||||||
/* Dark grey background */
|
|
||||||
background-image: url('/resources/images/watercolor_worldmap.jpg');
|
background-image: url('/resources/images/watercolor_worldmap.jpg');
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
@@ -23,17 +22,16 @@
|
|||||||
|
|
||||||
.content {
|
.content {
|
||||||
max-width: 1020px;
|
max-width: 1020px;
|
||||||
/* Allows for two 500px buttons side by side with some margin */
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
font-family: Arial, serif;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #ffffff;
|
color: #2e2e2e;
|
||||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
font-size: 8em;
|
||||||
font-size: 2.5em;
|
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,25 +48,12 @@
|
|||||||
background-color: rgba(255, 255, 255, 0.8);
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.lobby-button:hover {
|
|
||||||
background-color: rgba(0, 123, 255, 0.4);
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.player-count {
|
|
||||||
font-size: 24px;
|
|
||||||
margin-top: 20px;
|
|
||||||
color: #aaddff;
|
|
||||||
}
|
|
||||||
|
|
||||||
#lobbies-container {
|
#lobbies-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.lobby-button {
|
.lobby-button {
|
||||||
width: 500px;
|
width: 500px;
|
||||||
height: 500px;
|
height: 500px;
|
||||||
@@ -96,6 +81,13 @@
|
|||||||
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.4);
|
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lobby-button.highlighted {
|
||||||
|
background-color: rgba(0, 123, 255, 0.6);
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 6px 20px rgba(0, 123, 255, 0.6);
|
||||||
|
border-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
.lobby-name {
|
.lobby-name {
|
||||||
font-size: 36px;
|
font-size: 36px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
@@ -117,15 +109,6 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-family: Overpass, serif;
|
|
||||||
text-align: center;
|
|
||||||
color: #3d3c3c;
|
|
||||||
/* text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); */
|
|
||||||
font-size: 8em;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -136,7 +119,11 @@
|
|||||||
<input type="text" id="username" placeholder="Enter your username">
|
<input type="text" id="username" placeholder="Enter your username">
|
||||||
</div>
|
</div>
|
||||||
<div id="lobbies-container">
|
<div id="lobbies-container">
|
||||||
<!-- Lobby buttons will be inserted here by your existing JavaScript -->
|
<button id="lobby-button" class="lobby-button">
|
||||||
|
<div id="lobby-name" class="lobby-name"></div>
|
||||||
|
<div id="lobby-timer" class="lobby-timer"></div>
|
||||||
|
<div id="player-count" class="player-count"></div>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -71,11 +71,11 @@ export class DefaultPlayerConfig implements PlayerConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
troopAdditionRate(player: Player): number {
|
troopAdditionRate(player: Player): number {
|
||||||
const max = Math.sqrt(player.numTilesOwned()) * 1000 + 1000
|
const max = Math.sqrt(player.numTilesOwned()) * 1000 + 10000
|
||||||
|
|
||||||
let toAdd = 10 + (player.troops() + Math.sqrt(player.troops() * player.numTilesOwned())) / 250
|
let toAdd = 10 + (player.troops() + Math.sqrt(player.troops() * player.numTilesOwned())) / 250
|
||||||
|
|
||||||
return Math.min(player.troops() + toAdd, max)
|
return Math.min(Math.min(player.troops() + toAdd, max), 1_000_0000)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import {DefaultConfig, DefaultPlayerConfig, defaultPlayerConfig} from "./Default
|
|||||||
|
|
||||||
export const devConfig = new class extends DefaultConfig {
|
export const devConfig = new class extends DefaultConfig {
|
||||||
gameCreationRate(): number {
|
gameCreationRate(): number {
|
||||||
return 2 * 1000
|
return 21 * 1000
|
||||||
}
|
}
|
||||||
lobbyLifetime(): number {
|
lobbyLifetime(): number {
|
||||||
return 5 * 1000
|
return 20 * 1000
|
||||||
}
|
}
|
||||||
turnIntervalMs(): number {
|
turnIntervalMs(): number {
|
||||||
return 100
|
return 100
|
||||||
@@ -16,7 +16,7 @@ export const devConfig = new class extends DefaultConfig {
|
|||||||
return devPlayerConfig
|
return devPlayerConfig
|
||||||
}
|
}
|
||||||
numBots(): number {
|
numBots(): number {
|
||||||
return 500
|
return 250
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,6 +25,6 @@ export const devPlayerConfig = new class extends DefaultPlayerConfig {
|
|||||||
if (playerInfo.isBot) {
|
if (playerInfo.isBot) {
|
||||||
return 5000
|
return 5000
|
||||||
}
|
}
|
||||||
return 5000
|
return 10000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,6 +43,7 @@ export class GameServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (clientMsg.type == "leave") {
|
if (clientMsg.type == "leave") {
|
||||||
|
// TODO: get rid of leave message, just use on close?
|
||||||
const toRemove = this.clients.filter(c => c.id)
|
const toRemove = this.clients.filter(c => c.id)
|
||||||
if (toRemove.length == 0) {
|
if (toRemove.length == 0) {
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user