brackeys-12/components/Persister/persister.gd

385 lines
11 KiB
GDScript3
Raw Normal View History

2024-09-08 17:34:41 +00:00
@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()