From 2986da542acc2a4228a36b797ca93d246187f58b Mon Sep 17 00:00:00 2001 From: Will Thompson Date: Fri, 31 Jul 2026 17:46:06 +0100 Subject: [PATCH] =?UTF-8?q?Add=20scripts=20to=20make=20a=20pruned=20?= =?UTF-8?q?=E2=80=9CStoryQuest=20Kit=E2=80=9D=20bundle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rather than having Core: Threadbare participants start from a fork of the entire repo, we want to try giving participants a stripped-down copy of the project - specifically with all LoreQuests and other StoryQuests removed. The goals are manifold: - Reduce the cognitive burden of navigating a large project. - Reduce the need to take special steps to deep link to a StoryQuest for testing: the team's StoryQuest should be launched from the title screen. - Reduce the number and size of the files in the project, saving electrons and helping with performance in Backstitch. - Move us towards a model where rather than expecting that all storyquests will be merged upstream (requiring learners to take special care to only modify files in their folder, increasing complexity; and hitting human scalability cliffs in the form of there only being two full-time maintainers of the game reviewing StoryQuest submissions) we instead treat them more as stand-alone games. For the ones we choose to merge upstream, we can do the necessary work to confine them to their own folder at that point; and you could imagine adding a way to launch a separately-downloaded .pck for a StoryQuest. As well as removing the lore_quests and story_quests folders, we also have to remove the world_map folder (i.e. Fray's End and connected scenes) because these scenes now link directly to LoreQuests. I also decided to remove the non-CC0 Tiny Swords free pack, which we have special permission to redistribute but which is not used outside of one StoryQuest. This significantly reduces the number of files in the project, which is helpful for Backstitch. Having removed these files, we adjust the project settings so that the quest launched from the title screen is the NO_EDIT template, and the scene to return to when a quest is completed or abandoned is Dev Archipelago. In Dev Archipelago, add the Template Elder so that the NO_EDIT template can be accessed. Add the StoryQuest Elder too, because otherwise the team's SQ can't be played once completed or abandoned without erasing the game state, but remove him in normal versions. Add an alternative broken version of the bridge back to Fray's End which blocks access to that teleporter. Reconfigure that teleporter to point to the home scene rather than explicitly to Fray's End so that if it is reached it is a no-op. One approach would be to have learners modify the NO_EDIT template in-place, in which case we would want to rename all those files and delete the bootstrap tool. For now I just updated the StoryQuest bootstrap tool to change the initial quest to the newly-duplicated one. I checked for references into these folders from elsewhere in the game as follows: git grep -E \ '(lore_quests|story_quests|world_map|tiny-swords-non-cc0|uid://cufkthb25mpxy)' \ ':!scenes/quests/lore_quests' \ ':!scenes/quests/story_quests' \ ':!scenes/world_map' \ ':!assets/third_party/tiny-swords-non-cc0' In a perfect world there would be no output from this command. There are some lingering references: for example, the elders' dialogue references the paths to various `quest.tres` resources. However there are (as far as I can tell) no references that will cause a scene or resource to fail to load. (This command is imperfect because, for example, teleporters refer to other scenes by their UID stored as a string; that's why Fray's End's uid is in the command above.) Creating the bundle is implemented with 2-3 scripts: 1. A GDScript script which deletes files and adjusts project settings, which is most easily done using Godot's own APIs (and reuses some preexisting logic). 2. A shell script which drives the GDScript script, then does some additional stuff (downloading the backstitch launcher, running Git commands) which is more easily accomplished in shell. 3. A GitHub Actions workflow which drives the shell script and uploads the artifact. I couldn't resist calling the script StoryQuest Construction Kit Construction Kit (SQCKCK for short) but this is a placeholder name! Resolves https://github.com/endlessm/threadbare/issues/2563 --- .github/workflows/export.yml | 4 +- .github/workflows/make-storyquest-kit.yml | 70 ++++++++++++++++++++ addons/storyquest_bootstrap/copier.gd | 11 ++- addons/threadbare_git_describe/export.gd | 10 ++- scenes/dev/components/dev_archipelago.gd | 31 +++++++++ scenes/dev/components/dev_archipelago.gd.uid | 1 + scenes/dev/dev_archipelago.tscn | 60 ++++++++++++++++- tools/sqckck.gd | 50 ++++++++++++++ tools/sqckck.gd.uid | 1 + tools/sqckck.sh | 40 +++++++++++ 10 files changed, 269 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/make-storyquest-kit.yml create mode 100644 scenes/dev/components/dev_archipelago.gd create mode 100644 scenes/dev/components/dev_archipelago.gd.uid create mode 100644 tools/sqckck.gd create mode 100644 tools/sqckck.gd.uid create mode 100755 tools/sqckck.sh diff --git a/.github/workflows/export.yml b/.github/workflows/export.yml index 651231230..9e264e20e 100644 --- a/.github/workflows/export.yml +++ b/.github/workflows/export.yml @@ -42,7 +42,7 @@ jobs: - uses: ./.github/actions/lfs-cached-checkout - name: Export web build - uses: endlessm/godot-export-action@v2 + uses: endlessm/godot-export-action@v3 with: godot_version: ${{ env.GODOT_VERSION }} export_preset: "Web" @@ -89,7 +89,7 @@ jobs: - uses: ./.github/actions/lfs-cached-checkout - name: Export Windows build - uses: endlessm/godot-export-action@v2 + uses: endlessm/godot-export-action@v3 with: godot_version: ${{ env.GODOT_VERSION }} export_preset: "Windows x86_64" diff --git a/.github/workflows/make-storyquest-kit.yml b/.github/workflows/make-storyquest-kit.yml new file mode 100644 index 000000000..e69a6eb69 --- /dev/null +++ b/.github/workflows/make-storyquest-kit.yml @@ -0,0 +1,70 @@ +# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +name: "Make StoryQuest Kit" +on: + workflow_dispatch: + pull_request: + paths: + - 'tools/sqckck.*' + - '.github/workflows/make-storyquest-kit.yml' + release: + types: + - published + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + +env: + GODOT_VERSION: "4.7.1" + +jobs: + make-storyquest-kit: + name: Make StoryQuest Kit + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + # Fetch full history of commits and tags so that git describe --tags works + fetch-depth: 0 + + - uses: ./.github/actions/lfs-cached-checkout + + - name: Install Godot + uses: endlessm/godot-export-action/download-godot@v3 + with: + godot_version: ${{ env.GODOT_VERSION }} + + - name: Restore .godot directory cache + uses: actions/cache/restore@v6 + with: + path: | + .godot + key: ${{ runner.os }}-dotgodot-${{ inputs.godot_version }}.${{ inputs.godot_flavor }}-${{ hashFiles('**/*.import') }} + restore-keys: | + ${{ runner.os }}-dotgodot-${{ inputs.godot_version }}.${{ inputs.godot_flavor }}- + + - name: Make StoryQuest Kit + id: sqckck + run: ./tools/sqckck.sh + env: + GODOT: build/godot + GIT_AUTHOR_NAME: Threadbare StoryQuest Kit Maker + GIT_COMMITTER_NAME: Threadbare StoryQuest Kit Maker + GIT_AUTHOR_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com + + - name: Upload kit artifact + uses: actions/upload-artifact@v7 + with: + path: ${{ steps.sqckck.outputs.zip_file }} + archive: false + + - name: Attach to release + if: ${{ github.event_name == 'release' }} + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + set -ex + gh release upload '${{ github.ref_name }}' "${{ steps.sqckck.outputs.zip_file }}" --repo '${{ github.repository }}' diff --git a/addons/storyquest_bootstrap/copier.gd b/addons/storyquest_bootstrap/copier.gd index e420fbec0..6182a660e 100644 --- a/addons/storyquest_bootstrap/copier.gd +++ b/addons/storyquest_bootstrap/copier.gd @@ -263,5 +263,14 @@ func create_storyquest() -> void: copy_tilesets() var quest: StoryQuest = load(TEMPLATE_PATH.path_join(QUEST_FILENAME)) - await copy_resource(quest) + var copied: StoryQuest = await copy_resource(quest) EditorInterface.save_all_scenes() + + var opening_quest := ThreadbareProjectSettings.get_setting( + ThreadbareProjectSettings.OPENING_QUEST + ) + if ResourceUID.ensure_path(opening_quest) == ResourceUID.ensure_path(quest.resource_path): + ProjectSettings.set( + ThreadbareProjectSettings.OPENING_QUEST, ResourceUID.path_to_uid(copied.resource_path) + ) + ProjectSettings.save() diff --git a/addons/threadbare_git_describe/export.gd b/addons/threadbare_git_describe/export.gd index 89e605715..2a9be0dff 100644 --- a/addons/threadbare_git_describe/export.gd +++ b/addons/threadbare_git_describe/export.gd @@ -9,12 +9,16 @@ func _get_name() -> String: return "threadbare_git_describe_exporter" -func _export_begin( - _features: PackedStringArray, _is_debug: bool, _path: String, _flags: int -) -> void: +static func set_versions() -> void: var describe := Version.git_describe(true) ProjectSettings.set_setting(Version.FULL_VERSION_KEY, describe) ProjectSettings.set_setting(Version.VERSION_KEY, Version.simplify(describe)) + + +func _export_begin( + _features: PackedStringArray, _is_debug: bool, _path: String, _flags: int +) -> void: + set_versions() var err := ProjectSettings.save() if err != OK: printerr("Failed to save project settings: %s" % error_string(err)) diff --git a/scenes/dev/components/dev_archipelago.gd b/scenes/dev/components/dev_archipelago.gd new file mode 100644 index 000000000..0abddafaa --- /dev/null +++ b/scenes/dev/components/dev_archipelago.gd @@ -0,0 +1,31 @@ +# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +extends Node2D + +@onready var bridge_to_frays_end: ToggleableTileMapLayer = %BridgeToFraysEnd +@onready var bridge_to_frays_end_broken: ToggleableTileMapLayer = %BridgeToFraysEndBroken +@onready var story_quest_elder: Elder = %StoryQuestElder + + +func _ready() -> void: + # In the stripped-back StoryQuest kit, this is the home scene and Fray's End + # is not present. + var home_scene: String = ThreadbareProjectSettings.get_setting( + ThreadbareProjectSettings.HOME_SCENE + ) + var is_storyquest_kit := ResourceUID.ensure_path(home_scene) == scene_file_path + + # Conditionally block the route back to Fray's End + bridge_to_frays_end.set_toggled(is_storyquest_kit) + bridge_to_frays_end_broken.set_toggled(not is_storyquest_kit) + + if is_storyquest_kit and GameState.quest: + # The player has returned here after completing a quest. Silently mark + # it as completed. + GameState.mark_quest_completed() + GameState.save() + + if not is_storyquest_kit: + # Don't need the StoryQuest Elder here in the normal project. + story_quest_elder.queue_free() + story_quest_elder = null diff --git a/scenes/dev/components/dev_archipelago.gd.uid b/scenes/dev/components/dev_archipelago.gd.uid new file mode 100644 index 000000000..de551609c --- /dev/null +++ b/scenes/dev/components/dev_archipelago.gd.uid @@ -0,0 +1 @@ +uid://db0hjoby7su6n diff --git a/scenes/dev/dev_archipelago.tscn b/scenes/dev/dev_archipelago.tscn index 930a4caab..22f9468d1 100644 --- a/scenes/dev/dev_archipelago.tscn +++ b/scenes/dev/dev_archipelago.tscn @@ -1,23 +1,29 @@ [gd_scene format=4 uid="uid://bhdpwut7lgfnm"] +[ext_resource type="Script" uid="uid://db0hjoby7su6n" path="res://scenes/dev/components/dev_archipelago.gd" id="1_kt7u4"] [ext_resource type="TileSet" uid="uid://oynx002hv8tl" path="res://tiles/water.tres" id="1_rqtu3"] [ext_resource type="TileSet" uid="uid://bjx3gvah0ycl1" path="res://tiles/foam_2.tres" id="2_jo5uf"] [ext_resource type="TileSet" uid="uid://b778cuoftt88r" path="res://tiles/elevation_2.tres" id="3_tq71w"] [ext_resource type="TileSet" uid="uid://b8qnr0owsbhhn" path="res://tiles/exterior_floors.tres" id="4_pr8dh"] [ext_resource type="TileSet" uid="uid://do0ffypatd77h" path="res://tiles/bridges.tres" id="5_onjm3"] [ext_resource type="TileSet" uid="uid://41pk6xbhypue" path="res://tiles/fence.tres" id="6_qihih"] +[ext_resource type="Script" uid="uid://c5jp8y8mpge4w" path="res://scenes/game_elements/props/toggleable_tile_map_layer/toggleable_tile_map_layer.gd" id="7_elr7o"] [ext_resource type="PackedScene" uid="uid://0ull24fvmhwk" path="res://scenes/game_elements/props/teleporter/teleporter.tscn" id="7_xgiso"] [ext_resource type="PackedScene" uid="uid://iu2q66clupc6" path="res://scenes/game_elements/characters/player/player.tscn" id="8_oxwdn"] [ext_resource type="SpriteFrames" uid="uid://dtoylirwywk0j" path="res://scenes/game_elements/characters/components/sprite_frames/storyweaver_blue.tres" id="9_rl866"] [ext_resource type="PackedScene" uid="uid://covsdqqsd6rsy" path="res://scenes/game_elements/props/sign/sign.tscn" id="10_ee0kp"] [ext_resource type="PackedScene" uid="uid://vb5o7hh5an8j" path="res://scenes/game_elements/characters/npcs/elder/template_elder.tscn" id="11_ui3i3"] [ext_resource type="Resource" uid="uid://cjo0b4vsho0yh" path="res://scenes/dev/components/dev_archipelago_basics_elder.dialogue" id="12_dmgeu"] +[ext_resource type="PackedScene" uid="uid://v2uakefmp67y" path="res://scenes/game_elements/characters/npcs/elder/story_quest_elder.tscn" id="13_0jnxm"] +[ext_resource type="PackedScene" uid="uid://ccp4ayow34e7g" path="res://scenes/game_elements/props/decoration/books/books.tscn" id="13_1y87v"] [ext_resource type="Resource" uid="uid://b8afvr4fcfx0t" path="res://scenes/dev/components/dev_archipelago_repel_elder.dialogue" id="13_o15st"] +[ext_resource type="Texture2D" uid="uid://cyvi71ryavsld" path="res://scenes/game_elements/props/decoration/books/components/Books_2.png" id="14_2qj7x"] [ext_resource type="Resource" uid="uid://cmk5th3xqyb5j" path="res://scenes/dev/components/dev_archipelago_grapple_elder.dialogue" id="14_jm555"] [ext_resource type="Script" uid="uid://0enyu5v4ra34" path="res://scenes/game_elements/props/spawn_point/components/spawn_point.gd" id="14_jo5uf"] [ext_resource type="PackedScene" uid="uid://djmdhrse34vgq" path="res://scenes/game_elements/props/decoration/bunny/bunny.tscn" id="15_cx2s0"] [ext_resource type="PackedScene" uid="uid://crqjcicx0vdu" path="res://scenes/game_elements/props/decoration/bush/bush.tscn" id="16_0w3ee"] [ext_resource type="SpriteFrames" uid="uid://b6doys1c3yelx" path="res://scenes/game_elements/props/decoration/bush/components/bush_spriteframes_red_small.tres" id="17_6pvli"] +[ext_resource type="PackedScene" uid="uid://d0c4l7ev6ca3c" path="res://scenes/game_elements/props/spawn_point/spawn_point.tscn" id="18_elr7o"] [ext_resource type="PackedScene" uid="uid://dttyvh6kyru5y" path="res://scenes/game_elements/props/decoration/butterfly/butterfly.tscn" id="18_k0jlm"] [ext_resource type="SpriteFrames" uid="uid://b81ibkdhsfthe" path="res://scenes/game_elements/props/decoration/butterfly/components/butterfly_flower_loop_1_red.tres" id="19_0logk"] [ext_resource type="PackedScene" uid="uid://c4vbokn408f2c" path="res://scenes/game_elements/props/decoration/sheep/sheep.tscn" id="20_o36d1"] @@ -33,6 +39,7 @@ size = Vector2(64, 64) [node name="DevArchipelago" type="Node2D" unique_id=213943313] +script = ExtResource("1_kt7u4") [node name="TileMapLayers" type="Node2D" parent="." unique_id=503553272] @@ -53,7 +60,7 @@ tile_map_data = PackedByteArray("AAAFAAIAAQABAAEAAAAFAAMAAQABAAEAAAAFAAQAAQABAAE tile_set = ExtResource("4_pr8dh") [node name="Sand" type="TileMapLayer" parent="TileMapLayers" unique_id=1596241342] -tile_map_data = PackedByteArray("AAAEAPv/AQAFAAAAAAAFAPv/AQAGAAAAAAAGAPv/AQAGAAAAAAAHAPv/AQAGAAAAAAAIAPv/AQAHAAAAAAAIAPz/AQAHAAEAAAAIAP3/AQAHAAEAAAAEAPz/AQAFAAEAAAAEAP3/AQAFAAEAAAAEAP7/AQAFAAIAAAAFAP7/AQAGAAIAAAAGAP7/AQAGAAIAAAAHAP7/AQAGAAIAAAAIAP7/AQAHAAIAAAAIAP//AQAHAAEAAAAIAAAAAQAHAAEAAAAIAAEAAQAHAAEAAAAEAP//AQAFAAEAAAAEAAAAAQAFAAEAAAAEAAEAAQAFAAEAAAAFAAIAAQAGAAIAAAAEAAIAAQAFAAIAAAAIAAIAAQAHAAIAAAAHAAIAAQAGAAIAAAAGAAIAAQAGAAIAAAAFAAEAAQAGAAEAAAAGAAEAAQAGAAEAAAAHAAEAAQAGAAEAAAAHAAAAAQAGAAEAAAAHAP//AQAGAAEAAAAGAP//AQAGAAEAAAAFAP//AQAGAAEAAAAFAAAAAQAGAAEAAAAGAAAAAQAGAAEAAAAFAP3/AQAGAAEAAAAGAP3/AQAGAAEAAAAHAP3/AQAGAAEAAAAHAPz/AQAGAAEAAAAGAPz/AQAGAAEAAAAFAPz/AQAGAAEAAAACABQAAQAFAAMAAAADABQAAQAGAAMAAAAEABQAAQAGAAMAAAAFABQAAQAGAAMAAAAGABQAAQAGAAMAAAAHABQAAQAGAAMAAAAIABQAAQAGAAMAAAAJABQAAQAGAAIAAAAKABMAAQAGAAEAAAAKABIAAQAFAAAAAAALABIAAQAGAAEAAAAMABIAAQAGAAMAAAAKABQAAQAHAAIAAAAJABMAAQAFAAAAAAANABIAAQAGAAMAAAALABMAAQAHAAIAAAALABEAAQAIAAEAAAALABAAAQAIAAEAAAALAA8AAQAIAAEAAAALAA4AAQAIAAEAAAAOABIAAQAHAAMAAAA=") +tile_map_data = PackedByteArray("AAAEAPv/AQAFAAAAAAAFAPv/AQAGAAAAAAAGAPv/AQAGAAAAAAAHAPv/AQAGAAAAAAAIAPv/AQAHAAAAAAAIAPz/AQAHAAEAAAAIAP3/AQAHAAEAAAAEAPz/AQAFAAEAAAAEAP3/AQAFAAEAAAAEAP7/AQAFAAIAAAAFAP7/AQAGAAIAAAAGAP7/AQAGAAIAAAAHAP7/AQAGAAIAAAAIAP7/AQAHAAIAAAAIAP//AQAHAAEAAAAIAAAAAQAHAAEAAAAIAAEAAQAHAAEAAAAEAP//AQAFAAEAAAAEAAAAAQAFAAEAAAAEAAEAAQAFAAEAAAAFAAIAAQAGAAIAAAAEAAIAAQAFAAIAAAAIAAIAAQAHAAIAAAAHAAIAAQAGAAIAAAAGAAIAAQAGAAIAAAAFAAEAAQAGAAEAAAAGAAEAAQAGAAEAAAAHAAEAAQAGAAEAAAAHAAAAAQAGAAEAAAAHAP//AQAGAAEAAAAGAP//AQAGAAEAAAAFAP//AQAGAAEAAAAFAAAAAQAGAAEAAAAGAAAAAQAGAAEAAAAFAP3/AQAGAAEAAAAGAP3/AQAGAAEAAAAHAP3/AQAGAAEAAAAHAPz/AQAGAAEAAAAGAPz/AQAGAAEAAAAFAPz/AQAGAAEAAAACABQAAQAFAAMAAAADABQAAQAGAAMAAAAEABQAAQAGAAMAAAAFABQAAQAGAAMAAAAGABQAAQAGAAMAAAAHABQAAQAGAAMAAAAIABQAAQAGAAMAAAAJABQAAQAGAAIAAAAKABMAAQAGAAAAAAALABIAAQAFAAEAAAAMABIAAQAGAAMAAAAKABQAAQAHAAIAAAAJABMAAQAFAAEAAAANABIAAQAGAAMAAAALABMAAQAHAAIAAAALABEAAQAIAAEAAAALABAAAQAIAAEAAAALAA8AAQAIAAEAAAALAA4AAQAIAAEAAAAOABIAAQAHAAMAAAAJABIAAQAHAAEAAAAIABIAAQAGAAMAAAAHABIAAQAFAAIAAAAHABEAAQAIAAAAAAAJABEAAQAIAAEAAAAJABAAAQAIAAEAAAAJAA8AAQAIAAAAAAA=") tile_set = ExtResource("4_pr8dh") [node name="Paths" type="TileMapLayer" parent="TileMapLayers" unique_id=1538165868] @@ -61,9 +68,21 @@ tile_map_data = PackedByteArray("AAALAAcABQADAAIAAAALAAYABQADAAEAAAALAAUABQAAAAA tile_set = ExtResource("4_pr8dh") [node name="Bridges" type="TileMapLayer" parent="TileMapLayers" unique_id=498682731] -tile_map_data = PackedByteArray("AAALAPX/AwAAAAIAAAALAPb/AwAAAAIAAAALAPf/AwAAAAMAAAALAPP/AwAAAAEAAAALAPT/AwAAAAIAAAALAAcAAwAAAAEAAAALAAgAAwAAAAIAAAALAAkAAwAAAAIAAAALAAoAAwAAAAIAAAALAAsAAwAAAAIAAAALAAwAAwAAAAIAAAALAA0AAwAAAAIAAAALAA4AAwAAAAMAAAACABQAAwACAAAAAAABABQAAwABAAAAAAAAABQAAwABAAAAAAD//xQAAwABAAAAAAD+/xQAAwABAAAAAAD9/xQAAwABAAAAAAD8/xQAAwABAAAAAAD7/xQAAwABAAAAAAD6/xQAAwABAAAAAAD5/xQAAwABAAAAAAD4/xQAAwABAAAAAAASAP3/AwAAAAAAAAATAP3/AwABAAAAAAAUAP3/AwABAAAAAAAVAP3/AwABAAAAAAAWAP3/AwABAAAAAAAXAP3/AwABAAAAAAAYAP3/AwACAAAAAAAhAAQAAwAAAAAAAAAnAAQAAwACAAAAAAAiAAQAAwABAAAAAAAjAAQAAwABAAAAAAAkAAQAAwABAAAAAAAlAAQAAwABAAAAAAAmAAQAAwABAAAAAAA=") +tile_map_data = PackedByteArray("AAALAPX/AwAAAAIAAAALAPb/AwAAAAIAAAALAPf/AwAAAAMAAAALAPP/AwAAAAEAAAALAPT/AwAAAAIAAAALAAcAAwAAAAEAAAALAAgAAwAAAAIAAAALAAkAAwAAAAIAAAALAAoAAwAAAAIAAAALAAsAAwAAAAIAAAALAAwAAwAAAAIAAAALAA0AAwAAAAIAAAALAA4AAwAAAAMAAAASAP3/AwAAAAAAAAATAP3/AwABAAAAAAAUAP3/AwABAAAAAAAVAP3/AwABAAAAAAAWAP3/AwABAAAAAAAXAP3/AwABAAAAAAAYAP3/AwACAAAAAAAhAAQAAwAAAAAAAAAnAAQAAwACAAAAAAAiAAQAAwABAAAAAAAjAAQAAwABAAAAAAAkAAQAAwABAAAAAAAlAAQAAwABAAAAAAAmAAQAAwABAAAAAAA=") tile_set = ExtResource("5_onjm3") +[node name="BridgeToFraysEnd" type="TileMapLayer" parent="TileMapLayers" unique_id=602016082] +unique_name_in_owner = true +tile_map_data = PackedByteArray("AAACABQAAwACAAAAAAABABQAAwABAAAAAAAAABQAAwABAAAAAAD//xQAAwABAAAAAAD+/xQAAwABAAAAAAD9/xQAAwABAAAAAAD8/xQAAwABAAAAAAD7/xQAAwABAAAAAAD6/xQAAwABAAAAAAD5/xQAAwABAAAAAAD4/xQAAwABAAAAAAA=") +tile_set = ExtResource("5_onjm3") +script = ExtResource("7_elr7o") + +[node name="BridgeToFraysEndBroken" type="TileMapLayer" parent="TileMapLayers" unique_id=20751796] +unique_name_in_owner = true +tile_map_data = PackedByteArray("AAACABQAAwACAAAAAAABABQAAwACAAIAAAAAABQAAwABAAAAAAD//xQAAwABAAAAAAD+/xQAAwABAAAAAAD9/xQAAwABAAAAAAD8/xQAAwABAAAAAAD7/xQAAwABAAAAAAD6/xQAAwABAAAAAAD5/xQAAwABAAAAAAD4/xQAAwABAAAAAAA=") +tile_set = ExtResource("5_onjm3") +script = ExtResource("7_elr7o") + [node name="Volleyball" type="TileMapLayer" parent="TileMapLayers" unique_id=163662558] tile_map_data = PackedByteArray("AAAEAP//AQAJAAAAADAFAP//AQAJAAAAADAGAP//AQAJAAAAADAHAP//AQAJAAAAADAIAP//AQAJAAAAADA=") tile_set = ExtResource("4_pr8dh") @@ -76,7 +95,7 @@ tile_set = ExtResource("6_qihih") [node name="Teleporter" parent="Teleporters" unique_id=1779636661 instance=ExtResource("7_xgiso")] position = Vector2(32, 1309) -next_scene = "uid://cufkthb25mpxy" +target = 1 [node name="CollisionShape2D" type="CollisionShape2D" parent="Teleporters/Teleporter" unique_id=1543850848] position = Vector2(4.5, -0.5) @@ -102,6 +121,41 @@ text = "Fray's End" [node name="Elders" type="Node2D" parent="OnTheGround" unique_id=452918502] y_sort_enabled = true +[node name="SQ" type="Node2D" parent="OnTheGround/Elders" unique_id=2119212521] +y_sort_enabled = true +position = Vector2(609, 968) + +[node name="Books5" parent="OnTheGround/Elders/SQ" unique_id=1772091157 instance=ExtResource("13_1y87v")] +position = Vector2(67, -2) +texture = ExtResource("14_2qj7x") +flip_h = true + +[node name="Books6" parent="OnTheGround/Elders/SQ" unique_id=908878077 instance=ExtResource("13_1y87v")] +position = Vector2(57, -3) +offset = Vector2(0, -38.745) + +[node name="Books7" parent="OnTheGround/Elders/SQ" unique_id=866446867 instance=ExtResource("13_1y87v")] +position = Vector2(-93, -11) +texture = ExtResource("14_2qj7x") + +[node name="Books8" parent="OnTheGround/Elders/SQ" unique_id=1714169129 instance=ExtResource("13_1y87v")] +position = Vector2(-63, -24) +flip_h = true + +[node name="StoryQuestElder" parent="OnTheGround/Elders/SQ" unique_id=426003527 node_paths=PackedStringArray("abandon_point") instance=ExtResource("13_0jnxm")] +unique_name_in_owner = true +abandon_point = NodePath("SpawnPoint") + +[node name="SpawnPoint" parent="OnTheGround/Elders/SQ/StoryQuestElder" unique_id=2047736206 instance=ExtResource("18_elr7o")] +position = Vector2(0, 64) + +[node name="TemplateElder" parent="OnTheGround/Elders" unique_id=1625796178 node_paths=PackedStringArray("abandon_point") instance=ExtResource("11_ui3i3")] +position = Vector2(481, 1107) +abandon_point = NodePath("SpawnPoint") + +[node name="SpawnPoint" parent="OnTheGround/Elders/TemplateElder" unique_id=1656237801 instance=ExtResource("18_elr7o")] +position = Vector2(0, 64) + [node name="BasicsElder" parent="OnTheGround/Elders" unique_id=1472383914 node_paths=PackedStringArray("abandon_point") instance=ExtResource("11_ui3i3")] position = Vector2(883, 1116) quest_directory = "res://scenes/dev/basics" diff --git a/tools/sqckck.gd b/tools/sqckck.gd new file mode 100644 index 000000000..463179301 --- /dev/null +++ b/tools/sqckck.gd @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +@tool +extends MainLoop +## StoryQuest Construction Kit Construction Kit +## +## Prunes the game's source tree to make a reduced-size bundle that can be used +## as the basis for a StoryQuest. + +const GitDescribeExport = preload("res://addons/threadbare_git_describe/export.gd") + +const HOME_SCENE := "res://scenes/dev/dev_archipelago.tscn" + +const TEMPLATE_QUEST := "res://scenes/quests/template_quests/NO_EDIT/quest.tres" + +const DELETE_PATHS := [ + "res://assets/third_party/tiny-swords-non-cc0", + "res://scenes/quests/lore_quests", + "res://scenes/quests/story_quests", + "res://scenes/world_map", +] + + +func rm_rf(path: String) -> void: + # You can only delete empty folders, so using DirAccess.remove() means + # traversing the whole folder tree and deleting each leaf before its parent + # folder. But you can trash a non-empty folder in a single step! + var global_path := ProjectSettings.globalize_path(path) + var ret := OS.move_to_trash(global_path) + if ret != OK: + push_error("Failed to trash ", global_path, ": ", error_string(ret)) + + +func _process(_delta: float) -> bool: + var home_scene := ResourceUID.path_to_uid(HOME_SCENE) + ProjectSettings.set_setting(ThreadbareProjectSettings.HOME_SCENE, home_scene) + + var opening_quest := ResourceUID.path_to_uid(TEMPLATE_QUEST) + ProjectSettings.set_setting(ThreadbareProjectSettings.OPENING_QUEST, opening_quest) + + ProjectSettings.set_setting("application/config/name", "Threadbare StoryQuest Kit") + GitDescribeExport.set_versions() + + if ProjectSettings.save() != OK: + push_error("Failed to save project settings") + + for path: String in DELETE_PATHS: + rm_rf(path) + + return true # End the main loop diff --git a/tools/sqckck.gd.uid b/tools/sqckck.gd.uid new file mode 100644 index 000000000..388a18449 --- /dev/null +++ b/tools/sqckck.gd.uid @@ -0,0 +1 @@ +uid://leeahp1ik7lj diff --git a/tools/sqckck.sh b/tools/sqckck.sh new file mode 100755 index 000000000..b84d20a41 --- /dev/null +++ b/tools/sqckck.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +set -ex +: ${GODOT:=godot} +pushd $(dirname "$(dirname "$0")") + +VERSION=$(git describe --tags) + +echo "::group::Importing project" +$GODOT --headless --import +echo "::endgroup::" + +echo "::group::Pruning project" +$GODOT --headless --script tools/sqckck.gd +rm -r .godot +echo "::endgroup::" + +echo "::group::Adding Backstitch launcher" +gh release download -R inkandswitch/backstitch-launcher --pattern 'backstitch-launcher-*.zip' +unzip backstitch-launcher-*.zip +rm backstitch-launcher-*.zip +rm .gitignore.template +git add backstitch-launcher-* +echo "::endgroup::" + +echo "::group::Committing pruned project" +git switch --detach +git commit --no-verify -am "Create StoryQuest kit for $VERSION" +echo "::endgroup::" + +# TODO: merge this to a special branch? Push this as a tag? + +echo "::group::Creating zip file" +git archive --format=zip --prefix=threadbare-storyquest/ --output="threadbare-storyquest-$VERSION.zip" @ +echo "::endgroup::" + +if [[ -n "$GITHUB_OUTPUT" ]]; then + echo "zip_file=threadbare-storyquest-$VERSION.zip" >> "$GITHUB_OUTPUT" +fi