Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ func _ready() -> void:
guard_movement.destination_reached.connect(self._on_destination_reached)
guard_movement.still_time_finished.connect(self._on_still_time_finished)
guard_movement.path_blocked.connect(self._on_path_blocked)
add_to_group("persistence_listeners")
call_deferred("_load_state")


func _process(delta: float) -> void:
Expand Down Expand Up @@ -480,3 +482,45 @@ func _on_detection_area_body_exited(body: Node2D) -> void:
if state == State.DETECTING:
guard_movement.stop_moving()
state = State.INVESTIGATING

## Responds to the checkpoint's activated signal and records patrol parameters.
func _on_checkpoint_activated(checkpoint: Checkpoint) -> void:
if not checkpoint.save_void_and_enemies:
return

if GameState.scene == null or not "facts" in GameState.scene:
return

var save_key := str(get_path())
var data := {
"position": global_position,
"state": state,
"current_idx": current_patrol_point_idx,
"prev_idx": previous_patrol_point_idx
}

if guard_movement:
data["movement_dest"] = guard_movement.destination

GameState.scene.facts[save_key] = data


## Reconstructs the guard's patrol route context upon scene load.
func _load_state() -> void:
if GameState.scene == null or not "facts" in GameState.scene:
return

var save_key := str(get_path())
if GameState.scene.facts.has(save_key):
var data: Dictionary = GameState.scene.facts[save_key]

global_position = data["position"]
if "_last_position" in self:
set("_last_position", data["position"])

state = data["state"]
current_patrol_point_idx = data["current_idx"]
previous_patrol_point_idx = data["prev_idx"]

if guard_movement and data.has("movement_dest"):
guard_movement.set_destination(data["movement_dest"])
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ func _ready() -> void:

for coord: Vector2i in get_used_cells():
consume(coord, true)
add_to_group("persistence_listeners")
_load_state()


## Cover all [param cells] with [member terrain_name], hiding any nodes in those cells which are
Expand Down Expand Up @@ -176,5 +178,27 @@ func uncover_all(duration: float, animate_modulate: bool = false) -> void:

await tween.finished
clear()
if animate_modulate:
self.modulate.a = 1.0
self.modulate.a = 1.0

## Responds to the checkpoint's activated signal and saves consumed tiles if permitted.
func _on_checkpoint_activated(checkpoint: Checkpoint) -> void:
if not checkpoint.save_void_and_enemies:
return

if GameState.scene == null or not "facts" in GameState.scene:
return

var save_key := str(get_path())
GameState.scene.facts[save_key] = get_used_cells()


## Reconstructs the holes in the TileMap immediately on scene load.
## Executes without animation to prevent visual glitches upon respawn.
func _load_state() -> void:
if GameState.scene == null or not "facts" in GameState.scene:
return

var save_key := str(get_path())
if GameState.scene.facts.has(save_key):
var saved_tiles: Array = GameState.scene.facts[save_key]
consume_cells(saved_tiles, true)
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ func _ready() -> void:
idle_patrol_path = idle_patrol_path
state = state
_last_position = position
add_to_group("persistence_listeners")
_load_state()

if Engine.is_editor_hint():
set_process(false)
Expand Down Expand Up @@ -203,3 +205,42 @@ func _on_player_capture_area_body_entered(body: Node2D) -> void:

var player := body as Player
player.defeat(true)


## Responds to the checkpoint's activated signal and records position and path progress.
func _on_checkpoint_activated(checkpoint: Checkpoint) -> void:
if not checkpoint.save_void_and_enemies:
return

if GameState.scene == null or not "facts" in GameState.scene:
return

var save_key := str(get_path())
var data := {
"position": global_position,
"last_position": _last_position,
"state": state
}

if path_walk_behavior:
data["path_progress"] = path_walk_behavior.get("progress_ratio")

GameState.scene.facts[save_key] = data


## Reconstructs the enemy's exact position and path progress on scene load.
## Overrides the internal last_position variable to prevent massive particle bursts caused by teleportation.
func _load_state() -> void:
if GameState.scene == null or not "facts" in GameState.scene:
return

var save_key := str(get_path())
if GameState.scene.facts.has(save_key):
var data: Dictionary = GameState.scene.facts[save_key]

global_position = data["position"]
_last_position = data["last_position"]
state = data["state"]

if path_walk_behavior and data.has("path_progress"):
path_walk_behavior.set("progress_ratio", data["path_progress"])
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
@tool
class_name Checkpoint
extends Area2D
signal activated
## A place where the player respawns if the current scene is reloaded.
##
## A checkpoint is initially invisible. It becomes visible when the player enters the area, which
Expand All @@ -29,6 +30,10 @@ const REQUIRED_ANIMATIONS := [&"idle", &"appear"]
## be able to interact with the checkpoint.
@export var dialogue: DialogueResource = preload("uid://bug2aqd47jgyu")

## Determines if the enemies and the void layer should save their state when
## the player activates this checkpoint.
@export var save_void_and_enemies: bool = false

## The point where the player will spawn.
@onready var spawn_point: SpawnPoint = %SpawnPoint

Expand Down Expand Up @@ -72,6 +77,10 @@ func _ready() -> void:

## Makes this the active checkpoint.
func activate() -> void:
for listener: Node in get_tree().get_nodes_in_group("persistence_listeners"):
if listener.has_method("_on_checkpoint_activated") and not activated.is_connected(listener._on_checkpoint_activated):
activated.connect(listener._on_checkpoint_activated)
activated.emit(self)
GameState.scene.spawn_point = owner.get_path_to(spawn_point)
GameState.save()

Expand Down
3 changes: 3 additions & 0 deletions scenes/globals/game_state/per_scene_state.gd
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ signal lights_changed(lights_on: bool, immediate: bool)
## Set when any introductory dialogue has been played for the current scene.
@export var intro_dialogue_shown: bool

## Dictionary to store arbitrary persistent scene facts, such as enemy states and consumed tiles.
@export var facts: Dictionary = {}

## Current state of artificial lights. Set with [member set_lights_on]. This is
## not saved to disk since the lights are controlled programmatically.
var lights_on: bool
Expand Down