385 lines
11 KiB
GDScript3
385 lines
11 KiB
GDScript3
|
@icon("res://components/Persister/hard-drive.svg")
|
||
|
extends Base
|
||
|
## Data persister to persist data through locations
|
||
|
##
|
||
|
## A data persister that is used to persist data through the game, run, round,
|
||
|
## or room. A function is called to set data with a key in one of the categories
|
||
|
## and then other functions can be called to clear the data for a key or all
|
||
|
## data in a category.
|
||
|
|
||
|
signal data_persisted(key: String, value, category: PersisterEnums.Scope)
|
||
|
|
||
|
var _persisted = {}
|
||
|
|
||
|
|
||
|
func _spawned():
|
||
|
if _triggerer:
|
||
|
_info("Connecting to data scope triggers")
|
||
|
_triggerer.listen("game", _on_game_triggered)
|
||
|
_triggerer.listen("run", _on_run_triggered)
|
||
|
_triggerer.listen("round", _on_round_triggered)
|
||
|
_triggerer.listen("room", _on_room_triggered)
|
||
|
_load()
|
||
|
|
||
|
|
||
|
## Store data in a category
|
||
|
func persist_data(key: String, value, category := PersisterEnums.Scope.RUN) -> void:
|
||
|
# Only allow ints, bools or strings to be persisted and as Strings
|
||
|
var original_pass = value
|
||
|
|
||
|
if value is int:
|
||
|
value = str(value)
|
||
|
|
||
|
if value is bool:
|
||
|
value = str(value)
|
||
|
|
||
|
if not value is String:
|
||
|
_warn("Attempted to persist data for key <%s> with an invalid data type |%s|" % [key, type_string(typeof(value))])
|
||
|
return
|
||
|
|
||
|
# Create category if does not exist
|
||
|
if not _persisted.has(category):
|
||
|
_info("Created new persister category {%s}" % [_get_category_name(category)])
|
||
|
_persisted[category] = {}
|
||
|
|
||
|
# If key is already set to value exit early
|
||
|
if _persisted[category].has(key) and _persisted[category][key] == value:
|
||
|
return
|
||
|
|
||
|
_info("Set key <%s> in category {%s} to value |%s|" % [key, _get_category_name(category), value])
|
||
|
_persisted[category][key] = value
|
||
|
|
||
|
if _triggerer:
|
||
|
_triggerer.trigger(key, {"value": original_pass})
|
||
|
|
||
|
_emit_change(key, value, category)
|
||
|
|
||
|
|
||
|
## Get the value associated with a key in the highest priority or specified category
|
||
|
func get_value(key: String, category: PersisterEnums.Scope = PersisterEnums.Scope.UNKNOWN, default = null):
|
||
|
if category == PersisterEnums.Scope.UNKNOWN:
|
||
|
category = _get_key_category(key)
|
||
|
|
||
|
if category == PersisterEnums.Scope.UNKNOWN:
|
||
|
return default
|
||
|
|
||
|
if _persisted[category][key].is_valid_int():
|
||
|
return int(_persisted[category][key])
|
||
|
|
||
|
if _persisted[category][key] == "true" || _persisted[category][key] == "false":
|
||
|
return _persisted[category][key] == "true"
|
||
|
|
||
|
return _persisted[category][key]
|
||
|
|
||
|
|
||
|
## Delete data associated with a key from a certain category
|
||
|
func clear_data(key: String, category := PersisterEnums.Scope.RUN) -> void:
|
||
|
if not _persisted.has(category):
|
||
|
_info("Attempted to clear key <%s> in category {%s} but the category did not exist" % [key, _get_category_name(category)])
|
||
|
return
|
||
|
|
||
|
if not _persisted[category].has(key):
|
||
|
_info("Attempted to clear key <%s> in category {%s} but it did not exist" % [key, _get_category_name(category)])
|
||
|
return
|
||
|
|
||
|
_info("Cleared key <%s> in category {%s}" % [key, _get_category_name(category)])
|
||
|
_persisted[category].erase(key)
|
||
|
|
||
|
_emit_removal(key, category)
|
||
|
|
||
|
|
||
|
## Delete a category including all data within it
|
||
|
func clear_category(category := PersisterEnums.Scope.RUN) -> void:
|
||
|
if not _persisted.has(category):
|
||
|
_error("Attempted to clear category {%s} but it did not exist" % [_get_category_name(category)])
|
||
|
return
|
||
|
|
||
|
var keys = _persisted[category].keys()
|
||
|
|
||
|
_info("Cleared category {%s}" % [_get_category_name(category)])
|
||
|
_persisted.erase(category)
|
||
|
|
||
|
for key in keys:
|
||
|
_emit_removal(key, category)
|
||
|
|
||
|
|
||
|
## Add a number to a number value with the given key from a certain category
|
||
|
func change_value(key: String, value: int, category := PersisterEnums.Scope.RUN) -> void:
|
||
|
if not _persisted.has(category):
|
||
|
_persisted[category] = {}
|
||
|
|
||
|
if not _persisted[category].has(key):
|
||
|
persist_data(key, value, category)
|
||
|
return
|
||
|
|
||
|
if is_nan(int(_persisted[category][key])):
|
||
|
_error("Attempted to add number |%d| to key <%s> in category {%s} that is not a number (value: |%s|)" % [value, key, category, _persisted[category][key]])
|
||
|
return
|
||
|
|
||
|
var old_value = int(_persisted[category][key])
|
||
|
_info("Added number |%d| to key <%s> in category {%s} (old: |%d|) (new: |%d|)" % [value, key, _get_category_name(category), old_value, old_value + value])
|
||
|
persist_data(key, value + old_value, category)
|
||
|
|
||
|
|
||
|
func change_value_clamp_min(key: String, value: int, min: int, category := PersisterEnums.Scope.RUN) -> void:
|
||
|
change_value_clamp(key, value, min, INF, category)
|
||
|
|
||
|
|
||
|
func change_value_clamp_max(key: String, value: int, max: int, category := PersisterEnums.Scope.RUN) -> void:
|
||
|
change_value_clamp(key, value, -INF, max, category)
|
||
|
|
||
|
|
||
|
func change_value_clamp(key: String, value: int, min: int, max: int, category := PersisterEnums.Scope.RUN) -> void:
|
||
|
if min > max and not min == -9223372036854775808 and not max == -9223372036854775808:
|
||
|
_warn("Attempted to change clamp value %s with higher min %s than max %s" % [key, min, max])
|
||
|
return
|
||
|
|
||
|
if not _persisted.has(category):
|
||
|
_persisted[category] = {}
|
||
|
|
||
|
if not _persisted[category].has(key):
|
||
|
if value < min and not min == -9223372036854775808:
|
||
|
value = min
|
||
|
|
||
|
if value > max and not max == -9223372036854775808:
|
||
|
value = max
|
||
|
|
||
|
persist_data(key, value, category)
|
||
|
return
|
||
|
|
||
|
if is_nan(int(_persisted[category][key])):
|
||
|
_error("Attempted to add number |%d| to key <%s> in category {%s} that is not a number (value: |%s|)" % [value, key, category, _persisted[category][key]])
|
||
|
return
|
||
|
|
||
|
var old_value = int(_persisted[category][key])
|
||
|
var new_value = value + old_value
|
||
|
|
||
|
if new_value < min and not min == -9223372036854775808:
|
||
|
new_value = min
|
||
|
|
||
|
if new_value > max and not max == -9223372036854775808:
|
||
|
new_value = max
|
||
|
|
||
|
if old_value == new_value:
|
||
|
return
|
||
|
|
||
|
_info("Added clamped number |%d| to key <%s> in category {%s} (old: |%d|) (new: |%d|)" % [value, key, _get_category_name(category), old_value, new_value])
|
||
|
persist_data(key, new_value, category)
|
||
|
|
||
|
func change_save(index: int):
|
||
|
_save()
|
||
|
persist_data("save", index)
|
||
|
_load()
|
||
|
|
||
|
|
||
|
func save():
|
||
|
_save()
|
||
|
|
||
|
|
||
|
func _emit_change(key: String, value: String, category: PersisterEnums.Scope) -> void:
|
||
|
var key_category = _get_key_category(key)
|
||
|
|
||
|
# Only emit change if the category set has the highest priority
|
||
|
if key_category == category:
|
||
|
if is_nan(int(value)):
|
||
|
data_persisted.emit(key, value, category)
|
||
|
else:
|
||
|
data_persisted.emit(key, int(value), category)
|
||
|
|
||
|
|
||
|
func _emit_removal(key: String, category: PersisterEnums.Scope) -> void:
|
||
|
var key_category = _get_key_category(key)
|
||
|
|
||
|
if key_category == PersisterEnums.Scope.UNKNOWN:
|
||
|
return
|
||
|
|
||
|
var category_priority = _get_category_priority(category)
|
||
|
var key_category_priority = _get_category_priority(key_category)
|
||
|
|
||
|
if category_priority > key_category_priority:
|
||
|
var value = _persister[key_category][key]
|
||
|
|
||
|
if is_nan(int(value)):
|
||
|
data_persisted.emit(key, value, key_category)
|
||
|
else:
|
||
|
data_persisted.emit(key, int(value), key_category)
|
||
|
|
||
|
|
||
|
func _get_category_name(category: PersisterEnums.Scope) -> String:
|
||
|
match category:
|
||
|
PersisterEnums.Scope.PERMANENT:
|
||
|
return "permanent"
|
||
|
PersisterEnums.Scope.SAVE:
|
||
|
return "save"
|
||
|
PersisterEnums.Scope.GAME:
|
||
|
return "game"
|
||
|
PersisterEnums.Scope.RUN:
|
||
|
return "run"
|
||
|
PersisterEnums.Scope.ROUND:
|
||
|
return "round"
|
||
|
PersisterEnums.Scope.ROOM:
|
||
|
return "room"
|
||
|
_:
|
||
|
return "unknown"
|
||
|
|
||
|
|
||
|
func _get_category_priority(category: PersisterEnums.Scope) -> int:
|
||
|
match category:
|
||
|
PersisterEnums.Scope.PERMANENT:
|
||
|
return 1
|
||
|
PersisterEnums.Scope.SAVE:
|
||
|
return 2
|
||
|
PersisterEnums.Scope.GAME:
|
||
|
return 3
|
||
|
PersisterEnums.Scope.RUN:
|
||
|
return 4
|
||
|
PersisterEnums.Scope.ROUND:
|
||
|
return 5
|
||
|
PersisterEnums.Scope.ROOM:
|
||
|
return 6
|
||
|
_:
|
||
|
return 0
|
||
|
|
||
|
|
||
|
func _get_key_category(key: String) -> PersisterEnums.Scope:
|
||
|
var category_order = []
|
||
|
|
||
|
for category in PersisterEnums.Scope:
|
||
|
var category_value = PersisterEnums.Scope[category]
|
||
|
|
||
|
category_order.push_back(
|
||
|
{
|
||
|
"priority": _get_category_priority(category_value),
|
||
|
"category": category_value
|
||
|
}
|
||
|
)
|
||
|
|
||
|
category_order.sort_custom(func(a, b):
|
||
|
return a.priority > b.priority
|
||
|
)
|
||
|
|
||
|
for value in category_order:
|
||
|
var category = value.category
|
||
|
|
||
|
if _persisted.has(category):
|
||
|
if _persisted[category].has(key):
|
||
|
return category
|
||
|
|
||
|
return PersisterEnums.Scope.UNKNOWN
|
||
|
|
||
|
|
||
|
func _get_all_category_data(category: PersisterEnums.Scope) -> Dictionary:
|
||
|
if not _persisted.has(category):
|
||
|
return {}
|
||
|
|
||
|
return _persisted[category]
|
||
|
|
||
|
|
||
|
func _on_game_triggered(data: Dictionary) -> void:
|
||
|
clear_category(PersisterEnums.Scope.GAME)
|
||
|
|
||
|
|
||
|
func _on_run_triggered(data: Dictionary) -> void:
|
||
|
clear_category(PersisterEnums.Scope.RUN)
|
||
|
|
||
|
|
||
|
func _on_round_triggered(data: Dictionary) -> void:
|
||
|
clear_category(PersisterEnums.Scope.ROUND)
|
||
|
|
||
|
|
||
|
func _on_room_triggered(data: Dictionary) -> void:
|
||
|
clear_category(PersisterEnums.Scope.ROOM)
|
||
|
|
||
|
|
||
|
func _save():
|
||
|
_info("Saving")
|
||
|
_save_permanent_data()
|
||
|
_save_save_data()
|
||
|
|
||
|
|
||
|
func _load():
|
||
|
_info("Loading")
|
||
|
_load_permanent_data()
|
||
|
|
||
|
if not get_value("save"):
|
||
|
_info("Creating starting save")
|
||
|
persist_data("save", 1, PersisterEnums.Scope.PERMANENT)
|
||
|
|
||
|
clear_category(PersisterEnums.Scope.SAVE)
|
||
|
_load_save_data()
|
||
|
|
||
|
|
||
|
func _save_permanent_data():
|
||
|
var save_file = FileAccess.open("user://data.json", FileAccess.WRITE)
|
||
|
|
||
|
var data = _get_all_category_data(PersisterEnums.Scope.PERMANENT)
|
||
|
var data_string = JSON.stringify(data)
|
||
|
|
||
|
_info("Saving permanent save data")
|
||
|
|
||
|
save_file.store_line(data_string)
|
||
|
|
||
|
|
||
|
func _save_save_data():
|
||
|
if not FileAccess.file_exists("user://saves"):
|
||
|
DirAccess.make_dir_absolute("user://saves")
|
||
|
|
||
|
var save = get_value("save")
|
||
|
var save_file = FileAccess.open("user://saves/save-%d.json" % [save], FileAccess.WRITE)
|
||
|
|
||
|
var data = _get_all_category_data(PersisterEnums.Scope.SAVE)
|
||
|
var data_string = JSON.stringify(data)
|
||
|
|
||
|
_info("Saving save save data")
|
||
|
|
||
|
save_file.store_line(data_string)
|
||
|
|
||
|
|
||
|
func _load_permanent_data():
|
||
|
var save_file = FileAccess.open("user://data.json", FileAccess.READ)
|
||
|
|
||
|
if not save_file:
|
||
|
return
|
||
|
|
||
|
_info("Loading permanent save data")
|
||
|
var json_string = ""
|
||
|
|
||
|
while save_file.get_position() < save_file.get_length():
|
||
|
json_string += save_file.get_line()
|
||
|
|
||
|
var json = JSON.new()
|
||
|
|
||
|
var result = json.parse(json_string)
|
||
|
var data = json.get_data()
|
||
|
|
||
|
for key in data:
|
||
|
Persister.persist_data(key, data[key], PersisterEnums.Scope.PERMANENT)
|
||
|
|
||
|
|
||
|
func _load_save_data():
|
||
|
var save = get_value("save")
|
||
|
var save_file = FileAccess.open("user://saves/save-%s.json" % [save], FileAccess.READ)
|
||
|
|
||
|
if not save_file:
|
||
|
return
|
||
|
|
||
|
_info("Loading save save data")
|
||
|
var json_string = ""
|
||
|
|
||
|
while save_file.get_position() < save_file.get_length():
|
||
|
json_string += save_file.get_line()
|
||
|
|
||
|
var json = JSON.new()
|
||
|
|
||
|
var result = json.parse(json_string)
|
||
|
var data = json.get_data()
|
||
|
|
||
|
Persister.clear_category(PersisterEnums.Scope.SAVE)
|
||
|
|
||
|
for key in data:
|
||
|
Persister.persist_data(key, data[key], PersisterEnums.Scope.SAVE)
|
||
|
|
||
|
|
||
|
func _on_timer_timeout():
|
||
|
_save()
|