9var blocked_cells_cache:Array = []
10var material_nodes_by_cell:Dictionary = {}
11var spell_interactions_by_cell:Dictionary = {}
13var cache_misses:int = 0
17func configure(config:Dictionary, next_tile_size := 40.0) -> void:
18 world_config = config.duplicate(true)
19 tile_size = max(1.0, float(next_tile_size))
20 _rebuild_runtime_indexes()
23func get_grid_size() -> Vector2i:
24 return _cell_from_value(world_config.get(
"grid_size", [12, 12]))
27func get_bounds() -> Rect2:
28 return Rect2(Vector2.ZERO, Vector2(get_grid_size()) * tile_size)
31func get_blocked_cells() -> Array:
33 return blocked_cells_cache.duplicate()
36func get_movement_blocked_cells(occupied_cells:Array) -> Array:
37 var cells = get_blocked_cells()
38 for cell
in occupied_cells:
39 cells.append(_cell_from_value(cell))
43func cells_from_obstacle(obstacle:Dictionary) -> Array:
45 if obstacle.has(
"cells"):
46 for cell
in obstacle.get(
"cells", []):
47 cells.append(_cell_from_value(cell))
48 elif obstacle.has(
"rect"):
49 cells = rect_to_cells(_rect_from_value(obstacle.get(
"rect", [])))
53func rect_to_cells(rect:Rect2) -> Array:
54 var min_cell =
Vector2i(
int(floor(rect.position.x / tile_size)),
int(floor(rect.position.y / tile_size)))
56 int(floor((rect.position.x + max(0.0, rect.size.x - 0.01)) / tile_size)),
57 int(floor((rect.position.y + max(0.0, rect.size.y - 0.01)) / tile_size))
60 for y
in range(min_cell.y, max_cell.y + 1):
61 for x
in range(min_cell.x, max_cell.x + 1):
67func collect_material_at(cell:Vector2i, collected_nodes:Dictionary) -> Dictionary:
68 var nodes:Array = material_nodes_by_cell.get(_cell_key(cell), [])
if material_nodes_by_cell.get(_cell_key(cell), [])
is Array
else []
74 if not node
is Dictionary:
76 var node_id = str(node.get(
"id",
""))
77 if node_id ==
"" or collected_nodes.has(node_id):
79 if _cell_from_value(node.get(
"cell", [0, 0])) != cell:
84 "element": str(node.get(
"element",
"")),
85 "amount":
int(node.get(
"amount", 1))
87 return {
"collected": false}
91func resolve_spell_interactions(card:Dictionary, affected_cells:Array, activated_interactions:Dictionary) -> Array:
93 for affected_cell
in affected_cells:
94 var cell:Vector2i = _cell_from_value(affected_cell)
95 var interactions:Array = spell_interactions_by_cell.get(_cell_key(cell), [])
if spell_interactions_by_cell.get(_cell_key(cell), [])
is Array
else []
96 if interactions.is_empty():
100 for interaction
in interactions:
101 if not interaction
is Dictionary:
103 var id = str(interaction.get(
"id",
""))
104 if id ==
"" or activated_interactions.has(id):
106 if not _card_matches_interaction(card, interaction):
108 var entry = interaction.duplicate(true)
109 entry[
"activated"] = true
110 activated.append(entry)
114func get_cache_stats() -> Dictionary:
116 "blocked_cells": blocked_cells_cache.size(),
117 "material_cells": material_nodes_by_cell.size(),
118 "interaction_cells": spell_interactions_by_cell.size(),
119 "cache_hits": cache_hits,
120 "cache_misses": cache_misses
124func clear_caches() -> void:
125 blocked_cells_cache.clear()
126 material_nodes_by_cell.clear()
127 spell_interactions_by_cell.clear()
132func _rect_from_value(value) -> Rect2:
135 if value
is Array
and value.size() >= 4:
136 return Rect2(Vector2(float(value[0]), float(value[1])), Vector2(float(value[2]), float(value[3])))
140func _cell_from_value(value) -> Vector2i:
141 if value
is Vector2i:
145 if value
is Array
and value.size() >= 2:
147 if value
is Dictionary
and value.has(
"cell"):
148 return _cell_from_value(value.get(
"cell"))
152func _rebuild_runtime_indexes() -> void:
154 for obstacle
in world_config.get(
"obstacles", []):
155 if not obstacle
is Dictionary:
157 for cell
in cells_from_obstacle(obstacle):
158 blocked_cells_cache.append(_cell_from_value(cell))
159 for node
in world_config.get(
"material_nodes", []):
160 if not node
is Dictionary:
162 var node_cell:Vector2i = _cell_from_value(node.get(
"cell", [0, 0]))
163 var material_key:String = _cell_key(node_cell)
164 if not material_nodes_by_cell.has(material_key):
165 material_nodes_by_cell[material_key] = []
166 material_nodes_by_cell[material_key].append(node.duplicate(true))
167 for interaction
in world_config.get(
"spell_interactions", []):
168 if not interaction
is Dictionary:
170 var interaction_cell:Vector2i = _cell_from_value(interaction.get(
"cell", [0, 0]))
171 var interaction_key:String = _cell_key(interaction_cell)
172 if not spell_interactions_by_cell.has(interaction_key):
173 spell_interactions_by_cell[interaction_key] = []
174 spell_interactions_by_cell[interaction_key].append(interaction.duplicate(true))
177func _cell_key(cell:Vector2i) -> String:
178 return "%d,%d" % [cell.x, cell.y]
181func _card_matches_interaction(card:Dictionary, interaction:Dictionary) -> bool:
182 var requires = interaction.get(
"requires", {})
183 if not requires
is Dictionary
or requires.is_empty():
185 for key
in requires.keys():
186 var expected = requires[key]
187 if key ==
"compound" and str(card.get(
"compound",
"")) != str(expected):
189 if key ==
"manifestation" and str(card.get(
"manifestation",
"")) != str(expected):
191 if key ==
"card_id" and str(card.get(
"id",
"")) != str(expected):
193 if key ==
"modifier" and not card.get(
"modifiers", []).has(str(expected)):