docs: rewrite skin creation workflow for current Lua backend

2026-07-05 13:45:41 +08:00
parent d205c8ecf2
commit e506b98e86
2 changed files with 320 additions and 45 deletions
+78
@@ -0,0 +1,78 @@
<a id="top"></a>
# Tekton Dash Armageddon - Server Architecture
## Nakama Backend (`server/nakama/lua/`)
[Back to top](#top)
The backend runs on Nakama, written in Lua. It provides authoritative server-side logic for the economy, anti-cheat, mail, and gacha pulls.
Godot clients call these endpoints via `BackendService.api_rpc_async()`.
### Key Lua Modules
[Back to top](#top)
- **`economy.lua`:**
- `buy_currency`: Validates mock/Google/Apple receipts and directly adds Gold or subtracts Gold for Stars via `nk.wallet_update`.
- `purchase_item`: Buys cosmetic items from the shop catalog. Validates wallet funds and writes to `inventory` storage.
- **`gacha.lua`:**
- `perform_gacha_pull`: Reads banner drop rates, rolls RNG on the server, manages pity counters, and deducts the pull cost from the wallet. Prevents client-side RNG manipulation.
- **`inbox.lua`:**
- `get_mail` / `claim_mail_reward`: Server-authoritative mail system. Rewards attached to mail are granted directly to the wallet on claim.
- **`user.lua` & `admin.lua`:**
- `admin_topup_gold`: Admin-only RPC to grant 999,999 gold.
- `admin_ban_player`: Banning tool.
- Profile and friend sync logic.
## Client / Server Wallet Synchronization
[Back to top](#top)
There is **only one authoritative wallet** per user stored on the Nakama backend.
1. Lua calls `nk.wallet_update(changeset)` upon any transaction.
2. The Godot client calls `UserProfileManager._reload_wallet()`.
3. Godot fetches the new JSON string from `account.wallet`.
4. `UserProfileManager` emits `profile_updated`.
5. All connected UI components (Lobby Top Bar, Shop Panel, Gacha Panel) instantly update their text labels to reflect the unified `gold` and `star` balances.
## CI/CD and Hot-Patching Pipeline (`.gitea/workflows/`)
[Back to top](#top)
The game features two distinct automated deployment pipelines.
### Full Releases (`ci.yml`)
[Back to top](#top)
- **Trigger:** Pushing a git tag (e.g. `v3.0.0`).
- **Purpose:** Compiling heavy native binaries (Windows `.exe`, Linux binaries, macOS `.app`).
- **Flow:**
1. Clones repository.
2. Pulls cached Godot 4.7 binary and export templates from `/cache/` on the runner to skip the 1.3GB download.
3. Runs `godot --headless --export-release` for each platform.
4. Zips outputs and uploads to Gitea's release asset API.
- **Player Experience:** Must download and replace their entire game folder/executable.
### Hot-Patching (`deploy_patch.yml`)
[Back to top](#top)
- **Trigger:** Manual `workflow_dispatch` button from the Gitea UI.
- **Purpose:** Pushing lightweight Godot asset changes (Scripts, Scenes, VFX, Sounds) instantly.
- **Flow:**
1. Python script `generate_version_json.py` consumes `CHANGELOG_DRAFT.md` and bumps `version.json`.
2. Runs `godot --headless --export-pack` to build a single, shared `patch.pck` file.
3. Force-pushes `patch.pck` and `version.json` to the orphan `patches` git branch.
4. A `gitea-pages` Docker container serves this branch publicly at `https://raw.klud.top`.
- **Player Experience:**
1. Game boots to `boot_screen.tscn`.
2. Fetches `https://raw.klud.top/danchie/tekton/version.json`.
3. Downloads `patch.pck` if the version is newer.
4. Mounts it into `user://` to override the base `res://` filesystem seamlessly.
+242 -45
@@ -1,60 +1,257 @@
# Skin Creation Workflow
How to author a new character skin and register it in the in-game shop.
<a id="top"></a>
## 1. Create the Skin Material
How to author a new character skin, register it in the game's shop/gacha catalog, and ship it to players.
Use the in-editor tool to build a color-maskable 3D material for a new character skin.
---
- Open the **Skin Shader Generator**: `res://scenes/tools/skin_shader_generator.tscn`
- Run the scene (F6).
- Import your base albedo and mask textures.
- Use the UI to visualize UV overlays and adjust color channels (Red, Green, Blue, Alpha masks).
- Export the configured material as a `.tres` file into `assets/materials/skins/`.
## Overview
## 2. Add the Skin to the Shop
Each skin is defined in two places that must stay in sync:
Update the game's catalog. The editor tool rewrites both the local script and the server-side module in one click.
| Layer | File | What it stores |
|---|---|---|
| **Client (visual)** | `scripts/managers/skin_manager.gd` | Mesh slots, material paths, override/overlay mode |
| **Server (shop)** | `server/nakama/lua/economy.lua` | Item ID, name, category, price (gold/star) |
- Open the **Skin Catalog Editor**: `res://scenes/tools/skin_catalog_editor.tscn`
- Press **F6** (or Right-click → Run Current Scene).
- Click **" New Skin"** and fill in:
- **ID**
- **Name**
- **Category**
- **Price** (Gold / Stars)
- The `.tres` material path exported in step 1.
- Click **"💾 Save & Generate"**. This rewrites:
- `res://scripts/managers/skin_manager.gd` (local catalog)
- `res://server/nakama/tekton_admin.js` (server-side shop logic)
The item `id` in `economy.lua` must match the dictionary key in `skin_manager.gd` `SKIN_CATALOG` exactly -- the Godot client looks up the item ID from the wallet/inventory and applies the matching skin data.
## Flow
[Back to top](#top)
```mermaid
flowchart TD
subgraph phase1 [Skin Material Creation]
A[Albedo & Mask Textures] --> B{Skin Shader Generator}
B -->|Export| C[material.tres]
end
---
subgraph phase2 [Catalog Definition]
C --> D{Skin Catalog Editor}
D -->|Save & Generate| E[tekton_admin.js]
D -->|Save & Generate| F[skin_manager.gd]
end
## Step 1: Create the Skin Material
subgraph phase3 [Shop Backend]
E -->|SSH & nano| G[VPS: ~/tekton_admin.js]
G -->|docker cp| H[Nakama Container]
H -->|Restart| I[Live Shop Logic Sync]
end
Open the **Skin Shader Generator** at `res://scenes/tools/skin_shader_generator.tscn`.
subgraph phase4 [Asset Delivery]
C --> J[Git Push]
F --> J
J -->|CI/CD| K[patch.pck]
K -->|Auto Download| L[Player Client Assets]
end
1. Run the scene (F6).
2. Import your base albedo and mask textures (PNG with color/alpha channels).
3. Use the UI to visualize UV overlays and adjust color channels (Red, Green, Blue, Alpha).
4. Export the configured material as a `.tres` file into `assets/materials/skins/` or a subfolder:
- `assets/characters/skins/hat/`
- `assets/characters/skins/clothing/`
- `assets/characters/skins/gloves/`
**Material path conventions (Oldpop character):**
| Category | Example path |
|---|---|
| hat | `res://assets/characters/skins/hat/oldpop_mat_hat_blue.tres` |
| costume/clothing | `res://assets/characters/skins/clothing/oldpop_mat_cloth_red_pant.tres` |
| glove | `res://assets/characters/skins/gloves/oldpop_mat_gloves_blue.tres` |
| accessory | `res://assets/characters/skins/accessory/` |
[Back to top](#top)
---
## Step 2: Register the Skin in SkinManager (Client)
Open `res://scripts/managers/skin_manager.gd` and add a new entry inside `SKIN_CATALOG` (between `[BEGIN_SKIN_CATALOG]` and `[END_SKIN_CATALOG]` markers).
### Entry format
```gdscript
"item_id": {
"category": "head", # head | costume | glove | accessory
"character": "Oldpop", # node name under CharacterRoot
"slots": [
{
"mesh": "oldpop-hat1", # MeshInstance3D child name
"mode": "override", # "override" | "overlay"
"material": "res://path/to/material.tres"
},
]
}
```
After step 2, follow the [Nakama Deployment](./Nakama-Deployment) page to sync the backend, then the [Patch Release Workflow](./Patch-Release-Workflow) page to ship the assets.
### Slot modes
- **`override`** -- `set_surface_override_material(0, mat)`. Replaces the base material entirely. Preserves the outline shader (`next_pass`) automatically.
- **`overlay`** -- `material_overlay = mat`. Transparent layer on top of the base material. Good for costume/pant patterns.
### Multi-slot skins (costume example)
Costumes typically touch 3 meshes:
```gdscript
"oldpop-grey-pant": {
"category": "costume",
"character": "Oldpop",
"slots": [
{ "mesh": "oldpop-body", "mode": "overlay", "material": "res://assets/characters/skins/clothing/oldpop_mat_cloth_grey_pant.tres" },
{ "mesh": "oldpop-bottom1", "mode": "override", "material": "res://assets/characters/skins/clothing/oldpop_mat_cloth_grey_pant.tres" },
{ "mesh": "oldpop-bottom2", "mode": "override", "material": "res://assets/characters/skins/clothing/oldpop_mat_cloth_grey_pant.tres" },
]
},
```
### Tips
- Leave `"material"` as `""` if the `.tres` file is not ready yet. The slot is skipped gracefully.
- Use the **Skin Catalog Editor** (`res://scenes/tools/skin_catalog_editor.tscn`) to avoid manual edits. Click **Save & Generate** to rewrite both `skin_manager.gd` and `economy.lua`.
[Back to top](#top)
---
## Step 3: Register the Skin in Economy (Server)
Open `server/nakama/lua/economy.lua` and add a new entry to `SHOP_CATALOG_DEFS`.
### Catalog entry format
```lua
{ id = "oldpop-blue-hat", name = "Oldpop Blue Hat", category = "head", gold = 100, star = 0, rarity = "Common", character = "Oldpop" },
```
| Field | Type | Description |
|---|---|---|
| `id` | string | Must match the `SKIN_CATALOG` key in `skin_manager.gd` exactly |
| `name` | string | Display name shown in shop |
| `category` | string | `head` / `costume` / `glove` / `accessory` |
| `gold` | number | Gold coin price (0 = not sold for gold) |
| `star` | number | Star gem price (0 = not sold for stars) |
| `rarity` | string | `"Common"` / `"Uncommon"` / `"Rare"` -- cosmetic label only |
| `character` | string | Character this skin belongs to (e.g. `"Oldpop"`) |
### Existing catalog (12 items)
```
oldpop-blue-hat head 100 gold Common Oldpop
oldpop-green-hat head 100 gold Common Oldpop
oldpop-red-hat head 100 gold Common Oldpop
oldpop-yellow-hat head 100 gold Common Oldpop
oldpop-og-pant costume 0 gold Common Oldpop (free)
oldpop-grey-pant costume 150 gold Common Oldpop
oldpop-red-pant costume 150 gold Common Oldpop
oldpop-yellow-pant costume 150 gold Common Oldpop
oldpop-blue-gloves glove 75 gold Common Oldpop
oldpop-green-gloves glove 75 gold Common Oldpop
oldpop-red-gloves glove 75 gold Common Oldpop
oldpop-yellow-gloves glove 75 gold Common Oldpop
```
[Back to top](#top)
---
## Step 4 (Optional): Add Skin as Gacha Prize
Gacha-only skins are registered in `server/nakama/lua/gacha.lua` inside `GACHA_DATA.real_prize_catalog`.
### Existing gacha skins (4 items)
```lua
skin_gacha_rainbow_suit = { name = "Rainbow Suit", category = "costume", rarity = "real_prize", character = "" }
skin_gacha_dragon_hat = { name = "Dragon Hat", category = "head", rarity = "real_prize", character = "" }
skin_gacha_phantom_gloves = { name = "Phantom Gloves", category = "glove", rarity = "real_prize", character = "" }
skin_gacha_neon_acc = { name = "Neon Accessory", category = "accessory", rarity = "real_prize", character = "" }
```
Gacha skins also need a `skin_manager.gd` entry (same step 2 format) and a `skin_catalog_editor` entry so `SkinManager.apply_loadout()` can render them. The server catalog is optional -- gacha skins are not sold in the shop directly.
The gacha pulls these IDs from `GACHA_DATA.pools.real_prize`, so add your item_id there too.
```lua
pools = {
common = {"frag_common"},
uncommon = {"frag_uncommon"},
rare = {"frag_rare"},
real_prize = {
"skin_gacha_rainbow_suit",
"skin_gacha_dragon_hat",
"skin_gacha_phantom_gloves",
"skin_gacha_neon_acc",
-- add new skin here
}
},
```
[Back to top](#top)
---
## Step 5: Deploy
### Hot patch (content only) -- recommended for skins
1. Commit changes to `experimental` branch:
- `scripts/managers/skin_manager.gd`
- `server/nakama/lua/economy.lua` (if shop item)
- `server/nakama/lua/gacha.lua` (if gacha prize)
- Material `.tres` files
2. Push to `experimental`.
3. Trigger `deploy_patch.yml` via Gitea UI workflow dispatch.
- CI runs `--export-pack` to build `patch.pck`.
- CI force-pushes `patch.pck` + `version.json` to `patches` branch.
- Existing players auto-download on next boot via `GameUpdateManager`.
### Full binary release (if engine/templates changed)
Tag a version (e.g. `v2.5.0`) and push. CI builds all platform binaries and uploads to the release.
[Back to top](#top)
---
## Full Flow Diagram
```
┌──────────────────────┐
│ 1. Create Material │
│ skin_shader_generator │
│ ────────────────── │
│ Export .tres file │
└────────┬─────────────┘
v
┌──────────────────────────────┐
│ 2. Register in SkinManager │
│ skin_manager.gd │
│ ────────────────── │
│ Add SKIN_CATALOG entry │
│ (mesh slots + material) │
└────────┬─────────────────────┘
v
┌──────────────────────────────┐
│ 3. Register in Economy │
│ economy.lua │
│ ────────────────── │
│ Add SHOP_CATALOG_DEFS │
│ (price, name, category) │
└────────┬─────────────────────┘
v (optional)
┌──────────────────────────────┐
│ 4. Gacha Prize │
│ gacha.lua │
│ ────────────────── │
│ real_prize_catalog + pools │
└────────┬─────────────────────┘
v
┌──────────────────────┐
│ 5. Git Push & CI │
│ deploy_patch.yml │
│ ────────────────── │
│ patch.pck → players │
└──────────────────────┘
```
[Back to top](#top)
---
## Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Skin visible in editor but not in-game | Material path wrong or `.tres` not exported | Verify `res://` path in SKIN_CATALOG, run `--export-pack` |
| Skin purchase fails: "NotEnoughFunds" | Wallet balance insufficient | Check gold/star prices in economy.lua |
| Skin visible on all characters | `character` field wrong | Set correct character node name |
| Skin purchase fails: "ItemNotFound" | Item ID not in SHOP_CATALOG_DEFS | Add entry matching SKIN_CATALOG key |
| Player downloads patch but skin missing | econmy.lua change didn't reach server | Nakama hot-reload: restart Nakama container or wait for next restart |
| Outline shader lost on skin | next_pass not preserved | SkinManager preserves it automatically -- verify with latest `skin_manager.gd` |
[Back to top](#top)