mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-26 12:04:37 +00:00
Merge branch 'openfrontio:main' into scrollAttackRatio
This commit is contained in:
+2
-1
@@ -1,4 +1,5 @@
|
||||
*.bin
|
||||
*.svg
|
||||
*.png
|
||||
*.jpg
|
||||
*.jpeg
|
||||
@@ -6,4 +7,4 @@
|
||||
*.webp
|
||||
*.txt
|
||||
.prettierignore
|
||||
.gitignore
|
||||
.gitignore
|
||||
|
||||
@@ -1,29 +1,104 @@
|
||||
# OpenFront.io
|
||||
# OpenFrontIO
|
||||
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="resources/images/OpenFrontLogoDark.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="resources/images/OpenFrontLogo.svg">
|
||||
<img src="resources/images/OpenFrontLogo.svg" alt="OpenFrontIO Logo" width="300">
|
||||
</picture>
|
||||
</p>
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 75.294 75.294" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M66.097,12.089h-56.9C4.126,12.089,0,16.215,0,21.286v32.722c0,5.071,4.126,9.197,9.197,9.197h56.9
|
||||
c5.071,0,9.197-4.126,9.197-9.197V21.287C75.295,16.215,71.169,12.089,66.097,12.089z M61.603,18.089L37.647,33.523L13.691,18.089
|
||||
H61.603z M66.097,57.206h-56.9C7.434,57.206,6,55.771,6,54.009V21.457l29.796,19.16c0.04,0.025,0.083,0.042,0.124,0.065
|
||||
c0.043,0.024,0.087,0.047,0.131,0.069c0.231,0.119,0.469,0.215,0.712,0.278c0.025,0.007,0.05,0.01,0.075,0.016
|
||||
c0.267,0.063,0.537,0.102,0.807,0.102c0.001,0,0.002,0,0.002,0c0.002,0,0.003,0,0.004,0c0.27,0,0.54-0.038,0.807-0.102
|
||||
c0.025-0.006,0.05-0.009,0.075-0.016c0.243-0.063,0.48-0.159,0.712-0.278c0.044-0.022,0.088-0.045,0.131-0.069
|
||||
c0.041-0.023,0.084-0.04,0.124-0.065l29.796-19.16v32.551C69.295,55.771,67.86,57.206,66.097,57.206z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1364 259" width="100%" height="100%" fill="currentColor">
|
||||
<g>
|
||||
<path d="M0,174V51h15.24v-17.14h16.81v-16.98h16.96V0h1266v17.23h17.13v16.81h16.98v16.96h14.88v123h-15.13v17.08h-17.08v17.08h-16.9v17.04H324.9v16.86h-16.9v16.95h-102v-17.12h-17.07v-17.05H48.73v-17.05h-16.89v-16.89H14.94v-16.89H0ZM1297.95,17.35H65.9v16.7h-17.08v17.08h-14.5v123.08h14.85v16.9h17.08v17.08h139.9v17.08h17.08v16.36h67.9v-16.72h17.08v-17.07h989.88v-17.07h17.08v-16.9h14.44V50.8h-14.75v-17.08h-16.9v-16.37Z" fill="#ffffff"/>
|
||||
<path d="M189.1,154.78v17.07h-16.9v16.75h-51.07v-16.42h-16.9v-17.07h-16.97v-84.88h16.63v-17.07h16.9v-16.84h51.07v16.5h17.07v17.07h16.7v84.89h-16.54ZM137.87,53.1v17.15h-16.6v84.86h16.97v16.61h16.89v-16.97h16.6v-84.86h-16.97v-16.79h-16.89Z" fill="#ffffff"/>
|
||||
<path d="M273.91,104.06v-16.73h50.92v16.45h16.85v68.05h-16.44v17.06h-50.96v16.88h16.4v16.96h-67.28v-16.61h16.33v-101.86h-16.38v-16.98h33.4v16.63c6.12,0,11.72,0,17.31,0,0,22.56,0,45.13,0,67.75h33.59v-67.61h-33.73Z" fill="#ffffff"/>
|
||||
<path d="M631.12,188.64v-16.36h16.53V53.2h-16.25v-16.86h118.33v33.29h-16.65v-16.36h-50.72v50.44h33.36v-16.35h16.99v50.25h-16.6v-16.33h-33.73v50.65h16.37v16.72h-67.63Z" fill="#ffffff"/>
|
||||
<path d="M596.78,103.8v84.94h-33.54v-84.39h-34.03v84.25h-33.85v-101.29h84.5v16.49h16.93Z" fill="#ffffff"/>
|
||||
<path d="M1107.12,188.71v-84.34h-34.03v84.37h-33.7v-101.41h84.42v16.41h16.86v84.96h-33.54Z" fill="#ffffff"/>
|
||||
<path d="M988.1,171.78v16.87h-67.88v-16.38h-16.87v-68.06h16.38v-16.87h68.06v16.38h16.87v68.06h-16.55ZM970.78,104.35h-33.39v67.38h33.39v-67.38Z" fill="#ffffff"/>
|
||||
<path d="M460.77,155.38v16.49h-16.58v16.83h-68.05v-16.5h-16.83v-68.05h16.49v-16.83h68.05v16.49h16.83v34.06h-67.31v33.82h33.47v-16.31h33.92ZM393.39,104.18v16.56h33.3v-16.56h-33.3Z" fill="#ffffff"/>
|
||||
<path d="M1209.13,172h-16.9v-67.9h-16.96v-16.9h16.68v-17.08h16.9v-16.82h16.9v33.58h50.98v16.91h-50.4v67.96h16.48v-16.43h50.95v16.54h-16.55v16.76h-68.08v-16.6Z" fill="#ffffff"/>
|
||||
<path d="M834.91,120.94v16.96h-16.65v33.88h16.41v16.96h-67.29v-16.63h16.34v-67.87h-16.4v-16.97h50.42v33.81h17.3l-.14-.14Z" fill="#ffffff"/>
|
||||
<path d="M835.05,121.08v-33.75h33.76v16.43h16.85v33.96h-33.43v-16.79c-6.13,0-11.73,0-17.32,0,0,0,.14.14.14.14Z" fill="#ffffff"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
+4
-1
@@ -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(),
|
||||
|
||||
@@ -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`
|
||||
<button
|
||||
@@ -225,6 +249,15 @@ export class Leaderboard extends LitElement implements Layer {
|
||||
>
|
||||
Hide
|
||||
</button>
|
||||
<button
|
||||
class="leaderboard-top-five-button"
|
||||
@click=${() => {
|
||||
this.showTopFive = !this.showTopFive;
|
||||
this.updateLeaderboard();
|
||||
}}
|
||||
>
|
||||
${this.showTopFive ? "Show All" : "Show Top 5"}
|
||||
</button>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -232,6 +265,7 @@ export class Leaderboard extends LitElement implements Layer {
|
||||
<th>Player</th>
|
||||
<th>Owned</th>
|
||||
<th>Gold</th>
|
||||
<th>Troops</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -245,6 +279,7 @@ export class Leaderboard extends LitElement implements Layer {
|
||||
<td class="player-name">${unsafeHTML(player.name)}</td>
|
||||
<td>${player.score}</td>
|
||||
<td>${player.gold}</td>
|
||||
<td>${player.troops}</td>
|
||||
</tr>
|
||||
`,
|
||||
)}
|
||||
|
||||
@@ -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<PlayerView> = 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 (
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<PlayerProfile> {
|
||||
return this.game.worker.playerProfile(this.smallID());
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user