From 3a78d37d595141736b75a367433071ca9fb46e90 Mon Sep 17 00:00:00 2001 From: Will Thompson Date: Wed, 29 Jul 2026 10:54:51 +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 the title screen. When we have Dev Island/Archipelago, we will change the latter to that. 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)' \ ':!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.) Creating the bundle is implemented with two 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. I couldn't resist calling the script StoryQuest Construction Kit Construction Kit (SQCKCK for short) but this is a placeholder name! Still to do: a GitHub Actions workflow to run this. Helps https://github.com/endlessm/threadbare/issues/2563 --- .github/workflows/make-storyquest-kit.yml | 54 +++++++++++++++++++++++ addons/storyquest_bootstrap/copier.gd | 11 ++++- addons/threadbare_git_describe/export.gd | 10 +++-- tools/sqckck.gd | 50 +++++++++++++++++++++ tools/sqckck.gd.uid | 1 + tools/sqckck.sh | 40 +++++++++++++++++ 6 files changed, 162 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/make-storyquest-kit.yml create mode 100644 tools/sqckck.gd create mode 100644 tools/sqckck.gd.uid create mode 100755 tools/sqckck.sh diff --git a/.github/workflows/make-storyquest-kit.yml b/.github/workflows/make-storyquest-kit.yml new file mode 100644 index 0000000000..166e64f0e6 --- /dev/null +++ b/.github/workflows/make-storyquest-kit.yml @@ -0,0 +1,54 @@ +# 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/install-godot@wjt/split-setup-steps + with: + godot_version: ${{ env.GODOT_VERSION }} + + # TODO: .godot cache restore not save + + - 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 diff --git a/addons/storyquest_bootstrap/copier.gd b/addons/storyquest_bootstrap/copier.gd index e420fbec06..6182a660e9 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 89e6057156..2a9be0dffe 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/tools/sqckck.gd b/tools/sqckck.gd new file mode 100644 index 0000000000..463179301c --- /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 0000000000..388a184493 --- /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 0000000000..b84d20a419 --- /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