Skip to content
Draft
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 @@ -86,9 +86,9 @@ func _before_dialogue() -> void:
func _on_interaction_ended() -> void:
if chosen_quest:
interact_area.disabled = true
GameState.start_quest(chosen_quest)
var target := GameState.start_quest(chosen_quest)
SceneSwitcher.change_to_file_with_transition(
chosen_quest.first_scene, ^"", Transition.Effect.FADE, Transition.Effect.FADE
target.scene_path, target.spawn_point, Transition.Effect.FADE, Transition.Effect.FADE
)
chosen_quest = null

Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,16 @@
# SPDX-FileCopyrightText: The Threadbare Authors
# SPDX-License-Identifier: MPL-2.0
~ start
LoreQuest Elder: [[Hi|Hello|Greetings]], StoryWeaver. We've been waiting for you. Our world is unraveling!
LoreQuest Elder: We need you to recover the Sacred Elements: the threads of Memory, Imagination, and Spirit! Will you help us?
do show_storybook()
if chosen_quest == null:
% LoreQuest Elder: Please stay. Our world will perish without your help!
% LoreQuest Elder: I hope to see you again. We really need you!
% LoreQuest Elder: The people of Threadbare are counting on you. I hope you change your mind!
elif chosen_quest.resource_path in GameState.completed_quests:
# Custom dialogues for completed quests
if chosen_quest.resource_path == "res://scenes/quests/lore_quests/quest_001/quest.tres":
% LoreQuest Elder: Ah, the Song Sanctuary sings once more... a melody you already helped restore.
% LoreQuest Elder: If you wish to revisit its echoes, traveler, the sanctuary always welcomes a gentle hand to tune its harmony again.

elif chosen_quest.resource_path == "res://scenes/quests/lore_quests/quest_002/quest.tres":
% LoreQuest Elder: The Void still stirs at the edges… even after the balance you once brought.
% LoreQuest Elder: Should you feel ready to face it again, your presence can steady the archipelago’s stories once more.

elif chosen_quest.resource_path == "res://scenes/quests/lore_quests/quest_003/quest.tres":
% LoreQuest Elder: You’ve walked this path before—where Imagination and Spirit intertwine.
% LoreQuest Elder: If you seek its lessons again, the Loom is always willing to be rewoven by steady hands.

else:
% LoreQuest Elder: This tale is one you already helped mend, traveler… yet its threads shift with each return.
% LoreQuest Elder: If your heart pulls you back to it, the story will open itself to you once again.
if not ("res://scenes/quests/lore_quests/quest_001/quest.tres" in GameState.completed_quests):
LoreQuest Elder: [[Hi|Hello|Greetings]], StoryWeaver. We've been waiting for you. Our world is unraveling!
LoreQuest Elder: We need you to recover the Sacred Elements: the threads of Memory, Imagination, and Spirit! Will you help us?
LoreQuest Elder: Seek out the musician. He lives in the Song Sanctuary.
elif not ("res://scenes/quests/lore_quests/quest_002/quest.tres" in GameState.completed_quests):
LoreQuest Elder: Hello again, StoryWeaver. Have you encountered the Void?
LoreQuest Elder: You must find Moss the Monk. She lives on an island outside Linenville.
else:
LoreQuest Elder: We believe in you, StoryWeaver.
LoreQuest Elder: OK, I'm out of ideas now. Maybe you can still find some stuff to do.
LoreQuest Elder: We believe in you, StoryWeaver.
=> END
~ go_to_loom
LoreQuest Elder: Go on, StoryWeaver: take the threads to the Eternal Loom.
Expand Down
88 changes: 51 additions & 37 deletions scenes/globals/game_state/game_state.gd
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ signal completed_quests_changed
const GAME_STATE_PATH := "user://game_state.cfg"
const INVENTORY_SECTION := "inventory"
const INVENTORY_ITEMS_KEY := "items_collected"
const QUEST_SECTION := "quest"
const QUEST_PATH_KEY := "resource_path"
const QUEST_CURRENTSCENE_KEY := "current_scene"
const QUEST_SPAWNPOINT_KEY := "current_spawn_point"
const QUEST_CHALLENGE_START_KEY := "challenge_start_scene"
const QUEST_ABANDON_SCENE_KEY := "abandon_scene"
const QUEST_ABANDON_SPAWNPOINT_KEY := "abandon_spawn_point"
const GLOBAL_SECTION := "global"
const GLOBAL_INCORPORATING_THREADS_KEY := "incorporating_threads"
const COMPLETED_QUESTS_KEY := "completed_quests"
const QUEST_PATH_KEY := "quest_path"
const CURRENTSCENE_KEY := "current_scene"
const SPAWNPOINT_KEY := "current_spawn_point"
const LIVES_KEY := "current_lives"
const MAX_LIVES := 2 ** 53
const DEBUG_LIVES := false
Expand Down Expand Up @@ -108,20 +109,29 @@ func set_incorporating_threads(new_incorporating_threads: bool) -> void:

