RapidGameFramework
Reusable Godot managers for data-driven small games
Loading...
Searching...
No Matches
targeting_manager.gd
Go to the documentation of this file.
1extends RefCounted
2
3
6
7const DEFAULT_CONFIG := {
8 "tile_size": 40.0,
9 "projectile_range": 6,
10 "aoe_radius": 1
11}
12
13
14
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:
18 targeting = {}
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", "")) == "":
23 type = "projectile"
24 max_range = 1
25 match type:
26 "aoe":
27 return cells_in_radius(origin, int(targeting.get("aoe_radius", merged.get("aoe_radius", DEFAULT_CONFIG["aoe_radius"]))), bounds, merged)
28 "homing":
29 if primary_target == null:
30 return []
31 return [_cell_from_any(primary_target)]
32 "lob":
33 if direction == Vector2i.ZERO:
34 return []
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)
37 _:
38 if direction == Vector2i.ZERO:
39 return []
40 return trace_grid_line(origin, direction, max_range, bounds, blocked_cells, merged)
41
42
43
44func cells_in_radius(center:Vector2i, radius:int, bounds:Rect2, config := {}) -> Array:
45 var cells := []
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):
51 cells.append(cell)
52 return cells
53
54
55
56func trace_grid_line(origin:Vector2i, direction:Vector2i, max_range:int, bounds:Rect2, blocked_cells:Array, config := {}) -> Array:
57 var cells := []
58 var current = origin
59 var merged = _merged_config(config)
60 for _index in range(max(0, max_range)):
61 current += direction
62 if not _cell_in_bounds(current, bounds, merged) or _cell_blocked(current, blocked_cells):
63 break
64 cells.append(current)
65 return cells
66
67
68
69func get_attack_trace_cells(actions:Array, bounds:Rect2, blocked_cells:Array, config := {}) -> Array:
70 var cells := []
71 for action in actions:
72 if not action is Dictionary or str(action.get("type", "")) != "attack":
73 continue
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):
80 cells.append(cell)
81 else:
82 cells.append(target_cell)
83 return cells
84
85
86
87func get_targeting_summary(card:Dictionary, config := {}) -> Dictionary:
88 var targeting = card.get("targeting", {})
89 if not targeting is Dictionary:
90 targeting = {}
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", "")) == "":
98 type = "element"
99 attack_range = 1
100 range_text = "1 tile ahead"
101 match type:
102 "element":
103 mode = "Element"
104 "aoe":
105 mode = "AOE"
106 range_text = "radius %d" % radius
107 "beam":
108 mode = "Beam"
109 range_text = "%d squares ahead" % attack_range
110 "lob":
111 mode = "Lob"
112 range_text = "%d squares, radius %d" % [attack_range, radius]
113 "homing":
114 mode = "Homing"
115 range_text = "nearest target"
116 return {
117 "mode": mode,
118 "range": attack_range,
119 "radius": radius,
120 "range_text": range_text,
121 "label": "%s | %s" % [mode, range_text]
122 }
123
124
125
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"))
132 ]
133
134
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)
139
140
141func _cell_blocked(cell:Vector2i, blocked_cells:Array) -> bool:
142 for blocked in blocked_cells:
143 if _cell_from_any(blocked) == cell:
144 return true
145 return false
146
147
148func _grid_distance(a:Vector2i, b:Vector2i) -> int:
149 return abs(a.x - b.x) + abs(a.y - b.y)
150
151
152func _cell_from_any(value) -> Vector2i:
153 if value is Vector2i:
154 return value
155 if value is Vector2:
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"))
161 return Vector2i.ZERO
162
163
164func _merged_config(config:Dictionary) -> Dictionary:
165 var merged = DEFAULT_CONFIG.duplicate(true)
166 for key in config.keys():
167 merged[key] = config[key]
168 return merged
169
170