RapidGameFramework
Reusable Godot managers for data-driven small games
Loading...
Searching...
No Matches
card_inventory_adapter.gd
Go to the documentation of this file.
1extends RefCounted
2
3
8
9const DEFAULT_RARITY_ORDER := {
10 "Common": 0,
11 "Rare": 1,
12 "Epic": 2,
13 "Legendary": 3
14}
15
16var card_manager
17var modifier_manager
18var rarity_order := DEFAULT_RARITY_ORDER.duplicate(true)
19var resolved_card_cache:Dictionary = {}
20var base_name_cache:Dictionary = {}
21var cache_hits:int = 0
22var cache_misses:int = 0
23
24
25func configure(next_card_manager, next_modifier_manager = null, options:Dictionary = {}) -> void:
26 card_manager = next_card_manager
27 modifier_manager = next_modifier_manager
28 rarity_order = DEFAULT_RARITY_ORDER.duplicate(true)
29 for key in options.get("rarity_order", {}).keys():
30 rarity_order[str(key)] = int(options["rarity_order"][key])
31 clear_caches()
32
33
34func filter_instances(instances:Array, filter_id:String, inventory_manager = null) -> Array:
35 var filtered:Array = []
36 var base_counts:Dictionary = _base_counts_for_instances(instances) if filter_id == "duplicates" else {}
37 for instance in instances:
38 if not instance is Dictionary:
39 continue
40 if _matches_filter_with_counts(instance, filter_id, inventory_manager, base_counts):
41 filtered.append(instance)
42 return filtered
43
44
45func matches_filter(instance:Dictionary, filter_id:String, inventory_manager = null) -> bool:
46 return _matches_filter_with_counts(instance, filter_id, inventory_manager, {})
47
48
49func _matches_filter_with_counts(instance:Dictionary, filter_id:String, inventory_manager = null, base_counts:Dictionary = {}) -> bool:
50 var card = card_from_instance(instance)
51 match filter_id:
52 "duplicates":
53 if not base_counts.is_empty():
54 return int(base_counts.get(str(instance.get("base_id", "")), 0)) > 1
55 if inventory_manager != null and inventory_manager.has_method("count_card"):
56 return inventory_manager.count_card(str(instance.get("base_id", ""))) > 1
57 return false
58 "upgraded":
59 return int(instance.get("level", 1)) > 1 or has_boosted_stats(card) or not instance.get("modifier_ids", []).is_empty()
60 "locked":
61 return bool(instance.get("locked", false))
62 return true
63
64
65func sort_instances(instances:Array, sort_key:String = "rarity", ascending:bool = true) -> Array:
66 var sorted:Array = instances.duplicate(true)
67 sorted.sort_custom(func(a, b): return compare_instances(a, b, sort_key, ascending))
68 return sorted
69
70
71func compare_instances(a:Dictionary, b:Dictionary, sort_key:String = "rarity", ascending:bool = true) -> bool:
72 var card_a = card_from_instance(a)
73 var card_b = card_from_instance(b)
74 var result := 0
75 match sort_key:
76 "name":
77 result = compare_values(instance_base_name(a), instance_base_name(b))
78 "sell":
79 result = compare_values(int(card_a.get("value", 0)), int(card_b.get("value", 0)))
80 "stats":
81 result = compare_values(card_stat_total(card_a), card_stat_total(card_b))
82 "hp":
83 result = compare_values(int(card_a.get("health", 0)), int(card_b.get("health", 0)))
84 "atk":
85 result = compare_values(int(card_a.get("attack", 0)), int(card_b.get("attack", 0)))
86 "def":
87 result = compare_values(int(card_a.get("defense", 0)), int(card_b.get("defense", 0)))
88 "rarity":
89 result = compare_values(rarity_rank(card_a), rarity_rank(card_b))
90 if result == 0:
91 result = compare_values(instance_base_name(a), instance_base_name(b))
92 _:
93 result = compare_values(instance_base_name(a), instance_base_name(b))
94 if result == 0:
95 result = compare_values(str(a.get("uid", "")), str(b.get("uid", "")))
96 return result < 0 if ascending else result > 0
97
98
99func table_record(instance:Dictionary, options:Dictionary = {}) -> Dictionary:
100 var card = card_from_instance(instance)
101 var selected_id := str(options.get("selected_id", ""))
102 var include_sell := bool(options.get("include_sell", true))
103 var text_color:Color = options.get("text_color", Color.WHITE)
104 var muted_color:Color = options.get("muted_color", Color(0.7, 0.7, 0.7))
105 var accent_color:Color = options.get("accent_color", Color(0.95, 0.78, 0.42))
106 var boost_color:Color = options.get("boost_color", Color(0.33, 0.85, 0.47))
107 var icon_callback:Callable = options.get("icon_callback", Callable())
108 var uid := str(instance.get("uid", ""))
109 var values = {
110 "name": {
111 "text": "%s%s" % ["Selected: " if uid == selected_id and not include_sell else "", format_card_name(card)],
112 "color": accent_color if uid == selected_id and not include_sell else text_color
113 },
114 "rarity": {"text": format_rarity_effects(card), "color": accent_color if bool(instance.get("locked", false)) else boost_color if has_boosted_stats(card) else muted_color},
115 "hp": {"text": str(int(card.get("health", 0))), "color": stat_display_color(card, "health", text_color, boost_color), "align": HORIZONTAL_ALIGNMENT_CENTER},
116 "atk": {"text": str(int(card.get("attack", 0))), "color": stat_display_color(card, "attack", text_color, boost_color), "align": HORIZONTAL_ALIGNMENT_CENTER},
117 "def": {"text": str(int(card.get("defense", 0))), "color": stat_display_color(card, "defense", text_color, boost_color), "align": HORIZONTAL_ALIGNMENT_CENTER}
118 }
119 if include_sell:
120 values["sell"] = {"text": "$%d" % int(card.get("value", 0)), "color": accent_color, "align": HORIZONTAL_ALIGNMENT_RIGHT}
121 var border:Color = options.get("border_color", Color(0.25, 0.25, 0.25))
122 if bool(instance.get("locked", false)):
123 border = accent_color
124 elif has_boosted_stats(card):
125 border = boost_color
126 var record := {
127 "id": uid,
128 "selected": uid == selected_id,
129 "border_color": border,
130 "tooltip": _card_tooltip(card),
131 "values": values
132 }
133 if icon_callback.is_valid():
134 record["icon"] = icon_callback.call(card, instance, int(options.get("icon_size", 38)))
135 return record
136
137
138func _card_tooltip(card:Dictionary) -> String:
139 var lines := [
140 format_card_name(card),
141 "%s | HP %d | ATK %d | DEF %d" % [
142 format_rarity_effects(card),
143 int(card.get("health", 0)),
144 int(card.get("attack", 0)),
145 int(card.get("defense", 0))
146 ]
147 ]
148 var description := str(card.get("description", "")).strip_edges()
149 if description != "":
150 lines.append(description)
151 return "\n".join(lines)
152
153
154func table_records(instances:Array, options:Dictionary = {}) -> Array:
155 var records:Array = []
156 for instance in instances:
157 if instance is Dictionary:
158 records.append(table_record(instance, options))
159 return records
160
161
162func card_from_instance(instance:Dictionary) -> Dictionary:
163 var cache_key:String = _instance_cache_key(instance)
164 if resolved_card_cache.has(cache_key):
165 cache_hits += 1
166 return resolved_card_cache[cache_key].duplicate(true)
167 cache_misses += 1
168 if card_manager == null or not card_manager.has_method("get_card"):
169 var fallback:Dictionary = instance.duplicate(true)
170 resolved_card_cache[cache_key] = fallback
171 return fallback.duplicate(true)
172 var base_id = str(instance.get("base_id", instance.get("id", "")))
173 var card = card_manager.get_card(base_id).duplicate(true)
174 if card.is_empty():
175 var missing:Dictionary = instance.duplicate(true)
176 resolved_card_cache[cache_key] = missing
177 return missing.duplicate(true)
178 if modifier_manager != null and modifier_manager.has_method("apply_instance"):
179 card = modifier_manager.apply_instance(card, instance)
180 resolved_card_cache[cache_key] = card
181 return card.duplicate(true)
182
183
184func base_card_for(card:Dictionary) -> Dictionary:
185 if card_manager == null or not card_manager.has_method("get_card"):
186 return {}
187 var base_id = str(card.get("base_id", card.get("base_card_id", card.get("id", ""))))
188 return card_manager.get_card(base_id)
189
190
191func has_boosted_stats(card:Dictionary) -> bool:
192 var base_card = base_card_for(card)
193 if base_card.is_empty():
194 return false
195 return int(card.get("health", 0)) > int(base_card.get("health", 0)) \
196 or int(card.get("attack", 0)) > int(base_card.get("attack", 0)) \
197 or int(card.get("defense", 0)) > int(base_card.get("defense", 0))
198
199
200func stat_display_color(card:Dictionary, stat:String, normal_color:Color, boost_color:Color) -> Color:
201 var base_card = base_card_for(card)
202 if not base_card.is_empty() and int(card.get(stat, 0)) > int(base_card.get(stat, 0)):
203 return boost_color
204 return normal_color
205
206
207func format_card_name(card:Dictionary) -> String:
208 return str(card.get("name", "Card"))
209
210
211func format_rarity_effects(card:Dictionary) -> String:
212 var parts:Array = [str(card.get("rarity", "Common"))]
213 var level := int(card.get("level", 1))
214 if level > 1:
215 parts.append("+%d" % (level - 1))
216 var modifier_names:Array = []
217 if modifier_manager != null and modifier_manager.has_method("get_modifier_names"):
218 modifier_names = modifier_manager.get_modifier_names(card.get("modifier_ids", []))
219 for name in modifier_names:
220 parts.append(str(name))
221 return ", ".join(parts)
222
223
224func card_stat_total(card:Dictionary) -> int:
225 return int(card.get("health", 0)) + int(card.get("attack", 0)) + int(card.get("defense", 0))
226
227
228func instance_base_name(instance:Dictionary) -> String:
229 var base_id:String = str(instance.get("base_id", instance.get("id", "")))
230 if base_name_cache.has(base_id):
231 cache_hits += 1
232 return str(base_name_cache[base_id])
233 cache_misses += 1
234 if card_manager == null or not card_manager.has_method("get_card"):
235 var fallback_name:String = str(instance.get("name", "")).to_lower()
236 base_name_cache[base_id] = fallback_name
237 return fallback_name
238 var base_card = card_manager.get_card(base_id)
239 var name:String = str(base_card.get("name", "")).to_lower()
240 base_name_cache[base_id] = name
241 return name
242
243
244func rarity_rank(card:Dictionary) -> int:
245 return int(rarity_order.get(str(card.get("rarity", "Common")), 99))
246
247
248func compare_values(a, b) -> int:
249 if a < b:
250 return -1
251 if a > b:
252 return 1
253 return 0
254
255
256func clear_caches() -> void:
257 resolved_card_cache.clear()
258 base_name_cache.clear()
259 cache_hits = 0
260 cache_misses = 0
261
262
263func get_cache_stats() -> Dictionary:
264 return {
265 "resolved_cards": resolved_card_cache.size(),
266 "base_names": base_name_cache.size(),
267 "cache_hits": cache_hits,
268 "cache_misses": cache_misses
269 }
270
271
272func _base_counts_for_instances(instances:Array) -> Dictionary:
273 var counts:Dictionary = {}
274 for instance in instances:
275 if not instance is Dictionary:
276 continue
277 var base_id:String = str(Dictionary(instance).get("base_id", Dictionary(instance).get("id", "")))
278 if base_id == "":
279 continue
280 counts[base_id] = int(counts.get(base_id, 0)) + 1
281 return counts
282
283
284func _instance_cache_key(instance:Dictionary) -> String:
285 var modifier_ids:Array = instance.get("modifier_ids", []) if instance.get("modifier_ids", []) is Array else []
286 var modifier_parts:Array = []
287 for id in modifier_ids:
288 modifier_parts.append(str(id))
289 modifier_parts.sort()
290 return "%s|%s|%d|%s|%s" % [
291 str(instance.get("uid", "")),
292 str(instance.get("base_id", instance.get("id", ""))),
293 int(instance.get("level", 1)),
294 "1" if bool(instance.get("locked", false)) else "0",
295 ",".join(modifier_parts)
296 ]