## Set [member current_quest] and clear the [member inventory].
## Also resets lives to maximum when starting a quest.
func start_quest(quest: Quest) -> void:
func start_quest(
quest: Quest,
abandon_scene: String = "",
abandon_spawn_point: NodePath = ^"",
) -> Dictionary:
_do_clear_inventory()
_update_inventory_state()
current_quest = quest
_state.set_value(QUEST_SECTION, QUEST_PATH_KEY, quest.resource_path)
_do_set_scene(quest.first_scene, ^"")
_state.set_value(GLOBAL_SECTION, QUEST_PATH_KEY, quest.resource_path)

# Set the challenge start scene to the first scene of the quest
_state.set_value(QUEST_SECTION, QUEST_CHALLENGE_START_KEY, quest.first_scene)
_state.set_value(quest.resource_path, QUEST_ABANDON_SCENE_KEY, abandon_scene)
_state.set_value(quest.resource_path, QUEST_ABANDON_SPAWNPOINT_KEY, abandon_spawn_point)

# Reset lives when starting a new quest
reset_lives()
_save()

var ret := {
"scene_path": _state.get_value(quest.resource_path, CURRENTSCENE_KEY, quest.first_scene),
"spawn_point": _state.get_value(quest.resource_path, SPAWNPOINT_KEY, ^""),
}
return ret


## Guess which quest the given scene is part of, and set [member current_quest]
## accordingly. If the quest cannot be determined, unset [member current_quest].
Expand Down Expand Up @@ -156,35 +166,29 @@ func set_scene(scene_path: String, spawn_point: NodePath = ^"") -> void:
## Set the current spawn point and save it.
func set_current_spawn_point(spawn_point: NodePath = ^"") -> void:
current_spawn_point = spawn_point
_state.set_value(QUEST_SECTION, QUEST_SPAWNPOINT_KEY, current_spawn_point)
_state.set_value(GLOBAL_SECTION, SPAWNPOINT_KEY, current_spawn_point)
if current_quest:
_state.set_value(current_quest.resource_path, SPAWNPOINT_KEY, current_spawn_point)
_save()


## Set the challenge start scene. This is the scene the player returns to
## when they run out of lives.
func set_challenge_start_scene(scene_path: String) -> void:
_state.set_value(QUEST_SECTION, QUEST_CHALLENGE_START_KEY, scene_path)

if DEBUG_LIVES:
prints("[LIVES DEBUG] Challenge start set to:", scene_path)

_save()
if current_quest:
_state.set_value(current_quest.resource_path, QUEST_CHALLENGE_START_KEY, scene_path)
_save()


## Get the challenge start scene, or the first scene of the current quest
## if no challenge start has been set.
func get_challenge_start_scene() -> String:
var challenge_start: String = _state.get_value(QUEST_SECTION, QUEST_CHALLENGE_START_KEY, "")

if challenge_start.is_empty() and current_quest:
challenge_start = current_quest.first_scene

if DEBUG_LIVES:
prints(
"[LIVES DEBUG] No challenge start set, using quest first scene:", challenge_start
)
if not current_quest:
return ""

