From 1efc95001d86384b03dd530ba96901732862181f Mon Sep 17 00:00:00 2001 From: Andreas Rey Malissein Date: Sat, 1 Mar 2025 02:29:05 +0100 Subject: [PATCH 1/4] possibility to see the whole scoreboard or the top 5 only + show players troops in leaderboards --- src/client/graphics/layers/Leaderboard.ts | 81 ++++++++++++++++------- 1 file changed, 58 insertions(+), 23 deletions(-) diff --git a/src/client/graphics/layers/Leaderboard.ts b/src/client/graphics/layers/Leaderboard.ts index d2a146f29..95c6b9789 100644 --- a/src/client/graphics/layers/Leaderboard.ts +++ b/src/client/graphics/layers/Leaderboard.ts @@ -1,17 +1,18 @@ -import { LitElement, html, css } from "lit"; -import { customElement, property, state } from "lit/decorators.js"; -import { Layer } from "./Layer"; -import { ClientID } from "../../../core/Schemas"; +import { LitElement, css, html } from "lit"; +import { customElement, state } from "lit/decorators.js"; import { unsafeHTML } from "lit/directives/unsafe-html.js"; import { EventBus, GameEvent } from "../../../core/EventBus"; -import { renderNumber } from "../../Utils"; import { GameView, PlayerView } from "../../../core/game/GameView"; +import { ClientID } from "../../../core/Schemas"; +import { renderNumber } from "../../Utils"; +import { Layer } from "./Layer"; interface Entry { name: string; position: number; score: string; gold: string; + troops: string; isMyPlayer: boolean; player: PlayerView; } @@ -26,6 +27,13 @@ export class Leaderboard extends LitElement implements Layer { public clientID: ClientID; public eventBus: EventBus; + players: Entry[] = []; + + @state() + private _leaderboardHidden = true; + private _shownOnInit = false; + private showTopFive = true; + init() {} tick() { @@ -58,14 +66,25 @@ export class Leaderboard extends LitElement implements Layer { const numTilesWithoutFallout = this.game.numLandTiles() - this.game.numTilesWithFallout(); - this.players = sorted.slice(0, 5).map((player, index) => ({ - name: player.displayName(), - position: index + 1, - score: formatPercentage(player.numTilesOwned() / numTilesWithoutFallout), - gold: renderNumber(player.gold()), - isMyPlayer: player == myPlayer, - player: player, - })); + const playersToShow = this.showTopFive ? sorted.slice(0, 5) : sorted; + + this.players = playersToShow.map((player, index) => { + let troops = player.troops() / 10; + if (!player.isAlive()) { + troops = 0; + } + return { + name: player.displayName(), + position: index + 1, + score: formatPercentage( + player.numTilesOwned() / numTilesWithoutFallout, + ), + gold: renderNumber(player.gold()), + troops: renderNumber(troops), + isMyPlayer: player == myPlayer, + player: player, + }; + }); if (myPlayer != null && this.players.find((p) => p.isMyPlayer) == null) { let place = 0; @@ -76,6 +95,10 @@ export class Leaderboard extends LitElement implements Layer { } } + let myPlayerTroops = myPlayer.troops() / 10; + if (!myPlayer.isAlive()) { + myPlayerTroops = 0; + } this.players.pop(); this.players.push({ name: myPlayer.displayName(), @@ -84,6 +107,7 @@ export class Leaderboard extends LitElement implements Layer { myPlayer.numTilesOwned() / this.game.numLandTiles(), ), gold: renderNumber(myPlayer.gold()), + troops: renderNumber(myPlayerTroops), isMyPlayer: true, player: myPlayer, }); @@ -119,10 +143,10 @@ export class Leaderboard extends LitElement implements Layer { padding-top: 0px; box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); border-radius: 10px; - max-width: 300px; - max-height: 80vh; + max-width: 500px; + max-height: 30vh; overflow-y: auto; - width: 300px; + width: 400px; backdrop-filter: blur(5px); } table { @@ -180,6 +204,13 @@ export class Leaderboard extends LitElement implements Layer { cursor: pointer; } + .leaderboard-top-five-button { + background: none; + border: none; + color: white; + cursor: pointer; + } + .player-name { max-width: 10ch; overflow: hidden; @@ -198,13 +229,6 @@ export class Leaderboard extends LitElement implements Layer { } `; - players: Entry[] = []; - - @state() - private _leaderboardHidden = true; - - private _shownOnInit = false; - render() { return html` + @@ -232,6 +265,7 @@ export class Leaderboard extends LitElement implements Layer { + @@ -245,6 +279,7 @@ export class Leaderboard extends LitElement implements Layer { + `, )} From 1ffa06b5a8f425949090026c9743d838b4e3ed3e Mon Sep 17 00:00:00 2001 From: PilkeySEK Date: Sat, 1 Mar 2025 09:12:43 +0100 Subject: [PATCH 2/4] dont send the non-flag to the server --- src/client/Main.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/client/Main.ts b/src/client/Main.ts index ed24a6ff8..8804a0d65 100644 --- a/src/client/Main.ts +++ b/src/client/Main.ts @@ -143,7 +143,10 @@ class Client { this.gameStop = joinLobby( { gameType: gameType, - flag: (): string => this.flagInput.getCurrentFlag(), + flag: (): string => + this.flagInput.getCurrentFlag() == "xx" + ? "" + : this.flagInput.getCurrentFlag(), playerName: (): string => this.usernameInput.getCurrentUsername(), gameID: lobby.id, persistentID: getPersistentIDFromCookie(), From ae1ca1046f836b0d7e7c556fb436f1ce88a986db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Jurkovi=C4=87?= Date: Sat, 1 Mar 2025 21:25:31 +0100 Subject: [PATCH 3/4] expanded and prettified readme --- README.md | 97 +++++++++++++++++++++++--- resources/images/OpenFrontLogoDark.svg | 15 ++++ 2 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 resources/images/OpenFrontLogoDark.svg diff --git a/README.md b/README.md index 7b73c8687..e3765ca1f 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,104 @@ -# OpenFront.io +# OpenFrontIO + +

+ + + + OpenFrontIO Logo + +

![Prettier Check](https://github.com/openfrontio/OpenFrontIO/actions/workflows/prettier.yml/badge.svg) -OpenFront is an online rts. +OpenFront is an online real-time strategy game focused on territorial control and alliance building. Players compete to expand their territory, build structures, and form strategic alliances in various maps based on real-world geography. This is a fork/rewrite of WarFront.io. Credit to https://github.com/WarFrontIO. -## Building +## 🌟 Features -To build the project, you will need to have Node.js and npm installed. +- **Real-time Strategy Gameplay**: Expand your territory and engage in strategic battles +- **Alliance System**: Form alliances with other players for mutual defense +- **Multiple Maps**: Play across various geographical regions including Europe, Asia, Africa, and more +- **Resource Management**: Balance your expansion with defensive capabilities +- **Cross-platform**: Play in any modern web browser -Before building the project, install the dependencies: +## 📋 Prerequisites -```bash -npm install -``` +- [Node.js](https://nodejs.org/) (v16.x or higher) +- [npm](https://www.npmjs.com/) (v8.x or higher) +- A modern web browser (Chrome, Firefox, Edge, etc.) -To run dev build: +## 🚀 Installation + +1. **Clone the repository** + + ```bash + git clone https://github.com/openfrontio/OpenFrontIO.git + cd OpenFrontIO + ``` + +2. **Install dependencies** + + ```bash + npm install + ``` + +## 🎮 Running the Game + +### Development Mode + +Run both the client and server in development mode with live reloading: ```bash npm run dev ``` -Make sure to format code using prettier extension or by running: +This will: + +- Start the webpack dev server for the client +- Launch the game server with development settings +- Open the game in your default browser + +### Client Only + +To run just the client with hot reloading: ```bash -npm run format +npm run start:client ``` + +### Server Only + +To run just the server with development settings: + +```bash +npm run start:server-dev +``` + +## 🛠️ Development Tools + +- **Format code**: + ```bash + npm run format + ``` + +## 🏗️ Project Structure + +- `/src/client` - Frontend game client +- `/src/core` - Shared game logic +- `/src/server` - Backend game server +- `/resources` - Static assets (images, maps, etc.) + +## 📝 License + +This project is licensed under the terms found in the [LICENSE](LICENSE) file. + +## 🤝 Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +1. Fork the repository +2. Create your feature branch (`git checkout -b amazing-feature`) +3. Commit your changes (`git commit -m 'Add some amazing feature'`) +4. Push to the branch (`git push origin amazing-feature`) +5. Open a Pull Request diff --git a/resources/images/OpenFrontLogoDark.svg b/resources/images/OpenFrontLogoDark.svg new file mode 100644 index 000000000..2e2600480 --- /dev/null +++ b/resources/images/OpenFrontLogoDark.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file From c2b90a52a3e711a8a69ed3a00fd47e7995adb8be Mon Sep 17 00:00:00 2001 From: ilan schemoul Date: Sun, 2 Mar 2025 00:13:05 +0100 Subject: [PATCH 4/4] feat: add icon when player request alliance To spot who asked alliance on the map add an email icon next to them --- .prettierignore | 3 ++- resources/images/AllianceRequestIcon.svg | 14 ++++++++++++++ src/client/graphics/layers/NameLayer.ts | 21 +++++++++++++++++++++ src/core/game/GameUpdates.ts | 2 ++ src/core/game/GameView.ts | 4 ++++ src/core/game/PlayerImpl.ts | 5 +++++ 6 files changed, 48 insertions(+), 1 deletion(-) create mode 100755 resources/images/AllianceRequestIcon.svg diff --git a/.prettierignore b/.prettierignore index 8ae3f9b70..584bf56da 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,5 @@ *.bin +*.svg *.png *.jpg *.jpeg @@ -6,4 +7,4 @@ *.webp *.txt .prettierignore -.gitignore \ No newline at end of file +.gitignore diff --git a/resources/images/AllianceRequestIcon.svg b/resources/images/AllianceRequestIcon.svg new file mode 100755 index 000000000..f5981e241 --- /dev/null +++ b/resources/images/AllianceRequestIcon.svg @@ -0,0 +1,14 @@ + + + + + + + \ No newline at end of file diff --git a/src/client/graphics/layers/NameLayer.ts b/src/client/graphics/layers/NameLayer.ts index 555627039..734507917 100644 --- a/src/client/graphics/layers/NameLayer.ts +++ b/src/client/graphics/layers/NameLayer.ts @@ -11,6 +11,7 @@ import { Layer } from "./Layer"; import { TransformHandler } from "../TransformHandler"; import traitorIcon from "../../../../resources/images/TraitorIcon.svg"; import allianceIcon from "../../../../resources/images/AllianceIcon.svg"; +import allianceRequestIcon from "../../../../resources/images/AllianceRequestIcon.svg"; import crownIcon from "../../../../resources/images/CrownIcon.svg"; import targetIcon from "../../../../resources/images/TargetIcon.svg"; import { ClientID } from "../../../core/Schemas"; @@ -40,6 +41,7 @@ export class NameLayer implements Layer { private renders: RenderInfo[] = []; private seenPlayers: Set = new Set(); private traitorIconImage: HTMLImageElement; + private allianceRequestIconImage: HTMLImageElement; private allianceIconImage: HTMLImageElement; private targetIconImage: HTMLImageElement; private crownIconImage: HTMLImageElement; @@ -57,6 +59,8 @@ export class NameLayer implements Layer { this.traitorIconImage.src = traitorIcon; this.allianceIconImage = new Image(); this.allianceIconImage.src = allianceIcon; + this.allianceRequestIconImage = new Image(); + this.allianceRequestIconImage.src = allianceRequestIcon; this.crownIconImage = new Image(); this.crownIconImage.src = crownIcon; this.targetIconImage = new Image(); @@ -314,6 +318,23 @@ export class NameLayer implements Layer { existingAlliance.remove(); } + // Alliance request icon + const data = '[data-icon="alliance-request"]'; + const existingRequestAlliance = iconsDiv.querySelector(data); + if (myPlayer != null && render.player.isRequestingAllianceWith(myPlayer)) { + if (!existingRequestAlliance) { + iconsDiv.appendChild( + this.createIconElement( + this.allianceRequestIconImage.src, + iconSize, + "alliance-request", + ), + ); + } + } else if (existingRequestAlliance) { + existingRequestAlliance.remove(); + } + // Target icon const existingTarget = iconsDiv.querySelector('[data-icon="target"]'); if ( diff --git a/src/core/game/GameUpdates.ts b/src/core/game/GameUpdates.ts index 815136884..d3896eee0 100644 --- a/src/core/game/GameUpdates.ts +++ b/src/core/game/GameUpdates.ts @@ -1,5 +1,6 @@ import { ClientID } from "../Schemas"; import { + AllianceRequest, EmojiMessage, GameUpdates, MapPos, @@ -99,6 +100,7 @@ export interface PlayerUpdate { outgoingEmojis: EmojiMessage[]; outgoingAttacks: AttackUpdate[]; incomingAttacks: AttackUpdate[]; + outgoingAllianceRequests: PlayerID[]; } export interface AllianceRequestUpdate { diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts index 9370c8895..ad1e9622a 100644 --- a/src/core/game/GameView.ts +++ b/src/core/game/GameView.ts @@ -187,6 +187,10 @@ export class PlayerView { return this.data.allies.some((n) => other.smallID() == n); } + isRequestingAllianceWith(other: PlayerView) { + return this.data.outgoingAllianceRequests.some((id) => other.id() == id); + } + profile(): Promise { return this.game.worker.playerProfile(this.smallID()); } diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index c1019c59a..0ca8e55ee 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -102,6 +102,10 @@ export class PlayerImpl implements Player { largestClusterBoundingBox: { min: Cell; max: Cell } | null; toUpdate(): PlayerUpdate { + const outgoingAllianceRequests = this.outgoingAllianceRequests().map((ar) => + ar.recipient().id(), + ); + return { type: GameUpdateType.Player, clientID: this.clientID(), @@ -138,6 +142,7 @@ export class PlayerImpl implements Player { troops: a.troops(), }) as AttackUpdate, ), + outgoingAllianceRequests: outgoingAllianceRequests, }; }
Player Owned GoldTroops
${unsafeHTML(player.name)} ${player.score} ${player.gold}${player.troops}