9const DEFAULT_RARITY_ORDER := {
18var rarity_order := DEFAULT_RARITY_ORDER.duplicate(true)
19var resolved_card_cache:Dictionary = {}
20var base_name_cache:Dictionary = {}
22var cache_misses:int = 0
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])
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:
40 if _matches_filter_with_counts(instance, filter_id, inventory_manager, base_counts):
41 filtered.append(instance)
45func matches_filter(instance:Dictionary, filter_id:String, inventory_manager = null) -> bool:
46 return _matches_filter_with_counts(instance, filter_id, inventory_manager, {})
49func _matches_filter_with_counts(instance:Dictionary, filter_id:String, inventory_manager = null, base_counts:Dictionary = {}) -> bool:
50 var card = card_from_instance(instance)
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
59 return int(instance.get(
"level", 1)) > 1
or has_boosted_stats(card)
or not instance.get(
"modifier_ids", []).is_empty()
61 return bool(instance.get(
"locked", false))
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))
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)
77 result = compare_values(instance_base_name(a), instance_base_name(b))
79 result = compare_values(
int(card_a.get(
"value", 0)),
int(card_b.get(
"value", 0)))
81 result = compare_values(card_stat_total(card_a), card_stat_total(card_b))
83 result = compare_values(
int(card_a.get(
"health", 0)),
int(card_b.get(
"health", 0)))
85 result = compare_values(
int(card_a.get(
"attack", 0)),
int(card_b.get(
"attack", 0)))
87 result = compare_values(
int(card_a.get(
"defense", 0)),
int(card_b.get(
"defense", 0)))
89 result = compare_values(rarity_rank(card_a), rarity_rank(card_b))
91 result = compare_values(instance_base_name(a), instance_base_name(b))
93 result = compare_values(instance_base_name(a), instance_base_name(b))
95 result = compare_values(str(a.get(
"uid",
"")), str(b.get(
"uid",
"")))
96 return result < 0
if ascending
else result > 0
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",
""))
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
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}
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):
128 "selected": uid == selected_id,
129 "border_color": border,
130 "tooltip": _card_tooltip(card),
133 if icon_callback.is_valid():
134 record[
"icon"] = icon_callback.call(card, instance,
int(options.get(
"icon_size", 38)))
138func _card_tooltip(card:Dictionary) -> String:
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))
148 var description := str(card.get(
"description",
"")).strip_edges()
149 if description !=
"":
150 lines.append(description)
151 return "\n".join(lines)
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))
162func card_from_instance(instance:Dictionary) -> Dictionary:
163 var cache_key:String = _instance_cache_key(instance)
164 if resolved_card_cache.has(cache_key):
166 return resolved_card_cache[cache_key].duplicate(true)
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)
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)
184func base_card_for(card:Dictionary) -> Dictionary:
185 if card_manager == null
or not card_manager.has_method(
"get_card"):
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)
191func has_boosted_stats(card:Dictionary) -> bool:
192 var base_card = base_card_for(card)
193 if base_card.is_empty():
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))
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)):
207func format_card_name(card:Dictionary) -> String:
208 return str(card.get(
"name",
"Card"))
211func format_rarity_effects(card:Dictionary) -> String:
212 var parts:Array = [str(card.get(
"rarity",
"Common"))]
213 var level :=
int(card.get(
"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)
224func card_stat_total(card:Dictionary) -> int:
225 return int(card.get(
"health", 0)) +
int(card.get(
"attack", 0)) +
int(card.get(
"defense", 0))
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):
232 return str(base_name_cache[base_id])
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
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
244func rarity_rank(card:Dictionary) -> int:
245 return int(rarity_order.get(str(card.get(
"rarity",
"Common")), 99))
248func compare_values(a, b) -> int:
256func clear_caches() -> void:
257 resolved_card_cache.clear()
258 base_name_cache.clear()
263func get_cache_stats() -> Dictionary:
265 "resolved_cards": resolved_card_cache.size(),
266 "base_names": base_name_cache.size(),
267 "cache_hits": cache_hits,
268 "cache_misses": cache_misses
272func _base_counts_for_instances(instances:Array) -> Dictionary:
273 var counts:Dictionary = {}
274 for instance
in instances:
275 if not instance
is Dictionary:
277 var base_id:String = str(
Dictionary(instance).get(
"base_id",
Dictionary(instance).get(
"id",
"")))
280 counts[base_id] =
int(counts.get(base_id, 0)) + 1
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)