lpc-forge 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/CREDITS.csv +22985 -0
  2. package/LICENSE +674 -0
  3. package/README.md +281 -0
  4. package/assets/music/.gitkeep +0 -0
  5. package/dist/character/batch.d.ts +17 -0
  6. package/dist/character/batch.js +48 -0
  7. package/dist/character/batch.js.map +1 -0
  8. package/dist/character/composer.d.ts +3 -0
  9. package/dist/character/composer.js +164 -0
  10. package/dist/character/composer.js.map +1 -0
  11. package/dist/character/definitions.d.ts +16 -0
  12. package/dist/character/definitions.js +116 -0
  13. package/dist/character/definitions.js.map +1 -0
  14. package/dist/character/presets.d.ts +6 -0
  15. package/dist/character/presets.js +246 -0
  16. package/dist/character/presets.js.map +1 -0
  17. package/dist/character/slicer.d.ts +8 -0
  18. package/dist/character/slicer.js +66 -0
  19. package/dist/character/slicer.js.map +1 -0
  20. package/dist/character/types.d.ts +48 -0
  21. package/dist/character/types.js +32 -0
  22. package/dist/character/types.js.map +1 -0
  23. package/dist/cli.d.ts +2 -0
  24. package/dist/cli.js +938 -0
  25. package/dist/export/frames.d.ts +5 -0
  26. package/dist/export/frames.js +15 -0
  27. package/dist/export/frames.js.map +1 -0
  28. package/dist/export/godot.d.ts +17 -0
  29. package/dist/export/godot.js +464 -0
  30. package/dist/export/godot.js.map +1 -0
  31. package/dist/export/types.d.ts +11 -0
  32. package/dist/export/types.js +2 -0
  33. package/dist/export/types.js.map +1 -0
  34. package/dist/license.d.ts +49 -0
  35. package/dist/license.js +271 -0
  36. package/dist/map/cellular.d.ts +3 -0
  37. package/dist/map/cellular.js +191 -0
  38. package/dist/map/cellular.js.map +1 -0
  39. package/dist/map/dungeon.d.ts +3 -0
  40. package/dist/map/dungeon.js +238 -0
  41. package/dist/map/dungeon.js.map +1 -0
  42. package/dist/map/multifloor.d.ts +20 -0
  43. package/dist/map/multifloor.js +57 -0
  44. package/dist/map/multifloor.js.map +1 -0
  45. package/dist/map/overworld.d.ts +3 -0
  46. package/dist/map/overworld.js +205 -0
  47. package/dist/map/overworld.js.map +1 -0
  48. package/dist/map/town.d.ts +7 -0
  49. package/dist/map/town.js +181 -0
  50. package/dist/map/town.js.map +1 -0
  51. package/dist/map/types.d.ts +65 -0
  52. package/dist/map/types.js +16 -0
  53. package/dist/map/types.js.map +1 -0
  54. package/dist/map/wfc.d.ts +18 -0
  55. package/dist/map/wfc.js +192 -0
  56. package/dist/map/wfc.js.map +1 -0
  57. package/dist/tileset/atlas.d.ts +15 -0
  58. package/dist/tileset/atlas.js +55 -0
  59. package/dist/tileset/atlas.js.map +1 -0
  60. package/dist/tileset/registry.d.ts +12 -0
  61. package/dist/tileset/registry.js +71 -0
  62. package/dist/tileset/registry.js.map +1 -0
  63. package/dist/tileset/terrain.d.ts +3 -0
  64. package/dist/tileset/terrain.js +110 -0
  65. package/dist/tileset/terrain.js.map +1 -0
  66. package/dist/utils/credits.d.ts +11 -0
  67. package/dist/utils/credits.js +74 -0
  68. package/dist/utils/credits.js.map +1 -0
  69. package/dist/utils/image.d.ts +17 -0
  70. package/dist/utils/image.js +94 -0
  71. package/dist/utils/image.js.map +1 -0
  72. package/dist/utils/rng.d.ts +18 -0
  73. package/dist/utils/rng.js +48 -0
  74. package/dist/utils/rng.js.map +1 -0
  75. package/package.json +77 -0