return challenge_start
return _state.get_value(
current_quest.resource_path, QUEST_CHALLENGE_START_KEY, current_quest.first_scene
)


## Returns [code]true[/code] if the player is currently on a quest; i.e. if
Expand All @@ -195,8 +199,8 @@ func is_on_quest() -> bool:

## Clear all quest-related state from the config file.
func _clear_quest_state() -> void:
if _state.has_section(QUEST_SECTION):
_state.erase_section(QUEST_SECTION)
if _state.has_section_key(GLOBAL_SECTION, QUEST_PATH_KEY):
_state.erase_section_key(GLOBAL_SECTION, QUEST_PATH_KEY)


## If [member current_quest] is set, record this quest as having been completed,
Expand All @@ -215,8 +219,11 @@ func _do_set_scene(scene_path: String, spawn_point: NodePath = ^"") -> void:
intro_dialogue_shown = false

current_spawn_point = spawn_point
_state.set_value(QUEST_SECTION, QUEST_CURRENTSCENE_KEY, scene_path)
_state.set_value(QUEST_SECTION, QUEST_SPAWNPOINT_KEY, current_spawn_point)
_state.set_value(GLOBAL_SECTION, CURRENTSCENE_KEY, scene_path)
_state.set_value(GLOBAL_SECTION, SPAWNPOINT_KEY, current_spawn_point)
if current_quest:
_state.set_value(current_quest.resource_path, CURRENTSCENE_KEY, scene_path)
_state.set_value(current_quest.resource_path, SPAWNPOINT_KEY, current_spawn_point)


## Add the [InventoryItem] to the [member inventory].
Expand All @@ -230,11 +237,18 @@ func add_collected_item(item: InventoryItem) -> void:

## If [member current_quest] is set, unset it, without recording the quest as
## having been completed. Also resets lives to maximum.
func abandon_quest() -> void:
func abandon_quest() -> Dictionary:
assert(current_quest)
var ret := {
"scene_path": _state.get_value(current_quest.resource_path, QUEST_ABANDON_SCENE_KEY, ""),
"spawn_point":
_state.get_value(current_quest.resource_path, QUEST_ABANDON_SPAWNPOINT_KEY, ^""),
}
set_incorporating_threads(false)
_clear_quest_state()
current_quest = null
clear_inventory()
return ret


## Updates [member completed_quests] to include [param quest] if [param
Expand Down Expand Up @@ -341,12 +355,12 @@ func clear() -> void:

## Check if there is persisted state.
func can_restore() -> bool:
return _state.get_sections().size()
return get_scene_to_restore() != ""


## If there is a scene to restore, return it.
func get_scene_to_restore() -> String:
return _state.get_value(QUEST_SECTION, QUEST_CURRENTSCENE_KEY, "")
return _state.get_value(GLOBAL_SECTION, CURRENTSCENE_KEY, "")


## Restore the persisted state.
Expand All @@ -358,11 +372,11 @@ func restore() -> Dictionary:
var item := InventoryItem.with_type(item_type)
inventory.append(item)

if _state.has_section_key(QUEST_SECTION, QUEST_PATH_KEY):
current_quest = load(_state.get_value(QUEST_SECTION, QUEST_PATH_KEY)) as Quest
if _state.has_section_key(GLOBAL_SECTION, QUEST_PATH_KEY):
current_quest = load(_state.get_value(GLOBAL_SECTION, QUEST_PATH_KEY)) as Quest

var scene_path: String = _state.get_value(QUEST_SECTION, QUEST_CURRENTSCENE_KEY, "")
current_spawn_point = _state.get_value(QUEST_SECTION, QUEST_SPAWNPOINT_KEY, ^"")
var scene_path: String = _state.get_value(GLOBAL_SECTION, CURRENTSCENE_KEY, "")
current_spawn_point = _state.get_value(GLOBAL_SECTION, SPAWNPOINT_KEY, ^"")
incorporating_threads = _state.get_value(
GLOBAL_SECTION, GLOBAL_INCORPORATING_THREADS_KEY, false
)
Expand Down
7 changes: 5 additions & 2 deletions scenes/globals/pause/pause_overlay.gd
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,12 @@ func toggle_pause() -> void:

func _on_abandon_quest_pressed() -> void:
toggle_pause()
GameState.abandon_quest()
var abandon_target := GameState.abandon_quest()
var scene_path: String = abandon_target.get("scene_path", frays_end)
var spawn_point: NodePath = abandon_target.get("spawn_point", ^"")

SceneSwitcher.change_to_file_with_transition(
frays_end, ^"", Transition.Effect.FADE, Transition.Effect.FADE
scene_path, spawn_point, Transition.Effect.FADE, Transition.Effect.FADE
)


Expand Down
8 changes: 8 additions & 0 deletions scenes/globals/scene_switcher/scene_switcher.gd
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ func change_to_file_with_transition(
enter_transition: Transition.Effect = Transition.Effect.RIGHT_TO_LEFT_WIPE,
exit_transition: Transition.Effect = Transition.Effect.LEFT_TO_RIGHT_WIPE
) -> void:
assert(scene_path != "")

var err := ResourceLoader.load_threaded_request(scene_path)
if err != OK:
push_error("Failed to start loading %s: %s" % [scene_path, error_string(err)])
Expand All @@ -122,6 +124,8 @@ func change_to_packed_with_transition(
enter_transition: Transition.Effect = Transition.Effect.RIGHT_TO_LEFT_WIPE,
exit_transition: Transition.Effect = Transition.Effect.LEFT_TO_RIGHT_WIPE
) -> void:
assert(scene != null)

