Wire all twelve levels with billboard prefabs, autoload systems (inventory, achievements, mock shop, DE/EN/BY), and satire audio locks so the Title→End flow is playable with placeholder assets.
161 lines
4.5 KiB
GDScript
161 lines
4.5 KiB
GDScript
extends Node
|
|
## Global game flow, HP, death, playtime, save/load.
|
|
## TODO: Expand save schema when real progression exists.
|
|
|
|
signal hp_changed(hp: int, max_hp: int)
|
|
signal died(cause_key: String)
|
|
signal scene_changed(scene_id: String)
|
|
|
|
const SAVE_PATH := "user://save.json"
|
|
const SCENE_ORDER: Array[String] = [
|
|
"title_screen",
|
|
"intro_cutscene",
|
|
"village",
|
|
"shoe_boss",
|
|
"meadow",
|
|
"massage_studio",
|
|
"farm_path",
|
|
"farm",
|
|
"moon",
|
|
"final_boss",
|
|
"end_screen",
|
|
]
|
|
|
|
const SCENE_PATHS := {
|
|
"title_screen": "res://scenes/title_screen.tscn",
|
|
"intro_cutscene": "res://scenes/intro_cutscene.tscn",
|
|
"village": "res://scenes/village.tscn",
|
|
"shoe_boss": "res://scenes/shoe_boss.tscn",
|
|
"meadow": "res://scenes/meadow.tscn",
|
|
"massage_studio": "res://scenes/massage_studio.tscn",
|
|
"farm_path": "res://scenes/farm_path.tscn",
|
|
"farm": "res://scenes/farm.tscn",
|
|
"moon": "res://scenes/moon.tscn",
|
|
"final_boss": "res://scenes/final_boss.tscn",
|
|
"end_screen": "res://scenes/end_screen.tscn",
|
|
"death_screen": "res://scenes/death_screen.tscn",
|
|
}
|
|
|
|
var current_scene_id: String = "title_screen"
|
|
var max_hp: int = 5
|
|
var hp: int = 5
|
|
var death_count: int = 0
|
|
var last_death_cause_key: String = "death_default"
|
|
var playtime_seconds: float = 0.0
|
|
var house_built: bool = false
|
|
var flags: Dictionary = {}
|
|
|
|
func _ready() -> void:
|
|
process_mode = Node.PROCESS_MODE_ALWAYS
|
|
load_game()
|
|
|
|
func _process(delta: float) -> void:
|
|
if get_tree().paused:
|
|
return
|
|
if current_scene_id in ["title_screen", "death_screen"]:
|
|
return
|
|
playtime_seconds += delta
|
|
if playtime_seconds >= 3600.0:
|
|
Achievements.unlock("played_hour")
|
|
|
|
func goto_scene(scene_id: String) -> void:
|
|
if not SCENE_PATHS.has(scene_id):
|
|
push_error("Unknown scene: %s" % scene_id)
|
|
return
|
|
current_scene_id = scene_id
|
|
scene_changed.emit(scene_id)
|
|
if scene_id == "moon":
|
|
Achievements.unlock("moon_cheese")
|
|
save_game()
|
|
get_tree().change_scene_to_file(SCENE_PATHS[scene_id])
|
|
|
|
func goto_next() -> void:
|
|
var idx := SCENE_ORDER.find(current_scene_id)
|
|
if idx < 0:
|
|
goto_scene("village")
|
|
return
|
|
if idx >= SCENE_ORDER.size() - 1:
|
|
goto_scene("end_screen")
|
|
return
|
|
goto_scene(SCENE_ORDER[idx + 1])
|
|
|
|
func skip_to_final_boss() -> void:
|
|
Achievements.unlock("milk_skip")
|
|
flags["milk_skip_used"] = true
|
|
goto_scene("final_boss")
|
|
|
|
func start_new_game() -> void:
|
|
hp = max_hp
|
|
flags.clear()
|
|
house_built = false
|
|
goto_scene("intro_cutscene")
|
|
|
|
func heal_full() -> void:
|
|
hp = max_hp
|
|
hp_changed.emit(hp, max_hp)
|
|
|
|
func take_damage(amount: int = 1, cause_key: String = "death_default") -> void:
|
|
hp = max(0, hp - amount)
|
|
hp_changed.emit(hp, max_hp)
|
|
if hp <= 0:
|
|
die(cause_key)
|
|
|
|
func die(cause_key: String = "death_default") -> void:
|
|
last_death_cause_key = cause_key
|
|
death_count += 1
|
|
died.emit(cause_key)
|
|
if death_count >= 100:
|
|
Achievements.unlock("died_100")
|
|
save_game()
|
|
goto_scene("death_screen")
|
|
|
|
func set_flag(key: String, value: Variant = true) -> void:
|
|
flags[key] = value
|
|
save_game()
|
|
|
|
func get_flag(key: String, default_value: Variant = false) -> Variant:
|
|
return flags.get(key, default_value)
|
|
|
|
func save_game() -> void:
|
|
var data := {
|
|
"hp": hp,
|
|
"max_hp": max_hp,
|
|
"death_count": death_count,
|
|
"last_death_cause_key": last_death_cause_key,
|
|
"playtime_seconds": playtime_seconds,
|
|
"current_scene_id": current_scene_id,
|
|
"house_built": house_built,
|
|
"flags": flags,
|
|
"inventory": Inventory.serialize(),
|
|
"achievements": Achievements.serialize(),
|
|
"shop": Shop.serialize(),
|
|
"settings": Settings.serialize(),
|
|
}
|
|
var file := FileAccess.open(SAVE_PATH, FileAccess.WRITE)
|
|
if file == null:
|
|
push_warning("Could not write save file")
|
|
return
|
|
file.store_string(JSON.stringify(data, "\t"))
|
|
|
|
func load_game() -> void:
|
|
if not FileAccess.file_exists(SAVE_PATH):
|
|
return
|
|
var file := FileAccess.open(SAVE_PATH, FileAccess.READ)
|
|
if file == null:
|
|
return
|
|
var parsed: Variant = JSON.parse_string(file.get_as_text())
|
|
if typeof(parsed) != TYPE_DICTIONARY:
|
|
return
|
|
var data: Dictionary = parsed
|
|
hp = int(data.get("hp", max_hp))
|
|
max_hp = int(data.get("max_hp", max_hp))
|
|
death_count = int(data.get("death_count", 0))
|
|
last_death_cause_key = str(data.get("last_death_cause_key", "death_default"))
|
|
playtime_seconds = float(data.get("playtime_seconds", 0.0))
|
|
current_scene_id = str(data.get("current_scene_id", "title_screen"))
|
|
house_built = bool(data.get("house_built", false))
|
|
flags = data.get("flags", {})
|
|
Inventory.deserialize(data.get("inventory", {}))
|
|
Achievements.deserialize(data.get("achievements", {}))
|
|
Shop.deserialize(data.get("shop", {}))
|
|
Settings.deserialize(data.get("settings", {}))
|