Texte, Tasten und Dateien
Alle Texte stehen in einer Tabelle mit Deutsch und Englisch, die das Spiel zur Laufzeit liest. Die Tasten werden in Code angelegt, nach physischer Position. SaveStore verwaltet den Spielerschlüssel dieser Installation, schreibt den Speicherstand über eine temporäre Datei, legt unbrauchbare Stände beiseite und behält die zehn neuesten Aufzeichnungen. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
class_name SaveStore
|
||||
extends RefCounted
|
||||
## The game's files on the player's machine: the player key, the save and
|
||||
## the session recordings for bug reports.
|
||||
|
||||
const KEY_FILE := "player.key"
|
||||
const SAVE_FILE := "world.grw"
|
||||
const RECORDINGS_DIR := "recordings"
|
||||
## Recordings beyond this number are deleted, oldest first.
|
||||
const KEEP_RECORDINGS := 10
|
||||
const KEY_BYTES := 16
|
||||
|
||||
var base_dir: String
|
||||
|
||||
|
||||
func _init(directory: String = "user://") -> void:
|
||||
base_dir = directory
|
||||
DirAccess.make_dir_recursive_absolute(base_dir)
|
||||
|
||||
|
||||
## The key that identifies this installation in every world. It is created
|
||||
## on first use and kept from then on.
|
||||
func player_key() -> PackedByteArray:
|
||||
var path := base_dir.path_join(KEY_FILE)
|
||||
var key := FileAccess.get_file_as_bytes(path)
|
||||
if key.size() != KEY_BYTES:
|
||||
key = Crypto.new().generate_random_bytes(KEY_BYTES)
|
||||
_write(path, key)
|
||||
return key
|
||||
|
||||
|
||||
func has_save() -> bool:
|
||||
return FileAccess.file_exists(base_dir.path_join(SAVE_FILE))
|
||||
|
||||
|
||||
func read_save() -> PackedByteArray:
|
||||
return FileAccess.get_file_as_bytes(base_dir.path_join(SAVE_FILE))
|
||||
|
||||
|
||||
## Writes the save through a temporary file, so that a crash while writing
|
||||
## never destroys the previous save. Returns false if nothing was written.
|
||||
func write_save(bytes: PackedByteArray) -> bool:
|
||||
if bytes.is_empty():
|
||||
return false
|
||||
var path := base_dir.path_join(SAVE_FILE)
|
||||
var temporary := path + ".tmp"
|
||||
if not _write(temporary, bytes):
|
||||
return false
|
||||
return DirAccess.rename_absolute(temporary, path) == OK
|
||||
|
||||
|
||||
## Moves an unusable save out of the way, so that the next save does not
|
||||
## overwrite it. An older set-aside save is replaced. Returns the new path.
|
||||
func set_aside_save() -> String:
|
||||
var path := base_dir.path_join(SAVE_FILE)
|
||||
var aside := path + ".broken"
|
||||
DirAccess.rename_absolute(path, aside)
|
||||
return aside
|
||||
|
||||
|
||||
## Stores a session recording and deletes the oldest ones beyond
|
||||
## KEEP_RECORDINGS. Returns the path, or an empty text if nothing was written.
|
||||
func write_recording(bytes: PackedByteArray) -> String:
|
||||
if bytes.is_empty():
|
||||
return ""
|
||||
var directory := base_dir.path_join(RECORDINGS_DIR)
|
||||
DirAccess.make_dir_recursive_absolute(directory)
|
||||
# Microseconds since 1970, padded so that names sort by age.
|
||||
var stamp := int(Time.get_unix_time_from_system() * 1_000_000.0)
|
||||
var path := directory.path_join("session-%016d.grwr" % stamp)
|
||||
if not _write(path, bytes):
|
||||
return ""
|
||||
var names := recordings()
|
||||
while names.size() > KEEP_RECORDINGS:
|
||||
DirAccess.remove_absolute(directory.path_join(names[0]))
|
||||
names.remove_at(0)
|
||||
return path
|
||||
|
||||
|
||||
## File names of the stored recordings, oldest first.
|
||||
func recordings() -> PackedStringArray:
|
||||
var names := DirAccess.get_files_at(base_dir.path_join(RECORDINGS_DIR))
|
||||
names.sort()
|
||||
return names
|
||||
|
||||
|
||||
func _write(path: String, bytes: PackedByteArray) -> bool:
|
||||
var file := FileAccess.open(path, FileAccess.WRITE)
|
||||
if file == null:
|
||||
return false
|
||||
file.store_buffer(bytes)
|
||||
file.close()
|
||||
return true
|
||||
Reference in New Issue
Block a user