{"id":33013002,"url":"https://github.com/TheWalruzz/godot-questify","last_synced_at":"2025-11-18T11:02:27.476Z","repository":{"id":225863393,"uuid":"762966929","full_name":"TheWalruzz/godot-questify","owner":"TheWalruzz","description":"Graph-based quest editor and manager for Godot 4.","archived":false,"fork":false,"pushed_at":"2025-07-27T09:35:35.000Z","size":292,"stargazers_count":204,"open_issues_count":2,"forks_count":8,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-07-27T11:36:15.575Z","etag":null,"topics":["gdscript","godot","godot4","quest-system"],"latest_commit_sha":null,"homepage":"","language":"GDScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TheWalruzz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-02-25T07:33:30.000Z","updated_at":"2025-07-27T09:35:39.000Z","dependencies_parsed_at":"2024-03-11T21:28:56.031Z","dependency_job_id":"44f26232-e1be-4522-952d-0592b6e1ea26","html_url":"https://github.com/TheWalruzz/godot-questify","commit_stats":{"total_commits":37,"total_committers":3,"mean_commits":"12.333333333333334","dds":"0.16216216216216217","last_synced_commit":"749e38750089231b1ba1c62222522861377d7df8"},"previous_names":["thewalruzz/godot-questify"],"tags_count":16,"template":false,"template_full_name":null,"purl":"pkg:github/TheWalruzz/godot-questify","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheWalruzz%2Fgodot-questify","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheWalruzz%2Fgodot-questify/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheWalruzz%2Fgodot-questify/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheWalruzz%2Fgodot-questify/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TheWalruzz","download_url":"https://codeload.github.com/TheWalruzz/godot-questify/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheWalruzz%2Fgodot-questify/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":285051560,"owners_count":27106642,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-18T02:00:05.759Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gdscript","godot","godot4","quest-system"],"created_at":"2025-11-13T17:00:27.801Z","updated_at":"2025-11-18T11:02:27.470Z","avatar_url":"https://github.com/TheWalruzz.png","language":"GDScript","funding_links":[],"categories":["Plugins and scripts"],"sub_categories":["XR"],"readme":"# Questify\n\nA graph-based quest editor and manager for Godot 4.\n\n![Example quest graph](docs/main.png)\n\n# Usage\n\n## Installation\n\n1. Copy addons/questify directory to you addons directory.\n2. Enable Questify plugin.\n3. Restart the editor.\n4. It's done! You can access the editor screen by clicking on the *Questify* button in the top of the editor.\n\n## Basics\n\nEach quest resource is composed of graph nodes. Quest should have exactly one Start Node and one End Node, to which all paths should converge.\n\n### Creating/editing a quest\n\nA new quest can be created in the Questify tab by clicking on the new file icon, clicking on load button or by double-clicking an existing quest resource in the FileSystem tab in Godot.\n\nYou can add nodes to the graph by either using an Add button in the top or by right-clicking anywhere in the graph plane.\n\nQuestify also handles undo/redo for most actions, like pasting and deleting.\n\n### Starting the quest\n\nIn order to start the quest in your code, you have to load the `QuestResource` you have created and instantiate it. This is done to ensure no modification to the original resource was done. Then, you can simply start it using `Questify` singleton like this:\n\n```gdscript\n@export var quest: QuestResource\n\nfunc _ready() -\u003e void:\n  var instance := quest.instantiate()\n  Questify.start_quest(instance)\n```\n\n### Observing quest states\n\nQuestify singleton exposes several signals to keep track of active quests.\n\n```gdscript\nsignal quest_started(quest: QuestResource)\nsignal quest_objective_added(quest: QuestResource, objective: QuestObjective)\nsignal quest_objective_completed(quest: QuestResource, objective: QuestObjective)\nsignal quest_completed(quest: QuestResource)\n```\n\n`quest_started` is emitted every time a new quest is started.\n\n`quest_objective_added` is emitted every time a new objective becomes active.\n\n`quest_objective_completed` is emitted when objective is completed and becomes inactive.\n\n`quest_completed` is emitted when quest is finished, i.e. end node has completed.\n\nC# wrapper uses a special way of connecting to those signals. See [here](#c-support).\n\n### Handling condition queries\n\nQuestify uses an architecture-agnostic query system to handle quest conditions. This is due to some of the limitations of Godot plugins, but has some advantages. This way Questify can be setup without much work on your part.\nQuestify will emit proper query signal and you just have to handle each type of query and set the objective as completed.\n\nThis signal has following signature:\n\n```gdscript\nsignal condition_query_requested(type: String, key: String, value: Variant, requester: QuestCondition)\n```\n\nAs an example, you can setup this simple handler somewhere in your DataManager and quests will be updated properly:\n\n```gdscript\nQuestify.condition_query_requested.connect(\n  func(type: String, key: String, value: Variant, requester: QuestCondition):\n\tif type == \"variable\":\n\t  if get_value(key) == value:\n\t\trequester.set_completed(true)\n)\n```\n\nC# wrapper uses a special way of setting the completed state. See [here](#c-support).\n\nOf course, you might need to use more operators than equality in queries, but this can be easily done by handling different subtypes of query type string, making for a quite elaborate and powerful system. For example, it could look like this in your DataManager or equivalent class:\n\n```gdscript\nQuestify.condition_query_requested.connect(\n  func(type: String, key: String, value: Variant, requester: QuestCondition):\n\tif type.begins_with(\"var\"):\n\t  var operator := type.get_slice(\":\", 1)\n\t  var variable := get_value(key)\n\t  var result := false\n\t  match operator:\n\t\ttype, \"eq\", \"==\":\n\t\t  result = variable == value\n\t\t\"neq\", \"ne\", \"!eq\", \"!=\":\n\t\t  result = variable != value\n\t\t\"lt\", \"\u003c\":\n\t\t  assert(not variable is bool, \"Incorrect variable type for quest condition query operator\")\n\t\t  result = variable \u003c value\n\t\t\"lte\", \"\u003c=\":\n\t\t  assert(not variable is bool, \"Incorrect variable type for quest condition query operator\")\n\t\t  result = variable \u003c= value\n\t\t\"gt\", \"\u003e\":\n\t\t  assert(not variable is bool, \"Incorrect variable type for quest condition query operator\")\n\t\t  result = variable \u003e value\n\t\t\"gte\", \"\u003e=\":\n\t\t  assert(not variable is bool, \"Incorrect variable type for quest condition query operator\")\n\t\t  result = variable \u003e= value\n\t\t_:\n\t\t  printerr(\"Unknown operator '%s' in quest condition query\" % operator)\n\t  requester.set_completed(result)\n)\n```\n\nIn the example above, you can now use more complex query types, like: `var`, `var:\u003e`, `variable:lte` etc.\n\n### Triggering condition checks\n\nBy default, Questify uses a simple periodical polling mechanism to send a query request via its signal.\nCheck interval is 0.5 seconds by default, but can be changed in `Project Settings -\u003e Questify -\u003e General -\u003e Update Interval`.\n\nSince this might be less performant than on-demand updates and is more suited for smaller projects, this behavior can be toggled off in settings: `Project Settings -\u003e Questify -\u003e General -\u003e Update Polling`.\nIf it's disabled, you have to manually trigger quest updates using `Questify.update_quests()` when e.g. some data changes in your DataManager (or its equivalent). This will immediately send query requests from all active objectives.\n\nCondition polling can also be paused from code when necessary (e.g. when the game is paused):\n\n```gdscript\nQuestify.toggle_update_polling(false) # or `true` if enabling it back.\n```\n\nThis will do nothing if `Update Polling` setting is off.\n\n### Parametrized quests\n\nQuestify includes a mechanism to start a quest with a Dictionary of params that will be injected into condition values when query is requested.\n\n```gdscript\nQuestify.start(quest, { \"param_name\": \"Rat\" })\n```\n\nThis is analogous in C#.\n\nTo use it in Condition Node, select String type for value and put `{param_name}` in the text field.\nMultiple params can be used in one value if needed, as all param occurences will be replaced, but the resulting value will always be a formatted String.\n\nFor the example above, when a condition query request is sent, it will replace `{param_name}` with `Rat`.\nIf single param is provided, the type will be preserved, e.g. for `{ \"param_name\": 123 }`, the resulting query value will be an int.\n\nSince translations for quests could be handled in many different ways, descriptions are not automatically filled with quest param values.\nHowever, it can easily be done with a simple built-in `format()` function:\n\n```gdscript\nquest_objective.description.format(quest.params)\n```\n\n### Translations\n\nThis repository includes a separate plugin, `QuestResourceTranslationParser`, that will extract translatable strings from quest resources into POT files.\nSimply download it to your `addons` directory, enable it in the Project Settings and reload the project.\n\n**NOTE: this plugin is compatible with Godot 4.4+ due to API differences in EditorTranslationParserPlugin, but it's pretty easy to adapt to older versions if you wish so.**\n\nPlease note that due to possible conflicts with other EditorTranslationParserPlugins that handle .tres extension,\nyou might need to parse quest resource manually in other EditorTranslationParserPlugin.\nThis is because Godot uses the first translation parser that matches the handled extension and ignore the others.\n\nRead more here: https://docs.godotengine.org/en/stable/classes/class_editortranslationparserplugin.html\n\nAlso, Questify enables automatic addition of recently saved quests to the POT generation list in `Project Settings -\u003e Localization -\u003e POT Generation`.\nSimply enable `Add quests to POT generation` in Questify settings to make it work.\n\n### Serialization and deserialization\nFor most cases, Questify can holistically serialize and deserialize state of current quests using methods provided to the autoload:\n\n```gdscript\nvar serialized_state := Questify.serialize() # returns Array\nQuestify.deserialize(serialized_state)\n```\n\nAll the saved quests will be instantiated and deserialized immediately.\n\nIf you need to serialize/deserialize quests on your own, quest state can be serialized to a `Dictionary` using `serialize()` and `deserialize()` methods provided by `QuestResource`. However, to be deserialized, quests have to be instantiated from the original resource and then added to the Questify singleton manually. This can be done by passing an array of deserialized quests to Questify. This could look like this:\n\n```gdscript\n# before saving\nfor quest_instance in quests:\n\tvar serialized_quest := quest_instance.serialize() # returns Dictionary\n\t# getting resource path using this method is necessary, since instances do not have `resource_path` property\n\tvar quest_path = quest_instance.get_resource_path()\n\t# ...do other serialization stuff\n\n# after loading\nvar quests: Array[QuestResource] = []\nfor quest in serialized_quests:\n\t# ...load proper quest resource\n\tvar instance := quest.resource.instantiate()\n\tinstance.deserialize(quest.data) # quest.data could be whatever property you use to store the serialized data\n\tquests.append(instance)\nQuestify.set_quests(quests)\n```\n\nAnd that's it!\n\nC# wrapper uses a special way of getting the resource path. See [here](#c-support).\n\n### Utilities\n\nYou can get an Array of active and completed quests at any time, by using `Questify.get_active_quests()` and `Questify.get_completed_quests()` respectively.\n\n# Quest Nodes\n\nNodes have two internal states:\n\n* `active`\n* `completed`\n\n## Active state\n\nActive state marks the node as active and processable. For example, active state in Start Node means that the quest was started and the system can process its child nodes. Most nodes will retain their active state once they become active, with a major exception for objective node. Objective will become inactive when completed. Also, condition nodes do not use active state in general, since they're only active when connected objective is active.\n\n## Completed state\n\nCompleted state means that the node has passed all the conditions and won't be processed anymore. Each node has its own rules for being completed. However, most importantly, objectives can only become completed if all of the attached condition nodes become completed (i.e. conditions are true).\n\n## Available nodes\n\n### Start Node\n\nThe entry point of any quest. There can be only exactly one in each quest. Quest is considered started, when start node's `active` state is (manually) set to true.\n\n### End Node\n\nThe node marking the end (and completion) of a quest. There should be only one in quest. End node (and quest in general) is considered completed, when all connected previous nodes are completed.\n\n### Objective Node\n\nThe main building block of a quest, the objective node denotes any steps in quest that player need to take in order to finish it. Node itself stores the objective description, whether it's optional and metadata, useful for adding things like coordinates for quest markers on minimap. There can be many objectives in parallel, but I will talk about branching a little bit later.\n\nBy setting `optional` to true, any next node will treat this objective as completed, regardless of actual `completed` state.\n\nMetadata make use of Godot's meta functionality (see: https://docs.godotengine.org/en/stable/classes/class_object.html#class-object-method-get-meta for more details). This allows objective nodes to keep some dynamic data that can be accessed by your game at any time. This can be useful for storing things like map markers etc.\n\nIf you create a meta value called `marker`, you can later retrieve the data using `get_meta`:\n\n```gdscript\nobjective.get_meta(\"marker\")\n```\n\n### Condition Nodes (Condition Node, Any Condition Node, Not Condition Node)\n\nIn order for objectives to work, one or more conditions need to be attached to the objective node. Condition nodes use a special type of connection, so you don't have to worry about connecting them to nodes you're not supposed to.\n\nBy default, ALL the attached conditions need to pass for objective to be completed. You can modify this behavior by attaching `Any Condition` node and `Not Condition` node which do exactly what they say on the tin:\n* `Any Condition` will make the condition pass when at least one of the attached conditions is true.\n* `Not Condition` will negate the output of connected conditions, including `Any Condition` node.\n\n### Branching Nodes (Any Previous Node, Exclusive Branch Connector Node)\n\nWhen connecting objectives in parallel, each branch will have to be fully completed in order for the graph to go further. However, quests should allow for proper branching - you might want to allow only one of many paths to finish the quest. For this purpose, Questify includes `Any Previous` and `Exclusive Branch Connector` nodes.\n\n* `Any Previous` will be completed when at least one of its parent objectives is completed (Disclaimer: `optional` will be treated as `completed` in this case!).\n* `Exclusive Branch Connector` is a special kind of node with no outputs and is necessary for longer branches to operate correctly. While simple `Any Previous` node at the end of short, one-layer deep branches will work fine, `Exclusive Branch Connector` is necessary for longer ones. Attach outputs of the first objectives of each branch to this nodes inputs and whenever player completes one of them, others will become inactive and unavailable. This takes advantage of the `active`/`completed` state model, as objective cannot be active when one of its children is active - in this case it means that `Exclusive Branch Connector` becomes active and in turn disables other branches.\n![Exclusive Branch Connector Example](docs/exclusive.png)\n\n### Conditional Branch Node\n\nIn case you want some quest branches to be visible only if certain conditions are met, you can use Conditional Branch Node. Simply attach inputs and outputs and proper conditions and voila! - The objectives attached to the output will be only available when conditions are met.\n\nHowever, please be aware that in this case use of Any Previous or Exclusive Branch Node might be necessary for quest to flow properly.\n\n## C# support\n\nThis plugin contains a simple wrapper for the main Questify singleton. There are a few caveats to using it:\n\nQuestResources can be instantiated using:\n```C#\nQuestify.Instantiate(Resource questResource);\n```\n\nCondition's completed state can be set using:\n\n```C#\nQuestify.SetConditionCompleted(Resource questCondition, bool complete);\n```\n\nQuestResource path can be retrieved using:\n\n```C#\nQuestify.GetResourcePath(Resource quest);\n```\n\nTo connect to any of the signals handled by Questify, use those convenience methods:\n\n```C#\nQuestify.ConnectQuestStarted(Action\u003cResource\u003e action);\nQuestify.ConnectConditionQueryRequested(Action\u003cstring,string, Variant, Resource\u003e action);\nQuestify.ConnectQuestObjectiveAdded(Action\u003cResource, Resource\u003e action);\nQuestify.ConnectQuestObjectiveCompleted(Action\u003cResource, Resource\u003e action);\nQuestify.ConnectQuestCompleted(Action\u003cResource\u003e action);\n```\n\nOther methods are wrapped pretty much 1 to 1 with their GDScript counterparts, but using PascalCase instead of snake_case for their names, for example:\n\n```C#\nQuestify.StartQuest(questResource);\nQuestify.SetQuests(questArray);\n```\n\n# License\nDistributed under the [MIT License](https://github.com/TheWalruzz/godot-questify/blob/main/LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FTheWalruzz%2Fgodot-questify","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FTheWalruzz%2Fgodot-questify","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FTheWalruzz%2Fgodot-questify/lists"}