c17769246f
Two related cleanups from the best-practice audit: Task 3 — typed Resources instead of MBase / MScene / MLevel / MSetting The old model classes wrapped the godot_db_manager Table API: each row read went through table.get_data_at_row_idx(int), Cell.get_data(), and 'as int' / 'as String' casts that don't actually parse anything in Godot 4. m_value, m_lock, m_label, ... members shadowed the cell indirection. Setters round-tripped through table.edit_data() + Global.database.save_db(). That's a lot of plumbing for what is, in the end, three flat tables of static strings. Introduce three @export-typed Resources: db/scene_entry.gd class_name SceneEntry db/level_entry.gd class_name LevelEntry db/settings_data.gd class_name SettingsData Rewrite scripts/Database.gd so Database.DB holds: settings: SettingsData levels: Array[LevelEntry] scenes: Array[SceneEntry] Build them once at startup from ahog.json, and serialise back to the same JSON shape on save() so existing progress files keep working. LevelEntry carries its own object_to_find / object_finding / reset methods (talking to Global.database for cross-table lookups), and SceneEntry carries its own mesh_path / audio_sound. Per-scene dissolve state (value, tick_reference, dissolved) lives on SceneEntry as non-exported runtime fields. Delete db/MBase.gd / db/MScene.gd / db/MLevel.gd / db/MSetting.gd. Update consumers: - scripts/Setting.gd: read/write Global.database.settings directly, call Global.database.save() after each setter. - scenes/levels/Levels.gd: iterate Global.database.scenes_for_level( current_scene_int) instead of mscene.new(i) for every row; scene state reads (scene.lock, scene.mesh, scene.counter, ...) replace scene.lock() / scene.mesh() / scene.counter() method calls; runtime dissolve state lives on the SceneEntry instance instead of mutable m_value / m_tick_reference members on MScene; 'dissolved' flag replaces set_mesh(null) signalling. - scenes/UI/choose_scenes/ChooseScene.gd: iterate Global.database .levels; level.name / level.thumb property access in place of level.name() / level.thumbnail(). configure_reset() loses its redundant index argument (LevelEntry knows its own index). - scripts/event.gd: _on_reset_level signature now takes LevelEntry, reset path drops index forwarding. Task 2 — type hints across the remaining scripts scripts/Global.gd, scenes/Main.gd, scenes/UI/ending/Ending.gd, scenes/UI/loading/Loading.gd, scenes/UI/settings/Settings.gd: add typed parameters and -> return annotations. current_scene_int is now 'int = -1' (sentinel) so callers don't fall into Variant comparisons; event.gd:_on_reset_level resets it to -1 instead of null. Settings.gd no longer wraps button_pressed in int() before passing to the now-typed bool setters. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
176 lines
5.0 KiB
GDScript
176 lines
5.0 KiB
GDScript
extends Node
|
|
|
|
# Loads ahog.json once into typed Resource arrays:
|
|
# Global.database.settings -> SettingsData
|
|
# Global.database.levels -> Array[LevelEntry]
|
|
# Global.database.scenes -> Array[SceneEntry]
|
|
#
|
|
# save() round-trips back to the same JSON format so game progress
|
|
# (per-scene lock state) persists across runs.
|
|
|
|
class DB extends RefCounted:
|
|
var settings: SettingsData
|
|
var levels: Array[LevelEntry] = []
|
|
var scenes: Array[SceneEntry] = []
|
|
var _path: String
|
|
|
|
const _SETTINGS_PROPS := [
|
|
{"name": "langue", "type": "1", "auto_increment": "0"},
|
|
{"name": "gyroscope", "type": "0", "auto_increment": "0"},
|
|
{"name": "ambient_sound", "type": "0", "auto_increment": "0"},
|
|
{"name": "resolution", "type": "3", "auto_increment": "0"},
|
|
{"name": "fullscreen", "type": "0", "auto_increment": "0"},
|
|
{"name": "version", "type": "0", "auto_increment": "0"},
|
|
]
|
|
const _LEVELS_PROPS := [
|
|
{"name": "name", "type": "3", "auto_increment": "0"},
|
|
{"name": "thumb", "type": "4", "auto_increment": "0"},
|
|
]
|
|
const _SCENES_PROPS := [
|
|
{"name": "lock", "type": "0", "auto_increment": "0"},
|
|
{"name": "label", "type": "3", "auto_increment": "0"},
|
|
{"name": "key", "type": "3", "auto_increment": "0"},
|
|
{"name": "level", "type": "table", "table_name": "levels", "auto_increment": "0"},
|
|
{"name": "mesh", "type": "3", "auto_increment": "0"},
|
|
{"name": "label_counter", "type": "1", "auto_increment": "0"},
|
|
{"name": "counter", "type": "1", "auto_increment": "0"},
|
|
]
|
|
|
|
func _init(path: String) -> void:
|
|
_path = path
|
|
var f := FileAccess.open(path, FileAccess.READ)
|
|
if f == null:
|
|
push_error("[Database] Cannot open " + path)
|
|
return
|
|
var doc = JSON.parse_string(f.get_as_text())
|
|
f.close()
|
|
if not (doc is Dictionary):
|
|
push_error("[Database] Invalid JSON in " + path)
|
|
return
|
|
for table in doc.get("tables", []):
|
|
match table.get("table_name", ""):
|
|
"settings": settings = _load_settings(table.get("data", []))
|
|
"levels": levels = _load_levels(table.get("data", []))
|
|
"scenes": scenes = _load_scenes(table.get("data", []))
|
|
|
|
func _load_settings(data: Array) -> SettingsData:
|
|
var s := SettingsData.new()
|
|
if data.size() >= 6:
|
|
s.langue = int(data[0])
|
|
s.gyroscope = bool(int(data[1]))
|
|
s.ambient_sound = bool(int(data[2]))
|
|
s.resolution = str(data[3])
|
|
s.fullscreen = bool(int(data[4]))
|
|
s.version = str(data[5])
|
|
return s
|
|
|
|
func _load_levels(data: Array) -> Array[LevelEntry]:
|
|
var result: Array[LevelEntry] = []
|
|
const W := 2
|
|
for i in range(data.size() / W):
|
|
var l := LevelEntry.new()
|
|
l.index = i
|
|
l.name = str(data[i * W + 0])
|
|
l.thumb = str(data[i * W + 1])
|
|
result.append(l)
|
|
return result
|
|
|
|
func _load_scenes(data: Array) -> Array[SceneEntry]:
|
|
var result: Array[SceneEntry] = []
|
|
const W := 7
|
|
for i in range(data.size() / W):
|
|
var s := SceneEntry.new()
|
|
s.lock = bool(int(data[i * W + 0]))
|
|
s.label = str(data[i * W + 1])
|
|
s.key = str(data[i * W + 2])
|
|
s.level = int(data[i * W + 3])
|
|
s.mesh = str(data[i * W + 4])
|
|
s.label_counter = str(data[i * W + 5])
|
|
s.counter = int(data[i * W + 6])
|
|
result.append(s)
|
|
return result
|
|
|
|
func scenes_for_level(level_idx: int) -> Array[SceneEntry]:
|
|
var result: Array[SceneEntry] = []
|
|
for s in scenes:
|
|
if s.level == level_idx:
|
|
result.append(s)
|
|
return result
|
|
|
|
func level_by_index(idx: int) -> LevelEntry:
|
|
for l in levels:
|
|
if l.index == idx:
|
|
return l
|
|
return null
|
|
|
|
func save() -> void:
|
|
var doc := {
|
|
"GDDB_ver": "2.0",
|
|
"db_name": "ahog",
|
|
"tables": [
|
|
{"table_name": "settings", "props": _SETTINGS_PROPS, "data": _dump_settings()},
|
|
{"table_name": "levels", "props": _LEVELS_PROPS, "data": _dump_levels()},
|
|
{"table_name": "scenes", "props": _SCENES_PROPS, "data": _dump_scenes()},
|
|
],
|
|
}
|
|
var f := FileAccess.open(_path, FileAccess.WRITE)
|
|
if f == null:
|
|
push_error("[Database] Cannot write " + _path)
|
|
return
|
|
f.store_string(JSON.stringify(doc))
|
|
f.close()
|
|
|
|
func _dump_settings() -> Array:
|
|
return [
|
|
str(settings.langue),
|
|
str(int(settings.gyroscope)),
|
|
str(int(settings.ambient_sound)),
|
|
settings.resolution,
|
|
str(int(settings.fullscreen)),
|
|
settings.version,
|
|
]
|
|
|
|
func _dump_levels() -> Array:
|
|
var out: Array = []
|
|
for l in levels:
|
|
out.append_array([l.name, l.thumb])
|
|
return out
|
|
|
|
func _dump_scenes() -> Array:
|
|
var out: Array = []
|
|
for s in scenes:
|
|
out.append_array([
|
|
str(int(s.lock)),
|
|
s.label,
|
|
s.key,
|
|
str(s.level),
|
|
s.mesh,
|
|
s.label_counter,
|
|
str(s.counter),
|
|
])
|
|
return out
|
|
|
|
func initialize() -> DB:
|
|
return DB.new(save_database_in())
|
|
|
|
func save_database_in() -> String:
|
|
if OS.get_name() == "Android":
|
|
copy_database()
|
|
return android_path()
|
|
return normal_path()
|
|
|
|
func android_path() -> String:
|
|
return "user://database.json"
|
|
|
|
func normal_path() -> String:
|
|
return "res://db/ahog.json"
|
|
|
|
func copy_database() -> void:
|
|
if FileAccess.file_exists(android_path()):
|
|
return
|
|
var src := FileAccess.open(normal_path(), FileAccess.READ)
|
|
var dst := FileAccess.open(android_path(), FileAccess.WRITE)
|
|
dst.store_string(src.get_as_text())
|
|
dst.close()
|
|
src.close()
|