feat: Savestates for important objects

This commit is contained in:
Jakob Feldmann 2023-10-12 16:29:15 +02:00
parent 32bc684f49
commit 7c77680bde
13 changed files with 227 additions and 103 deletions

View File

@ -41,87 +41,109 @@ func get_progress() -> Dictionary:
func save() -> void:
SaveManager.save_default()
func get_level_completed(levelName: String) -> bool:
if gsr.progress_dict.has(levelName) && gsr.progress_dict[levelName].has("levelcompleted"):
return gsr.progress_dict[levelName]["levelcompleted"]
# Returns if the level was completed or not
func get_level_completed(level_name: String) -> bool:
if gsr.progress_dict.has(level_name) && gsr.progress_dict[level_name].has("levelcompleted"):
return gsr.progress_dict[level_name]["levelcompleted"]
else:
return false
func set_level_completed(levelName: String, state: bool) -> void:
if !gsr.progress_dict.has(levelName):
gsr.progress_dict[levelName] = {}
gsr.progress_dict[levelName]["levelcompleted"] = state
func set_level_completed(level_name: String, state: bool) -> void:
if !gsr.progress_dict.has(level_name):
gsr.progress_dict[level_name] = {}
gsr.progress_dict[level_name]["levelcompleted"] = state
SaveManager.save_default()
func set_leveltime(levelName: String, time: float) -> void:
if !gsr.progress_dict.has(levelName):
gsr.progress_dict[levelName] = {}
gsr.progress_dict[levelName]["leveltime"] = time
func set_leveltime(level_name: String, time: float) -> void:
if !gsr.progress_dict.has(level_name):
gsr.progress_dict[level_name] = {}
gsr.progress_dict[level_name]["leveltime"] = time
SaveManager.save_default()
func get_level_time(levelName: String) -> float:
if gsr.progress_dict.has(levelName) && gsr.progress_dict[levelName].has("leveltime"):
return gsr.progress_dict[levelName]["leveltime"]
else:
return INF
func set_uncompleted_level_time(levelName: String, time: float) -> void:
if !gsr.progress_dict.has(levelName):
gsr.progress_dict[levelName] = {}
gsr.progress_dict[levelName]["uncompletedleveltime"] = time
SaveManager.save_default()
func get_uncompleted_level_time(levelName: String) -> float:
if gsr.progress_dict.has(levelName) && gsr.progress_dict[levelName].has("uncompletedleveltime"):
return gsr.progress_dict[levelName]["uncompletedleveltime"]
func get_level_time(level_name: String) -> float:
if gsr.progress_dict.has(level_name) && gsr.progress_dict[level_name].has("leveltime"):
return gsr.progress_dict[level_name]["leveltime"]
else:
return INF
func set_savepoint(levelName: String, position: Vector2) -> void:
func set_uncompleted_level_time(level_name: String, time: float) -> void:
if !gsr.progress_dict.has(level_name):
gsr.progress_dict[level_name] = {}
gsr.progress_dict[level_name]["uncompletedleveltime"] = time
SaveManager.save_default()
func get_uncompleted_level_time(level_name: String) -> float:
if gsr.progress_dict.has(level_name) && gsr.progress_dict[level_name].has("uncompletedleveltime"):
return gsr.progress_dict[level_name]["uncompletedleveltime"]
else:
return INF
func set_savepoint(level_name: String, position: Vector2) -> void:
#TODO You can free a frog, go to the checkpoint and it will be
# saved as freed forever
if !gsr.progress_dict.has(levelName):
gsr.progress_dict[levelName] = {}
gsr.progress_dict[levelName]["savepoint"] = position
if !gsr.progress_dict.has(level_name):
gsr.progress_dict[level_name] = {}
gsr.progress_dict[level_name]["savepoint"] = position
SaveManager.save_default()
func remove_savepoint(levelName: String) -> void:
if !gsr.progress_dict.has(levelName):
func set_level_state(level_name: String, property_dict: Dictionary) -> void:
if !gsr.progress_dict.has(level_name):
gsr.progress_dict[level_name] = {}
gsr.progress_dict[level_name]["savestate"] = property_dict
SaveManager.save_default()
func remove_savepoint(level_name: String) -> void:
if !gsr.progress_dict.has(level_name):
return
gsr.progress_dict[levelName].erase("savepoint")
gsr.progress_dict[levelName].erase("uncompletedleveltime")
gsr.progress_dict[level_name].erase("savepoint")
gsr.progress_dict[level_name].erase("uncompletedleveltime")
SaveManager.save_default()
func remove_savestate(level_name: String) -> void:
if !gsr.progress_dict.has(level_name):
return
gsr.progress_dict[level_name].erase("savestate")
SaveManager.save_default()
func get_property_value(levelName: String, propertyName: String) -> int:
if gsr.progress_dict.has(levelName) && gsr.progress_dict[levelName].has(propertyName):
return gsr.progress_dict[levelName][propertyName]
func get_property_value(level_name: String, propertyName: String) -> int:
if gsr.progress_dict.has(level_name) && gsr.progress_dict[level_name].has(propertyName):
return gsr.progress_dict[level_name][propertyName]
else:
return 0
func get_savepoint(levelName: String) -> Vector2:
if gsr.progress_dict.has(levelName) && gsr.progress_dict[levelName].has("savepoint"):
return gsr.progress_dict[levelName]["savepoint"]
func get_savepoint(level_name: String) -> Vector2:
if gsr.progress_dict.has(level_name) && gsr.progress_dict[level_name].has("savepoint"):
return gsr.progress_dict[level_name]["savepoint"]
else:
return Vector2()
func was_level_touched(levelName: String) -> bool:
func get_savestate(level_name: String) -> Dictionary:
if gsr.progress_dict.has(level_name) && gsr.progress_dict[level_name].has("savestate"):
return gsr.progress_dict[level_name]["savestate"]
else:
return {}
func was_level_touched(level_name: String) -> bool:
if OS.is_debug_build():
return true
if !gsr.progress_dict.has(levelName) || !gsr.progress_dict[levelName].has("touched"):
if !gsr.progress_dict.has(level_name) || !gsr.progress_dict[level_name].has("touched"):
return false
return gsr.progress_dict[levelName]["touched"]
return gsr.progress_dict[level_name]["touched"]
func touch_level(levelName: String) -> void:
if !gsr.progress_dict.has(levelName):
gsr.progress_dict[levelName] = {}
gsr.progress_dict[levelName]["touched"] = true
func touch_level(level_name: String) -> void:
if !gsr.progress_dict.has(level_name):
gsr.progress_dict[level_name] = {}
gsr.progress_dict[level_name]["touched"] = true
# TODO This is permanent immediatly

