Merge pull request 'Modernise & make fun' (#3) from asch/modernise into main
Reviewed-on: https://home.schmelczer.dev/git/git/andras/decla-red/pulls/3
This commit is contained in:
commit
5d854440ec
132 changed files with 11445 additions and 20695 deletions
|
|
@ -1,10 +0,0 @@
|
|||
# Path to your oh-my-zsh installation.
|
||||
export ZSH="/root/.oh-my-zsh"
|
||||
|
||||
# https://typewritten.dev/#/git_status_indicators
|
||||
ZSH_THEME="typewritten"
|
||||
|
||||
# Which plugins would you like to load?
|
||||
# plugins=(zsh-autosuggestions nvm git)
|
||||
|
||||
source $ZSH/oh-my-zsh.sh
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
ARG VARIANT="14-buster"
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:0-${VARIANT}
|
||||
|
||||
RUN apt update && export DEBIAN_FRONTEND=noninteractive \
|
||||
&& apt -y install --no-install-recommends git-lfs \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN git lfs install
|
||||
|
||||
RUN npm i && npm run init
|
||||
|
||||
ENV ZSH_CUSTOM /root/.oh-my-zsh/
|
||||
RUN git clone https://github.com/reobin/typewritten.git $ZSH_CUSTOM/themes/typewritten
|
||||
RUN ln -s "$ZSH_CUSTOM/themes/typewritten/typewritten.zsh-theme" "$ZSH_CUSTOM/themes/typewritten.zsh-theme"
|
||||
RUN ln -s "$ZSH_CUSTOM/themes/typewritten/async.zsh" "$ZSH_CUSTOM/themes/async"
|
||||
COPY .zshrc /root/.zshrc
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
{
|
||||
"name": "Node.js & TypeScript",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile",
|
||||
"args": {
|
||||
"VARIANT": "14"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"terminal.integrated.shell.linux": "/bin/zsh",
|
||||
"files.exclude": {
|
||||
"**/node_modules": true,
|
||||
"**/yarn.lock": true,
|
||||
"**/dist": true,
|
||||
"**/lib": true,
|
||||
"**/.firebase": true
|
||||
},
|
||||
"editor.tabSize": 2,
|
||||
"editor.detectIndentation": false,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"eslint.validate": ["json"],
|
||||
"markdown.extension.toc.levels": "2..4",
|
||||
// <style>
|
||||
"workbench.iconTheme": "material-icon-theme",
|
||||
"workbench.colorTheme": "Community Material Theme Palenight High Contrast",
|
||||
"editor.fontFamily": "'Fira Code'",
|
||||
"editor.fontLigatures": true
|
||||
// </style>
|
||||
},
|
||||
"extensions": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"yzhang.markdown-all-in-one",
|
||||
"hediet.vscode-drawio",
|
||||
"pkief.material-icon-theme",
|
||||
"equinusocio.vsc-community-material-theme"
|
||||
],
|
||||
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached",
|
||||
"workspaceFolder": "/workspace",
|
||||
"mounts": [
|
||||
"source=decla-red-root-node_modules-volume4,target=/workspace/node_modules,type=volume",
|
||||
"source=decla-red-frontend-node_modules-volume4,target=/workspace/frontend/node_modules,type=volume",
|
||||
"source=decla-red-backend-node_modules-volume4,target=/workspace/backend/node_modules,type=volume",
|
||||
"source=decla-red-shared-node_modules-volume4,target=/workspace/shared/node_modules,type=volume"
|
||||
],
|
||||
"forwardPorts": [3000, 8080],
|
||||
"postCreateCommand": "chown node:node ./**/node_modules && npm run build"
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
**/node_modules/**/*.js
|
||||
node_modules/**/*.js
|
||||
**/package-lock.json
|
||||
package-lock.json
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2020": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier/@typescript-eslint",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["unused-imports", "@typescript-eslint", "json-format", "prettier"],
|
||||
"settings": {
|
||||
"json/sort-package-json": true
|
||||
},
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"no-unused-vars": "off",
|
||||
"unused-imports/no-unused-imports-ts": "error",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-var-requires": "off"
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ jobs:
|
|||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 14
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Install dependencies
|
||||
# No lockfiles are committed (see .gitignore), so use `npm install`.
|
||||
|
|
@ -32,6 +32,12 @@ jobs:
|
|||
- name: Build (shared -> frontend -> backend)
|
||||
run: npm run build
|
||||
|
||||
- name: Test
|
||||
run: npm test
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint:check
|
||||
|
||||
- name: Deploy to host pages mount
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
uses: http://forgejo:3000/andras/ci-actions/deploy-pages@main
|
||||
|
|
|
|||
1
.node-version
Normal file
1
.node-version
Normal file
|
|
@ -0,0 +1 @@
|
|||
22
|
||||
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
22
|
||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
|
@ -3,7 +3,7 @@
|
|||
"Deserializable",
|
||||
"Deserialization",
|
||||
"Respawn",
|
||||
"decla",
|
||||
"doppler",
|
||||
"overridable",
|
||||
"serializable"
|
||||
]
|
||||
|
|
|
|||
12
Dockerfile
12
Dockerfile
|
|
@ -1,13 +1,13 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
|
||||
# decla.red game server (the `declared-server` package).
|
||||
# doppler game server (the `doppler-server` package).
|
||||
# The frontend is a static site and is NOT built here — see .forgejo/workflows.
|
||||
|
||||
# ---- Stage 1: build the shared lib, then bundle the server -------------------
|
||||
# Node 14 matches the project toolchain (webpack 4 / TypeScript 4, see
|
||||
# .devcontainer). The full (non-slim) image carries the build tools that
|
||||
# Node 22 (current LTS) matches the project toolchain (webpack 5 / TypeScript 6,
|
||||
# see .devcontainer). The full (non-slim) image carries the build tools that
|
||||
# socket.io's optional native deps (bufferutil/utf-8-validate) compile against.
|
||||
FROM node:14-bullseye AS build
|
||||
FROM node:22-bookworm AS build
|
||||
WORKDIR /app
|
||||
|
||||
# `shared` is consumed by the backend as `file:../shared` and is bundled into
|
||||
|
|
@ -28,7 +28,7 @@ RUN cd backend && npm run build
|
|||
RUN cd backend && npm prune --production
|
||||
|
||||
# ---- Stage 2: minimal runtime ----------------------------------------------
|
||||
FROM node:14-bullseye-slim
|
||||
FROM node:22-bookworm-slim
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
|
||||
|
|
@ -46,5 +46,5 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
|||
CMD node -e "require('http').get('http://localhost:3000/state',r=>process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))"
|
||||
|
||||
ENTRYPOINT ["node", "dist/main.js"]
|
||||
# Override these to tune the server, e.g. `--name`, `--playerLimit`, `--worldSize`.
|
||||
# Override these to tune the server, e.g. `--name`, `--playerLimit`, `--scoreLimit`.
|
||||
CMD ["--port", "3000"]
|
||||
|
|
|
|||
10
README.md
10
README.md
|
|
@ -1,8 +1,8 @@
|
|||
# [decla.red](https://decla.red)
|
||||
# doppler
|
||||
|
||||
A 2-dimensional multiplayer game utilising ray tracing.
|
||||
|
||||
> **Available at [decla.red](https://decla.red).**
|
||||
> **Available at [doppler.schmelczer.dev](https://doppler.schmelczer.dev).**
|
||||
|
||||

|
||||

|
||||
|
|
@ -15,7 +15,7 @@ CI/CD runs on Forgejo Actions (`.forgejo/workflows/deploy.yml`). On a push to
|
|||
`main` it:
|
||||
|
||||
- builds the static frontend and rsyncs `frontend/dist/` to the `/pages/declared`
|
||||
mount on the runner host, and
|
||||
mount on the runner host (the mount keeps its pre-rebrand name), and
|
||||
- builds the server image from the root `Dockerfile` and pushes it to the Forgejo
|
||||
container registry as `<registry>/<owner>/<repo>-server`.
|
||||
|
||||
|
|
@ -30,6 +30,6 @@ edit it to add or remove game-server origins.
|
|||
Run the server image locally:
|
||||
|
||||
```sh
|
||||
docker build -t declared-server .
|
||||
docker run -p 3000:3000 declared-server --name "My server" --playerLimit 16
|
||||
docker build -t doppler-server .
|
||||
docker run -p 3000:3000 doppler-server --name "My server" --playerLimit 16
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
# Setup a decla.red server on a fresh debian/ubuntu machine
|
||||
|
||||
> The method was tested on Debian 10 DigitalOcean droplets.
|
||||
|
||||
- Copy the files from this directory to the target machine.
|
||||
- Execute the following commands.
|
||||
- ```sh
|
||||
chmod +x setup.sh
|
||||
./setup.sh serverX
|
||||
```
|
||||
|
||||
> Where `serverX` is the serverX.decla.red subdomain pointing to the server.
|
||||
|
||||
- Optionally, change the arguments in [declared-servers.sh](declared-servers.sh).
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
[Unit]
|
||||
Description=declared-server
|
||||
|
||||
Requires=basic.target network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/bin/sh /root/declared-servers.sh
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
NODE_ENV=production declared-server --port=3000 --name="Simonyi" --playerLimit=256 --npcCount=48 --scoreLimit=20000
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
user www-data;
|
||||
worker_processes auto;
|
||||
pid /run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 768;
|
||||
}
|
||||
|
||||
http {
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
server_tokens off;
|
||||
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
ssl_certificate /etc/letsencrypt/live/serverName.decla.red/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/serverName.decla.red/privkey.pem;
|
||||
|
||||
server_name serverName.decla.red;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3000/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Please specify a domain" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
domain="$1"
|
||||
|
||||
apt install -y curl
|
||||
curl -sL https://deb.nodesource.com/setup_15.x | bash -
|
||||
apt update
|
||||
apt install -y nodejs nginx certbot python3-certbot-nginx
|
||||
npm i -g declared-server
|
||||
|
||||
certbot certonly --nginx -m schmelczerandras@gmail.com -d "$domain".decla.red --agree-tos --non-interactive
|
||||
|
||||
mv nginx.conf /etc/nginx/nginx.conf
|
||||
sed -i "s/serverName/$domain/g" /etc/nginx/nginx.conf
|
||||
nginx -s reload
|
||||
|
||||
chmod +x /root/declared-servers.sh
|
||||
chown root:root /root/declared-servers.sh
|
||||
cp declared-servers.service /etc/systemd/system/declared-servers.service
|
||||
systemctl enable declared-servers.service
|
||||
systemctl start declared-servers
|
||||
|
|
@ -1,48 +1,43 @@
|
|||
{
|
||||
"name": "declared-server",
|
||||
"name": "doppler-server",
|
||||
"version": "0.1.0",
|
||||
"description": "Game server for decla.red",
|
||||
"description": "Game server for doppler",
|
||||
"keywords": [],
|
||||
"author": "András Schmelczer <andras@schmelczer.dev> (https://schmelczer.dev/)",
|
||||
"main": "dist/main.js",
|
||||
"bin": {
|
||||
"declared-server": "dist/main.js"
|
||||
"doppler-server": "dist/main.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npx webpack --mode production",
|
||||
"start": "concurrently --kill-others-on-fail \"webpack --mode development -w\" \"nodemon --legacy-watch dist/main.js\"",
|
||||
"dev": "concurrently --kill-others-on-fail \"webpack --mode development -w\" \"nodemon --legacy-watch dist/main.js\"",
|
||||
"try-build": "npm run build && cd dist && node main.js && cd -"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/config": "0.0.36",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"cors": "^2.8.6",
|
||||
"express": "^5.2.1",
|
||||
"gl-matrix": "3.3.0",
|
||||
"minimist": "^1.2.5",
|
||||
"socket.io": "^2.3.0",
|
||||
"socket.io-msgpack-parser": "^2.0.0"
|
||||
"minimist": "^1.2.8",
|
||||
"socket.io": "^4.8.3",
|
||||
"socket.io-msgpack-parser": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.7",
|
||||
"@types/express": "^4.17.8",
|
||||
"@types/minimist": "^1.2.0",
|
||||
"@types/node": "^14.11.2",
|
||||
"@types/socket.io": "^2.1.11",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"concurrently": "^5.3.0",
|
||||
"file-loader": "^6.1.0",
|
||||
"html-webpack-plugin": "^4.5.0",
|
||||
"nodemon": "^2.0.4",
|
||||
"raw-loader": "^4.0.1",
|
||||
"resolve-url-loader": "^3.1.1",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/minimist": "^1.2.5",
|
||||
"@types/node": "^22.0.0",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"concurrently": "^10.0.3",
|
||||
"nodemon": "^3.1.14",
|
||||
"shared": "file:../shared",
|
||||
"terser-webpack-plugin": "^2.3.5",
|
||||
"ts-config-webpack-plugin": "^2.0.0",
|
||||
"ts-loader": "^8.0.3",
|
||||
"typescript": "^4.0.3",
|
||||
"webpack": "^4.44.2",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-dev-server": "^3.11.0",
|
||||
"webpack-node-externals": "^2.5.2"
|
||||
"terser-webpack-plugin": "^5.6.1",
|
||||
"ts-loader": "^9.6.0",
|
||||
"typescript": "^6.0.3",
|
||||
"webpack": "^5.107.2",
|
||||
"webpack-cli": "^7.0.3",
|
||||
"webpack-node-externals": "^3.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
backend/src/commands/announce.ts
Normal file
9
backend/src/commands/announce.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { Command } from 'shared';
|
||||
|
||||
// Internal request from a game object (e.g. a keystone planet flipping) asking
|
||||
// the GameServer to broadcast a one-off announcement to every connected client.
|
||||
export class AnnounceCommand extends Command {
|
||||
public constructor(public readonly text: string) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
import { Command } from 'shared';
|
||||
|
||||
export class GeneratePointsCommand extends Command {
|
||||
public constructor(public readonly decla: number, public readonly red: number) {
|
||||
public constructor(
|
||||
public readonly blue: number,
|
||||
public readonly red: number,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { Random, PlanetBase, hsl, settings } from 'shared';
|
||||
import { Random, PlanetBase, hsl, settings, evaluateSdf } from 'shared';
|
||||
import { LampPhysical } from './objects/lamp-physical';
|
||||
import { PlanetPhysical } from './objects/planet-physical';
|
||||
import { PhysicalContainer } from './physics/containers/physical-container';
|
||||
import { evaluateSdf } from './physics/functions/evaluate-sdf';
|
||||
import { Physical } from './physics/physicals/physical';
|
||||
|
||||
export const createWorld = (objectContainer: PhysicalContainer, worldRadius: number) => {
|
||||
export const createWorld = (objectContainer: PhysicalContainer) => {
|
||||
const objects: Array<Physical> = [];
|
||||
const lights: Array<Physical> = [];
|
||||
|
||||
for (let r = 0; r < worldRadius; r += settings.radiusSteps) {
|
||||
for (let r = 0; r < settings.worldRadius; r += settings.radiusSteps) {
|
||||
const circumference = 2 * Math.PI * r;
|
||||
const stepCount = circumference * settings.objectsOnCircleLength;
|
||||
for (let rad = 0; rad < 2 * Math.PI; rad += (2 * Math.PI) / stepCount) {
|
||||
|
|
@ -45,13 +44,16 @@ export const createWorld = (objectContainer: PhysicalContainer, worldRadius: num
|
|||
) {
|
||||
const planet =
|
||||
objects.length === 0
|
||||
? new PlanetPhysical(
|
||||
? // The first, central giant is the keystone "Heart": a named,
|
||||
// always-contested focal objective the whole match orbits.
|
||||
new PlanetPhysical(
|
||||
PlanetBase.createPlanetVertices(
|
||||
position,
|
||||
Random.getRandomInRange(1600, 2400),
|
||||
Random.getRandomInRange(1600, 2400),
|
||||
Random.getRandomInRange(80, 300),
|
||||
),
|
||||
true,
|
||||
)
|
||||
: new PlanetPhysical(
|
||||
PlanetBase.createPlanetVertices(
|
||||
|
|
@ -67,8 +69,29 @@ export const createWorld = (objectContainer: PhysicalContainer, worldRadius: num
|
|||
}
|
||||
}
|
||||
}
|
||||
console.info('Generated planet count', objects.length);
|
||||
console.info('Generated light count', lights.length);
|
||||
console.info(`Generated ${objects.length} planets`);
|
||||
console.info(`Generated ${lights.length} light`);
|
||||
|
||||
// Associate each lamp with its NEAREST planet, so a planet can repaint "its"
|
||||
// lamps to the owning team's colour when it flips. Lamps are already placed by
|
||||
// proximity during world-gen, so the nearest planet is the one whose capture
|
||||
// they should advertise. Distances use the planet SDF (negative inside), which
|
||||
// is exactly the "closest planet" metric we want.
|
||||
const planets = objects.filter((o): o is PlanetPhysical => o instanceof PlanetPhysical);
|
||||
lights
|
||||
.filter((l): l is LampPhysical => l instanceof LampPhysical)
|
||||
.forEach((lamp) => {
|
||||
let nearest: PlanetPhysical | undefined;
|
||||
let nearestDistance = Infinity;
|
||||
planets.forEach((planet) => {
|
||||
const distance = planet.distance(lamp.center);
|
||||
if (distance < nearestDistance) {
|
||||
nearestDistance = distance;
|
||||
nearest = planet;
|
||||
}
|
||||
});
|
||||
nearest?.addLamp(lamp);
|
||||
});
|
||||
|
||||
[...objects, ...lights].forEach((o) => objectContainer.addObject(o));
|
||||
};
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue