8const DataCacheManager = preload(
"res://scripts/systems/dataCacheManager/data_cache_manager.gd")
10var definitions:Dictionary = {}
16 "followup_min_delay": 0.7,
17 "followup_max_delay": 1.5,
19 "collect_radius": 12.0,
20 "x_margin_ratio": 0.25,
23var active_powerups:Array = []
24var spawn_timer:float = 0.0
25var data_cache_manager = DataCacheManager.new()
26var powerup_ids_cache:Array = []
27var labels_by_id:Dictionary = {}
28var colors_by_id:Dictionary = {}
29var effects_by_id:Dictionary = {}
31var cache_misses:int = 0
35func load_definitions(file_path:String, options:Dictionary = {}) -> Array:
36 var parsed = data_cache_manager.load_json(file_path)
37 if not parsed
is Dictionary:
38 configure([], options)
40 configure(parsed.get(
"powerups", []), options)
41 return get_powerup_ids()
45func get_cache_stats() -> Dictionary:
46 var stats:Dictionary = data_cache_manager.get_cache_stats()
47 stats[
"definitions"] = definitions.size()
48 stats[
"cached_ids"] = powerup_ids_cache.size()
49 stats[
"cached_labels"] = labels_by_id.size()
50 stats[
"cached_colors"] = colors_by_id.size()
51 stats[
"cached_effect_lists"] = effects_by_id.size()
52 stats[
"runtime_cache_hits"] = cache_hits
53 stats[
"runtime_cache_misses"] = cache_misses
58func configure(next_definitions, options:Dictionary = {}) -> void:
60 if next_definitions
is Array:
61 for row
in next_definitions:
63 var id:String = str(row.get(
"id",
""))
65 definitions[id] = row.duplicate(true)
67 definitions[str(row)] = {
"id": str(row)}
68 elif next_definitions
is Dictionary:
69 for id
in next_definitions.keys():
70 var value = next_definitions[id]
71 definitions[str(id)] = value.duplicate(true)
if value
is Dictionary
else {
"id": str(id)}
72 for key
in options.keys():
73 spawn_config[key] = options[key]
74 _rebuild_definition_indexes()
77func get_powerup_ids() -> Array:
79 return powerup_ids_cache.duplicate()
82func get_definition(powerup_id:String) -> Dictionary:
83 if definitions.has(powerup_id):
85 return Dictionary(definitions.get(powerup_id, {})).duplicate(true)
87 return definitions.get(powerup_id, {
"id": powerup_id,
"label": powerup_id.capitalize()}).duplicate(true)
90func get_label(powerup_id:String) -> String:
91 if labels_by_id.has(powerup_id):
93 return str(labels_by_id.get(powerup_id, powerup_id.capitalize()))
95 return powerup_id.capitalize()
98func get_color(powerup_id:String, fallback:Color = Color.WHITE) -> Color:
99 if colors_by_id.has(powerup_id):
101 return colors_by_id.get(powerup_id, fallback)
108func apply_effects(powerup_id:String, context:Dictionary = {}, handlers:Dictionary = {}) -> Array:
109 var operations:Array = []
110 var effects:Array = effects_by_id.get(powerup_id, [])
if effects_by_id.get(powerup_id, [])
is Array
else []
111 if effects.is_empty():
115 for effect
in effects:
116 if not effect
is Dictionary:
118 var operation := normalize_effect(powerup_id, effect, context)
119 if operation.is_empty():
121 operations.append(operation)
122 _dispatch_effect(operation, handlers)
127func normalize_effect(powerup_id:String, effect:Dictionary, context:Dictionary = {}) -> Dictionary:
128 var operation:Dictionary = effect.duplicate(true)
129 var effect_type:String = str(operation.get(
"type",
""))
130 if effect_type ==
"":
132 operation[
"type"] = effect_type
133 operation[
"powerup_id"] = powerup_id
134 operation[
"label"] = str(operation.get(
"label", get_label(powerup_id)))
135 if context.has(
"recipient"):
136 operation[
"recipient"] = str(context.get(
"recipient",
""))
137 if operation.has(
"duration"):
138 operation[
"duration"] =
float(operation.get(
"duration", 0.0))
141 operation[
"type"] =
"set_timer"
142 operation[
"duration"] = 0.0
144 operation[
"duration"] =
float(operation.get(
"duration", 5.0))
146 var slow_recipient:String = str(context.get(
"recipient",
"player"))
147 operation[
"timer"] =
"opponent_slow" if slow_recipient ==
"player" else "player_slow"
148 operation[
"type"] =
"set_timer"
149 operation[
"duration"] =
float(operation.get(
"duration", 10.0))
151 var curve_recipient:String = str(context.get(
"recipient",
"player"))
152 operation[
"timer"] =
"player_curve_boost" if curve_recipient ==
"player" else "opponent_curve_boost"
153 operation[
"type"] =
"set_timer"
154 operation[
"duration"] =
float(operation.get(
"duration", 10.0))
159func update(delta:float, playfield:Rect2, collectors:Array, options:Dictionary = {}) -> Dictionary:
160 for key
in options.keys():
161 spawn_config[key] = options[key]
162 var events:Dictionary = {
"spawned": [],
"collected": [],
"expired": []}
164 if active_powerups.size() <
int(spawn_config.get(
"max_active", 2))
and spawn_timer <= 0.0:
165 var spawned:Dictionary = spawn(playfield)
166 if not spawned.is_empty():
167 events[
"spawned"].append(spawned)
168 spawn_timer = next_spawn_delay(active_powerups.size() <
int(spawn_config.get(
"max_active", 2)))
169 var remaining:Array = []
170 for powerup
in active_powerups:
171 var next_powerup:Dictionary = powerup.duplicate(true)
172 next_powerup[
"age"] =
float(next_powerup.get(
"age", 0.0)) + delta
173 if float(next_powerup.get(
"age", 0.0)) >=
float(spawn_config.get(
"lifetime", 10.0)):
174 events[
"expired"].append(next_powerup)
175 spawn_timer = min(spawn_timer, next_spawn_delay(true) * 0.7)
177 var collector:Dictionary = find_collector(next_powerup, collectors)
178 if not collector.is_empty():
179 next_powerup[
"collector"] = str(collector.get(
"owner",
""))
180 next_powerup[
"collector_id"] = str(collector.get(
"id",
""))
181 events[
"collected"].append(next_powerup)
182 spawn_timer = min(spawn_timer, next_spawn_delay(true))
184 remaining.append(next_powerup)
185 active_powerups = remaining
190func spawn(playfield:Rect2, powerup_id:String =
"") -> Dictionary:
191 var ids:Array = definitions.keys()
194 var id:String = powerup_id
if powerup_id !=
"" else str(ids[randi() % ids.size()])
195 var x_margin:float = playfield.size.x *
float(spawn_config.get(
"x_margin_ratio", 0.25))
196 var y_margin:float =
float(spawn_config.get(
"y_margin", 28.0))
197 var row:Dictionary = {
198 "id":
"%s_%d" % [id, Time.get_ticks_msec()],
201 randf_range(playfield.position.x + x_margin, playfield.end.x - x_margin),
202 randf_range(playfield.position.y + y_margin, playfield.end.y - y_margin)
206 active_powerups.append(row)
207 return row.duplicate(true)
211func find_collector(powerup:Dictionary, collectors:Array) -> Dictionary:
212 var powerup_position:Vector2 = _vector(powerup.get(
"position", Vector2.ZERO))
213 for collector
in collectors:
214 if not collector
is Dictionary:
216 var radius:float =
float(collector.get(
"radius", 8.0)) +
float(spawn_config.get(
"collect_radius", 12.0))
217 if _vector(collector.get(
"position", Vector2.ZERO)).distance_to(powerup_position) <= radius:
218 return collector.duplicate(true)
222func next_spawn_delay(wants_followup:bool) -> float:
223 var min_delay:float =
float(spawn_config.get(
"followup_min_delay" if wants_followup
else "min_delay", 0.7))
224 var max_delay:float =
float(spawn_config.get(
"followup_max_delay" if wants_followup
else "max_delay", 1.5))
225 var pace:float = maxf(0.1,
float(spawn_config.get(
"pace", 1.0)))
226 return maxf(0.25, randf_range(min_delay, max_delay) / pace)
230 active_powerups.clear()
234func get_active() -> Array:
235 return active_powerups.duplicate(true)
238func set_active(rows:Array) -> void:
239 active_powerups = rows.duplicate(true)
242func get_state() -> Dictionary:
244 "active_powerups": active_powerups.duplicate(true),
245 "spawn_timer": spawn_timer,
246 "spawn_config": spawn_config.duplicate(true)
250func apply_state(state:Dictionary) -> void:
251 active_powerups = state.get(
"active_powerups", []).duplicate(true)
252 spawn_timer =
float(state.get(
"spawn_timer", spawn_timer))
253 var saved_config = state.get(
"spawn_config", {})
254 if saved_config
is Dictionary:
255 for key
in saved_config.keys():
256 spawn_config[key] = saved_config[key]
259func _dispatch_effect(operation:Dictionary, handlers:Dictionary) -> void:
260 var effect_type:String = str(operation.get(
"type",
""))
261 var handler_key:String = effect_type
262 if not handlers.has(handler_key):
263 handler_key =
"custom_effect"
264 var handler = handlers.get(handler_key, Callable())
265 if handler
is Callable
and handler.is_valid():
266 if handler_key ==
"custom_effect":
267 handler.call(operation)
269 handler.call(operation)
272func _vector(value) -> Vector2:
275 if value
is Vector2i:
276 return Vector2(value.x, value.y)
277 if value
is Array
and value.size() >= 2:
279 if value
is Dictionary
and value.has(
"x")
and value.has(
"y"):
284func _rebuild_definition_indexes() -> void:
285 powerup_ids_cache = definitions.keys()
286 powerup_ids_cache.sort()
289 effects_by_id.clear()
292 for id
in powerup_ids_cache:
293 var powerup_id:String = str(id)
294 var definition:Dictionary = definitions.get(powerup_id, {})
if definitions.get(powerup_id, {})
is Dictionary
else {}
295 labels_by_id[powerup_id] = str(definition.get(
"label", powerup_id.capitalize()))
296 if definition.has(
"color"):
297 colors_by_id[powerup_id] =
Color(str(definition.get(
"color",
"#ffffff")))
298 var effects:Array = []
299 for effect
in definition.get(
"effects", []):
300 if effect
is Dictionary:
301 effects.append(
Dictionary(effect).duplicate(true))
302 effects_by_id[powerup_id] = effects
Vector2
Return the first collector intersecting a field power-up.
Dictionary
Load power-up definitions from JSON containing powerups: [].