View File

@ -1,9 +1,22 @@
extends Area2D
onready var anim_player: AnimationPlayer = get_node("AnimationPlayer")
onready var level_state := $"%LevelState"
onready var level_state := get_tree().root.get_child(4).get_node("%LevelState")
export var currencyValue: = 1
export onready var was_collected := false
export var currencyValue := 1
var scene_saved_id := 0
func _ready() -> void:
scene_saved_id = level_state.register_saveable_object(self)
var collected_saved = level_state.get_saved_object_property(scene_saved_id, "was_collected")
if collected_saved != null:
was_collected = collected_saved
if was_collected:
collected()
func _on_body_entered(_body: Node) -> void:
if $AudioStreamPlayer.playing:

View File

@ -3,7 +3,7 @@ extends Node2D
onready var level_state := get_tree().root.get_child(4).get_node("%LevelState")
func _ready() -> void:
if(GlobalState.get_savepoint(level_state.levelName) == global_position + Vector2(0,18)):
if(GlobalState.get_savepoint(level_state.level_name) == global_position + Vector2(0,18)):
$Flag.material.set_shader_param("speed", 0.6)
$Flag.material.set_shader_param("amplitude", 1)
$Flag.material.set_shader_param("inclination", 1)
@ -12,7 +12,7 @@ func _ready() -> void:
#TODO What should be saved when reaching a savepoint besides the position in the level
func _on_SaveArea_area_entered(area: Area2D) -> void:
#TODO Spawnheight fixed
if(!GlobalState.get_savepoint(level_state.levelName) == global_position + Vector2(0,18)):
if(!GlobalState.get_savepoint(level_state.level_name) == global_position + Vector2(0,18)):
$AnimationPlayer.play("rolloutflag")
$AudioStreamPlayer.play()
level_state.set_savepoint(global_position + Vector2(0,18))

