# 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 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://:3000" game-ws: loadBalancer: servers: - url: "http://: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 |