68c910f406
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
345 lines
10 KiB
Markdown
345 lines
10 KiB
Markdown
# DFlike
|
|
|
|
A multiplayer browser game inspired by Dwarf Fortress. Watch autonomous NPCs with needs-driven AI wander, eat, rest, and socialize in a shared world. Each NPC has unique stats (physical and personality) that influence their behavior, needs, and social interactions. Connect as an observer or take control of an avatar.
|
|
|
|
## 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 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), move avatar (avatar mode), or cycle NPCs (follow mode)
|
|
- **TAB** -- toggle between camera, avatar, and follow modes
|
|
|
|
### Follow Mode
|
|
|
|
In follow mode, the camera tracks an NPC and a panel slides in from the right showing:
|
|
- Composited character portrait (layered from skin + accessories + facial features)
|
|
- NPC name (randomly generated fantasy names)
|
|
- Current activity (Wandering, Eating, Resting, Socializing, Idle)
|
|
- Live-updating needs bars (Hunger, Energy) with color-coded fill levels
|
|
- NPC stats in a two-column grid (STR, DEX, CON, INT, PER / SOC, COU, CUR, EMP, TMP)
|
|
- **Relations tab** showing relationships grouped by tier with slider visualization
|
|
|
|
The panel uses an EarthBound/SNES RPG-inspired aesthetic with doubled borders, pixel font, and smooth slide transitions.
|
|
|
|
## 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
|
|
|
|
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
|
|
```
|
|
|
|
### Deploying with Traefik (Reverse Proxy)
|
|
|
|
If you're exposing the game through Traefik on a domain (e.g. `game.example.com`), you need two routes: one for the static client and one for the Socket.io game server.
|
|
|
|
**Traefik dynamic config** (e.g. `game.yaml`):
|
|
|
|
```yaml
|
|
http:
|
|
routers:
|
|
game-ws:
|
|
rule: "Host(`game.example.com`) && PathPrefix(`/socket.io`)"
|
|
entryPoints:
|
|
- websecure
|
|
service: game-ws
|
|
tls:
|
|
certResolver: letsencrypt
|
|
game:
|
|
rule: "Host(`game.example.com`)"
|
|
entryPoints:
|
|
- websecure
|
|
service: game
|
|
tls:
|
|
certResolver: letsencrypt
|
|
services:
|
|
game:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://<LXC_IP>:3000"
|
|
game-ws:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://<LXC_IP>:3001"
|
|
```
|
|
|
|
Set `VITE_SERVER_URL` to the public domain so Socket.io connects through the proxy:
|
|
|
|
```bash
|
|
# In client/.env.local
|
|
VITE_SERVER_URL=https://game.example.com
|
|
```
|
|
|
|
**Security note:** Do not expose `vite dev` to the internet. Use `vite preview` or serve `client/dist/` with a static file server (nginx, serve, etc.) for production.
|
|
|
|
### Summary of Ports
|
|
|
|
| Service | Port | Protocol |
|
|
|---------------|------|----------|
|
|
| Game server | 3001 | TCP |
|
|
| Client (dev) | 3000 | TCP |
|
|
| Client (prod) | 80 | TCP |
|
|
|
|
## NPC Stats
|
|
|
|
Each NPC is generated with 10 stats using 3d6 rolls (range 3-18, bell curve centered at ~10.5):
|
|
|
|
| Physical | Personality |
|
|
|----------|-------------|
|
|
| **Strength** -- physical tasks | **Sociability** -- social initiation, cooldown |
|
|
| **Dexterity** -- movement, precision | **Courage** -- exploration, threat response |
|
|
| **Constitution** -- needs decay rate | **Curiosity** -- wander variety, distance |
|
|
| **Intelligence** -- learning/drift rate | **Empathy** -- social outcome bias |
|
|
| **Perception** -- awareness radius | **Temperament** -- emotional stability |
|
|
|
|
Stats influence gameplay systems:
|
|
- **Constitution** scales hunger/energy decay rates
|
|
- **Perception** extends or shrinks the social awareness radius
|
|
- **Sociability** reduces social interaction cooldowns
|
|
- **Empathy** biases social interactions toward positive outcomes
|
|
- **Courage** influences willingness to interact with disliked NPCs
|
|
- **Curiosity** extends awareness radius for NPCs not yet encountered
|
|
|
|
Stats also have **transient modifiers** (e.g., low hunger debuffs Intelligence) and **baseline drift** (tiny permanent shifts from experience over time).
|
|
|
|
## Relationships
|
|
|
|
NPCs form relationships through social interactions. Each relationship is tracked independently per NPC (asymmetric — A's view of B can differ from B's view of A).
|
|
|
|
**How it works:**
|
|
- Relationships are lazy-initialized on first encounter (NPCs don't "know" each other until they interact)
|
|
- Each interaction updates relationship values (-100 to 100) based on outcome, with **diminishing returns** on repeated interactions
|
|
- Deltas are **blended asymmetrically**: 30% shared outcome + 70% individual perception
|
|
- Stats influence relationships: **Empathy** boosts positive gains, **Temperament** amplifies negative losses, **Sociability** adds a general bonus and determines friend capacity
|
|
|
|
**Classification tiers** (derived from value):
|
|
|
|
| Range | Tier |
|
|
|-------|------|
|
|
| 80 to 100 | Partner |
|
|
| 50 to 79 | Close Friend |
|
|
| 20 to 49 | Friend |
|
|
| 5 to 19 | Acquaintance |
|
|
| -4 to 4 | Stranger |
|
|
| -19 to -5 | Wary |
|
|
| -49 to -20 | Rival |
|
|
| -79 to -50 | Enemy |
|
|
| -100 to -80 | Nemesis |
|
|
|
|
**Caps:** Partner cap defaults to 1 (2 for high sociability + empathy NPCs). Friend cap scales with sociability.
|
|
|
|
**Despawn behavior:** Strong relationships (|value| >= 20) persist as **memories** when an NPC is removed. Weaker relationships fade toward zero and are cleaned up.
|
|
|
|
All relationship tuning values are config-driven in `server/src/config/relationshipConfig.ts`, designed for future admin UI integration.
|
|
|
|
## Game Constants
|
|
|
|
All tunable game constants live in `shared/src/constants.ts`:
|
|
|
|
| Constant | Default | Purpose |
|
|
|----------|---------|---------|
|
|
| `MAX_NPC_COUNT` | 50 | Maximum NPCs in the world |
|
|
| `TICK_RATE` | 10 | Server ticks per second |
|
|
| `WORLD_WIDTH` / `WORLD_HEIGHT` | 64 | World size in tiles |
|
|
| `MOVE_SPEED` | 0.75 | NPC movement speed (tiles/tick) |
|
|
| `HUNGER_DECAY_PER_TICK` | 0.05 | Hunger drain rate |
|
|
| `ENERGY_DECAY_PER_TICK` | 0.03 | Energy drain rate |
|
|
| `HUNGER_THRESHOLD` | 30 | Below this, NPC seeks food |
|
|
| `ENERGY_THRESHOLD` | 20 | Below this, NPC seeks rest |
|
|
| `AWARENESS_RADIUS` | 5 | Base social detection range (tiles) |
|
|
| `SOCIAL_GLOBAL_COOLDOWN` | 50 | Ticks between social interactions |
|
|
| `SOCIAL_PAIR_COOLDOWN` | 300 | Ticks before same pair can interact again |
|
|
|
|
## 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 |
|
|
| `npx -w shared tsc` | Rebuild shared types |
|