View File

@ -4,7 +4,6 @@ extends Area2D
onready var anim_player: AnimationPlayer = $AnimationPlayer
onready var level_state := get_tree().root.get_child(4).get_node("%LevelState")
onready var signal_manager := get_tree().root.get_child(4).get_node("%SignalManager")
onready var levelName := get_tree().get_current_scene().get_name()
export(String, FILE, "*.tscn") var next_scene
export(bool) var is_active

View File

@ -2,16 +2,27 @@ extends Node2D
signal button_pushed
onready var level_state := get_tree().root.get_child(4).get_node("%LevelState")
onready var activatorArea = $"%ActivatorArea"
onready var indicatorPlayer = $"%IndicatorPlayer"
onready var signal_manager := get_tree().root.get_child(4).get_node("%SignalManager")
onready var unactivatable_timer := $Timer
export(int) var frog_number := 0
onready var activated := false
var scene_saved_id := 0
var activatable = true
func _ready() -> void:
scene_saved_id = level_state.register_saveable_object(self)
var activated_saved = level_state.get_saved_object_property(scene_saved_id, "activated")
if activated_saved != null:
activated = activated_saved
$Digit.frame = frog_number
if activated:
selfActivate()
func selfActivate():

View File

@ -3,18 +3,38 @@ extends Node2D
onready var activatorArea = $"%ActivatorArea"
onready var indicatorPlayer = $"%IndicatorPlayer"
onready var signal_manager := get_tree().root.get_child(4).get_node("%SignalManager")
onready var level_state := get_tree().root.get_child(4).get_node("%LevelState")
onready var unactivatable_timer := $Timer
var activatable = true
export onready var activated := false
func selfActivate():
var scene_saved_id := 0
var activatable := true
func _ready() -> void:
scene_saved_id = level_state.register_saveable_object(self)
var activated_saved = level_state.get_saved_object_property(scene_saved_id, "activated")
if activated_saved != null:
activated = activated_saved
signal_manager.connect("unlocked", self, "receive_unlock")
if activated:
selfActivate()
func selfActivate() -> void:
indicatorPlayer.play("onning")
#TODO dis importante
activatorArea.set_deferred("monitoring", false)
#TODO Close gate again?
signal_manager.emit_signal("unlocked", "gateblock")
activated = true
func receive_unlock(event: String) -> void:
# TODO For god sake make the events an enum? #debatable #polymorphism
if event == "gateblock":
indicatorPlayer.play("onning")
activated = true
func _on_ActivatorArea_body_entered(body: Node) -> void:
if(!body.is_in_group("player") && !body.is_in_group("frog")):

View File