Transitions.do_transition(
change_to_packed.bind(scene, spawn_point), enter_transition, exit_transition
)
Expand All @@ -135,12 +139,16 @@ func reload_with_transition(


func change_to_file(scene_path: String, spawn_point: NodePath = ^"") -> void:
assert(scene_path != "")

var scene: PackedScene = load(scene_path)
if scene:
change_to_packed(scene, spawn_point)


func change_to_packed(scene: PackedScene, spawn_point: NodePath = ^"") -> void:
assert(scene != null)

GameState.clear_per_scene_state()

if get_tree().change_scene_to_packed(scene) == OK:
Expand Down
2 changes: 1 addition & 1 deletion scenes/menus/title/components/main_menu.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[ext_resource type="Script" uid="uid://bkl8j1as8ylag" path="res://scenes/menus/title/components/main_menu.gd" id="1_xuf5f"]
[ext_resource type="Script" uid="uid://dfx8s2ybd11mt" path="res://scenes/menus/storybook/components/animated_texture_rect.gd" id="4_rpgwp"]
[ext_resource type="Script" uid="uid://i4urwefxrrdt" path="res://scenes/menus/title/components/version_label.gd" id="4_snvmp"]
[ext_resource type="SpriteFrames" uid="uid://bg538lufloka6" path="res://scenes/menus/title/components/threadbare_logo_animation.tres" id="5_rpgwp"]
[ext_resource type="SpriteFrames" uid="uid://bg538lufloka6" path="res://scenes/menus/title/components/logos/threadbare_logo_animation.tres" id="5_rpgwp"]

[node name="MainMenu" type="Control" unique_id=1449128276]
layout_mode = 3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
[ext_resource type="PackedScene" uid="uid://dv4f232y8w8dv" path="res://scenes/game_elements/props/decoration/water_rock/water_rock.tscn" id="28_oo13r"]
[ext_resource type="PackedScene" uid="uid://cfcgrfvtn04yp" path="res://scenes/ui_elements/hud/hud.tscn" id="29_gbo6l"]
[ext_resource type="PackedScene" uid="uid://cf3cbl12wt6vr" path="res://scenes/game_elements/props/buildings/temple/temple.tscn" id="37_kgem8"]
[ext_resource type="Script" uid="uid://hqdquinbimce" path="res://scenes/game_elements/props/teleporter/teleporter.gd" id="39_5y4tu"]

[sub_resource type="NavigationPolygon" id="NavigationPolygon_05yrb"]
vertices = PackedVector2Array(2927.9531, 1448.9609, 2920.039, 3273.8203, -1078, 3206.1563, -1078, 1433.0313)
Expand All @@ -61,6 +62,9 @@ script = ExtResource("23_amu0w")
name = "Memory"
metadata/_custom_type_script = "uid://bgmwplmj3bfls"

[sub_resource type="RectangleShape2D" id="RectangleShape2D_7xlw5"]
size = Vector2(151, 167)

[node name="VoidRunner" type="Node2D" unique_id=506997450]
metadata/_edit_lock_ = true

Expand Down Expand Up @@ -152,7 +156,7 @@ shape = SubResource("RectangleShape2D_kgem8")
y_sort_enabled = true

[node name="Player" parent="OnTheGround" unique_id=1051216917 instance=ExtResource("4_85y51")]
position = Vector2(38, 2855)
position = Vector2(-864, 2917)
player_name = "StoryWeaver"
sprite_frames = ExtResource("5_46vkj")

Expand Down Expand Up @@ -1175,5 +1179,17 @@ position = Vector2(2461, 2732)

[node name="HUD" parent="." unique_id=1953354605 instance=ExtResource("29_gbo6l")]

[node name="OverworldTeleporter" type="Area2D" parent="." unique_id=1303186387]
collision_layer = 4
script = ExtResource("39_5y4tu")
next_scene = "uid://ds7q0lkarf2q5"
spawn_point_path = NodePath("TeleportersAndSpawnPoints/Temple")
exit_transition = 5
metadata/_custom_type_script = "uid://hqdquinbimce"

[node name="CollisionShape2D" type="CollisionShape2D" parent="OverworldTeleporter" unique_id=1819198331]
position = Vector2(-1013, 3047.5)
shape = SubResource("RectangleShape2D_7xlw5")

[connection signal="body_entered" from="EnemyTrigger" to="OnTheGround/VoidSpreadingEnemy" method="start" flags=6]
[connection signal="body_entered" from="GoalReachedTrigger" to="OnTheGround/VoidSpreadingEnemy" method="defeat" flags=6 unbinds=1]
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,8 @@ stream = ExtResource("2_wfapt")
script = ExtResource("4_wfapt")
dialogue = ExtResource("5_nphcy")
animation_player = NodePath("../AnimationPlayer")
next_scene = "uid://cufkthb25mpxy"
next_scene = "uid://4brs22b6le5p"
spawn_point_path = "SpawnPoints/Linenville"
metadata/_custom_type_script = "uid://x1mxt6bmei2o"

[node name="TileMapLayers" type="Node2D" parent="." unique_id=1076311957]
Expand Down
11 changes: 11 additions & 0 deletions scenes/world_map/components/bridget.dialogue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# SPDX-FileCopyrightText: The Threadbare Authors
# SPDX-License-Identifier: MPL-2.0
~ start
# She's called Bridget because she's by a bridge.
Bridget: This bridge has been broken for ages.
Bridget: The musician would say that it’s because we stopped singing the old songs.
Bridget: Have you met him? He lives north of here.
% Bridget: Maybe you can help him put this place back together.
% Bridget: I still can’t figure out how he plays that triple-necked guitar. He only has two hands!
% Bridget: To be honest, I blame him. He stopped turning up for our weekly singalongs.
=> END
16 changes: 16 additions & 0 deletions scenes/world_map/components/bridget.dialogue.import
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[remap]

importer="dialogue_manager"
importer_version=15
type="Resource"
uid="uid://dy14woa33pg8m"
path="res://.godot/imported/bridget.dialogue-ceb3cc9bbaead3839e5d86ff4316d56e.tres"

[deps]

source_file="res://scenes/world_map/components/bridget.dialogue"
dest_files=["res://.godot/imported/bridget.dialogue-ceb3cc9bbaead3839e5d86ff4316d56e.tres"]

[params]

defaults=true
Loading
Loading