lpc-forge 1.0.1 → 1.0.4

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 (99) hide show
  1. package/dist/assets/manager.js +101 -9
  2. package/dist/assets/manager.js.map +1 -1
  3. package/dist/audio/index.d.ts +4 -0
  4. package/dist/audio/index.js +4 -0
  5. package/dist/audio/index.js.map +1 -0
  6. package/dist/audio/music-catalog.d.ts +11 -0
  7. package/dist/audio/music-catalog.js +96 -0
  8. package/dist/audio/music-catalog.js.map +1 -0
  9. package/dist/audio/sfx-generator.d.ts +11 -0
  10. package/dist/audio/sfx-generator.js +77 -0
  11. package/dist/audio/sfx-generator.js.map +1 -0
  12. package/dist/audio/sfx-presets.d.ts +2 -0
  13. package/dist/audio/sfx-presets.js +286 -0
  14. package/dist/audio/sfx-presets.js.map +1 -0
  15. package/dist/audio/types.d.ts +39 -0
  16. package/dist/audio/types.js +2 -0
  17. package/dist/audio/types.js.map +1 -0
  18. package/dist/character/composer.d.ts +4 -1
  19. package/dist/character/composer.js +46 -7
  20. package/dist/character/composer.js.map +1 -1
  21. package/dist/character/presets.js +5 -5
  22. package/dist/character/presets.js.map +1 -1
  23. package/dist/cli.js +92 -24
  24. package/dist/export/godot.js +78 -19
  25. package/dist/export/godot.js.map +1 -1
  26. package/dist/license.js +1 -1
  27. package/dist/lighting/index.d.ts +16 -0
  28. package/dist/lighting/index.js +95 -0
  29. package/dist/lighting/index.js.map +1 -0
  30. package/dist/lighting/particles.d.ts +2 -0
  31. package/dist/lighting/particles.js +192 -0
  32. package/dist/lighting/particles.js.map +1 -0
  33. package/dist/lighting/presets.d.ts +2 -0
  34. package/dist/lighting/presets.js +146 -0
  35. package/dist/lighting/presets.js.map +1 -0
  36. package/dist/lighting/types.d.ts +36 -0
  37. package/dist/lighting/types.js +2 -0
  38. package/dist/lighting/types.js.map +1 -0
  39. package/dist/systems/day-night.d.ts +2 -0
  40. package/dist/systems/day-night.js +109 -0
  41. package/dist/systems/day-night.js.map +1 -0
  42. package/dist/systems/dialog.d.ts +2 -0
  43. package/dist/systems/dialog.js +266 -0
  44. package/dist/systems/dialog.js.map +1 -0
  45. package/dist/systems/enemy-ai.d.ts +2 -0
  46. package/dist/systems/enemy-ai.js +204 -0
  47. package/dist/systems/enemy-ai.js.map +1 -0
  48. package/dist/systems/hud-full.d.ts +2 -0
  49. package/dist/systems/hud-full.js +158 -0
  50. package/dist/systems/hud-full.js.map +1 -0
  51. package/dist/systems/index.d.ts +10 -0
  52. package/dist/systems/index.js +59 -0
  53. package/dist/systems/index.js.map +1 -0
  54. package/dist/systems/inventory.d.ts +2 -0
  55. package/dist/systems/inventory.js +324 -0
  56. package/dist/systems/inventory.js.map +1 -0
  57. package/dist/systems/loot.d.ts +2 -0
  58. package/dist/systems/loot.js +109 -0
  59. package/dist/systems/loot.js.map +1 -0
  60. package/dist/systems/menu.d.ts +2 -0
  61. package/dist/systems/menu.js +453 -0
  62. package/dist/systems/menu.js.map +1 -0
  63. package/dist/systems/quest.d.ts +2 -0
  64. package/dist/systems/quest.js +210 -0
  65. package/dist/systems/quest.js.map +1 -0
  66. package/dist/systems/save-load.d.ts +2 -0
  67. package/dist/systems/save-load.js +163 -0
  68. package/dist/systems/save-load.js.map +1 -0
  69. package/dist/systems/scene-transition.d.ts +2 -0
  70. package/dist/systems/scene-transition.js +107 -0
  71. package/dist/systems/scene-transition.js.map +1 -0
  72. package/dist/systems/types.d.ts +20 -0
  73. package/dist/systems/types.js +2 -0
  74. package/dist/systems/types.js.map +1 -0
  75. package/dist/systems/writer.d.ts +11 -0
  76. package/dist/systems/writer.js +31 -0
  77. package/dist/systems/writer.js.map +1 -0
  78. package/dist/ui/generator.d.ts +7 -0
  79. package/dist/ui/generator.js +307 -0
  80. package/dist/ui/generator.js.map +1 -0
  81. package/dist/ui/icons.d.ts +10 -0
  82. package/dist/ui/icons.js +320 -0
  83. package/dist/ui/icons.js.map +1 -0
  84. package/dist/ui/index.d.ts +6 -0
  85. package/dist/ui/index.js +6 -0
  86. package/dist/ui/index.js.map +1 -0
  87. package/dist/ui/portrait.d.ts +12 -0
  88. package/dist/ui/portrait.js +72 -0
  89. package/dist/ui/portrait.js.map +1 -0
  90. package/dist/ui/props.d.ts +9 -0
  91. package/dist/ui/props.js +247 -0
  92. package/dist/ui/props.js.map +1 -0
  93. package/dist/ui/themes.d.ts +2 -0
  94. package/dist/ui/themes.js +69 -0
  95. package/dist/ui/themes.js.map +1 -0
  96. package/dist/ui/types.d.ts +58 -0
  97. package/dist/ui/types.js +2 -0
  98. package/dist/ui/types.js.map +1 -0
  99. package/package.json +8 -2
