Profile, Startoptionen und vorsichtiges Lesen
Die Befehlszeile wählt ein Profil, einen Namen und einen Direktstart. Zwei Fenster auf einem Rechner sind so zwei Spieler. Ein Schlüssel oder Spielstand, der sich gerade nicht lesen lässt, wird nicht mehr ersetzt. Name und zuletzt genutzte Adresse bleiben gespeichert. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,18 +1,23 @@
|
||||
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.
|
||||
## The game's files on the player's machine: the player key, the save, the
|
||||
## session recordings for bug reports and the player's settings.
|
||||
|
||||
const KEY_FILE := "player.key"
|
||||
const SAVE_FILE := "world.grw"
|
||||
## Holds the previous save while write_save replaces it.
|
||||
const BACKUP_FILE := SAVE_FILE + ".bak"
|
||||
const RECORDINGS_DIR := "recordings"
|
||||
const SETTINGS_FILE := "settings.cfg"
|
||||
## Recordings beyond this number are deleted, oldest first.
|
||||
const KEEP_RECORDINGS := 10
|
||||
const KEY_BYTES := 16
|
||||
|
||||
var base_dir: String
|
||||
## The result of the last attempt to read the key or the save: OK, or why
|
||||
## the file could not be read. A file that exists but cannot be read right
|
||||
## now, for example because another program holds it, is never replaced.
|
||||
var last_read_error: Error = OK
|
||||
|
||||
|
||||
## Opens the folder and creates it if it is missing. If a crash interrupted a
|
||||
@@ -25,14 +30,19 @@ func _init(directory: String = "user://") -> void:
|
||||
|
||||
|
||||
## The key that identifies this installation in every world. It is created
|
||||
## on first use and kept from then on.
|
||||
## on first use and kept from then on. Returns an empty key if the key file
|
||||
## exists but cannot be read; last_read_error then says why.
|
||||
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)
|
||||
if not _write(path, key):
|
||||
push_error("player key could not be stored at %s" % path)
|
||||
if FileAccess.file_exists(path):
|
||||
var stored := _read_file(path)
|
||||
if last_read_error != OK:
|
||||
return PackedByteArray()
|
||||
if stored.size() == KEY_BYTES:
|
||||
return stored
|
||||
var key := Crypto.new().generate_random_bytes(KEY_BYTES)
|
||||
if not _write(path, key):
|
||||
push_error("player key could not be stored at %s" % path)
|
||||
return key
|
||||
|
||||
|
||||
@@ -40,8 +50,10 @@ func has_save() -> bool:
|
||||
return FileAccess.file_exists(base_dir.path_join(SAVE_FILE))
|
||||
|
||||
|
||||
## The bytes of the save. If it cannot be read, they are empty and
|
||||
## last_read_error says why.
|
||||
func read_save() -> PackedByteArray:
|
||||
return FileAccess.get_file_as_bytes(base_dir.path_join(SAVE_FILE))
|
||||
return _read_file(base_dir.path_join(SAVE_FILE))
|
||||
|
||||
|
||||
## Writes the save through a temporary file, so that a crash or a failed
|
||||
@@ -115,6 +127,40 @@ func recordings() -> PackedStringArray:
|
||||
return names
|
||||
|
||||
|
||||
## The name the player gave last time, or an empty text.
|
||||
func player_name() -> String:
|
||||
return _setting("player", "name")
|
||||
|
||||
|
||||
func set_player_name(player_name: String) -> void:
|
||||
_set_setting("player", "name", player_name)
|
||||
|
||||
|
||||
## The address of the host the player joined last time, or an empty text.
|
||||
func last_address() -> String:
|
||||
return _setting("network", "address")
|
||||
|
||||
|
||||
func set_last_address(address: String) -> void:
|
||||
_set_setting("network", "address", address)
|
||||
|
||||
|
||||
func _setting(section: String, key: String) -> String:
|
||||
var settings := ConfigFile.new()
|
||||
if settings.load(base_dir.path_join(SETTINGS_FILE)) != OK:
|
||||
return ""
|
||||
return str(settings.get_value(section, key, ""))
|
||||
|
||||
|
||||
func _set_setting(section: String, key: String, value: String) -> void:
|
||||
var path := base_dir.path_join(SETTINGS_FILE)
|
||||
var settings := ConfigFile.new()
|
||||
settings.load(path)
|
||||
settings.set_value(section, key, value)
|
||||
if settings.save(path) != OK:
|
||||
push_warning("settings could not be stored at %s" % path)
|
||||
|
||||
|
||||
## Moves the backup back to the save if there is no save, see write_save.
|
||||
func _restore_backup() -> void:
|
||||
var path := base_dir.path_join(SAVE_FILE)
|
||||
@@ -125,6 +171,13 @@ func _restore_backup() -> void:
|
||||
push_warning("the previous save could not be restored from %s" % backup)
|
||||
|
||||
|
||||
## Reads a whole file and records the result in last_read_error.
|
||||
func _read_file(path: String) -> PackedByteArray:
|
||||
var bytes := FileAccess.get_file_as_bytes(path)
|
||||
last_read_error = FileAccess.get_open_error()
|
||||
return bytes
|
||||
|
||||
|
||||
func _write(path: String, bytes: PackedByteArray) -> bool:
|
||||
var file := FileAccess.open(path, FileAccess.WRITE)
|
||||
if file == null:
|
||||
|
||||
Reference in New Issue
Block a user