RapidGameFramework
Reusable Godot managers for data-driven small games
Loading...
Searching...
No Matches
action_effect_manager.gd
Go to the documentation of this file.
1extends RefCounted
2
3
10
11const DataCacheManager = preload("res://scripts/systems/dataCacheManager/data_cache_manager.gd")
12
13var actions := {}
14var data_cache_manager = DataCacheManager.new()
15
16
17
18func load_actions(file_path:String) -> void:
19 load_action_data(data_cache_manager.load_json(file_path))
20
21
22
23func load_action_data(data:Dictionary) -> void:
24 actions.clear()
25 for raw_action in data.get("actions", []):
26 if not raw_action is Dictionary:
27 continue
28 var action = normalize_action(raw_action)
29 var id = str(action.get("id", ""))
30 if id != "":
31 actions[id] = action
32
33
34
35func get_action(action_id:String) -> Dictionary:
36 return actions.get(action_id, {}).duplicate(true)
37
38
39
40func get_actions() -> Array:
41 var result := []
42 for id in actions.keys():
43 result.append(actions[id].duplicate(true))
44 return result
45
46
47
48func normalize_action(action:Dictionary) -> Dictionary:
49 var normalized = action.duplicate(true)
50 normalized["id"] = str(normalized.get("id", ""))
51 normalized["name"] = str(normalized.get("name", normalized.get("id", "Action")))
52 normalized["kind"] = str(normalized.get("kind", "action"))
53 normalized["costs"] = _normalize_costs(normalized.get("costs", normalized.get("cost", [])))
54 normalized["cooldown"] = max(0.0, float(normalized.get("cooldown", 0.0)))
55 normalized["targeting"] = normalized.get("targeting", {}) if normalized.get("targeting", {}) is Dictionary else {}
56 normalized["effects"] = _normalize_effects(normalized.get("effects", []))
57 return normalized
58
59
60
61func can_pay_costs(action:Dictionary, pools:Dictionary) -> bool:
62 for cost in _normalize_costs(action.get("costs", action.get("cost", []))):
63 var pool = str(cost.get("pool", ""))
64 var amount = int(cost.get("amount", 0))
65 if pool == "" or amount <= 0:
66 continue
67 if int(pools.get(pool, 0)) < amount:
68 return false
69 return true
70
71
72
73func apply_costs(action:Dictionary, pools:Dictionary) -> Dictionary:
74 var next = pools.duplicate(true)
75 for cost in _normalize_costs(action.get("costs", action.get("cost", []))):
76 var pool = str(cost.get("pool", ""))
77 var amount = int(cost.get("amount", 0))
78 if pool == "" or amount <= 0:
79 continue
80 next[pool] = int(next.get(pool, 0)) - amount
81 return next
82
83
84
85func is_on_cooldown(action_id:String, cooldowns:Dictionary) -> bool:
86 return float(cooldowns.get(action_id, 0.0)) > 0.0
87
88
89
90func can_use_action(action:Dictionary, pools:Dictionary = {}, cooldowns:Dictionary = {}) -> Dictionary:
91 var action_id = str(action.get("id", ""))
92 if action_id != "" and is_on_cooldown(action_id, cooldowns):
93 return {"ok": false, "reason": "cooldown", "remaining": float(cooldowns.get(action_id, 0.0))}
94 if not can_pay_costs(action, pools):
95 return {"ok": false, "reason": "cost"}
96 return {"ok": true}
97
98
99
100func begin_cooldown(action:Dictionary, cooldowns:Dictionary = {}) -> Dictionary:
101 var next = cooldowns.duplicate(true)
102 var action_id = str(action.get("id", ""))
103 var cooldown = max(0.0, float(action.get("cooldown", 0.0)))
104 if action_id != "" and cooldown > 0.0:
105 next[action_id] = cooldown
106 return next
107
108
109
110func tick_cooldowns(cooldowns:Dictionary, delta:float) -> Dictionary:
111 var next := {}
112 for id in cooldowns.keys():
113 var remaining = max(0.0, float(cooldowns[id]) - delta)
114 if remaining > 0.0:
115 next[str(id)] = remaining
116 return next
117
118
119
126func resolve_effects(action:Dictionary, context:Dictionary = {}) -> Dictionary:
127 var results := []
128 for effect in _normalize_effects(action.get("effects", [])):
129 var type = str(effect.get("type", "custom"))
130 var callback = context.get(type, Callable())
131 if callback is Callable and callback.is_valid():
132 var resolved = callback.call(effect)
133 results.append(resolved if resolved is Dictionary else {"type": type, "ok": true, "value": resolved})
134 else:
135 results.append({"type": type, "ok": false, "reason": "unhandled", "effect": effect})
136 return {
137 "action_id": str(action.get("id", "")),
138 "results": results,
139 "ok": _results_ok(results)
140 }
141
142
143
144func use_action(action:Dictionary, pools:Dictionary = {}, cooldowns:Dictionary = {}, context:Dictionary = {}) -> Dictionary:
145 var availability = can_use_action(action, pools, cooldowns)
146 if not bool(availability.get("ok", false)):
147 return {
148 "ok": false,
149 "reason": str(availability.get("reason", "blocked")),
150 "pools": pools.duplicate(true),
151 "cooldowns": cooldowns.duplicate(true),
152 "effects": []
153 }
154 var next_pools = apply_costs(action, pools)
155 var next_cooldowns = begin_cooldown(action, cooldowns)
156 var resolved = resolve_effects(action, context)
157 return {
158 "ok": bool(resolved.get("ok", true)),
159 "action_id": str(action.get("id", "")),
160 "pools": next_pools,
161 "cooldowns": next_cooldowns,
162 "effects": resolved.get("results", [])
163 }
164
165
166func _normalize_costs(raw_costs) -> Array:
167 var normalized := []
168 var costs = raw_costs if raw_costs is Array else [raw_costs]
169 for raw_cost in costs:
170 if raw_cost is Dictionary:
171 var pool = str(raw_cost.get("pool", raw_cost.get("resource", raw_cost.get("id", ""))))
172 var amount = int(raw_cost.get("amount", raw_cost.get("value", 0)))
173 if pool != "" and amount != 0:
174 normalized.append({"pool": pool, "amount": amount})
175 return normalized
176
177
178func _normalize_effects(raw_effects) -> Array:
179 var normalized := []
180 var effects = raw_effects if raw_effects is Array else [raw_effects]
181 for raw_effect in effects:
182 if not raw_effect is Dictionary:
183 continue
184 var effect = raw_effect.duplicate(true)
185 effect["type"] = str(effect.get("type", "custom"))
186 normalized.append(effect)
187 return normalized
188
189
190func _results_ok(results:Array) -> bool:
191 for result in results:
192 if result is Dictionary and not bool(result.get("ok", true)):
193 return false
194 return true
195
196
197func get_cache_stats() -> Dictionary:
198 return data_cache_manager.get_cache_stats()