Merge branch 'main' of github.com:openfrontio/OpenFrontIO into feature/eslint

This commit is contained in:
BeGj
2025-03-07 16:52:42 +00:00
13 changed files with 3215 additions and 19463 deletions
+15
View File
@@ -0,0 +1,15 @@
node_modules
Dockerfile*
docker-compose*
.dockerignore
.git
.gitignore
README.md
LICENSE
.vscode
Makefile
helm-charts
.env
.editorconfig
.idea
coverage*
+6 -6
View File
@@ -8,14 +8,14 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
submodules: false submodules: false
- name: Setup node - name: Setup bun
uses: actions/setup-node@v4 uses: oven-sh/setup-bun@v2
with: with:
node-version: 20 bun-version: 1.2.4
- name: Setup npm - name: install packages
run: npm install run: bun install
- name: Build - name: Build
run: npm run build-prod run: bun run build-prod
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
path: out/index.html path: out/index.html
+4 -5
View File
@@ -10,9 +10,8 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: oven-sh/setup-bun@v2
with: with:
node-version: "20" bun-version: 1.2.4
cache: "npm" - run: bun i
- run: npm ci - run: bunx prettier --check .
- run: npx prettier --check .
-16
View File
@@ -1,16 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/src/server/GameManager.ts",
"outFiles": ["${workspaceFolder}/**/*.js"]
}
]
}
+4 -4
View File
@@ -1,5 +1,5 @@
# Use an official Node runtime as the base image # Use an official Node runtime as the base image
FROM node:18 FROM oven/bun:1
# Add environment variable # Add environment variable
ARG GAME_ENV=prod ARG GAME_ENV=prod
@@ -13,18 +13,18 @@ RUN apt-get update && apt-get install -y nginx supervisor git && \
WORKDIR /usr/src/app WORKDIR /usr/src/app
# Copy package.json and package-lock.json # Copy package.json and package-lock.json
COPY package*.json ./ COPY package.json bun.lock ./
# Install dependencies while bypassing Husky hooks # Install dependencies while bypassing Husky hooks
ENV HUSKY=0 ENV HUSKY=0
ENV NPM_CONFIG_IGNORE_SCRIPTS=1 ENV NPM_CONFIG_IGNORE_SCRIPTS=1
RUN mkdir -p .git && npm install --include=dev RUN mkdir -p .git && bun install --include=dev
# Copy the rest of the application code # Copy the rest of the application code
COPY . . COPY . .
# Build the client-side application # Build the client-side application
RUN npm run build-prod RUN bun run build-prod
ENV NODE_ENV=production ENV NODE_ENV=production
+6 -7
View File
@@ -24,8 +24,7 @@ This is a fork/rewrite of WarFront.io. Credit to https://github.com/WarFrontIO.
## 📋 Prerequisites ## 📋 Prerequisites
- [Node.js](https://nodejs.org/) (v16.x or higher) - [Bun.js](https://bun.sh/) (v1.2.4 or higher)
- [npm](https://www.npmjs.com/) (v8.x or higher)
- A modern web browser (Chrome, Firefox, Edge, etc.) - A modern web browser (Chrome, Firefox, Edge, etc.)
## 🚀 Installation ## 🚀 Installation
@@ -40,7 +39,7 @@ This is a fork/rewrite of WarFront.io. Credit to https://github.com/WarFrontIO.
2. **Install dependencies** 2. **Install dependencies**
```bash ```bash
npm install bun i
``` ```
## 🎮 Running the Game ## 🎮 Running the Game
@@ -50,7 +49,7 @@ This is a fork/rewrite of WarFront.io. Credit to https://github.com/WarFrontIO.
Run both the client and server in development mode with live reloading: Run both the client and server in development mode with live reloading:
```bash ```bash
npm run dev bun run dev
``` ```
This will: This will:
@@ -64,7 +63,7 @@ This will:
To run just the client with hot reloading: To run just the client with hot reloading:
```bash ```bash
npm run start:client bun run start:client
``` ```
### Server Only ### Server Only
@@ -72,7 +71,7 @@ npm run start:client
To run just the server with development settings: To run just the server with development settings:
```bash ```bash
npm run start:server-dev bun run start:server-dev
``` ```
## 🛠️ Development Tools ## 🛠️ Development Tools
@@ -80,7 +79,7 @@ npm run start:server-dev
- **Format code**: - **Format code**:
```bash ```bash
npm run format bun run format
``` ```
- **Lint code**: - **Lint code**:
+3083
View File
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
services:
openfront:
build: .
ports:
- "80:80"
- "443:443"
env_file:
- .env
-19364
View File
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -1,14 +1,14 @@
{ {
"name": "openfront-client", "name": "openfront-client",
"scripts": { "scripts": {
"build-map": "node --loader ts-node/esm --experimental-specifier-resolution=node src/scripts/TerrainMapGenerator.ts", "build-map": "bun src/scripts/TerrainMapGenerator.ts",
"build-dev": "webpack --config webpack.config.js --mode development", "build-dev": "webpack --config webpack.config.js --mode development",
"build-prod": "webpack --config webpack.config.js --mode production", "build-prod": "webpack --config webpack.config.js --mode production",
"start:client": "webpack serve --open --node-env development", "start:client": "webpack serve --open --node-env development",
"start:server": "node --loader ts-node/esm --experimental-specifier-resolution=node src/server/Server.ts", "start:server": "bun src/server/Server.ts",
"start:server-dev": "cross-env GAME_ENV=dev node --loader ts-node/esm --experimental-specifier-resolution=node src/server/Server.ts", "start:server-dev": "cross-env GAME_ENV=dev bun src/server/Server.ts",
"dev": "cross-env GAME_ENV=dev concurrently \"npm run start:client\" \"npm run start:server-dev\"", "dev": "cross-env GAME_ENV=dev concurrently \"bun run start:client\" \"bun run start:server-dev\"",
"tunnel": "npm run build-prod && npm run start:server", "tunnel": "bun run build-prod && bun run start:server",
"test": "jest", "test": "jest",
"format": "prettier --ignore-unknown --write .", "format": "prettier --ignore-unknown --write .",
"lint": "eslint", "lint": "eslint",
@@ -30,7 +30,7 @@
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
"@types/jquery": "^3.5.31", "@types/jquery": "^3.5.31",
"@types/mocha": "^10.0.7", "@types/mocha": "^10.0.7",
"@types/node": "^22.10.2", "@types/bun": "latest",
"@types/pg": "^8.11.11", "@types/pg": "^8.11.11",
"@types/sinon": "^17.0.3", "@types/sinon": "^17.0.3",
"@types/systeminformation": "^3.23.1", "@types/systeminformation": "^3.23.1",
+57 -29
View File
@@ -24,7 +24,9 @@ import {
CancelAttackIntentEvent, CancelAttackIntentEvent,
SendAllianceReplyIntentEvent, SendAllianceReplyIntentEvent,
} from "../../Transport"; } from "../../Transport";
import { unsafeHTML } from "lit/directives/unsafe-html.js"; import { unsafeHTML, UnsafeHTMLDirective } from "lit/directives/unsafe-html.js";
import { DirectiveResult } from "lit/directive.js";
import { onlyImages, sanitize } from "../../../core/Util"; import { onlyImages, sanitize } from "../../../core/Util";
import { GameView, PlayerView } from "../../../core/game/GameView"; import { GameView, PlayerView } from "../../../core/game/GameView";
import { renderTroops } from "../../Utils"; import { renderTroops } from "../../Utils";
@@ -46,6 +48,7 @@ interface Event {
// lower number: lower on the display // lower number: lower on the display
priority?: number; priority?: number;
duration?: Tick; duration?: Tick;
focusID?: number;
} }
@customElement("events-display") @customElement("events-display")
@@ -218,6 +221,7 @@ export class EventsDisplay extends LitElement implements Layer {
), ),
priority: 0, priority: 0,
duration: 150, duration: 150,
focusID: update.requestorID,
}); });
} }
@@ -238,6 +242,7 @@ export class EventsDisplay extends LitElement implements Layer {
type: update.accepted ? MessageType.SUCCESS : MessageType.ERROR, type: update.accepted ? MessageType.SUCCESS : MessageType.ERROR,
highlight: true, highlight: true,
createdAt: this.game.ticks(), createdAt: this.game.ticks(),
focusID: update.request.recipientID,
}); });
} }
@@ -254,6 +259,7 @@ export class EventsDisplay extends LitElement implements Layer {
type: MessageType.ERROR, type: MessageType.ERROR,
highlight: true, highlight: true,
createdAt: this.game.ticks(), createdAt: this.game.ticks(),
focusID: update.betrayedID,
}); });
} else if (betrayed === myPlayer) { } else if (betrayed === myPlayer) {
this.addEvent({ this.addEvent({
@@ -261,6 +267,7 @@ export class EventsDisplay extends LitElement implements Layer {
type: MessageType.ERROR, type: MessageType.ERROR,
highlight: true, highlight: true,
createdAt: this.game.ticks(), createdAt: this.game.ticks(),
focusID: update.traitorID,
}); });
} }
} }
@@ -283,6 +290,7 @@ export class EventsDisplay extends LitElement implements Layer {
type: MessageType.WARN, type: MessageType.WARN,
highlight: true, highlight: true,
createdAt: this.game.ticks(), createdAt: this.game.ticks(),
focusID: otherID,
}); });
} }
@@ -298,6 +306,7 @@ export class EventsDisplay extends LitElement implements Layer {
type: MessageType.INFO, type: MessageType.INFO,
highlight: true, highlight: true,
createdAt: this.game.ticks(), createdAt: this.game.ticks(),
focusID: event.targetID,
}); });
} }
@@ -307,6 +316,12 @@ export class EventsDisplay extends LitElement implements Layer {
this.eventBus.emit(new CancelAttackIntentEvent(myPlayer.id(), id)); this.eventBus.emit(new CancelAttackIntentEvent(myPlayer.id(), id));
} }
emitGoToPlayerEvent(attackerID: number) {
const attacker = this.game.playerBySmallID(attackerID) as PlayerView;
if (!attacker) return;
this.eventBus.emit(new GoToPlayerEvent(attacker));
}
onEmojiMessageEvent(update: EmojiUpdate) { onEmojiMessageEvent(update: EmojiUpdate) {
const myPlayer = this.game.playerByClientID(this.clientID); const myPlayer = this.game.playerByClientID(this.clientID);
if (!myPlayer) return; if (!myPlayer) return;
@@ -326,6 +341,7 @@ export class EventsDisplay extends LitElement implements Layer {
type: MessageType.INFO, type: MessageType.INFO,
highlight: true, highlight: true,
createdAt: this.game.ticks(), createdAt: this.game.ticks(),
focusID: update.emoji.senderID,
}); });
} else if (sender === myPlayer && recipient !== AllPlayers) { } else if (sender === myPlayer && recipient !== AllPlayers) {
this.addEvent({ this.addEvent({
@@ -336,6 +352,7 @@ export class EventsDisplay extends LitElement implements Layer {
type: MessageType.INFO, type: MessageType.INFO,
highlight: true, highlight: true,
createdAt: this.game.ticks(), createdAt: this.game.ticks(),
focusID: recipient.smallID(),
}); });
} }
} }
@@ -355,6 +372,14 @@ export class EventsDisplay extends LitElement implements Layer {
} }
} }
private getEventDescription(
event: Event,
): string | DirectiveResult<typeof UnsafeHTMLDirective> {
return event.unsafeDescription
? unsafeHTML(onlyImages(event.description))
: event.description;
}
private renderAttacks() { private renderAttacks() {
if ( if (
this.incomingAttacks.length === 0 && this.incomingAttacks.length === 0 &&
@@ -370,25 +395,18 @@ export class EventsDisplay extends LitElement implements Layer {
<td class="lg:p-3 p-1 text-left text-red-400"> <td class="lg:p-3 p-1 text-left text-red-400">
${this.incomingAttacks.map( ${this.incomingAttacks.map(
(attack) => html` (attack) => html`
<div class="ml-2"> <button
class="ml-2"
@click=${() =>
this.emitGoToPlayerEvent(attack.attackerID)}
>
${renderTroops(attack.troops)} ${renderTroops(attack.troops)}
${( ${(
this.game.playerBySmallID( this.game.playerBySmallID(
attack.attackerID, attack.attackerID,
) as PlayerView ) as PlayerView
)?.name()} )?.name()}
<button </button>
class="inline-block px-2 text-white rounded text-sm cursor-pointer transition-colors duration-300 bg-blue-500 hover:bg-blue-600 btn-gray"
@click=${() => {
const attacker = this.game.playerBySmallID(
attack.attackerID,
) as PlayerView;
this.eventBus.emit(new GoToPlayerEvent(attacker));
}}
>
Focus
</button>
</div>
`, `,
)} )}
</td> </td>
@@ -401,22 +419,26 @@ export class EventsDisplay extends LitElement implements Layer {
<td class="lg:p-3 p-1 text-left text-blue-400"> <td class="lg:p-3 p-1 text-left text-blue-400">
${this.outgoingAttacks.map( ${this.outgoingAttacks.map(
(attack) => html` (attack) => html`
<div class="ml-2"> <button
class="ml-2"
@click=${() => this.emitGoToPlayerEvent(attack.targetID)}
>
${renderTroops(attack.troops)} ${renderTroops(attack.troops)}
${( ${(
this.game.playerBySmallID(attack.targetID) as PlayerView this.game.playerBySmallID(attack.targetID) as PlayerView
)?.name()} )?.name()}
${!attack.retreating </button>
? html`<button
${attack.retreating ? "disabled" : ""} ${!attack.retreating
@click=${() => { ? html`<button
this.emitCancelAttackIntent(attack.id); ${attack.retreating ? "disabled" : ""}
}} @click=${() => {
> this.emitCancelAttackIntent(attack.id);
}}
</button>` >
: "(retreating...)"}
</div> </button>`
: "(retreating...)"}
`, `,
)} )}
</td> </td>
@@ -491,9 +513,15 @@ export class EventsDisplay extends LitElement implements Layer {
)}" )}"
> >
<td class="lg:p-3 p-1 text-left"> <td class="lg:p-3 p-1 text-left">
${event.unsafeDescription ${event.focusID
? unsafeHTML(onlyImages(event.description)) ? html`<button
: event.description} @click=${() => {
this.emitGoToPlayerEvent(event.focusID);
}}
>
${this.getEventDescription(event)}
</button>`
: this.getEventDescription(event)}
${event.buttons ${event.buttons
? html` ? html`
<div class="flex flex-wrap gap-1.5 mt-1"> <div class="flex flex-wrap gap-1.5 mt-1">
+25 -25
View File
@@ -283,32 +283,32 @@ export class AttackExecution implements Execution {
} }
private handleDeadDefender() { private handleDeadDefender() {
if (this.target.isPlayer() && this.target.numTilesOwned() < 100) { if (!(this.target.isPlayer() && this.target.numTilesOwned() < 100)) return;
const gold = this.target.gold();
this.mg.displayMessage(
`Conquered ${this.target.displayName()} received ${renderNumber(
gold,
)} gold`,
MessageType.SUCCESS,
this._owner.id(),
);
this.target.removeGold(gold);
this._owner.addGold(gold);
for (let i = 0; i < 10; i++) { const gold = this.target.gold();
for (const tile of this.target.tiles()) { this.mg.displayMessage(
const borders = this.mg `Conquered ${this.target.displayName()} received ${renderNumber(
.neighbors(tile) gold,
.some((t) => this.mg.owner(t) == this._owner); )} gold`,
if (borders) { MessageType.SUCCESS,
this._owner.conquer(tile); this._owner.id(),
} else { );
for (const neighbor of this.mg.neighbors(tile)) { this.target.removeGold(gold);
const no = this.mg.owner(neighbor); this._owner.addGold(gold);
if (no.isPlayer() && no != this.target) {
this.mg.player(no.id()).conquer(tile); for (let i = 0; i < 10; i++) {
break; for (const tile of this.target.tiles()) {
} const borders = this.mg
.neighbors(tile)
.some((t) => this.mg.owner(t) == this._owner);
if (borders) {
this._owner.conquer(tile);
} else {
for (const neighbor of this.mg.neighbors(tile)) {
const no = this.mg.owner(neighbor);
if (no.isPlayer() && no != this.target) {
this.mg.player(no.id()).conquer(tile);
break;
} }
} }
} }
+1 -1
View File
@@ -14,7 +14,7 @@ stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0 stderr_logfile_maxbytes=0
[program:node] [program:node]
command=npm run start:server command=bun run start:server
directory=/usr/src/app directory=/usr/src/app
autostart=true autostart=true
autorestart=true autorestart=true