RapidGameFramework
Reusable Godot managers for data-driven small games
Loading...
Searching...
No Matches
inventory_manager.gd
Go to the documentation of this file.
1extends RefCounted
2
3
18
19var card_ids := []
20var card_instances := []
21var item_stacks := {}
22var categorized_stacks := {}
23var next_legacy_uid := 1
24
25
26
27func add_item(item_id:String, amount:int = 1) -> void:
28 add_to_stack(item_stacks, item_id, amount)
29
30
31
32func remove_item(item_id:String, amount:int = 1) -> bool:
33 if amount <= 0 or get_item_count(item_id) < amount:
34 return false
35 var remaining = get_item_count(item_id) - amount
36 if remaining <= 0:
37 item_stacks.erase(item_id)
38 else:
39 item_stacks[item_id] = remaining
40 return true
41
42
43
44func get_item_count(item_id:String) -> int:
45 return int(item_stacks.get(item_id, 0))
46
47
48
49func get_items() -> Dictionary:
50 return item_stacks.duplicate(true)
51
52
53
55func add_to_stack(stacks:Dictionary, item_id:String, amount:int) -> void:
56 if item_id == "" or amount == 0:
57 return
58 var next_amount = int(stacks.get(item_id, 0)) + amount
59 if next_amount <= 0:
60 stacks.erase(item_id)
61 else:
62 stacks[item_id] = next_amount
63
64
65
66func positive_stack_ids(stacks:Dictionary) -> Array:
67 var ids := []
68 for id in stacks.keys():
69 if int(stacks.get(id, 0)) > 0:
70 ids.append(str(id))
71 ids.sort()
72 return ids
73
74
75
77func stack_rows(stacks:Dictionary, formatter:Callable = Callable()) -> Array:
78 var rows := []
79 for id in positive_stack_ids(stacks):
80 var label = id
81 if formatter.is_valid():
82 label = str(formatter.call(id))
83 rows.append({
84 "id": id,
85 "label": label,
86 "count": int(stacks.get(id, 0))
87 })
88 return rows
89
90
91
92func stack_lines(stacks:Dictionary, formatter:Callable = Callable(), empty_text:String = "None") -> Array:
93 var lines := []
94 for row in stack_rows(stacks, formatter):
95 lines.append("%s: %d" % [str(row.get("label", "")), int(row.get("count", 0))])
96 lines.sort()
97 if lines.is_empty():
98 lines.append(empty_text)
99 return lines
100
101
102
103func add_to_category(category:String, item_id:String, amount:int = 1) -> void:
104 if category == "" or item_id == "" or amount <= 0:
105 return
106 if not categorized_stacks.has(category):
107 categorized_stacks[category] = {}
108 var stacks = categorized_stacks[category]
109 stacks[item_id] = int(stacks.get(item_id, 0)) + amount
110 categorized_stacks[category] = stacks
111
112
113
114func remove_from_category(category:String, item_id:String, amount:int = 1) -> bool:
115 if category == "" or item_id == "" or amount <= 0 or get_category_count(category, item_id) < amount:
116 return false
117 var stacks = categorized_stacks.get(category, {})
118 var remaining = int(stacks.get(item_id, 0)) - amount
119 if remaining <= 0:
120 stacks.erase(item_id)
121 else:
122 stacks[item_id] = remaining
123 categorized_stacks[category] = stacks
124 return true
125
126
127
128func get_category_count(category:String, item_id:String) -> int:
129 return int(categorized_stacks.get(category, {}).get(item_id, 0))
130
131
132
133func set_category(category:String, stacks:Dictionary) -> void:
134 if category == "":
135 return
136 categorized_stacks[category] = _normalized_stacks(stacks)
137
138
139
140func get_category(category:String) -> Dictionary:
141 return categorized_stacks.get(category, {}).duplicate(true)
142
143
144
145func get_categories() -> Dictionary:
146 return categorized_stacks.duplicate(true)
147
148
149
150func apply_category_delta(category:String, delta:Dictionary) -> Dictionary:
151 for id in delta.keys():
152 var amount = int(delta.get(id, 0))
153 if amount > 0:
154 add_to_category(category, str(id), amount)
155 elif amount < 0:
156 remove_from_category(category, str(id), abs(amount))
157 return get_category(category)
158
159
160
162func add_card(card_id) -> void:
163 card_ids.append(str(card_id))
164 card_instances.append({
165 "uid": _make_legacy_uid(),
166 "base_id": str(card_id),
167 "modifier_ids": [],
168 "level": 1,
169 "locked": false
170 })
171
172
173
174func add_cards(ids:Array) -> void:
175 for id in ids:
176 add_card(id)
177
178
179
181func add_card_instance(instance:Dictionary) -> void:
182 var record = instance.duplicate(true)
183 if str(record.get("uid", "")) == "":
184 record["uid"] = _make_legacy_uid()
185 if not record.has("locked"):
186 record["locked"] = false
187 card_instances.append(record)
188 card_ids.append(str(record.get("base_id", record.get("id", ""))))
189
190
191
193func remove_card(card_id_or_uid) -> bool:
194 var id = str(card_id_or_uid)
195 var index = _find_instance_index(id)
196 if index < 0:
197 return false
198 if bool(card_instances[index].get("locked", false)):
199 return false
200 var base_id = str(card_instances[index].get("base_id", ""))
201 card_instances.remove_at(index)
202 var base_index = card_ids.find(base_id)
203 if base_index >= 0:
204 card_ids.remove_at(base_index)
205 return true
206
207
208
209func count_card(card_id) -> int:
210 var count = 0
211 for id in card_ids:
212 if id == str(card_id):
213 count += 1
214 return count
215
216
217
218func get_card_ids() -> Array:
219 return card_ids.duplicate()
220
221
222
223func get_unique_card_ids() -> Array:
224 var unique = []
225 for id in card_ids:
226 if not unique.has(id):
227 unique.append(id)
228 return unique
229
230
231
232func get_instances() -> Array:
233 return card_instances.duplicate(true)
234
235
236
237func get_instance(uid:String) -> Dictionary:
238 var index = _find_instance_index(uid)
239 if index < 0:
240 return {}
241 return card_instances[index].duplicate(true)
242
243
244
245func get_instances_for_base(card_id) -> Array:
246 var list = []
247 for instance in card_instances:
248 if str(instance.get("base_id", "")) == str(card_id):
249 list.append(instance.duplicate(true))
250 return list
251
252
253
256func upgrade_instance(uid:String, consumed_count:int = 1, max_level:int = 5) -> bool:
257 var index = _find_instance_index(uid)
258 if index < 0:
259 return false
260 if bool(card_instances[index].get("locked", false)):
261 return false
262 var base_id = str(card_instances[index].get("base_id", ""))
263 if _count_unlocked_merge_material(base_id, uid) < consumed_count:
264 return false
265 var material_index = _find_best_merge_material_index(base_id, uid, int(card_instances[index].get("level", 1)), max_level)
266 if material_index < 0:
267 return false
268 var material_level = int(card_instances[material_index].get("level", 1))
269 var new_level = int(card_instances[index].get("level", 1)) + material_level
270 if new_level > max_level:
271 return false
272 var consumed := 0
273 for i in [material_index]:
274 if consumed >= consumed_count:
275 break
276 if i == index:
277 continue
278 if str(card_instances[i].get("base_id", "")) == base_id and not bool(card_instances[i].get("locked", false)):
279 card_instances.remove_at(i)
280 var base_index = card_ids.find(base_id)
281 if base_index >= 0:
282 card_ids.remove_at(base_index)
283 consumed += 1
284 if consumed < consumed_count:
285 return false
286 index = _find_instance_index(uid)
287 if index < 0:
288 return false
289 card_instances[index]["level"] = new_level
290 return true
291
292
293
294func set_locked(uid:String, locked:bool) -> bool:
295 var index = _find_instance_index(uid)
296 if index < 0:
297 return false
298 card_instances[index]["locked"] = locked
299 return true
300
301
302
303func is_locked(uid:String) -> bool:
304 var index = _find_instance_index(uid)
305 if index < 0:
306 return false
307 return bool(card_instances[index].get("locked", false))
308
309
310
311func count_unlocked_merge_material(card_id, selected_uid:String = "") -> int:
312 return _count_unlocked_merge_material(str(card_id), selected_uid)
313
314
315
316func get_best_merge_material(card_id, selected_uid:String = "", max_level:int = 5) -> Dictionary:
317 var selected = get_instance(selected_uid)
318 var selected_level = int(selected.get("level", 1))
319 var index = _find_best_merge_material_index(str(card_id), selected_uid, selected_level, max_level)
320 if index < 0:
321 return {}
322 return card_instances[index].duplicate(true)
323
324
325
326func get_cards(card_manager) -> Array:
327 var list = []
328 for id in card_ids:
329 var card = card_manager.get_card(id)
330 if not card.is_empty():
331 list.append(card)
332 return list
333
334
335
336func get_state() -> Dictionary:
337 return {
338 "items": get_items(),
339 "categories": get_categories(),
340 "card_ids": get_card_ids(),
341 "card_instances": get_instances()
342 }
343
344
345
347func apply_state(state:Dictionary) -> void:
348 item_stacks = state.get("items", {}).duplicate(true)
349 categorized_stacks.clear()
350 for category in state.get("categories", {}).keys():
351 var raw_stacks = state.get("categories", {}).get(category, {})
352 if raw_stacks is Dictionary:
353 categorized_stacks[str(category)] = _normalized_stacks(raw_stacks)
354 card_ids.clear()
355 card_instances.clear()
356 if state.has("card_instances"):
357 for raw_instance in state.get("card_instances", []):
358 if not raw_instance is Dictionary:
359 continue
360 var instance = raw_instance.duplicate(true)
361 if str(instance.get("uid", "")) == "":
362 instance["uid"] = _make_legacy_uid()
363 instance["base_id"] = str(instance.get("base_id", instance.get("id", "")))
364 if not instance.has("modifier_ids"):
365 instance["modifier_ids"] = []
366 if not instance.has("level"):
367 instance["level"] = 1
368 if not instance.has("locked"):
369 instance["locked"] = false
370 card_instances.append(instance)
371 card_ids.append(str(instance.get("base_id", "")))
372 else:
373 for id in state.get("card_ids", []):
374 add_card(id)
375 _sync_legacy_counter()
376
377
378func _find_instance_index(id_or_uid:String) -> int:
379 for i in range(card_instances.size()):
380 if str(card_instances[i].get("uid", "")) == id_or_uid:
381 return i
382 for i in range(card_instances.size()):
383 if str(card_instances[i].get("base_id", "")) == id_or_uid:
384 return i
385 return -1
386
387
388func _count_unlocked_merge_material(base_id:String, selected_uid:String = "") -> int:
389 var count := 0
390 for instance in card_instances:
391 if str(instance.get("uid", "")) == selected_uid:
392 continue
393 if str(instance.get("base_id", "")) == base_id and not bool(instance.get("locked", false)):
394 count += 1
395 return count
396
397
398func _find_best_merge_material_index(base_id:String, selected_uid:String, selected_level:int, max_level:int) -> int:
399 var best_index := -1
400 var best_level := -1
401 for i in range(card_instances.size()):
402 var instance = card_instances[i]
403 if str(instance.get("uid", "")) == selected_uid:
404 continue
405 if str(instance.get("base_id", "")) != base_id:
406 continue
407 if bool(instance.get("locked", false)):
408 continue
409 var level = int(instance.get("level", 1))
410 if selected_level + level > max_level:
411 continue
412 if level > best_level:
413 best_level = level
414 best_index = i
415 return best_index
416
417
418func _make_legacy_uid() -> String:
419 var uid = "legacy_%d" % next_legacy_uid
420 next_legacy_uid += 1
421 return uid
422
423
424func _sync_legacy_counter() -> void:
425 var highest := 0
426 for instance in card_instances:
427 var uid = str(instance.get("uid", ""))
428 if uid.begins_with("legacy_"):
429 highest = max(highest, int(uid.trim_prefix("legacy_")))
430 next_legacy_uid = highest + 1
431
432
433func _normalized_stacks(stacks:Dictionary) -> Dictionary:
434 var normalized := {}
435 for id in stacks.keys():
436 var amount = int(stacks.get(id, 0))
437 if str(id) != "" and amount > 0:
438 normalized[str(id)] = amount
439 return normalized
Callable
Return stack rows for table or category-panel renderers.
int
Add amount to a generic item stack.