7const DEFAULT_CONFIG := {
15func get_target_cells(card:Dictionary, origin:Vector2i, direction:Vector2i, bounds:Rect2, blocked_cells:Array, config := {}, primary_target = null) -> Array:
16 var targeting = card.get(
"targeting", {})
17 if not targeting
is Dictionary:
19 var merged = _merged_config(config)
20 var type = str(targeting.get(
"type",
"projectile"))
21 var max_range = int(targeting.get(
"range", merged.get(
"projectile_range", DEFAULT_CONFIG[
"projectile_range"])))
22 if card.has(
"compound")
and str(card.get(
"manifestation",
"")) ==
"":
27 return cells_in_radius(origin, int(targeting.get(
"aoe_radius", merged.get(
"aoe_radius", DEFAULT_CONFIG[
"aoe_radius"]))), bounds, merged)
29 if primary_target == null:
31 return [_cell_from_any(primary_target)]
33 if direction == Vector2i.ZERO:
35 var landing = origin + direction * max_range
36 return cells_in_radius(landing, int(targeting.get(
"aoe_radius", merged.get(
"aoe_radius", DEFAULT_CONFIG[
"aoe_radius"]))), bounds, merged)
38 if direction == Vector2i.ZERO:
40 return trace_grid_line(origin, direction, max_range, bounds, blocked_cells, merged)
44func cells_in_radius(center:Vector2i, radius:int, bounds:Rect2, config := {}) -> Array:
46 var merged = _merged_config(config)
47 for y
in range(center.y - radius, center.y + radius + 1):
48 for x
in range(center.x - radius, center.x + radius + 1):
49 var cell = Vector2i(x, y)
50 if abs(x - center.x) + abs(y - center.y) <= radius
and _cell_in_bounds(cell, bounds, merged):
56func trace_grid_line(origin:Vector2i, direction:Vector2i, max_range:int, bounds:Rect2, blocked_cells:Array, config := {}) -> Array:
59 var merged = _merged_config(config)
60 for _index
in range(max(0, max_range)):
62 if not _cell_in_bounds(current, bounds, merged)
or _cell_blocked(current, blocked_cells):
69func get_attack_trace_cells(actions:Array, bounds:Rect2, blocked_cells:Array, config := {}) -> Array:
71 for action
in actions:
72 if not action
is Dictionary
or str(action.get(
"type",
"")) !=
"attack":
74 var from_cell = _cell_from_any(action.get(
"from", Vector2i.ZERO))
75 var target_cell = _cell_from_any(action.get(
"target", Vector2i.ZERO))
76 if str(action.get(
"attack_type",
"")) ==
"beam":
77 var direction = Vector2i(sign(target_cell.x - from_cell.x), sign(target_cell.y - from_cell.y))
78 if direction != Vector2i.ZERO:
79 for cell
in trace_grid_line(from_cell, direction, _grid_distance(from_cell, target_cell), bounds, blocked_cells, config):
82 cells.append(target_cell)
87func get_targeting_summary(card:Dictionary, config := {}) -> Dictionary:
88 var targeting = card.get(
"targeting", {})
89 if not targeting
is Dictionary:
91 var merged = _merged_config(config)
92 var type = str(targeting.get(
"type",
"projectile"))
93 var attack_range = int(targeting.get(
"range", merged.get(
"projectile_range", DEFAULT_CONFIG[
"projectile_range"])))
94 var radius = int(targeting.get(
"aoe_radius", merged.get(
"aoe_radius", DEFAULT_CONFIG[
"aoe_radius"])))
95 var mode :=
"Projectile"
96 var range_text :=
"%d squares ahead" % attack_range
97 if card.has(
"compound")
and str(card.get(
"manifestation",
"")) ==
"":
100 range_text =
"1 tile ahead"
106 range_text =
"radius %d" % radius
109 range_text =
"%d squares ahead" % attack_range
112 range_text =
"%d squares, radius %d" % [attack_range, radius]
115 range_text =
"nearest target"
118 "range": attack_range,
120 "range_text": range_text,
121 "label":
"%s | %s" % [mode, range_text]
126func format_card_summary(card:Dictionary, config := {}) -> String:
127 var targeting = get_targeting_summary(card, config)
128 return "%d energy | %d damage | %s" % [
129 int(card.get(
"energy", 1)),
130 int(card.get(
"damage", 0)),
131 str(targeting.get(
"label",
"Projectile"))
135func _cell_in_bounds(cell:Vector2i, bounds:Rect2, config:Dictionary) -> bool:
136 var tile_size = max(1.0, float(config.get(
"tile_size", DEFAULT_CONFIG[
"tile_size"])))
137 var world_position = Vector2(float(cell.x) * tile_size + tile_size * 0.5, float(cell.y) * tile_size + tile_size * 0.5)
138 return bounds.has_point(world_position)
141func _cell_blocked(cell:Vector2i, blocked_cells:Array) -> bool:
142 for blocked
in blocked_cells:
143 if _cell_from_any(blocked) == cell:
148func _grid_distance(a:Vector2i, b:Vector2i) -> int:
149 return abs(a.x - b.x) + abs(a.y - b.y)
152func _cell_from_any(value) -> Vector2i:
153 if value
is Vector2i:
156 return Vector2i(int(value.x), int(value.y))
157 if value
is Array
and value.size() >= 2:
158 return Vector2i(int(value[0]), int(value[1]))
159 if value
is Dictionary
and value.has(
"cell"):
160 return _cell_from_any(value.get(
"cell"))
164func _merged_config(config:Dictionary) -> Dictionary:
165 var merged = DEFAULT_CONFIG.duplicate(true)
166 for key
in config.keys():
167 merged[key] = config[key]