- Authoritative 30 Hz server (ENet, SQLite): accounts, characters, wild AI, tall grass / water / wandering spawns, fishing, captures, follower chains, clock + weather, LAN beacon - Client: title / join / character creator / play screens, prediction + reconciliation, interpolated players and parties, HD-2D UI (dialogue with Professor Henry, HUD, collection) - Shared deterministic zone generator, movement, protocol, roster, appearance - Paper-doll character art pipeline (PixelLab base walk + per-item animation edits) - Brandling roster from the company list (logo colors, jev types), Blender safari props - Headless test bot
203 lines
9.7 KiB
Markdown
203 lines
9.7 KiB
Markdown
# Brandling Safari
|
|
|
|
A small LAN-multiplayer creature-catching game set in a safari zone, rendered in the
|
|
**HD-2D** style (Octopath Traveler / Triangle Strategy) with the
|
|
[Vibe-HD-2D](../Vibe-HD-2D) engine. The creatures, **Brandlings**, are named after real
|
|
companies: each one's sprite is generated by [Company_Sprite_Gen](../Company_Sprite_Gen) from
|
|
the company name, recolored with its logo's brand colors, and its type comes from the jev
|
|
business classifier.
|
|
|
|

|
|
|
|
| | |
|
|
| --- | --- |
|
|
|  |  |
|
|
|  |  |
|
|
|  |  |
|
|
|  | |
|
|
|
|
## Features
|
|
|
|
- **Meadow Safari Zone**: terraced grass diorama with a north ridge and an east plateau, a lake
|
|
with a fishing dock, a pond, eight tall-grass patches, an entrance gate and plaza with
|
|
Professor Henry's field tent, lamps, fences and props (3D models built in Blender, pixel-art
|
|
ground and foliage).
|
|
- **Character creator** (persists server-side): masculine / feminine base body, 6 skin tones, 5
|
|
hair styles, 3 hats, 5 tops, 4 bottoms, 3 shoes, any color per slot. Every combination is
|
|
animated by one shared walk cycle (see [Character art](#character-art)).
|
|
- **Professor Henry** greets new rangers with an intro, hands out 30 Safari Capsules and
|
|
restocks them later.
|
|
- **Finding Brandlings**: they burst out of tall grass around you as you walk (more often when
|
|
running), swim in the lake and surface with splashes, wander the meadow, and bite on a
|
|
fishing line cast from the shore or dock.
|
|
- **Catching**: throw a Safari Capsule (closer and calmer = better odds; hooked ones are dazed);
|
|
misses can make them flee. Caught Brandlings join your party (up to 6) and follow behind you
|
|
in a chain, bouncing and turning to face their direction; the rest go to your box.
|
|
- **Multiplayer**: dedicated authoritative server, LAN discovery, client-side prediction with
|
|
server reconciliation, interpolated remote players and their parties, chat bubbles.
|
|
- **World state from the server**: time of day (dawn, day, dusk, night with stars, moon and
|
|
lantern light) and weather (clear, cloudy, rain, thunderstorm, mist) change the look on every
|
|
client and which Brandlings appear.
|
|
|
|
## Building
|
|
|
|
Requirements: CMake >= 3.21, a C++20 compiler, OpenGL 4.5, `libepoxy`, SQLite 3, and the
|
|
engine checked out next to this repo (`../Vibe-HD-2D`; set `HD2D_ENGINE_DIR` otherwise, or
|
|
leave it missing to fetch it from `HD2D_ENGINE_GIT`). GLFW, GLM, stb, cgltf and Dear ImGui
|
|
come from the engine; nlohmann/json is used from the system or fetched, ENet is always fetched.
|
|
|
|
```sh
|
|
# Arch / CachyOS
|
|
sudo pacman -S cmake ninja glfw glm libepoxy stb sqlite nlohmann-json
|
|
|
|
cmake -S . -B build -G Ninja
|
|
ninja -C build # safari_client, safari_server, safari_bot
|
|
```
|
|
|
|
Server only (no GPU needed): `cmake -S . -B build_srv -G Ninja -DBS_BUILD_CLIENT=OFF`.
|
|
|
|
## Playing
|
|
|
|
```sh
|
|
./build/safari_client # title screen: Host & Play, or Join a LAN Game
|
|
```
|
|
|
|
- **Host & Play** starts `safari_server` next to the client on port 27420 and joins it.
|
|
Other players on the network see it under **Join a LAN Game** (UDP broadcast on 27421) or
|
|
can type the host's address.
|
|
- A dedicated server: `./build/safari_server [--port 27420] [--name "My Safari"]`.
|
|
|
|
| Input | Action |
|
|
| --- | --- |
|
|
| WASD / arrows | Move (camera-relative); hold Shift to run |
|
|
| Space | Throw a Safari Capsule at the targeted Brandling |
|
|
| F | Cast a line toward the water you face / reel in when it bites |
|
|
| E | Talk to Professor Henry (advances dialogue too) |
|
|
| Tab | Your Brandlings: Enter moves one between party and box |
|
|
| Enter / T | Chat |
|
|
| Q / E, right-drag, wheel | Camera |
|
|
| Esc | Menu |
|
|
| F1 / F12 | Render tuning panels / screenshot |
|
|
|
|
Your identity is a random token in `~/.local/share/brandling-safari/client.json`; the server
|
|
keys your ranger, Brandlings and dex to it. `--profile NAME` uses a separate identity (several
|
|
clients on one machine).
|
|
|
|
### Server
|
|
|
|
```
|
|
safari_server [--port N] [--name NAME] [--db PATH] [--seed N] [--zone meadow]
|
|
[--roster PATH] [--manifest PATH] [--day-minutes M] [--hour H]
|
|
[--weather clear|cloudy|rain|storm|fog]
|
|
```
|
|
|
|
Everything is saved in SQLite (`~/.local/share/brandling-safari/world.db` by default):
|
|
players (name, look, position, capsules, intro), caught Brandlings, the per-player dex, the
|
|
world clock and weather. Console commands: `help`, `players`, `time <h>`,
|
|
`weather <name>`, `give <player> capsules <n>`, `spawn <species>`, `save`, `quit`.
|
|
|
|
`safari_bot` is a headless test client that walks into tall grass, throws capsules, fishes and
|
|
talks to Henry, printing everything it receives.
|
|
|
|
## Architecture
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
subgraph Server [safari_server, 30 Hz, authoritative]
|
|
G[Game: players, wild AI, spawns,<br/>fishing, captures, followers,<br/>clock + weather]
|
|
DB[(SQLite)]
|
|
G <--> DB
|
|
end
|
|
subgraph Client [safari_client]
|
|
N[NetClient] --> W[World: prediction,<br/>interpolation, effects]
|
|
W --> R[HD-2D Renderer]
|
|
A[App: screens + UI] --> N
|
|
end
|
|
N <-- "ENet UDP: inputs, actions /<br/>snapshots, events" --> G
|
|
S[shared/: Protocol, Zone,<br/>Movement, Roster, Appearance] --- G
|
|
S --- W
|
|
```
|
|
|
|
- `shared/` is compiled into both programs. `Zone` generates the whole zone deterministically
|
|
from `(zone id, seed)`, so the server (collision, spawn points, water, tall grass) and every
|
|
client (terrain, props, decoration) agree on the map and only the id + seed go over the
|
|
wire. `Movement` is the single player-movement function: the server applies it once per
|
|
input command, the client predicts with it and replays unacknowledged inputs when a
|
|
snapshot arrives.
|
|
- Clients send only intents: movement inputs (unreliable, last 4 resent) and actions (throw,
|
|
cast, reel, talk, party, chat). The server validates everything (ranges, capsules, speed
|
|
budget, walkability) and broadcasts snapshots (unreliable, 30 Hz) and events (reliable:
|
|
emerge, splash, throw, capture result, bite, hook, catch...).
|
|
- Protocol: `shared/Protocol.h`. Bump `kProtocolVersion` on any wire change.
|
|
|
|
| Path | What |
|
|
| --- | --- |
|
|
| `shared/` | protocol, ENet wrapper + LAN discovery, zone generator, movement, roster + appearance |
|
|
| `server/` | dedicated server: game simulation, spawns, creatures, SQLite persistence |
|
|
| `client/` | `App` (screens, HUD, input), `World` (scene, entities, effects), `CharacterSprite` (paper-doll compositor), `NetClient` |
|
|
| `bot/` | headless test client |
|
|
| `assets/` | characters, creatures (roster + sprites), models (`safari.glb`), dialogue |
|
|
| `tools/` | art + data pipelines (below) |
|
|
| `art_src/` | raw PixelLab output, caches and reports the tools build from |
|
|
|
|
## Content pipelines
|
|
|
|
The game ships the generated assets; the tools are only needed to change them. API keys go in
|
|
`.env` here or in the parent folder (see `.env.example`).
|
|
|
|
### Brandlings (`tools/build_roster.py`)
|
|
|
|
```sh
|
|
python3 tools/build_roster.py --csv ../clickup_web_design_export.csv [--limit N] [--jobs 16]
|
|
```
|
|
|
|
Reads only the company name, domain, staging-site URL (logo search only, never stored), service
|
|
list and logo-note colour words from the ClickUp export (the export itself holds private client
|
|
data and is never committed). For each company: logo fetched from its domain, then its staging
|
|
site (`Company_Sprite_Gen/logo_fetch.py`; a logo shared by 2+ companies is a template and is
|
|
dropped), type from the jev classifier, sprite from Company_Sprite_Gen coloured from the logo
|
|
(else the logo note's colour words, else a per-creature shade of the type colour), plus
|
|
deterministic rarity, catch/flee rates and habitat. Writes `assets/creatures/roster.json`,
|
|
`assets/creatures/sprites/`, a review `contact_sheet.png` and `art_src/creatures/report.json`
|
|
(counts, logo hit rate by method, type histogram). Logos, classifier answers and renders are
|
|
cached in `art_src/creatures/cache/`, so reruns only do new work. Later this will read from the
|
|
ClickUp API instead of the CSV.
|
|
|
|
Species ids are permanent, because saved creatures store them and the loader requires
|
|
`id == index`. The first run numbers species by slug; later runs keep every slug's id, append
|
|
new companies after the last id, and keep companies that left the CSV as `"retired": true`
|
|
entries (sprite kept) so no id is reused. A `--limit N` test run leaves unsampled species as
|
|
they were. Retired Brandlings stay in players' collections but no longer spawn.
|
|
|
|
### Character art
|
|
|
|
`tools/character_art.py` builds the player layer library and Professor Henry with PixelLab;
|
|
see [docs/CHARACTER_ART.md](docs/CHARACTER_ART.md) for how one base walk cycle animates every
|
|
outfit and skin tone with 20 generations per item.
|
|
|
|
### 3D props
|
|
|
|
```sh
|
|
python3 tools/safari_textures.py # painted textures -> assets/models/src/
|
|
blender -b -P tools/blender_safari.py # -> assets/models/safari.glb
|
|
```
|
|
|
|
Gate, tent, fences, lamps, signposts, dock, crates, barrels, benches and hay bales, following
|
|
the engine's glTF conventions (see the engine README). Trees, bushes and rocks come from the
|
|
engine's `scenery.glb`.
|
|
|
|
## Verification
|
|
|
|
Captures render in a hidden window into an offscreen target, so nothing appears on screen and
|
|
keyboard input cannot disturb them:
|
|
|
|
```sh
|
|
./build/safari_server --port 27500 --db /tmp/test.db &
|
|
./build/safari_client --screenshot /tmp/play.png --frames 600 --connect 127.0.0.1:27500 \
|
|
--auto-create --auto-dialogue --profile test --move 0.8,0.6 --run --key 450:space
|
|
```
|
|
|
|
`--look body,skin,hair,hat,top,bottom,shoes[,colors]` sets the look, `--screen creator`
|
|
captures the creator, `--hour` / `--weather` set the title backdrop, `--key frame:key`
|
|
presses space / e / f / tab / enter on a frame, `--show` makes the window visible.
|