10func create_run(options:Dictionary = {}) -> Dictionary:
11 var now = _now(options)
13 "id": str(options.get(
"id", _make_run_id(options, now))),
14 "game_id": str(options.get(
"game_id",
"")),
15 "mode": str(options.get(
"mode",
"run")),
17 "seed":
int(options.get(
"seed", 0)),
18 "attempt":
int(options.get(
"attempt", 1)),
22 "objectives": _normalize_objectives(options.get(
"objectives", [])),
23 "counters": _normalize_counts(options.get(
"counters", {})),
24 "collected": _normalize_counts(options.get(
"collected", {})),
25 "rewards": _normalize_counts(options.get(
"rewards", {})),
27 "metadata": options.get(
"metadata", {}).duplicate(true)
if options.get(
"metadata", {})
is Dictionary
else {}
32func is_active(run_state:Dictionary) -> bool:
33 return str(run_state.get(
"status",
"")) ==
"active"
37func is_finished(run_state:Dictionary) -> bool:
38 return [
"completed",
"failed",
"abandoned"].has(str(run_state.get(
"status",
"")))
42func set_counter(run_state:Dictionary, key:String, value:int) -> Dictionary:
43 var next = run_state.duplicate(true)
44 var counters = next.get(
"counters", {}).duplicate(true)
if next.get(
"counters", {})
is Dictionary
else {}
46 next[
"counters"] = counters
51func increment_counter(run_state:Dictionary, key:String, amount:int = 1) -> Dictionary:
52 var next = run_state.duplicate(true)
53 var counters = next.get(
"counters", {}).duplicate(true)
if next.get(
"counters", {})
is Dictionary
else {}
54 counters[key] =
int(counters.get(key, 0)) + amount
55 next[
"counters"] = counters
60func add_collected(run_state:Dictionary, collected:Dictionary) -> Dictionary:
61 var next = run_state.duplicate(true)
62 next[
"collected"] = _merge_counts(next.get(
"collected", {}), collected)
67func set_objective_complete(run_state:Dictionary, objective_id:String, complete:bool = true) -> Dictionary:
68 var next = run_state.duplicate(true)
70 for objective
in next.get(
"objectives", []):
71 if not objective
is Dictionary:
73 var entry = objective.duplicate(true)
74 if str(entry.get(
"id",
"")) == objective_id:
75 entry[
"complete"] = complete
76 objectives.append(entry)
77 next[
"objectives"] = objectives
82func required_objectives_complete(run_state:Dictionary) -> bool:
83 for objective
in run_state.get(
"objectives", []):
84 if objective
is Dictionary
and bool(objective.get(
"required", true))
and not bool(objective.get(
"complete", false)):
90func complete_run(run_state:Dictionary, rewards:Dictionary = {}, result:Dictionary = {}, options:Dictionary = {}) -> Dictionary:
91 var next = _finish(run_state,
"completed", result, options)
92 next[
"rewards"] = _merge_counts(next.get(
"rewards", {}), rewards)
97func fail_run(run_state:Dictionary, result:Dictionary = {}, options:Dictionary = {}) -> Dictionary:
98 return _finish(run_state,
"failed", result, options)
102func abandon_run(run_state:Dictionary, options:Dictionary = {}) -> Dictionary:
103 var result = options.get(
"result", {})
if options.get(
"result", {})
is Dictionary
else {}
104 result[
"rollback_collected"] =
bool(options.get(
"rollback_collected", true))
105 result[
"message"] = str(options.get(
"message", result.get(
"message",
"Run abandoned.")))
106 return _finish(run_state,
"abandoned", result, options)
110func retry_run(run_state:Dictionary, options:Dictionary = {}) -> Dictionary:
111 var retry_options = {
112 "game_id": str(run_state.get(
"game_id",
"")),
113 "mode": str(run_state.get(
"mode",
"run")),
114 "seed":
int(options.get(
"seed", run_state.get(
"seed", 0))),
115 "attempt":
int(run_state.get(
"attempt", 1)) + 1,
116 "objectives": run_state.get(
"objectives", []),
117 "metadata": run_state.get(
"metadata", {})
119 for key
in options.keys():
120 retry_options[key] = options[key]
121 return create_run(retry_options)
125func get_summary(run_state:Dictionary) -> Dictionary:
126 var objectives = run_state.get(
"objectives", [])
127 var required_count := 0
128 var required_complete := 0
129 for objective
in objectives:
130 if not objective
is Dictionary:
132 if bool(objective.get(
"required", true)):
134 if bool(objective.get(
"complete", false)):
135 required_complete += 1
137 "id": str(run_state.get(
"id",
"")),
138 "game_id": str(run_state.get(
"game_id",
"")),
139 "mode": str(run_state.get(
"mode",
"")),
140 "status": str(run_state.get(
"status",
"")),
141 "attempt":
int(run_state.get(
"attempt", 1)),
142 "elapsed": float(run_state.get(
"elapsed", 0.0)),
143 "required_objectives": required_count,
144 "completed_required_objectives": required_complete,
145 "counters": run_state.get(
"counters", {}).duplicate(true)
if run_state.get(
"counters", {})
is Dictionary
else {},
146 "collected": run_state.get(
"collected", {}).duplicate(true)
if run_state.get(
"collected", {})
is Dictionary
else {},
147 "rewards": run_state.get(
"rewards", {}).duplicate(true)
if run_state.get(
"rewards", {})
is Dictionary
else {},
148 "result": run_state.get(
"result", {}).duplicate(true)
if run_state.get(
"result", {})
is Dictionary
else {}
153func format_counts(counts:Dictionary, empty_label:String =
"none") -> String:
155 var keys = counts.keys()
158 var amount =
int(counts[key])
161 parts.append(
"%dx %s" % [amount, _labelize(str(key))])
162 return empty_label
if parts.is_empty()
else ", ".join(parts)
166func format_result_line(run_state:Dictionary) -> String:
167 var summary = get_summary(run_state)
168 var status = str(summary.get(
"status",
"run")).capitalize()
169 var mode = _labelize(str(summary.get(
"mode",
"run")))
170 var rewards = format_counts(summary.get(
"rewards", {}),
"no rewards")
171 return "%s %s: %s" % [mode, status, rewards]
174func _finish(run_state:Dictionary, status:String, result:Dictionary = {}, options:Dictionary = {}) -> Dictionary:
175 var next = run_state.duplicate(true)
176 var now = _now(options)
177 next[
"status"] = status
178 next[
"ended_at"] = now
179 next[
"elapsed"] = float(options.get(
"elapsed", _elapsed(next, now)))
180 next[
"result"] = result.duplicate(true)
184func _normalize_objectives(raw_objectives) -> Array:
186 if not raw_objectives
is Array:
188 for raw_objective
in raw_objectives:
189 if not raw_objective
is Dictionary:
191 var objective = raw_objective.duplicate(true)
192 objective[
"id"] = str(objective.get(
"id",
""))
193 objective[
"label"] = str(objective.get(
"label", objective.get(
"id",
"")))
194 objective[
"required"] =
bool(objective.get(
"required", true))
195 objective[
"complete"] =
bool(objective.get(
"complete", false))
196 objectives.append(objective)
200func _normalize_counts(raw_counts) -> Dictionary:
202 if not raw_counts
is Dictionary:
204 for key
in raw_counts.keys():
205 counts[str(key)] =
int(raw_counts[key])
209func _merge_counts(base_counts, added_counts) -> Dictionary:
210 var merged = _normalize_counts(base_counts)
211 if not added_counts
is Dictionary:
213 for key
in added_counts.keys():
214 merged[str(key)] =
int(merged.get(str(key), 0)) +
int(added_counts[key])
218func _now(options:Dictionary = {}) -> int:
219 if options.has(
"now"):
220 return int(options.get(
"now", 0))
221 return int(Time.get_unix_time_from_system())
224func _elapsed(run_state:Dictionary, now:int) -> float:
225 var started =
int(run_state.get(
"started_at", now))
226 return max(0.0, float(now - started))
229func _make_run_id(options:Dictionary, now:int) -> String:
230 var game_id = str(options.get(
"game_id",
"game"))
231 var mode = str(options.get(
"mode",
"run"))
232 return "%s_%s_%d" % [game_id, mode, now]
235func _labelize(value:String) -> String:
236 return value.replace(
"_",
" ").capitalize()
int
Increment a named counter and return a copied run state.
bool
Mark one objective complete/incomplete by id.