@ -5,12 +5,23 @@ onready var signal_manager := get_tree().root.get_child(4).get_node("%SignalMana
onready var level_state := get_tree().root.get_child(4).get_node("%LevelState")
onready var blobby := $"../%Blobby"
onready var unactivatable_timer := $Timer
export onready var activated := false
export var cost := 3
var scene_saved_id := 0
var activatable = false
func _ready():
scene_saved_id = level_state.register_saveable_object(self)
var activated_saved = level_state.get_saved_object_property(scene_saved_id, "activated")
if activated_saved != null:
activated = activated_saved
$Sprite.frame = 0
if activated:
selfActivate()
func _process(delta):
# TODO Global currency count?? Maybe just level intern currency
@ -26,6 +37,7 @@ func selfActivate():
$AudioStreamPlayer.play()
#TODO dis importante
activatorArea.set_deferred("monitoring", false)
$Sprite.frame = 1
func _on_ActivatorArea_area_entered(area:Area2D) -> void:
@ -42,4 +54,3 @@ func _on_Timer_timeout():
$Highlight.visible = false
$Label.visible = false
activatable = false
$Sprite.frame = 1

View File

@ -1,15 +1,22 @@
extends Node2D
# Declare member variables here. Examples:
# var a: int = 2
# var b: String = "text"
export var is_armed = false
onready var level_state := get_tree().root.get_child(4).get_node("%LevelState")
export onready var is_armed = false
var trigger_zone_entered: bool = false
var scene_saved_id := 0
var activatable = false
func _ready():
scene_saved_id = level_state.register_saveable_object(self)
var is_armed_saved = level_state.get_saved_object_property(scene_saved_id, "is_armed")
if is_armed_saved != null:
is_armed = is_armed_saved
func _ready() -> void:
if(!is_armed):
if($HarmfulArea.is_in_group("harmful")):
$HarmfulArea.remove_from_group("harmful")

View File

@ -47,7 +47,7 @@ func _ready():
# Zero Vector is false
if level_state.load_savepoint():
parent.global_position = GlobalState.get_savepoint(level_state.levelName)
parent.global_position = GlobalState.get_savepoint(level_state.level_name)
# Calls the parent behaviours according to state

View File

@ -3,6 +3,7 @@ extends AudibleButton
onready var level_state := get_tree().root.get_child(4).get_node("%LevelState")
func _on_button_up() -> void:
GlobalState.remove_savepoint(level_state.levelName)
GlobalState.remove_savepoint(level_state.level_name)
GlobalState.remove_savestate(level_state.level_name)
get_tree().paused = false
get_tree().reload_current_scene()

View File

@ -2,5 +2,6 @@ extends Control
func _ready() -> void:
$"%PlayButton".grab_focus()
GlobalAudio.play_scene_independent("res://assets/music/Shopping For The Future (LOOP).wav","Music", -17, true)
get_tree().paused = false
$"%PlayButton".grab_focus()
GlobalAudio.play_scene_independent("res://assets/music/Shopping For The Future (LOOP).wav","Music", -17, true)

View File

@ -1,7 +1,7 @@
extends Node
onready var signal_manager := get_tree().root.get_child(4).get_node("%SignalManager")
onready var levelName := get_tree().current_scene.filename
onready var level_name := get_tree().current_scene.filename
#TODO Easteregg pls
var currency := 0 setget set_currency, get_currency
@ -12,13 +12,18 @@ var is_dead := false setget set_dead
var level_time := 0.0
var saved_property_dictionary := {}
var saved_object_dictionary := {}
var object_in_scene_id: int = 0
func _ready() -> void:
GlobalState.touch_level(levelName)
GlobalState.gsr.last_played_level = levelName
GlobalState.touch_level(level_name)
GlobalState.gsr.last_played_level = level_name
SaveManager.save_default()
signal_manager.connect("level_completed", self, "_on_level_completed")
signal_manager.connect("player_died", self, "player_dying")
func _physics_process(delta: float) -> void:
level_time += delta
@ -29,8 +34,8 @@ func reset() -> void:
currency = 0
freed_frogs = []
# TODO Maybe not the place for this?
if GlobalState.gsr.progress_dict.has(levelName):
GlobalState.gsr.progress_dict[levelName].erase("savepoint")
if GlobalState.gsr.progress_dict.has(level_name):
GlobalState.gsr.progress_dict[level_name].erase("savepoint")
func set_currency(value: int) -> void:
@ -46,21 +51,58 @@ func set_deaths(value: int) -> void:
func set_dead(value: bool) -> void:
is_dead = value
func get_own_scene_id(obj: Object) -> int:
if "scene_id" in obj && saved_object_dictionary.has(obj.scene_id):
return obj.scene_id
else:
return register_saveable_object(obj)
func register_saveable_object(obj: Object) -> int:
var id = object_in_scene_id
saved_object_dictionary[id] = obj
object_in_scene_id += 1
return id
func save_object_properties() -> void:
for id in saved_object_dictionary.keys():
var object = saved_object_dictionary[id]
var property_list = object.get_property_list()
var saved_properties = {}
for property in property_list:
# Only script Variables and only "primitive" types
if property["usage"] == PROPERTY_USAGE_SCRIPT_VARIABLE && property["type"] <= 16:
saved_properties[property["name"]] = object.get(property["name"])
saved_property_dictionary[id] = saved_properties
func get_saved_object_property(id: int, property: String):
if saved_property_dictionary.has(id) && saved_property_dictionary[id].has(property):
return saved_property_dictionary[id][property]
else:
return null
func set_savepoint(pos: Vector2) -> void:
GlobalState.set_savepoint(levelName, pos)
GlobalState.set_uncompleted_level_time(levelName, level_time)
GlobalState.set_savepoint(level_name, pos)
save_object_properties()
GlobalState.set_uncompleted_level_time(level_name, level_time)
GlobalState.set_level_state(level_name, saved_property_dictionary)
func load_savepoint() -> bool:
if !GlobalState.get_savepoint(levelName):
if !GlobalState.get_savepoint(level_name):
return false
level_time = GlobalState.get_uncompleted_level_time(levelName)
saved_property_dictionary = GlobalState.get_savestate(level_name)
level_time = GlobalState.get_uncompleted_level_time(level_name)
return true
# Registers a new frog which exists in the loaded level, with the progress resource
func register_frog(number: int, freed: bool = false) -> void:
update_global_state()
if !GlobalState.gsr.progress_dict[levelName]["froggies"].has(number):
GlobalState.gsr.progress_dict[levelName]["froggies"][number] = freed
if !GlobalState.gsr.progress_dict[level_name]["froggies"].has(number):
GlobalState.gsr.progress_dict[level_name]["froggies"][number] = freed
GlobalState.save()
@ -96,13 +138,8 @@ func spend_currency(cost: int) -> bool:
return true
if check_balance() < cost:
return false
var remainder = currency - cost
if remainder >= 0:
set_currency(remainder)
# When level collected currency is not enough, deplete saved up currency in global state
else:
currency = 0
GlobalState.set_wallet(GlobalState.gsr.wallet + remainder)
# Can get negative if the cost is greater than what was collected in the same level
currency = currency - cost
return true
func check_balance() -> int:
@ -113,15 +150,17 @@ func _on_level_completed():
#if(OS.is_debug_build()):
# return
# TODO Extra screen for new best time
GlobalState.set_level_completed(levelName, true)
if(GlobalState.get_level_time(levelName) > level_time ):
GlobalState.set_leveltime(levelName, level_time)
GlobalState.set_uncompleted_level_time(levelName, INF)
GlobalState.remove_savepoint(levelName)
GlobalState.set_level_completed(level_name, true)
if(GlobalState.get_level_time(level_name) > level_time ):
GlobalState.set_leveltime(level_name, level_time)
GlobalState.set_uncompleted_level_time(level_name, INF)
GlobalState.remove_savepoint(level_name)
GlobalState.remove_savestate(level_name)
update_global_state()
reset()
# TODO This is now inconsistent as the level_completed property could be used to
# determine what was achieved for good and what progress was lost due to death/reloading
func update_global_state() -> void:
var progress_dict: Dictionary = GlobalState.get_progress()
var levelProgress: Dictionary = {}
@ -130,29 +169,28 @@ func update_global_state() -> void:
levelProgress["deaths"] = deaths
# TODO Doesnt account for multiple plays of same level
if !progress_dict.has(levelName):
progress_dict[levelName] = levelProgress
if !progress_dict.has(level_name):
progress_dict[level_name] = levelProgress
else:
progress_dict[levelName]["currency"] = (
GlobalState.get_property_value(levelName, "currency")
progress_dict[level_name]["currency"] = (
GlobalState.get_property_value(level_name, "currency")
+ currency
)
progress_dict[levelName]["deaths"] = (
GlobalState.get_property_value(levelName, "deaths")
progress_dict[level_name]["deaths"] = (
GlobalState.get_property_value(level_name, "deaths")
+ deaths
)
if !progress_dict[levelName].has("froggies"):
progress_dict[levelName]["froggies"] = {}
else:
if !progress_dict[level_name].has("froggies"):
progress_dict[level_name]["froggies"] = {}
elif GlobalState.get_level_completed(level_name):
for frog_number in freed_frogs:
if progress_dict[levelName]["froggies"].has(frog_number):
progress_dict[levelName]["froggies"][frog_number] = true
if progress_dict[level_name]["froggies"].has(frog_number):
progress_dict[level_name]["froggies"][frog_number] = true
# TODO Wallet is independant from progress_dict because???
GlobalState.set_wallet(GlobalState.gsr.wallet + currency)
GlobalState.set_progress(progress_dict)
func player_dying(animation_number: int = 0) -> void:
currency = 0
is_dead = true

View File

@ -3,4 +3,5 @@
[ext_resource path="res://src/Utilities/LevelState.gd" type="Script" id=1]
[node name="LevelState" type="Node"]
process_priority = -2
script = ExtResource( 1 )