feat: viewport modes, configurable server URL, deployment docs
- Add follow mode (F key) to cycle camera between NPCs - Make server URL configurable via VITE_SERVER_URL env var - Add comprehensive README with Debian LXC deployment guide - Add character sprite assets to repo - Add design docs and implementation plans Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
13
.claude/settings.local.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(ls:*)",
|
||||
"Bash(npm:*)",
|
||||
"Bash(npx:*)",
|
||||
"Bash(mkdir:*)",
|
||||
"Bash(netstat:*)",
|
||||
"Bash(taskkill:*)",
|
||||
"Bash(node:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
1
.gitignore
vendored
@@ -2,3 +2,4 @@ node_modules/
|
||||
dist/
|
||||
.vite/
|
||||
client/public/assets/chars/
|
||||
shared/tsconfig.tsbuildinfo
|
||||
|
||||
17
CLAUDE.md
@@ -17,9 +17,26 @@ Multiplayer NPC simulation game (Dwarf Fortress-inspired). Server-authoritative
|
||||
- `npm install` -- install all workspace dependencies
|
||||
- `npm -w server run dev` -- start server (port 3001)
|
||||
- `npm -w server run test` -- run server tests (22 tests)
|
||||
- `npm -w server run test:watch` -- run server tests in watch mode
|
||||
- `npm -w client run dev` -- start client dev server (port 3000)
|
||||
- `npm -w client run build` -- build client for production
|
||||
|
||||
## Tooling
|
||||
|
||||
- ESM modules throughout (`"type": "module"`)
|
||||
- Server: tsx (runtime), vitest (tests)
|
||||
- Client: vite (bundler), Phaser 3
|
||||
- Shared: compiled to `shared/dist/` (auto-built)
|
||||
|
||||
## Key Entry Points
|
||||
|
||||
- `server/src/main.ts` -- server startup
|
||||
- `server/src/game/GameLoop.ts` -- tick loop orchestration
|
||||
- `client/src/main.ts` -- Phaser game bootstrap
|
||||
- `client/src/scenes/GameScene.ts` -- main game scene
|
||||
- `shared/src/types.ts` -- protocol types
|
||||
- `shared/src/constants.ts` -- game constants
|
||||
|
||||
## Key Conventions
|
||||
|
||||
- ECS components are plain data objects, systems are pure functions
|
||||
|
||||
244
README.md
@@ -2,33 +2,225 @@
|
||||
|
||||
A multiplayer browser game inspired by Dwarf Fortress. Watch autonomous NPCs with needs-driven AI wander, eat, and rest in a shared world. Connect as an observer or take control of an avatar.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
npm install
|
||||
# Terminal 1: Start server
|
||||
npm -w server run dev
|
||||
# Terminal 2: Start client
|
||||
npm -w client run dev
|
||||
```
|
||||
|
||||
Open http://localhost:3000. Open multiple tabs for multiplayer.
|
||||
|
||||
### Asset Setup
|
||||
|
||||
Character assets must be present at `client/public/assets/chars/`. If missing:
|
||||
|
||||
```bash
|
||||
cp -r chars client/public/assets/
|
||||
```
|
||||
|
||||
## Controls
|
||||
|
||||
- **WASD / Arrow Keys** -- pan camera (camera mode) or move avatar (avatar mode)
|
||||
- **TAB** -- toggle between camera and avatar mode
|
||||
|
||||
## Architecture
|
||||
|
||||
- **Server:** Node.js + Socket.io, server-authoritative ECS simulation at 10 ticks/sec
|
||||
- **Client:** Phaser 3 + TypeScript, Vite bundler
|
||||
- **Shared:** TypeScript types and constants
|
||||
- **Shared:** TypeScript types and constants used by both server and client
|
||||
|
||||
```
|
||||
dflike/
|
||||
shared/ -- Types and constants (no runtime code)
|
||||
server/ -- Node.js + Socket.io game server with ECS
|
||||
client/ -- Phaser 3 + TypeScript renderer
|
||||
chars/ -- Character sprite assets
|
||||
docs/ -- Design and implementation docs
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Node.js** >= 20 (tested with v22)
|
||||
- **npm** >= 10
|
||||
|
||||
## Quick Start (Development)
|
||||
|
||||
```bash
|
||||
npm install
|
||||
|
||||
# Copy character assets into the client public directory
|
||||
cp -r chars client/public/assets/
|
||||
|
||||
# Terminal 1: Start server (port 3001)
|
||||
npm -w server run dev
|
||||
|
||||
# Terminal 2: Start client dev server (port 3000)
|
||||
npm -w client run dev
|
||||
```
|
||||
|
||||
Open http://localhost:3000. Open multiple tabs for multiplayer.
|
||||
|
||||
## Controls
|
||||
|
||||
- **WASD / Arrow Keys** -- pan camera (camera mode) or move avatar (avatar mode)
|
||||
- **TAB** -- toggle between camera, avatar, and follow modes
|
||||
|
||||
## Deployment on Debian LXC
|
||||
|
||||
These instructions cover running DFlike on a headless Debian (or Ubuntu) LXC container, serving the game on your local network.
|
||||
|
||||
### 1. System Setup
|
||||
|
||||
```bash
|
||||
apt update && apt upgrade -y
|
||||
apt install -y curl git
|
||||
```
|
||||
|
||||
### 2. Install Node.js
|
||||
|
||||
Install Node.js 22.x via NodeSource:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
||||
apt install -y nodejs
|
||||
node --version # should print v22.x.x
|
||||
```
|
||||
|
||||
### 3. Clone and Install
|
||||
|
||||
```bash
|
||||
cd /opt
|
||||
git clone <your-repo-url> dflike
|
||||
cd dflike
|
||||
npm install
|
||||
```
|
||||
|
||||
### 4. Copy Character Assets
|
||||
|
||||
The character sprite assets in `chars/` need to be copied into the client's public directory. They are excluded from the client build via `.gitignore`.
|
||||
|
||||
```bash
|
||||
cp -r chars client/public/assets/
|
||||
```
|
||||
|
||||
### 5. Build the Client
|
||||
|
||||
By default the client connects to the game server on the same hostname it was loaded from (port 3001). To override, set the `VITE_SERVER_URL` environment variable before building:
|
||||
|
||||
```bash
|
||||
# Default: auto-detects from browser hostname (works for most setups)
|
||||
npm -w client run build
|
||||
|
||||
# Or specify an explicit server URL
|
||||
VITE_SERVER_URL=http://192.168.1.50:3001 npm -w client run build
|
||||
```
|
||||
|
||||
For development, you can also create a `client/.env.local` file:
|
||||
|
||||
```
|
||||
VITE_SERVER_URL=http://192.168.1.50:3001
|
||||
```
|
||||
|
||||
### 6. Serve the Client Build
|
||||
|
||||
```bash
|
||||
npm -w client run build
|
||||
```
|
||||
|
||||
This outputs static files to `client/dist/`.
|
||||
|
||||
### 6. Serve the Client Build
|
||||
|
||||
Install a simple static file server to serve the built client:
|
||||
|
||||
```bash
|
||||
npm install -g serve
|
||||
```
|
||||
|
||||
Or use nginx (recommended for production):
|
||||
|
||||
```bash
|
||||
apt install -y nginx
|
||||
```
|
||||
|
||||
**nginx config** (`/etc/nginx/sites-available/dflike`):
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
root /opt/dflike/client/dist;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Enable and restart:
|
||||
|
||||
```bash
|
||||
ln -s /etc/nginx/sites-available/dflike /etc/nginx/sites-enabled/
|
||||
rm -f /etc/nginx/sites-enabled/default
|
||||
nginx -t && systemctl restart nginx
|
||||
```
|
||||
|
||||
### 7. Run the Game Server
|
||||
|
||||
Quick start:
|
||||
|
||||
```bash
|
||||
cd /opt/dflike
|
||||
npm -w server run start
|
||||
```
|
||||
|
||||
The server listens on port 3001 by default. Override with the `PORT` environment variable:
|
||||
|
||||
```bash
|
||||
PORT=4000 npm -w server run start
|
||||
```
|
||||
|
||||
### 8. Run as a systemd Service
|
||||
|
||||
Create `/etc/systemd/system/dflike.service`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=DFlike Game Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/opt/dflike
|
||||
ExecStart=/usr/bin/npx tsx server/src/main.ts
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
Environment=PORT=3001
|
||||
Environment=NODE_ENV=production
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Enable and start:
|
||||
|
||||
```bash
|
||||
systemctl daemon-reload
|
||||
systemctl enable dflike
|
||||
systemctl start dflike
|
||||
systemctl status dflike # verify it's running
|
||||
journalctl -u dflike -f # view logs
|
||||
```
|
||||
|
||||
### 9. Firewall
|
||||
|
||||
If you have a firewall enabled, open the necessary ports:
|
||||
|
||||
```bash
|
||||
# Game server (Socket.io)
|
||||
ufw allow 3001/tcp
|
||||
|
||||
# Web server (nginx)
|
||||
ufw allow 80/tcp
|
||||
```
|
||||
|
||||
### Summary of Ports
|
||||
|
||||
| Service | Port | Protocol |
|
||||
|---------------|------|----------|
|
||||
| Game server | 3001 | TCP |
|
||||
| Client (dev) | 3000 | TCP |
|
||||
| Client (prod) | 80 | TCP |
|
||||
|
||||
## Development Commands
|
||||
|
||||
| Command | Description |
|
||||
|--------------------------------|------------------------------------|
|
||||
| `npm install` | Install all workspace dependencies |
|
||||
| `npm -w server run dev` | Start server with hot reload |
|
||||
| `npm -w server run start` | Start server (production) |
|
||||
| `npm -w server run test` | Run server tests |
|
||||
| `npm -w server run test:watch` | Run server tests in watch mode |
|
||||
| `npm -w client run dev` | Start client dev server |
|
||||
| `npm -w client run build` | Build client for production |
|
||||
|
||||
BIN
chars/baseman/accessories/portraits/skin01/arms/00_c00.png
Normal file
|
After Width: | Height: | Size: 312 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/00_c01.png
Normal file
|
After Width: | Height: | Size: 283 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/00_c02.png
Normal file
|
After Width: | Height: | Size: 312 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/00_c03.png
Normal file
|
After Width: | Height: | Size: 321 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/01_c00_v00.png
Normal file
|
After Width: | Height: | Size: 606 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/01_c01_v00.png
Normal file
|
After Width: | Height: | Size: 632 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/01_c01_v01.png
Normal file
|
After Width: | Height: | Size: 668 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/01_c02_v00.png
Normal file
|
After Width: | Height: | Size: 623 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/01_c03_v00.png
Normal file
|
After Width: | Height: | Size: 623 B |
|
After Width: | Height: | Size: 705 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/02_c00.png
Normal file
|
After Width: | Height: | Size: 785 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/02_c01.png
Normal file
|
After Width: | Height: | Size: 813 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/02_c02.png
Normal file
|
After Width: | Height: | Size: 783 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/02_c03.png
Normal file
|
After Width: | Height: | Size: 796 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/03_c00.png
Normal file
|
After Width: | Height: | Size: 311 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/03_c01.png
Normal file
|
After Width: | Height: | Size: 291 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/03_c02.png
Normal file
|
After Width: | Height: | Size: 303 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/03_c03.png
Normal file
|
After Width: | Height: | Size: 296 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/04_c00.png
Normal file
|
After Width: | Height: | Size: 427 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/04_c01.png
Normal file
|
After Width: | Height: | Size: 422 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/04_c02.png
Normal file
|
After Width: | Height: | Size: 426 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/04_c03.png
Normal file
|
After Width: | Height: | Size: 430 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/05_c00.png
Normal file
|
After Width: | Height: | Size: 393 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/05_c01.png
Normal file
|
After Width: | Height: | Size: 364 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/05_c02.png
Normal file
|
After Width: | Height: | Size: 391 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/05_c03.png
Normal file
|
After Width: | Height: | Size: 387 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/06_c00.png
Normal file
|
After Width: | Height: | Size: 494 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/06_c01.png
Normal file
|
After Width: | Height: | Size: 461 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/06_c02.png
Normal file
|
After Width: | Height: | Size: 482 B |
BIN
chars/baseman/accessories/portraits/skin01/arms/06_c03.png
Normal file
|
After Width: | Height: | Size: 481 B |
BIN
chars/baseman/accessories/portraits/skin01/back/00_c00.png
Normal file
|
After Width: | Height: | Size: 759 B |
BIN
chars/baseman/accessories/portraits/skin01/back/00_c01.png
Normal file
|
After Width: | Height: | Size: 806 B |
BIN
chars/baseman/accessories/portraits/skin01/back/00_c02.png
Normal file
|
After Width: | Height: | Size: 802 B |
BIN
chars/baseman/accessories/portraits/skin01/back/00_c03.png
Normal file
|
After Width: | Height: | Size: 747 B |
BIN
chars/baseman/accessories/portraits/skin01/back/01_c00.png
Normal file
|
After Width: | Height: | Size: 643 B |
BIN
chars/baseman/accessories/portraits/skin01/back/01_c01.png
Normal file
|
After Width: | Height: | Size: 648 B |
BIN
chars/baseman/accessories/portraits/skin01/back/01_c02.png
Normal file
|
After Width: | Height: | Size: 646 B |
BIN
chars/baseman/accessories/portraits/skin01/back/01_c03.png
Normal file
|
After Width: | Height: | Size: 659 B |
BIN
chars/baseman/accessories/portraits/skin01/back/02_c00.png
Normal file
|
After Width: | Height: | Size: 769 B |
BIN
chars/baseman/accessories/portraits/skin01/back/02_c01.png
Normal file
|
After Width: | Height: | Size: 761 B |
BIN
chars/baseman/accessories/portraits/skin01/back/02_c02.png
Normal file
|
After Width: | Height: | Size: 787 B |
BIN
chars/baseman/accessories/portraits/skin01/back/02_c03.png
Normal file
|
After Width: | Height: | Size: 771 B |
BIN
chars/baseman/accessories/portraits/skin01/back/03_c00.png
Normal file
|
After Width: | Height: | Size: 814 B |
BIN
chars/baseman/accessories/portraits/skin01/back/03_c01.png
Normal file
|
After Width: | Height: | Size: 813 B |
BIN
chars/baseman/accessories/portraits/skin01/back/03_c02.png
Normal file
|
After Width: | Height: | Size: 810 B |
BIN
chars/baseman/accessories/portraits/skin01/back/03_c03.png
Normal file
|
After Width: | Height: | Size: 866 B |
BIN
chars/baseman/accessories/portraits/skin01/back/04_c00.png
Normal file
|
After Width: | Height: | Size: 615 B |
BIN
chars/baseman/accessories/portraits/skin01/back/04_c01.png
Normal file
|
After Width: | Height: | Size: 620 B |
BIN
chars/baseman/accessories/portraits/skin01/back/04_c02.png
Normal file
|
After Width: | Height: | Size: 617 B |
BIN
chars/baseman/accessories/portraits/skin01/back/04_c03.png
Normal file
|
After Width: | Height: | Size: 619 B |
BIN
chars/baseman/accessories/portraits/skin01/back/05_c00.png
Normal file
|
After Width: | Height: | Size: 263 B |
BIN
chars/baseman/accessories/portraits/skin01/back/05_c01.png
Normal file
|
After Width: | Height: | Size: 266 B |
BIN
chars/baseman/accessories/portraits/skin01/back/05_c02.png
Normal file
|
After Width: | Height: | Size: 269 B |
BIN
chars/baseman/accessories/portraits/skin01/back/05_c03.png
Normal file
|
After Width: | Height: | Size: 271 B |
BIN
chars/baseman/accessories/portraits/skin01/back/06_c00.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
chars/baseman/accessories/portraits/skin01/back/06_c01.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
chars/baseman/accessories/portraits/skin01/back/06_c02.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
chars/baseman/accessories/portraits/skin01/back/06_c03.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
chars/baseman/accessories/portraits/skin01/back/07_c00.png
Normal file
|
After Width: | Height: | Size: 691 B |
BIN
chars/baseman/accessories/portraits/skin01/back/07_c01.png
Normal file
|
After Width: | Height: | Size: 705 B |
BIN
chars/baseman/accessories/portraits/skin01/back/07_c02.png
Normal file
|
After Width: | Height: | Size: 699 B |
BIN
chars/baseman/accessories/portraits/skin01/back/07_c03.png
Normal file
|
After Width: | Height: | Size: 687 B |
BIN
chars/baseman/accessories/portraits/skin01/back/08_c00.png
Normal file
|
After Width: | Height: | Size: 640 B |
BIN
chars/baseman/accessories/portraits/skin01/back/08_c01.png
Normal file
|
After Width: | Height: | Size: 641 B |
BIN
chars/baseman/accessories/portraits/skin01/back/08_c02.png
Normal file
|
After Width: | Height: | Size: 638 B |
BIN
chars/baseman/accessories/portraits/skin01/back/08_c03.png
Normal file
|
After Width: | Height: | Size: 633 B |
BIN
chars/baseman/accessories/portraits/skin01/back/09_c00.png
Normal file
|
After Width: | Height: | Size: 589 B |
BIN
chars/baseman/accessories/portraits/skin01/back/09_c01.png
Normal file
|
After Width: | Height: | Size: 586 B |
BIN
chars/baseman/accessories/portraits/skin01/back/09_c02.png
Normal file
|
After Width: | Height: | Size: 603 B |
BIN
chars/baseman/accessories/portraits/skin01/back/09_c03.png
Normal file
|
After Width: | Height: | Size: 581 B |
BIN
chars/baseman/accessories/portraits/skin01/back/10_c00.png
Normal file
|
After Width: | Height: | Size: 611 B |
BIN
chars/baseman/accessories/portraits/skin01/back/10_c01.png
Normal file
|
After Width: | Height: | Size: 556 B |
BIN
chars/baseman/accessories/portraits/skin01/back/10_c02.png
Normal file
|
After Width: | Height: | Size: 596 B |
BIN
chars/baseman/accessories/portraits/skin01/back/10_c03.png
Normal file
|
After Width: | Height: | Size: 553 B |
BIN
chars/baseman/accessories/portraits/skin01/back/11_c00.png
Normal file
|
After Width: | Height: | Size: 539 B |
BIN
chars/baseman/accessories/portraits/skin01/back/11_c01.png
Normal file
|
After Width: | Height: | Size: 528 B |
BIN
chars/baseman/accessories/portraits/skin01/back/11_c02.png
Normal file
|
After Width: | Height: | Size: 537 B |
BIN
chars/baseman/accessories/portraits/skin01/back/11_c03.png
Normal file
|
After Width: | Height: | Size: 542 B |
BIN
chars/baseman/accessories/portraits/skin01/bottom/00_c00.png
Normal file
|
After Width: | Height: | Size: 342 B |
BIN
chars/baseman/accessories/portraits/skin01/bottom/00_c01.png
Normal file
|
After Width: | Height: | Size: 328 B |
BIN
chars/baseman/accessories/portraits/skin01/bottom/00_c02.png
Normal file
|
After Width: | Height: | Size: 356 B |
BIN
chars/baseman/accessories/portraits/skin01/bottom/00_c03.png
Normal file
|
After Width: | Height: | Size: 336 B |
BIN
chars/baseman/accessories/portraits/skin01/bottom/01_c00.png
Normal file
|
After Width: | Height: | Size: 342 B |
BIN
chars/baseman/accessories/portraits/skin01/bottom/01_c01.png
Normal file
|
After Width: | Height: | Size: 328 B |
BIN
chars/baseman/accessories/portraits/skin01/bottom/01_c02.png
Normal file
|
After Width: | Height: | Size: 356 B |
BIN
chars/baseman/accessories/portraits/skin01/bottom/01_c03.png
Normal file
|
After Width: | Height: | Size: 336 B |
BIN
chars/baseman/accessories/portraits/skin01/bottom/02_c00.png
Normal file
|
After Width: | Height: | Size: 391 B |
BIN
chars/baseman/accessories/portraits/skin01/bottom/02_c01.png
Normal file
|
After Width: | Height: | Size: 396 B |
BIN
chars/baseman/accessories/portraits/skin01/bottom/02_c02.png
Normal file
|
After Width: | Height: | Size: 400 B |
BIN
chars/baseman/accessories/portraits/skin01/bottom/02_c03.png
Normal file
|
After Width: | Height: | Size: 383 B |
BIN
chars/baseman/accessories/portraits/skin01/bottom/03_c00.png
Normal file
|
After Width: | Height: | Size: 339 B |
BIN
chars/baseman/accessories/portraits/skin01/bottom/03_c01.png
Normal file
|
After Width: | Height: | Size: 334 B |
BIN
chars/baseman/accessories/portraits/skin01/bottom/03_c02.png
Normal file
|
After Width: | Height: | Size: 345 B |
BIN
chars/baseman/accessories/portraits/skin01/bottom/03_c03.png
Normal file
|
After Width: | Height: | Size: 342 B |
BIN
chars/baseman/accessories/portraits/skin01/bottom/04_c00.png
Normal file
|
After Width: | Height: | Size: 339 B |
BIN
chars/baseman/accessories/portraits/skin01/bottom/04_c01.png
Normal file
|
After Width: | Height: | Size: 334 B |