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()
29func load_definitions(file_path:String, options:Dictionary = {}) -> Array:
30 var parsed = data_cache_manager.load_json(file_path)
31 if not parsed
is Dictionary:
32 configure([], options)
34 configure(parsed.get(
"powerups", []), options)
35 return get_powerup_ids()
39func get_cache_stats() -> Dictionary:
40 return data_cache_manager.get_cache_stats()
44func configure(next_definitions, options:Dictionary = {}) -> void:
46 if next_definitions
is Array:
47 for row
in next_definitions:
49 var id:String = str(row.get(
"id",
""))
51 definitions[id] = row.duplicate(true)
53 definitions[str(row)] = {
"id": str(row)}
54 elif next_definitions
is Dictionary:
55 for id
in next_definitions.keys():
56 var value = next_definitions[id]
57 definitions[str(id)] = value.duplicate(true)
if value
is Dictionary
else {
"id": str(id)}
58 for key
in options.keys():
59 spawn_config[key] = options[key]
62func get_powerup_ids() -> Array:
63 var ids := definitions.keys()
68func get_definition(powerup_id:String) -> Dictionary:
69 return definitions.get(powerup_id, {
"id": powerup_id,
"label": powerup_id.capitalize()}).duplicate(true)
72func get_label(powerup_id:String) -> String:
73 return str(get_definition(powerup_id).get(
"label", powerup_id.capitalize()))
76func get_color(powerup_id:String, fallback:Color = Color.WHITE) -> Color:
77 var definition := get_definition(powerup_id)
78 if definition.has(
"color"):
79 return Color(str(definition.get(
"color",
"#ffffff")))
85func apply_effects(powerup_id:String, context:Dictionary = {}, handlers:Dictionary = {}) -> Array:
86 var operations:Array = []
87 var definition:Dictionary = get_definition(powerup_id)
88 for effect
in definition.get(
"effects", []):
89 if not effect
is Dictionary:
91 var operation := normalize_effect(powerup_id, effect, context)
92 if operation.is_empty():
94 operations.append(operation)
95 _dispatch_effect(operation, handlers)
100func normalize_effect(powerup_id:String, effect:Dictionary, context:Dictionary = {}) -> Dictionary:
101 var operation:Dictionary = effect.duplicate(true)
102 var effect_type:String = str(operation.get(
"type",
""))
103 if effect_type ==
"":
105 operation[
"type"] = effect_type
106 operation[
"powerup_id"] = powerup_id
107 operation[
"label"] = str(operation.get(
"label", get_label(powerup_id)))
108 if context.has(
"recipient"):
109 operation[
"recipient"] = str(context.get(
"recipient",
""))
110 if operation.has(
"duration"):
111 operation[
"duration"] =
float(operation.get(
"duration", 0.0))
114 operation[
"type"] =
"set_timer"
115 operation[
"duration"] = 0.0
117 operation[
"duration"] =
float(operation.get(
"duration", 5.0))
119 var slow_recipient:String = str(context.get(
"recipient",
"player"))
120 operation[
"timer"] =
"opponent_slow" if slow_recipient ==
"player" else "player_slow"
121 operation[
"type"] =
"set_timer"
122 operation[
"duration"] =
float(operation.get(
"duration", 10.0))
124 var curve_recipient:String = str(context.get(
"recipient",
"player"))
125 operation[
"timer"] =
"player_curve_boost" if curve_recipient ==
"player" else "opponent_curve_boost"
126 operation[
"type"] =
"set_timer"
127 operation[
"duration"] =
float(operation.get(
"duration", 10.0))
132func update(delta:float, playfield:Rect2, collectors:Array, options:Dictionary = {}) -> Dictionary:
133 for key
in options.keys():
134 spawn_config[key] = options[key]
135 var events:Dictionary = {
"spawned": [],
"collected": [],
"expired": []}
137 if active_powerups.size() < int(spawn_config.get(
"max_active", 2))
and spawn_timer <= 0.0:
138 var spawned:Dictionary = spawn(playfield)
139 if not spawned.is_empty():
140 events[
"spawned"].append(spawned)
141 spawn_timer = next_spawn_delay(active_powerups.size() < int(spawn_config.get(
"max_active", 2)))
142 var remaining:Array = []
143 for powerup
in active_powerups:
144 var next_powerup:Dictionary = powerup.duplicate(true)
145 next_powerup[
"age"] =
float(next_powerup.get(
"age", 0.0)) + delta
146 if float(next_powerup.get(
"age", 0.0)) >=
float(spawn_config.get(
"lifetime", 10.0)):
147 events[
"expired"].append(next_powerup)
148 spawn_timer = min(spawn_timer, next_spawn_delay(true) * 0.7)
150 var collector:Dictionary = find_collector(next_powerup, collectors)
151 if not collector.is_empty():
152 next_powerup[
"collector"] = str(collector.get(
"owner",
""))
153 next_powerup[
"collector_id"] = str(collector.get(
"id",
""))
154 events[
"collected"].append(next_powerup)
155 spawn_timer = min(spawn_timer, next_spawn_delay(true))
157 remaining.append(next_powerup)
158 active_powerups = remaining
163func spawn(playfield:Rect2, powerup_id:String =
"") -> Dictionary:
164 var ids:Array = definitions.keys()
167 var id:String = powerup_id
if powerup_id !=
"" else str(ids[randi() % ids.size()])
168 var x_margin:float = playfield.size.x *
float(spawn_config.get(
"x_margin_ratio", 0.25))
169 var y_margin:float =
float(spawn_config.get(
"y_margin", 28.0))
170 var row:Dictionary = {
171 "id":
"%s_%d" % [id, Time.get_ticks_msec()],
174 randf_range(playfield.position.x + x_margin, playfield.end.x - x_margin),
175 randf_range(playfield.position.y + y_margin, playfield.end.y - y_margin)
179 active_powerups.append(row)
180 return row.duplicate(true)
184func find_collector(powerup:Dictionary, collectors:Array) -> Dictionary:
185 var powerup_position:Vector2 = _vector(powerup.get(
"position", Vector2.ZERO))
186 for collector
in collectors:
187 if not collector
is Dictionary:
189 var radius:float =
float(collector.get(
"radius", 8.0)) +
float(spawn_config.get(
"collect_radius", 12.0))
190 if _vector(collector.get(
"position", Vector2.ZERO)).distance_to(powerup_position) <= radius:
191 return collector.duplicate(true)
195func next_spawn_delay(wants_followup:bool) -> float:
196 var min_delay:float =
float(spawn_config.get(
"followup_min_delay" if wants_followup
else "min_delay", 0.7))
197 var max_delay:float =
float(spawn_config.get(
"followup_max_delay" if wants_followup
else "max_delay", 1.5))
198 var pace:float = maxf(0.1,
float(spawn_config.get(
"pace", 1.0)))
199 return maxf(0.25, randf_range(min_delay, max_delay) / pace)
203 active_powerups.clear()
207func get_active() -> Array:
208 return active_powerups.duplicate(true)
211func set_active(rows:Array) -> void:
212 active_powerups = rows.duplicate(true)
215func get_state() -> Dictionary:
217 "active_powerups": active_powerups.duplicate(true),
218 "spawn_timer": spawn_timer,
219 "spawn_config": spawn_config.duplicate(true)
223func apply_state(state:Dictionary) -> void:
224 active_powerups = state.get(
"active_powerups", []).duplicate(true)
225 spawn_timer =
float(state.get(
"spawn_timer", spawn_timer))
226 var saved_config = state.get(
"spawn_config", {})
227 if saved_config
is Dictionary:
228 for key
in saved_config.keys():
229 spawn_config[key] = saved_config[key]
232func _dispatch_effect(operation:Dictionary, handlers:Dictionary) -> void:
233 var effect_type:String = str(operation.get(
"type",
""))
234 var handler_key:String = effect_type
235 if not handlers.has(handler_key):
236 handler_key =
"custom_effect"
237 var handler = handlers.get(handler_key, Callable())
238 if handler
is Callable
and handler.is_valid():
239 if handler_key ==
"custom_effect":
240 handler.call(operation)
242 handler.call(operation)
245func _vector(value) -> Vector2:
248 if value
is Vector2i:
249 return Vector2(value.x, value.y)
250 if value
is Array
and value.size() >= 2:
252 if value
is Dictionary
and value.has(
"x")
and value.has(
"y"):
Vector2
Return the first collector intersecting a field power-up.