@@ -0,0 +1,59 @@
1
+ import { generateEnemyAI } from './enemy-ai.js';
2
+ import { generateInventory } from './inventory.js';
3
+ import { generateDialog } from './dialog.js';
4
+ import { generateSaveLoad } from './save-load.js';
5
+ import { generateSceneTransition } from './scene-transition.js';
6
+ import { generateLoot } from './loot.js';
7
+ import { generateDayNight } from './day-night.js';
8
+ import { generateMenuSystem } from './menu.js';
9
+ import { generateQuest } from './quest.js';
10
+ import { generateFullHUD } from './hud-full.js';
11
+ const SYSTEM_GENERATORS = {
12
+ enemy_ai: generateEnemyAI,
13
+ inventory: generateInventory,
14
+ dialog: generateDialog,
15
+ save_load: generateSaveLoad,
16
+ scene_transition: generateSceneTransition,
17
+ loot: generateLoot,
18
+ day_night: generateDayNight,
19
+ menu: generateMenuSystem,
20
+ quest: generateQuest,
21
+ hud_full: generateFullHUD,
22
+ };
23
+ /** Get a single system by name */
24
+ export function getSystem(name) {
25
+ const gen = SYSTEM_GENERATORS[name];
26
+ return gen ? gen() : null;
27
+ }
28
+ /** Get all systems */
29
+ export function getAllSystems() {
30
+ return Object.values(SYSTEM_GENERATORS).map(gen => gen());
31
+ }
32
+ /** List available system names */
33
+ export function listSystems() {
34
+ return Object.keys(SYSTEM_GENERATORS);
35
+ }
36
+ /** Get systems with resolved dependency order */
37
+ export function getSystemsInOrder(names) {
38
+ const all = names
39
+ ? names.map(n => getSystem(n)).filter((s) => s !== null)
40
+ : getAllSystems();
41
+ const resolved = [];
42
+ const seen = new Set();
43
+ function resolve(system) {
44
+ if (seen.has(system.name))
45
+ return;
46
+ for (const dep of system.dependencies) {
47
+ const depSystem = getSystem(dep);
48
+ if (depSystem)
49
+ resolve(depSystem);
50
+ }
51
+ seen.add(system.name);
52
+ resolved.push(system);
53
+ }
54
+ for (const sys of all) {
55
+ resolve(sys);
56
+ }
57
+ return resolved;
58
+ }
59
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/systems/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAIhD,MAAM,iBAAiB,GAAqC;IAC1D,QAAQ,EAAE,eAAe;IACzB,SAAS,EAAE,iBAAiB;IAC5B,MAAM,EAAE,cAAc;IACtB,SAAS,EAAE,gBAAgB;IAC3B,gBAAgB,EAAE,uBAAuB;IACzC,IAAI,EAAE,YAAY;IAClB,SAAS,EAAE,gBAAgB;IAC3B,IAAI,EAAE,kBAAkB;IACxB,KAAK,EAAE,aAAa;IACpB,QAAQ,EAAE,eAAe;CAC1B,CAAC;AAEF,kCAAkC;AAClC,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,MAAM,GAAG,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACpC,OAAO,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC5B,CAAC;AAED,sBAAsB;AACtB,MAAM,UAAU,aAAa;IAC3B,OAAO,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;AAC5D,CAAC;AAED,kCAAkC;AAClC,MAAM,UAAU,WAAW;IACzB,OAAO,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;AACxC,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,iBAAiB,CAAC,KAAgB;IAChD,MAAM,GAAG,GAAG,KAAK;QACf,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAmB,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;QACzE,CAAC,CAAC,aAAa,EAAE,CAAC;IAEpB,MAAM,QAAQ,GAAiB,EAAE,CAAC;IAClC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,SAAS,OAAO,CAAC,MAAkB;QACjC,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;YAAE,OAAO;QAClC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;YACtC,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;YACjC,IAAI,SAAS;gBAAE,OAAO,CAAC,SAAS,CAAC,CAAC;QACpC,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACtB,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,CAAC;IACf,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { GameSystem } from './types.js';
2
+ export declare function generateInventory(): GameSystem;
@@ -0,0 +1,324 @@
1
+ export function generateInventory() {
2
+ const inventoryManagerGd = `extends Node
3
+ ## Global inventory manager — add as autoload "Inventory".
4
+ ## Manages items, stacking, equip slots, and persistence.
5
+
6
+ signal inventory_changed
7
+ signal item_added(item_id: String, amount: int)
8
+ signal item_removed(item_id: String, amount: int)
9
+ signal item_equipped(slot: String, item_id: String)
10
+ signal item_unequipped(slot: String, item_id: String)
11
+
12
+ const MAX_SLOTS := 24
13
+ const MAX_STACK := 99
14
+
15
+ var items: Array[Dictionary] = []
16
+ var equipment: Dictionary = {} # slot_name → item_id
17
+
18
+ var _item_database: Dictionary = {}
19
+
20
+ func _ready() -> void:
21
+ \titems.resize(MAX_SLOTS)
22
+ \tfor i in range(MAX_SLOTS):
23
+ \t\titems[i] = {}
24
+ \t_load_item_database()
25
+ \tequipment = {
26
+ \t\t"weapon": "",
27
+ \t\t"shield": "",
28
+ \t\t"helmet": "",
29
+ \t\t"armor": "",
30
+ \t\t"boots": "",
31
+ \t\t"ring": "",
32
+ \t\t"amulet": "",
33
+ \t}
34
+
35
+ func _load_item_database() -> void:
36
+ \t_item_database = {
37
+ \t\t"iron_sword": {"name": "Iron Sword", "type": "weapon", "icon": 0, "damage": 10, "description": "A reliable iron sword.", "stackable": false, "value": 50},
38
+ \t\t"magic_staff": {"name": "Magic Staff", "type": "weapon", "icon": 1, "damage": 8, "description": "Channels arcane energy.", "stackable": false, "value": 80},
39
+ \t\t"health_potion": {"name": "Health Potion", "type": "consumable", "icon": 2, "heal": 30, "description": "Restores 30 HP.", "stackable": true, "value": 10},
40
+ \t\t"mana_potion": {"name": "Mana Potion", "type": "consumable", "icon": 3, "mana": 25, "description": "Restores 25 MP.", "stackable": true, "value": 15},
41
+ \t\t"bread": {"name": "Bread", "type": "consumable", "icon": 4, "heal": 10, "description": "Simple bread. Heals 10 HP.", "stackable": true, "value": 3},
42
+ \t\t"gold_key": {"name": "Gold Key", "type": "key", "icon": 5, "description": "Opens golden locks.", "stackable": false, "value": 0},
43
+ \t\t"shield": {"name": "Shield", "type": "shield", "icon": 6, "defense": 5, "description": "Wooden shield.", "stackable": false, "value": 40},
44
+ \t\t"helmet": {"name": "Iron Helmet", "type": "helmet", "icon": 7, "defense": 3, "description": "Protects your head.", "stackable": false, "value": 35},
45
+ \t\t"ruby": {"name": "Ruby", "type": "gem", "icon": 8, "description": "A precious red gem.", "stackable": true, "value": 100},
46
+ \t\t"spell_scroll": {"name": "Spell Scroll", "type": "consumable", "icon": 9, "description": "Contains a magical spell.", "stackable": true, "value": 25},
47
+ \t\t"coin": {"name": "Gold Coin", "type": "currency", "icon": 10, "description": "Shiny gold coin.", "stackable": true, "value": 1},
48
+ \t}
49
+
50
+ func get_item_data(item_id: String) -> Dictionary:
51
+ \treturn _item_database.get(item_id, {})
52
+
53
+ func add_item(item_id: String, amount: int = 1) -> bool:
54
+ \tvar data := get_item_data(item_id)
55
+ \tif data.is_empty():
56
+ \t\treturn false
57
+
58
+ \tvar stackable: bool = data.get("stackable", false)
59
+
60
+ \tif stackable:
61
+ \t\tfor i in range(MAX_SLOTS):
62
+ \t\t\tif items[i].get("id", "") == item_id:
63
+ \t\t\t\tvar current: int = items[i].get("amount", 0)
64
+ \t\t\t\tvar can_add := mini(amount, MAX_STACK - current)
65
+ \t\t\t\tif can_add > 0:
66
+ \t\t\t\t\titems[i]["amount"] = current + can_add
67
+ \t\t\t\t\tamount -= can_add
68
+ \t\t\t\t\tif amount <= 0:
69
+ \t\t\t\t\t\titem_added.emit(item_id, can_add)
70
+ \t\t\t\t\t\tinventory_changed.emit()
71
+ \t\t\t\t\t\treturn true
72
+
73
+ \tfor i in range(MAX_SLOTS):
74
+ \t\tif items[i].is_empty():
75
+ \t\t\titems[i] = {"id": item_id, "amount": amount if stackable else 1}
76
+ \t\t\titem_added.emit(item_id, amount if stackable else 1)
77
+ \t\t\tinventory_changed.emit()
78
+ \t\t\treturn true
79
+
80
+ \treturn false
81
+
82
+ func remove_item(item_id: String, amount: int = 1) -> bool:
83
+ \tfor i in range(MAX_SLOTS):
84
+ \t\tif items[i].get("id", "") == item_id:
85
+ \t\t\tvar current: int = items[i].get("amount", 0)
86
+ \t\t\tif current <= amount:
87
+ \t\t\t\titems[i] = {}
88
+ \t\t\telse:
89
+ \t\t\t\titems[i]["amount"] = current - amount
90
+ \t\t\titem_removed.emit(item_id, amount)
91
+ \t\t\tinventory_changed.emit()
92
+ \t\t\treturn true
93
+ \treturn false
94
+
95
+ func has_item(item_id: String, amount: int = 1) -> int:
96
+ \tvar total := 0
97
+ \tfor i in range(MAX_SLOTS):
98
+ \t\tif items[i].get("id", "") == item_id:
99
+ \t\t\ttotal += items[i].get("amount", 0)
100
+ \treturn total >= amount
101
+
102
+ func count_item(item_id: String) -> int:
103
+ \tvar total := 0
104
+ \tfor i in range(MAX_SLOTS):
105
+ \t\tif items[i].get("id", "") == item_id:
106
+ \t\t\ttotal += items[i].get("amount", 0)
107
+ \treturn total
108
+
109
+ func equip_item(slot: String, item_id: String) -> bool:
110
+ \tif not equipment.has(slot):
111
+ \t\treturn false
112
+ \tif not has_item(item_id):
113
+ \t\treturn false
114
+ \tvar current: String = equipment[slot]
115
+ \tif current != "":
116
+ \t\tadd_item(current)
117
+ \t\titem_unequipped.emit(slot, current)
118
+ \tremove_item(item_id)
119
+ \tequipment[slot] = item_id
120
+ \titem_equipped.emit(slot, item_id)
121
+ \tinventory_changed.emit()
122
+ \treturn true
123
+
124
+ func unequip_item(slot: String) -> bool:
125
+ \tif not equipment.has(slot):
126
+ \t\treturn false
127
+ \tvar current: String = equipment[slot]
128
+ \tif current == "":
129
+ \t\treturn false
130
+ \tif add_item(current):
131
+ \t\tequipment[slot] = ""
132
+ \t\titem_unequipped.emit(slot, current)
133
+ \t\tinventory_changed.emit()
134
+ \t\treturn true
135
+ \treturn false
136
+
137
+ func get_total_stat(stat: String) -> int:
138
+ \tvar total := 0
139
+ \tfor slot in equipment:
140
+ \t\tvar item_id: String = equipment[slot]
141
+ \t\tif item_id != "":
142
+ \t\t\tvar data := get_item_data(item_id)
143
+ \t\t\ttotal += data.get(stat, 0)
144
+ \treturn total
145
+
146
+ func use_item(item_id: String) -> bool:
147
+ \tvar data := get_item_data(item_id)
148
+ \tif data.is_empty() or not has_item(item_id):
149
+ \t\treturn false
150
+ \tvar item_type: String = data.get("type", "")
151
+ \tif item_type != "consumable":
152
+ \t\treturn false
153
+ \tremove_item(item_id)
154
+ \treturn true
155
+
156
+ func save_data() -> Dictionary:
157
+ \treturn {"items": items.duplicate(true), "equipment": equipment.duplicate(true)}
158
+
159
+ func load_data(data: Dictionary) -> void:
160
+ \titems = data.get("items", [])
161
+ \tequipment = data.get("equipment", {})
162
+ \tinventory_changed.emit()
163
+ `;
164
+ const inventoryUIGd = `extends Control
165
+ ## Inventory UI panel. Add as child of a CanvasLayer.
166
+ ## Requires the Inventory autoload.
167
+
168
+ @onready var grid: GridContainer = $Panel/MarginContainer/VBoxContainer/GridContainer
169
+ @onready var tooltip_label: Label = $Panel/TooltipPanel/MarginContainer/Label
170
+ @onready var tooltip_panel: PanelContainer = $Panel/TooltipPanel
171
+
172
+ var slot_scene: PackedScene
173
+ var selected_slot := -1
174
+
175
+ func _ready() -> void:
176
+ \tInventory.inventory_changed.connect(_refresh)
177
+ \ttooltip_panel.visible = false
178
+ \t_refresh()
179
+
180
+ func _refresh() -> void:
181
+ \tfor child in grid.get_children():
182
+ \t\tchild.queue_free()
183
+
184
+ \tfor i in range(Inventory.MAX_SLOTS):
185
+ \t\tvar slot := _create_slot(i)
186
+ \t\tgrid.add_child(slot)
187
+
188
+ func _create_slot(index: int) -> Button:
189
+ \tvar btn := Button.new()
190
+ \tbtn.custom_minimum_size = Vector2(48, 48)
191
+ \tbtn.toggle_mode = true
192
+
193
+ \tvar item: Dictionary = Inventory.items[index]
194
+ \tif not item.is_empty():
195
+ \t\tvar data := Inventory.get_item_data(item["id"])
196
+ \t\tbtn.text = str(item.get("amount", 1)) if item.get("amount", 1) > 1 else ""
197
+ \t\tbtn.tooltip_text = data.get("name", item["id"])
198
+ \t\tbtn.mouse_entered.connect(_on_slot_hover.bind(index))
199
+ \t\tbtn.mouse_exited.connect(_on_slot_unhover)
200
+ \telse:
201
+ \t\tbtn.text = ""
202
+ \t\tbtn.tooltip_text = "Empty"
203
+
204
+ \tbtn.pressed.connect(_on_slot_pressed.bind(index))
205
+ \treturn btn
206
+
207
+ func _on_slot_pressed(index: int) -> void:
208
+ \tif selected_slot == -1:
209
+ \t\tif not Inventory.items[index].is_empty():
210
+ \t\t\tselected_slot = index
211
+ \telif selected_slot == index:
212
+ \t\tselected_slot = -1
213
+ \telse:
214
+ \t\tvar temp: Dictionary = Inventory.items[selected_slot].duplicate(true)
215
+ \t\tInventory.items[selected_slot] = Inventory.items[index].duplicate(true)
216
+ \t\tInventory.items[index] = temp
217
+ \t\tselected_slot = -1
218
+ \t\tInventory.inventory_changed.emit()
219
+
220
+ func _on_slot_hover(index: int) -> void:
221
+ \tvar item: Dictionary = Inventory.items[index]
222
+ \tif item.is_empty():
223
+ \t\ttooltip_panel.visible = false
224
+ \t\treturn
225
+ \tvar data := Inventory.get_item_data(item["id"])
226
+ \tvar desc := data.get("name", "???") + "\\n" + data.get("description", "")
227
+ \tif data.has("damage"):
228
+ \t\tdesc += "\\nDamage: " + str(data["damage"])
229
+ \tif data.has("defense"):
230
+ \t\tdesc += "\\nDefense: " + str(data["defense"])
231
+ \tif data.has("heal"):
232
+ \t\tdesc += "\\nHeals: " + str(data["heal"])
233
+ \tdesc += "\\nValue: " + str(data.get("value", 0)) + "g"
234
+ \ttooltip_label.text = desc
235
+ \ttooltip_panel.visible = true
236
+
237
+ func _on_slot_unhover() -> void:
238
+ \ttooltip_panel.visible = false
239
+
240
+ func _input(event: InputEvent) -> void:
241
+ \tif event.is_action_pressed("inventory"):
242
+ \t\tvisible = !visible
243
+ `;
244
+ const inventoryUITscn = `[gd_scene load_steps=2 format=3]
245
+
246
+ [ext_resource type="Script" path="res://scripts/inventory_ui.gd" id="1"]
247
+
248
+ [node name="InventoryUI" type="Control"]
249
+ layout_mode = 3
250
+ anchors_preset = 15
251
+ anchor_right = 1.0
252
+ anchor_bottom = 1.0
253
+ visible = false
254
+ script = ExtResource("1")
255
+
256
+ [node name="Panel" type="PanelContainer" parent="."]
257
+ layout_mode = 1
258
+ anchors_preset = 8
259
+ anchor_left = 0.5
260
+ anchor_top = 0.5
261
+ anchor_right = 0.5
262
+ anchor_bottom = 0.5
263
+ offset_left = -160.0
264
+ offset_top = -200.0
265
+ offset_right = 160.0
266
+ offset_bottom = 200.0
267
+
268
+ [node name="MarginContainer" type="MarginContainer" parent="Panel"]
269
+ layout_mode = 2
270
+ theme_override_constants/margin_left = 8
271
+ theme_override_constants/margin_top = 8
272
+ theme_override_constants/margin_right = 8
273
+ theme_override_constants/margin_bottom = 8
274
+
275
+ [node name="VBoxContainer" type="VBoxContainer" parent="Panel/MarginContainer"]
276
+ layout_mode = 2
277
+
278
+ [node name="Title" type="Label" parent="Panel/MarginContainer/VBoxContainer"]
279
+ layout_mode = 2
280
+ text = "Inventory"
281
+ horizontal_alignment = 1
282
+
283
+ [node name="HSeparator" type="HSeparator" parent="Panel/MarginContainer/VBoxContainer"]
284
+ layout_mode = 2
285
+
286
+ [node name="GridContainer" type="GridContainer" parent="Panel/MarginContainer/VBoxContainer"]
287
+ layout_mode = 2
288
+ columns = 6
289
+
290
+ [node name="TooltipPanel" type="PanelContainer" parent="Panel"]
291
+ layout_mode = 1
292
+ anchors_preset = 1
293
+ anchor_left = 1.0
294
+ anchor_right = 1.0
295
+ offset_left = 8.0
296
+ offset_right = 208.0
297
+ offset_bottom = 120.0
298
+ visible = false
299
+
300
+ [node name="MarginContainer" type="MarginContainer" parent="Panel/TooltipPanel"]
301
+ layout_mode = 2
302
+ theme_override_constants/margin_left = 8
303
+ theme_override_constants/margin_top = 8
304
+ theme_override_constants/margin_right = 8
305
+ theme_override_constants/margin_bottom = 8
306
+
307
+ [node name="Label" type="Label" parent="Panel/TooltipPanel/MarginContainer"]
308
+ layout_mode = 2
309
+ autowrap_mode = 2
310
+ `;
311
+ return {
312
+ name: 'inventory',
313
+ description: 'Grid inventory with equip slots, stacking, tooltips, and drag-swap',
314
+ files: [
315
+ { path: 'scripts/inventory_manager.gd', content: inventoryManagerGd },
316
+ { path: 'scripts/inventory_ui.gd', content: inventoryUIGd },
317
+ { path: 'scenes/inventory_ui.tscn', content: inventoryUITscn },
318
+ ],
319
+ dependencies: [],
320
+ autoloads: [{ name: 'Inventory', path: 'res://scripts/inventory_manager.gd' }],
321
+ inputActions: ['inventory'],
322
+ };
323
+ }
324
+ //# sourceMappingURL=inventory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inventory.js","sourceRoot":"","sources":["../../src/systems/inventory.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,iBAAiB;IAC/B,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiK5B,CAAC;IAEA,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+EvB,CAAC;IAEA,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkEzB,CAAC;IAEA,OAAO;QACL,IAAI,EAAE,WAAW;QACjB,WAAW,EAAE,oEAAoE;QACjF,KAAK,EAAE;YACL,EAAE,IAAI,EAAE,8BAA8B,EAAE,OAAO,EAAE,kBAAkB,EAAE;YACrE,EAAE,IAAI,EAAE,yBAAyB,EAAE,OAAO,EAAE,aAAa,EAAE;YAC3D,EAAE,IAAI,EAAE,0BAA0B,EAAE,OAAO,EAAE,eAAe,EAAE;SAC/D;QACD,YAAY,EAAE,EAAE;QAChB,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,oCAAoC,EAAE,CAAC;QAC9E,YAAY,EAAE,CAAC,WAAW,CAAC;KAC5B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { GameSystem } from './types.js';
2
+ export declare function generateLoot(): GameSystem;
@@ -0,0 +1,109 @@
1
+ export function generateLoot() {
2
+ const lootManagerGd = `extends Node
3
+ ## Loot drop manager. Call drop_loot(position, table_name) to spawn pickups.
4
+
5
+ signal item_picked_up(item_id: String, amount: int)
6
+
7
+ var loot_tables: Dictionary = {}
8
+
9
+ func _ready() -> void:
10
+ \t_init_default_tables()
11
+
12
+ func _init_default_tables() -> void:
13
+ \tloot_tables = {
14
+ \t\t"common_enemy": [
15
+ \t\t\t{"item": "coin", "amount": [1, 5], "weight": 60},
16
+ \t\t\t{"item": "health_potion", "amount": [1, 1], "weight": 20},
17
+ \t\t\t{"item": "bread", "amount": [1, 2], "weight": 15},
18
+ \t\t\t{"item": "", "amount": [0, 0], "weight": 5}, # nothing
19
+ \t\t],
20
+ \t\t"boss": [
21
+ \t\t\t{"item": "coin", "amount": [10, 25], "weight": 40},
22
+ \t\t\t{"item": "ruby", "amount": [1, 1], "weight": 20},
23
+ \t\t\t{"item": "iron_sword", "amount": [1, 1], "weight": 15},
24
+ \t\t\t{"item": "health_potion", "amount": [2, 3], "weight": 15},
25
+ \t\t\t{"item": "spell_scroll", "amount": [1, 1], "weight": 10},
26
+ \t\t],
27
+ \t\t"chest": [
28
+ \t\t\t{"item": "coin", "amount": [5, 15], "weight": 30},
29
+ \t\t\t{"item": "health_potion", "amount": [1, 2], "weight": 25},
30
+ \t\t\t{"item": "gold_key", "amount": [1, 1], "weight": 10},
31
+ \t\t\t{"item": "ruby", "amount": [1, 1], "weight": 10},
32
+ \t\t\t{"item": "emerald", "amount": [1, 1], "weight": 10},
33
+ \t\t\t{"item": "mana_potion", "amount": [1, 2], "weight": 15},
34
+ \t\t],
35
+ \t}
36
+
37
+ func roll_loot(table_name: String) -> Array:
38
+ \tif not loot_tables.has(table_name):
39
+ \t\treturn []
40
+ \tvar table: Array = loot_tables[table_name]
41
+ \tvar total_weight := 0
42
+ \tfor entry in table:
43
+ \t\ttotal_weight += entry.get("weight", 0)
44
+
45
+ \tvar roll := randi() % total_weight
46
+ \tvar cumulative := 0
47
+ \tfor entry in table:
48
+ \t\tcumulative += entry.get("weight", 0)
49
+ \t\tif roll < cumulative:
50
+ \t\t\tvar item_id: String = entry.get("item", "")
51
+ \t\t\tif item_id == "":
52
+ \t\t\t\treturn []
53
+ \t\t\tvar amt_range: Array = entry.get("amount", [1, 1])
54
+ \t\t\tvar amount := randi_range(amt_range[0], amt_range[1])
55
+ \t\t\treturn [{"item": item_id, "amount": amount}]
56
+ \treturn []
57
+
58
+ func drop_loot(pos: Vector2, table_name: String) -> void:
59
+ \tvar drops := roll_loot(table_name)
60
+ \tfor drop in drops:
61
+ \t\t_spawn_pickup(pos, drop["item"], drop["amount"])
62
+
63
+ func _spawn_pickup(pos: Vector2, item_id: String, amount: int) -> void:
64
+ \tvar pickup := Area2D.new()
65
+ \tpickup.name = "Pickup_" + item_id
66
+ \tpickup.global_position = pos + Vector2(randf_range(-16, 16), randf_range(-16, 16))
67
+
68
+ \tvar collision := CollisionShape2D.new()
69
+ \tvar shape := CircleShape2D.new()
70
+ \tshape.radius = 12.0
71
+ \tcollision.shape = shape
72
+ \tpickup.add_child(collision)
73
+
74
+ \tvar sprite := Sprite2D.new()
75
+ \tsprite.modulate = Color(1, 1, 0.5)
76
+ \tpickup.add_child(sprite)
77
+
78
+ \tvar meta := {"item_id": item_id, "amount": amount}
79
+ \tpickup.set_meta("loot", meta)
80
+
81
+ \tpickup.body_entered.connect(func(body: Node2D) -> void:
82
+ \t\tif body.is_in_group("player"):
83
+ \t\t\tvar inv := get_node_or_null("/root/Inventory")
84
+ \t\t\tif inv and inv.has_method("add_item"):
85
+ \t\t\t\tif inv.add_item(item_id, amount):
86
+ \t\t\t\t\titem_picked_up.emit(item_id, amount)
87
+ \t\t\t\t\tpickup.queue_free()
88
+ \t)
89
+
90
+ \t# Float animation
91
+ \tvar tween := pickup.create_tween()
92
+ \ttween.set_loops()
93
+ \ttween.tween_property(pickup, "position:y", pos.y - 4, 0.5).set_trans(Tween.TRANS_SINE)
94
+ \ttween.tween_property(pickup, "position:y", pos.y + 4, 0.5).set_trans(Tween.TRANS_SINE)
95
+
96
+ \tget_tree().current_scene.add_child(pickup)
97
+ `;
98
+ return {
99
+ name: 'loot',
100
+ description: 'Loot drop system with weighted tables, pickup spawning, and inventory integration',
101
+ files: [
102
+ { path: 'scripts/loot_manager.gd', content: lootManagerGd },
103
+ ],
104
+ dependencies: ['inventory'],
105
+ autoloads: [{ name: 'LootManager', path: 'res://scripts/loot_manager.gd' }],
106
+ inputActions: [],
107
+ };
108
+ }
109
+ //# sourceMappingURL=loot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loot.js","sourceRoot":"","sources":["../../src/systems/loot.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,YAAY;IAC1B,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+FvB,CAAC;IAEA,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,WAAW,EAAE,mFAAmF;QAChG,KAAK,EAAE;YACL,EAAE,IAAI,EAAE,yBAAyB,EAAE,OAAO,EAAE,aAAa,EAAE;SAC5D;QACD,YAAY,EAAE,CAAC,WAAW,CAAC;QAC3B,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,+BAA+B,EAAE,CAAC;QAC3E,YAAY,EAAE,EAAE;KACjB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { GameSystem } from './types.js';
2
+ export declare function generateMenuSystem(): GameSystem;