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.
- package/dist/assets/manager.js +101 -9
- package/dist/assets/manager.js.map +1 -1
- package/dist/audio/index.d.ts +4 -0
- package/dist/audio/index.js +4 -0
- package/dist/audio/index.js.map +1 -0
- package/dist/audio/music-catalog.d.ts +11 -0
- package/dist/audio/music-catalog.js +96 -0
- package/dist/audio/music-catalog.js.map +1 -0
- package/dist/audio/sfx-generator.d.ts +11 -0
- package/dist/audio/sfx-generator.js +77 -0
- package/dist/audio/sfx-generator.js.map +1 -0
- package/dist/audio/sfx-presets.d.ts +2 -0
- package/dist/audio/sfx-presets.js +286 -0
- package/dist/audio/sfx-presets.js.map +1 -0
- package/dist/audio/types.d.ts +39 -0
- package/dist/audio/types.js +2 -0
- package/dist/audio/types.js.map +1 -0
- package/dist/character/composer.d.ts +4 -1
- package/dist/character/composer.js +46 -7
- package/dist/character/composer.js.map +1 -1
- package/dist/character/presets.js +5 -5
- package/dist/character/presets.js.map +1 -1
- package/dist/cli.js +92 -24
- package/dist/export/godot.js +78 -19
- package/dist/export/godot.js.map +1 -1
- package/dist/license.js +1 -1
- package/dist/lighting/index.d.ts +16 -0
- package/dist/lighting/index.js +95 -0
- package/dist/lighting/index.js.map +1 -0
- package/dist/lighting/particles.d.ts +2 -0
- package/dist/lighting/particles.js +192 -0
- package/dist/lighting/particles.js.map +1 -0
- package/dist/lighting/presets.d.ts +2 -0
- package/dist/lighting/presets.js +146 -0
- package/dist/lighting/presets.js.map +1 -0
- package/dist/lighting/types.d.ts +36 -0
- package/dist/lighting/types.js +2 -0
- package/dist/lighting/types.js.map +1 -0
- package/dist/systems/day-night.d.ts +2 -0
- package/dist/systems/day-night.js +109 -0
- package/dist/systems/day-night.js.map +1 -0
- package/dist/systems/dialog.d.ts +2 -0
- package/dist/systems/dialog.js +266 -0
- package/dist/systems/dialog.js.map +1 -0
- package/dist/systems/enemy-ai.d.ts +2 -0
- package/dist/systems/enemy-ai.js +204 -0
- package/dist/systems/enemy-ai.js.map +1 -0
- package/dist/systems/hud-full.d.ts +2 -0
- package/dist/systems/hud-full.js +158 -0
- package/dist/systems/hud-full.js.map +1 -0
- package/dist/systems/index.d.ts +10 -0
- package/dist/systems/index.js +59 -0
- package/dist/systems/index.js.map +1 -0
- package/dist/systems/inventory.d.ts +2 -0
- package/dist/systems/inventory.js +324 -0
- package/dist/systems/inventory.js.map +1 -0
- package/dist/systems/loot.d.ts +2 -0
- package/dist/systems/loot.js +109 -0
- package/dist/systems/loot.js.map +1 -0
- package/dist/systems/menu.d.ts +2 -0
- package/dist/systems/menu.js +453 -0
- package/dist/systems/menu.js.map +1 -0
- package/dist/systems/quest.d.ts +2 -0
- package/dist/systems/quest.js +210 -0
- package/dist/systems/quest.js.map +1 -0
- package/dist/systems/save-load.d.ts +2 -0
- package/dist/systems/save-load.js +163 -0
- package/dist/systems/save-load.js.map +1 -0
- package/dist/systems/scene-transition.d.ts +2 -0
- package/dist/systems/scene-transition.js +107 -0
- package/dist/systems/scene-transition.js.map +1 -0
- package/dist/systems/types.d.ts +20 -0
- package/dist/systems/types.js +2 -0
- package/dist/systems/types.js.map +1 -0
- package/dist/systems/writer.d.ts +11 -0
- package/dist/systems/writer.js +31 -0
- package/dist/systems/writer.js.map +1 -0
- package/dist/ui/generator.d.ts +7 -0
- package/dist/ui/generator.js +307 -0
- package/dist/ui/generator.js.map +1 -0
- package/dist/ui/icons.d.ts +10 -0
- package/dist/ui/icons.js +320 -0
- package/dist/ui/icons.js.map +1 -0
- package/dist/ui/index.d.ts +6 -0
- package/dist/ui/index.js +6 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/portrait.d.ts +12 -0
- package/dist/ui/portrait.js +72 -0
- package/dist/ui/portrait.js.map +1 -0
- package/dist/ui/props.d.ts +9 -0
- package/dist/ui/props.js +247 -0
- package/dist/ui/props.js.map +1 -0
- package/dist/ui/themes.d.ts +2 -0
- package/dist/ui/themes.js +69 -0
- package/dist/ui/themes.js.map +1 -0
- package/dist/ui/types.d.ts +58 -0
- package/dist/ui/types.js +2 -0
- package/dist/ui/types.js.map +1 -0
- 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,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,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"}
|