package/README.md ADDED
@@ -0,0 +1,281 @@
1
+ <div align="center">
2
+
3
+ # ⚔️ LPC Forge
4
+
5
+ **Generate a complete 2D RPG from one command.**
6
+
7
+ Characters · Maps · Enemy AI · Inventory · Dialog · Menus · SFX · Lighting · Particles · Godot 4.6
8
+
9
+ [![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-blue.svg)](LICENSE)
10
+ [![Node.js](https://img.shields.io/badge/Node.js-21+-green.svg)](https://nodejs.org)
11
+ [![Godot](https://img.shields.io/badge/Godot-4.6+-purple.svg)](https://godotengine.org)
12
+ [![CI](https://github.com/LaunchDay-Studio-Inc/lpc-forge/actions/workflows/ci.yml/badge.svg)](https://github.com/LaunchDay-Studio-Inc/lpc-forge/actions)
13
+ [![Discord](https://img.shields.io/badge/Discord-Join-5865F2?logo=discord&logoColor=white)](https://discord.gg/bJDGXc4DvW)
14
+
15
+ [Website](https://blueth.online) · [Get Premium](https://launchday.gumroad.com/l/lpc-forge-premium) · [Discord](https://discord.gg/bJDGXc4DvW) · [Report Bug](https://github.com/LaunchDay-Studio-Inc/lpc-forge/issues)
16
+
17
+ </div>
18
+
19
+ ---
20
+
21
+ ## What is LPC Forge?
22
+
23
+ LPC Forge generates **playable 2D RPG projects** for Godot 4.6 using [Liberated Pixel Cup](https://lpc.opengameart.org/) assets. Not just sprites — complete game projects with working systems you can customize.
24
+
25
+ **Free tier** gives you characters, maps, and a basic playable project.
26
+ **Premium** ($10) gives you the full RPG game kit — inventory, dialog, enemy AI, menus, save/load, sound effects, lighting, particles, and more. All from one command.
27
+
28
+ ```bash
29
+ # Free — character + map + basic project
30
+ lpc-forge init my-rpg
31
+
32
+ # Premium — complete playable RPG
33
+ lpc-forge init my-rpg --full
34
+ ```
35
+
36
+ Open in Godot → Press F5 → **Play.**
37
+
38
+ ---
39
+
40
+ ## Quick Start
41
+
42
+ ```bash
43
+ # Clone and install
44
+ git clone https://github.com/LaunchDay-Studio-Inc/lpc-forge.git
45
+ cd lpc-forge
46
+ npm install && npm run build
47
+
48
+ # Generate a free project
49
+ npx lpc-forge init my-rpg
50
+
51
+ # Open in Godot 4.6 → Import → Select my-rpg/project.godot → Press F5
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Free vs Premium
57
+
58
+ | Feature | Free | Premium ($10) |
59
+ |---------|:----:|:-------------:|
60
+ | **Character Compositor** (17 presets, custom specs) | ✅ | ✅ |
61
+ | **Map Generation** (dungeon, cave, overworld, town, WFC, multifloor) | ✅ | ✅ |
62
+ | **Godot Project Scaffold** (player controller, hitbox, camera, HUD) | ✅ | ✅ |
63
+ | **Batch Character Generation** | ✅ | ✅ |
64
+ | **Enemy AI** (patrol, chase, attack, flee, boss patterns) | — | ✅ |
65
+ | **Inventory System** (grid UI, equip, stack, drag-drop, tooltips) | — | ✅ |
66
+ | **Dialog System** (text box, choices, portraits, typing effect) | — | ✅ |
67
+ | **Menu System** (main, pause, settings, game over, credits) | — | ✅ |
68
+ | **Save/Load** (JSON, 3 slots, auto-save) | — | ✅ |
69
+ | **Scene Transitions** (doors, stairs, fade, area loading) | — | ✅ |
70
+ | **Loot & Drop System** (drop tables, item pickup, XP/gold drops) | — | ✅ |
71
+ | **Quest Tracker** (objectives, markers, completion) | — | ✅ |
72
+ | **Day/Night Cycle** (time-of-day modulation, lamp auto-on) | — | ✅ |
73
+ | **Full HUD** (HP, MP, XP, gold, minimap, hotbar, buffs) | — | ✅ |
74
+ | **Sound Effects** (45 procedural SFX: combat, UI, movement, magic) | — | ✅ |
75
+ | **Music Catalog** (curated CC0 BGM tracks for every scene type) | — | ✅ |
76
+ | **UI Kit** (panels, buttons, frames, tooltips, medieval theme) | — | ✅ |
77
+ | **Item Icons** (swords, potions, scrolls, armor, food, keys) | — | ✅ |
78
+ | **Props** (chests, barrels, torches, signs, wells, fences) | — | ✅ |
79
+ | **Character Portraits** (auto-cropped, 3 sizes) | — | ✅ |
80
+ | **Lighting Presets** (8 presets: dungeon, overworld, cave, boss arena) | — | ✅ |
81
+ | **Particle Effects** (8 effects: rain, snow, fireflies, fire, magic) | — | ✅ |
82
+ | **Enemy Characters** (skeleton, guard, thief — full spritesheets) | — | ✅ |
83
+ | **NPC Characters** (merchant, healer, peasant — full spritesheets) | — | ✅ |
84
+ | **Autoload Wiring** (systems auto-registered in project.godot) | — | ✅ |
85
+ | **Input Actions** (inventory, interact, pause, quest log pre-configured) | — | ✅ |
86
+
87
+ ### Get Premium
88
+
89
+ ```bash
90
+ # 1. Purchase at https://launchday.gumroad.com/l/lpc-forge-premium
91
+ # 2. Activate your license key
92
+ lpc-forge activate <your-license-key>
93
+
94
+ # 3. Generate a complete RPG
95
+ lpc-forge init my-rpg --full
96
+ ```
97
+
98
+ $10. One-time purchase. Unlimited projects. No subscription.
99
+
100
+ ---
101
+
102
+ ## Commands
103
+
104
+ ### Free Commands
105
+
106
+ ```bash
107
+ # Character compositor
108
+ lpc-forge character --preset paladin -o ./output
109
+ lpc-forge character --body female --hair plain:blonde --armor plate:gold
110
+ lpc-forge character --list-layers
111
+
112
+ # Batch generation
113
+ lpc-forge batch --presets warrior,mage,rogue -o ./output
114
+
115
+ # Map generation
116
+ lpc-forge map --type dungeon --width 50 --height 50 -o ./output
117
+ lpc-forge map --type overworld --seed "my-world" -o ./output
118
+
119
+ # List all presets
120
+ lpc-forge list
121
+
122
+ # Project scaffold (free tier)
123
+ lpc-forge init my-rpg --character warrior --map dungeon
124
+ ```
125
+
126
+ ### Premium Commands
127
+
128
+ ```bash
129
+ # Full RPG project (all systems, all assets)
130
+ lpc-forge init my-rpg --full
131
+
132
+ # Individual premium generators
133
+ lpc-forge systems --list # Preview (free)
134
+ lpc-forge systems -o ./output # Generate (premium)
135
+ lpc-forge sfx --list # Preview (free)
136
+ lpc-forge sfx -o ./output # Generate (premium)
137
+ lpc-forge ui --list-themes # Preview (free)
138
+ lpc-forge ui -o ./output # Generate (premium)
139
+ lpc-forge lighting --list # Preview (free)
140
+ lpc-forge lighting -o ./output # Generate (premium)
141
+ lpc-forge particles --list # Preview (free)
142
+ lpc-forge particles -o ./output # Generate (premium)
143
+ lpc-forge icons -o ./output # Generate (premium)
144
+ lpc-forge props -o ./output # Generate (premium)
145
+ lpc-forge portrait --character warrior -o ./output # Generate (premium)
146
+
147
+ # License management
148
+ lpc-forge activate <key> # Activate license
149
+ lpc-forge activate --status # Check license status
150
+ lpc-forge activate --deactivate # Remove license
151
+ ```
152
+
153
+ ---
154
+
155
+ ## What `init --full` Generates
156
+
157
+ ```
158
+ my-rpg/
159
+ ├── project.godot # Pre-configured with autoloads + input actions
160
+ ├── sprites/
161
+ │ ├── warrior/ # Player character (8-dir, all animations)
162
+ │ ├── skeleton/ # Enemy character
163
+ │ ├── guard/ # Enemy character
164
+ │ ├── thief/ # Enemy character
165
+ │ ├── npc_merchant/ # NPC character
166
+ │ ├── npc_healer/ # NPC character
167
+ │ └── npc_peasant/ # NPC character
168
+ ├── scripts/
169
+ │ ├── player.gd # State machine player controller
170
+ │ ├── enemy_ai.gd # Patrol/chase/attack FSM
171
+ │ ├── inventory_manager.gd # Grid inventory system (autoload)
172
+ │ ├── inventory_ui.gd # Drag-drop inventory UI
173
+ │ ├── dialog_manager.gd # Dialog system (autoload)
174
+ │ ├── dialog_box.gd # Text box with typing effect
175
+ │ ├── save_manager.gd # Save/load system (autoload)
176
+ │ ├── scene_manager.gd # Scene transitions (autoload)
177
+ │ ├── loot_manager.gd # Drop tables and item pickup (autoload)
178
+ │ ├── quest_manager.gd # Quest tracker (autoload)
179
+ │ ├── day_night.gd # Day/night cycle (autoload)
180
+ │ ├── menu_manager.gd # Menu system (autoload)
181
+ │ ├── hud.gd # Full HUD (autoload)
182
+ │ └── game_config.gd # Global game constants
183
+ ├── dungeon.tscn # Generated dungeon map
184
+ ├── tileset/ # Terrain tiles
185
+ ├── ui/ # UI kit (panels, buttons, frames)
186
+ ├── icons/ # Item icon sprites
187
+ ├── props/ # Prop sprites
188
+ ├── portraits/ # Character portraits (3 sizes)
189
+ ├── lighting/ # 8 lighting preset scenes
190
+ ├── particles/ # 8 particle effect scenes
191
+ ├── sfx/ # 45 sound effects (.wav)
192
+ ├── music/ # BGM tracks catalog
193
+ └── map_preview.png # Map overview image
194
+ ```
195
+
196
+ **Open in Godot → Press F5 → Walk around, fight enemies, open inventory, talk to NPCs.**
197
+
198
+ ---
199
+
200
+ ## Character Presets
201
+
202
+ | Preset | Layers | Style |
203
+ |--------|--------|-------|
204
+ | `warrior` | Plate armor, longsword, brown hair | Classic RPG fighter |
205
+ | `mage` | Robe, staff, white hair | Spellcaster |
206
+ | `rogue` | Leather armor, dagger, black hair | Stealth class |
207
+ | `ranger` | Leather, bow, green hood | Ranged fighter |
208
+ | `paladin` | Gold plate, greatsword, blonde hair | Holy knight |
209
+ | `necromancer` | Dark robe, skull staff, bald | Dark magic |
210
+ | `cleric` | White robe, mace, brown hair | Healer |
211
+ | `barbarian` | Fur armor, battleaxe, red hair | Berserker |
212
+ | `monk` | Simple clothes, bo staff, shaved head | Martial arts |
213
+ | `bard` | Fancy clothes, lute, curly hair | Support class |
214
+ | ...and 7 more | | |
215
+
216
+ ```bash
217
+ # See all presets
218
+ lpc-forge list
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Map Types
224
+
225
+ | Type | Algorithm | Features |
226
+ |------|-----------|----------|
227
+ | `dungeon` | BSP (Binary Space Partition) | Rooms, corridors, doors, spawn/treasure/boss POIs |
228
+ | `cave` | Cellular Automata | Organic caverns, varied openness |
229
+ | `overworld` | Multi-octave noise | Biomes, rivers, mountains, forests |
230
+ | `town` | District-based | Houses, shops, roads, town square |
231
+ | `wfc` | Wave Function Collapse | Pattern-based, highly varied |
232
+ | `multifloor` | Stacked BSP | Multi-level dungeons with stairs |
233
+
234
+ ```bash
235
+ lpc-forge map --type dungeon --width 60 --height 60 --seed "my-dungeon" -o ./output
236
+ ```
237
+
238
+ ---
239
+
240
+ ## License & Credits
241
+
242
+ ### Tool License
243
+
244
+ LPC Forge (the CLI tool) is licensed under **GPL-3.0-or-later**. See [LICENSE](LICENSE).
245
+
246
+ ### Asset Licenses
247
+
248
+ Character sprites use [Liberated Pixel Cup](https://lpc.opengameart.org/) assets under **CC-BY-SA 3.0** and **CC-BY-SA 4.0**. Full artist credits in [CREDITS.csv](CREDITS.csv).
249
+
250
+ Premium assets include curated content from OpenGameArt.org under CC0/CC-BY-SA licenses. When distributing games made with LPC Forge, include the generated `CREDITS.md` file.
251
+
252
+ ### Premium License
253
+
254
+ LPC Forge Premium (the `--full` content pack, game systems, SFX presets, and curated assets) is sold separately at [blueth.online](https://launchday.gumroad.com/l/lpc-forge-premium). One-time purchase, unlimited projects, no subscription.
255
+
256
+ ---
257
+
258
+ ## Contributing
259
+
260
+ See [CONTRIBUTING.md](CONTRIBUTING.md). PRs welcome for the open-source core — character presets, map algorithms, export improvements.
261
+
262
+ Premium features (systems, SFX, UI, lighting, particles) are maintained by [LaunchDay Studio](https://launchdaystudio.com).
263
+
264
+ ---
265
+
266
+ ## Links
267
+
268
+ - 🌐 [blueth.online](https://blueth.online) — Blueth Plugin Marketplace
269
+ - 💬 [Discord](https://discord.gg/bJDGXc4DvW) — Community & Support
270
+ - 🐛 [Issues](https://github.com/LaunchDay-Studio-Inc/lpc-forge/issues) — Bug Reports
271
+ - 📖 [LPC Wiki](https://lpc.opengameart.org/) — Liberated Pixel Cup
272
+
273
+ ---
274
+
275
+ <div align="center">
276
+
277
+ **Made by [LaunchDay Studio](https://launchdaystudio.com) 🚀**
278
+
279
+ *Stop spending weeks on placeholder art. Start building your game.*
280
+
281
+ </div>
File without changes
@@ -0,0 +1,17 @@
1
+ import type { CharacterSpec } from './types.js';
2
+ export interface BatchEntry {
3
+ name: string;
4
+ preset?: string;
5
+ spec?: CharacterSpec;
6
+ slice?: boolean;
7
+ godot?: boolean;
8
+ }
9
+ export interface BatchConfig {
10
+ characters: BatchEntry[];
11
+ outputDir?: string;
12
+ }
13
+ export declare function runBatch(configPath: string, repoRoot: string, outputBase: string): Promise<{
14
+ name: string;
15
+ success: boolean;
16
+ error?: string;
17
+ }[]>;
@@ -0,0 +1,48 @@
1
+ import { composeCharacter } from './composer.js';
2
+ import { PRESETS } from './presets.js';
3
+ import { sliceCharacter } from './slicer.js';
4
+ import { exportCharacterToGodot } from '../export/godot.js';
5
+ import { mkdir, writeFile, readFile } from 'node:fs/promises';
6
+ import { join, resolve } from 'node:path';
7
+ export async function runBatch(configPath, repoRoot, outputBase) {
8
+ const raw = await readFile(configPath, 'utf-8');
9
+ const config = JSON.parse(raw);
10
+ const results = [];
11
+ for (const entry of config.characters) {
12
+ const outDir = resolve(outputBase, entry.name);
13
+ await mkdir(outDir, { recursive: true });
14
+ try {
15
+ let spec;
16
+ if (entry.preset) {
17
+ const preset = PRESETS[entry.preset];
18
+ if (!preset)
19
+ throw new Error(`Unknown preset: ${entry.preset}`);
20
+ spec = preset.spec;
21
+ }
22
+ else if (entry.spec) {
23
+ spec = entry.spec;
24
+ }
25
+ else {
26
+ throw new Error('Each entry needs either "preset" or "spec"');
27
+ }
28
+ const buffer = await composeCharacter(spec, repoRoot);
29
+ await writeFile(join(outDir, 'spritesheet.png'), buffer);
30
+ if (entry.slice) {
31
+ await sliceCharacter(buffer, join(outDir, 'frames'));
32
+ }
33
+ if (entry.godot) {
34
+ await exportCharacterToGodot(buffer, outDir, entry.name);
35
+ }
36
+ results.push({ name: entry.name, success: true });
37
+ }
38
+ catch (err) {
39
+ results.push({
40
+ name: entry.name,
41
+ success: false,
42
+ error: err instanceof Error ? err.message : String(err),
43
+ });
44
+ }
45
+ }
46
+ return results;
47
+ }
48
+ //# sourceMappingURL=batch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch.js","sourceRoot":"","sources":["../../src/character/batch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAe1C,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,UAAkB,EAClB,QAAgB,EAChB,UAAkB;IAElB,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAChD,MAAM,MAAM,GAAgB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAyD,EAAE,CAAC;IAEzE,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACtC,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/C,MAAM,KAAK,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAEzC,IAAI,CAAC;YACH,IAAI,IAAmB,CAAC;YACxB,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;gBACjB,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;gBACrC,IAAI,CAAC,MAAM;oBAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;gBAChE,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;YACrB,CAAC;iBAAM,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;gBACtB,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;YACpB,CAAC;iBAAM,CAAC;gBACN,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;YAChE,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YACtD,MAAM,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,CAAC,CAAC;YAEzD,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;gBAChB,MAAM,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;YACvD,CAAC;YAED,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;gBAChB,MAAM,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3D,CAAC;YAED,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QACpD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACxD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type CharacterSpec } from './types.js';
2
+ /** Compose a complete character spritesheet from a CharacterSpec */
3
+ export declare function composeCharacter(spec: CharacterSpec, repoRoot: string): Promise<Buffer>;
@@ -0,0 +1,164 @@
1
+ import sharp from 'sharp';
2
+ import { existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { ANIMATIONS, FRAME_SIZE, SHEET_WIDTH, SHEET_HEIGHT, ANIMATION_FOLDER_MAP, } from './types.js';
5
+ import { loadDefinitions, findDefinition } from './definitions.js';
6
+ /** Compose a complete character spritesheet from a CharacterSpec */
7
+ export async function composeCharacter(spec, repoRoot) {
8
+ const registry = await loadDefinitions(repoRoot);
9
+ const spritesDir = join(repoRoot, 'spritesheets');
10
+ // Resolve all layers to concrete sprite paths
11
+ const resolvedLayers = [];
12
+ for (const layer of spec.layers) {
13
+ const def = findDefinition(registry, layer.category, layer.subcategory);
14
+ if (!def) {
15
+ console.warn(`Warning: No definition found for ${layer.category}/${layer.subcategory}, skipping`);
16
+ continue;
17
+ }
18
+ for (const entry of def.layers) {
19
+ // Skip custom animation layers for now (oversize attacks, etc.)
20
+ if (entry.customAnimation)
21
+ continue;
22
+ const basePath = entry.paths[spec.bodyType];
23
+ if (!basePath) {
24
+ // Try to find a fallback body type
25
+ const fallback = findFallbackBodyType(entry.paths, spec.bodyType);
26
+ if (!fallback) {
27
+ console.warn(`Warning: ${def.name} layer has no path for body type "${spec.bodyType}", skipping`);
28
+ continue;
29
+ }
30
+ resolvedLayers.push({
31
+ zPos: entry.zPos,
32
+ basePath: fallback,
33
+ variant: layer.variant,
34
+ animations: def.animations,
35
+ });
36
+ }
37
+ else {
38
+ resolvedLayers.push({
39
+ zPos: entry.zPos,
40
+ basePath,
41
+ variant: layer.variant,
42
+ animations: def.animations,
43
+ });
44
+ }
45
+ }
46
+ }
47
+ // Sort by zPos ascending (back to front)
48
+ resolvedLayers.sort((a, b) => a.zPos - b.zPos);
49
+ // Build universal sheets for each resolved layer
50
+ const layerBuffers = [];
51
+ for (const layer of resolvedLayers) {
52
+ const buffer = await buildUniversalSheet(layer, spritesDir);
53
+ if (buffer) {
54
+ layerBuffers.push(buffer);
55
+ }
56
+ }
57
+ if (layerBuffers.length === 0) {
58
+ throw new Error('No valid layers could be loaded. Check your CharacterSpec.');
59
+ }
60
+ // Composite all layers together
61
+ const composites = layerBuffers.map((input) => ({
62
+ input,
63
+ top: 0,
64
+ left: 0,
65
+ blend: 'over',
66
+ }));
67
+ return sharp({
68
+ create: {
69
+ width: SHEET_WIDTH,
70
+ height: SHEET_HEIGHT,
71
+ channels: 4,
72
+ background: { r: 0, g: 0, b: 0, alpha: 0 },
73
+ },
74
+ })
75
+ .png()
76
+ .composite(composites)
77
+ .toBuffer();
78
+ }
79
+ function findFallbackBodyType(paths, bodyType) {
80
+ // Fallback chain
81
+ const fallbacks = {
82
+ muscular: ['male'],
83
+ pregnant: ['female'],
84
+ teen: ['male', 'female'],
85
+ child: ['teen', 'male'],
86
+ };
87
+ const chain = fallbacks[bodyType] ?? [];
88
+ for (const fb of chain) {
89
+ if (paths[fb])
90
+ return paths[fb];
91
+ }
92
+ return null;
93
+ }
94
+ /** Build a universal 832×3456 sheet from individual animation PNGs */
95
+ async function buildUniversalSheet(layer, spritesDir) {
96
+ const composites = [];
97
+ // Map animation names used in definitions to folder names
98
+ const animationList = Object.keys(ANIMATIONS);
99
+ for (const animName of animationList) {
100
+ const animInfo = ANIMATIONS[animName];
101
+ // Check if this layer supports this animation
102
+ const defAnimNames = layer.animations.map((a) => {
103
+ const mapped = ANIMATION_FOLDER_MAP[a];
104
+ return mapped ?? a;
105
+ });
106
+ // The folder name might differ from the animation name
107
+ const folderName = ANIMATION_FOLDER_MAP[animName] ?? animName;
108
+ // Check if the definition lists this animation (using either name)
109
+ const supportsAnim = layer.animations.length === 0 || // no filter = all
110
+ layer.animations.some((a) => {
111
+ const mapped = ANIMATION_FOLDER_MAP[a] ?? a;
112
+ return mapped === animName || mapped === folderName || a === animName;
113
+ });
114
+ if (!supportsAnim)
115
+ continue;
116
+ // Convert variant name: spaces to underscores for filenames
117
+ const variantFile = layer.variant.replace(/ /g, '_');
118
+ // Try to find the animation PNG
119
+ const animPath = join(spritesDir, layer.basePath, folderName, `${variantFile}.png`);
120
+ if (!existsSync(animPath)) {
121
+ // Also try without folder mapping
122
+ const altPath = join(spritesDir, layer.basePath, animName, `${variantFile}.png`);
123
+ if (existsSync(altPath)) {
124
+ try {
125
+ const buf = await sharp(altPath).png().toBuffer();
126
+ composites.push({
127
+ input: buf,
128
+ top: animInfo.row * FRAME_SIZE,
129
+ left: 0,
130
+ });
131
+ }
132
+ catch {
133
+ // Skip unreadable files
134
+ }
135
+ }
136
+ continue;
137
+ }
138
+ try {
139
+ const buf = await sharp(animPath).png().toBuffer();
140
+ composites.push({
141
+ input: buf,
142
+ top: animInfo.row * FRAME_SIZE,
143
+ left: 0,
144
+ });
145
+ }
146
+ catch {
147
+ // Skip unreadable files
148
+ }
149
+ }
150
+ if (composites.length === 0)
151
+ return null;
152
+ return sharp({
153
+ create: {
154
+ width: SHEET_WIDTH,
155
+ height: SHEET_HEIGHT,
156
+ channels: 4,
157
+ background: { r: 0, g: 0, b: 0, alpha: 0 },
158
+ },
159
+ })
160
+ .png()
161
+ .composite(composites)
162
+ .toBuffer();
163
+ }
164
+ //# sourceMappingURL=composer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composer.js","sourceRoot":"","sources":["../../src/character/composer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { LayerDefinition } from './types.js';
2
+ export interface DefinitionRegistry {
3
+ /** All parsed definitions keyed by "category/subcategory" */
4
+ definitions: Map<string, LayerDefinition>;
5
+ /** All categories (body, hair, torso, etc.) */
6
+ categories: string[];
7
+ }
8
+ /** Recursively read all JSON definitions from sheet_definitions/ */
9
+ export declare function loadDefinitions(repoRoot: string): Promise<DefinitionRegistry>;
10
+ /** List all available layers grouped by category */
11
+ export declare function listLayers(registry: DefinitionRegistry): Record<string, {
12
+ name: string;
13
+ variants: string[];
14
+ }[]>;
15
+ /** Find a definition by searching category and subcategory patterns */
16
+ export declare function findDefinition(registry: DefinitionRegistry, category: string, subcategory: string): LayerDefinition | undefined;
@@ -0,0 +1,116 @@
1
+ import { readFile, readdir, stat } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ /** Recursively read all JSON definitions from sheet_definitions/ */
4
+ export async function loadDefinitions(repoRoot) {
5
+ const defsDir = join(repoRoot, 'sheet_definitions');
6
+ const definitions = new Map();
7
+ const categories = await readdir(defsDir);
8
+ for (const category of categories) {
9
+ const categoryPath = join(defsDir, category);
10
+ const s = await stat(categoryPath);
11
+ if (!s.isDirectory())
12
+ continue;
13
+ await loadCategoryDefinitions(categoryPath, category, definitions);
14
+ }
15
+ return {
16
+ definitions,
17
+ categories: [...new Set([...definitions.values()].map((d) => d.name))],
18
+ };
19
+ }
20
+ async function loadCategoryDefinitions(dirPath, categoryPrefix, definitions) {
21
+ const entries = await readdir(dirPath);
22
+ for (const entry of entries) {
23
+ const fullPath = join(dirPath, entry);
24
+ const s = await stat(fullPath);
25
+ if (s.isDirectory()) {
26
+ // Recurse into subdirectories
27
+ await loadCategoryDefinitions(fullPath, categoryPrefix, definitions);
28
+ }
29
+ else if (entry.endsWith('.json') && !entry.startsWith('meta_')) {
30
+ try {
31
+ const def = await parseDefinitionFile(fullPath, categoryPrefix);
32
+ if (def) {
33
+ definitions.set(def.name, def);
34
+ }
35
+ }
36
+ catch {
37
+ // Skip invalid JSON files
38
+ }
39
+ }
40
+ }
41
+ }
42
+ async function parseDefinitionFile(filePath, category) {
43
+ const content = await readFile(filePath, 'utf-8');
44
+ const json = JSON.parse(content);
45
+ if (!json.name)
46
+ return null;
47
+ // Extract layer entries (layer_1, layer_2, etc.)
48
+ const layers = [];
49
+ for (let i = 1; i <= 20; i++) {
50
+ const layerKey = `layer_${i}`;
51
+ const layerData = json[layerKey];
52
+ if (!layerData)
53
+ break;
54
+ const paths = {};
55
+ const bodyTypes = ['male', 'muscular', 'female', 'pregnant', 'teen', 'child'];
56
+ for (const bt of bodyTypes) {
57
+ if (layerData[bt]) {
58
+ paths[bt] = layerData[bt];
59
+ }
60
+ }
61
+ layers.push({
62
+ zPos: layerData.zPos ?? 0,
63
+ paths,
64
+ customAnimation: layerData.custom_animation,
65
+ });
66
+ }
67
+ if (layers.length === 0)
68
+ return null;
69
+ // Derive a subcategory key from the filename
70
+ const fileName = filePath.split('/').pop().replace('.json', '');
71
+ return {
72
+ name: `${category}/${fileName}`,
73
+ priority: json.priority ?? 0,
74
+ layers,
75
+ variants: json.variants ?? [],
76
+ animations: json.animations ?? [],
77
+ matchBodyColor: json.match_body_color ?? false,
78
+ typeName: json.type_name,
79
+ };
80
+ }
81
+ /** List all available layers grouped by category */
82
+ export function listLayers(registry) {
83
+ const result = {};
84
+ for (const [key, def] of registry.definitions) {
85
+ const category = key.split('/')[0];
86
+ if (!result[category])
87
+ result[category] = [];
88
+ result[category].push({
89
+ name: key,
90
+ variants: def.variants,
91
+ });
92
+ }
93
+ return result;
94
+ }
95
+ /** Find a definition by searching category and subcategory patterns */
96
+ export function findDefinition(registry, category, subcategory) {
97
+ // Try exact key match first
98
+ const exactKey = `${category}/${subcategory}`;
99
+ if (registry.definitions.has(exactKey)) {
100
+ return registry.definitions.get(exactKey);
101
+ }
102
+ // Try partial matching
103
+ for (const [key, def] of registry.definitions) {
104
+ if (key.startsWith(category + '/') && key.includes(subcategory)) {
105
+ return def;
106
+ }
107
+ }
108
+ // Try matching by type_name
109
+ for (const [, def] of registry.definitions) {
110
+ if (def.typeName === subcategory) {
111
+ return def;
112
+ }
113
+ }
114
+ return undefined;
115
+ }
116
+ //# sourceMappingURL=definitions.js.map