├── .editorconfig
├── .gitattributes
├── .gitignore
├── DotnetQuestSystemAddon.csproj
├── DotnetQuestSystemAddon.sln
├── LICENSE
├── README.md
├── addons
└── dotnetquestsystem
│ ├── MainPage.cs
│ ├── MainPage.tscn
│ ├── Plugin.cs
│ ├── RewardOption.cs
│ ├── api
│ ├── Quest.cs
│ ├── QuestController.cs
│ ├── QuestDatabase.cs
│ ├── QuestManager.cs
│ ├── QuestStatus.cs
│ ├── QuestSystemAPI.csproj
│ ├── Reward
│ │ ├── ExperienceReward.cs
│ │ ├── IReward.cs
│ │ └── ItemReward.cs
│ └── Save
│ │ ├── ISaveSystemStrategy.cs
│ │ ├── QuestDatabaseSave.cs
│ │ └── QuestLocalSave.cs
│ ├── assets
│ ├── dotnetquestsystem_node.svg
│ └── godot_asset_icon.png
│ └── plugin.cfg
├── docs
├── api
│ ├── quest.md
│ ├── quest_controller.md
│ ├── quest_database.md
│ ├── quest_manager.md
│ ├── quest_status.md
│ ├── reward.md
│ └── save_system.md
├── contributing.md
└── getting_started.md
├── project.godot
└── test
└── QuestSystemAPITest
├── QuestControllerTest.cs
├── QuestControllerTest.cs.uid
├── QuestDatabaseTest.cs
├── QuestDatabaseTest.cs.uid
├── QuestManagerTest.cs
├── QuestManagerTest.cs.uid
├── QuestSystemAPITest.csproj
├── QuestTest.cs
└── QuestTest.cs.uid
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Normalize EOL for all files that Git considers text files.
2 | * text=auto eol=lf
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Godot 4+ specific ignores
2 | .godot/
3 | /android/
4 | .import/
5 | .mono/
6 |
7 | # Ignore standard build folders created during project compilation
8 | bin/
9 | obj/
10 | Debug/
11 | Release/
12 |
13 | # Ignore Visual Studio Code settings and configurations
14 | .vscode/
15 |
16 | # Ignore temporary files that may be created during development
17 | tmp/
18 | *.tmp
19 | *.temp
--------------------------------------------------------------------------------
/DotnetQuestSystemAddon.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net9.0
4 | true
5 | enable
6 |
7 |
8 |
9 |
10 |
11 | $(DefaultItemExcludes);test\**\*
12 |
13 |
14 |
--------------------------------------------------------------------------------
/DotnetQuestSystemAddon.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.13.35913.81 d17.13
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotnetQuestSystemAddon", "DotnetQuestSystemAddon.csproj", "{37491199-0456-4B57-9CD5-DE2BBC4CAD5C}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | ExportDebug|Any CPU = ExportDebug|Any CPU
12 | ExportRelease|Any CPU = ExportRelease|Any CPU
13 | EndGlobalSection
14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
15 | {37491199-0456-4B57-9CD5-DE2BBC4CAD5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
16 | {37491199-0456-4B57-9CD5-DE2BBC4CAD5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
17 | {37491199-0456-4B57-9CD5-DE2BBC4CAD5C}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
18 | {37491199-0456-4B57-9CD5-DE2BBC4CAD5C}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
19 | {37491199-0456-4B57-9CD5-DE2BBC4CAD5C}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
20 | {37491199-0456-4B57-9CD5-DE2BBC4CAD5C}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
21 | EndGlobalSection
22 | GlobalSection(SolutionProperties) = preSolution
23 | HideSolutionNode = FALSE
24 | EndGlobalSection
25 | GlobalSection(ExtensibilityGlobals) = postSolution
26 | SolutionGuid = {8D3A86B4-78EB-495D-A152-9246578A1F87}
27 | EndGlobalSection
28 | EndGlobal
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 TRUINGLol
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
DOTNET QUEST SYSTEM
2 |
3 | [](https://godotengine.org/)
4 | [](https://github.com/godotengine/awesome-godot)
5 | 
6 |
7 | # Simple quest system for Godot
8 | DotnetQuestSystem - is a simple and powerful [Godot](https://godotengine.org/) add-on that greatly speeds up the process of creating a quest system. Its clean API, written in C#, allows you to quickly integrate quest mechanics into your games.
9 |
10 | ## Features
11 |
12 | * 🌈 Easy to use API
13 | * 🧩 Support for custom quests
14 | * 📋 Tested API with [Xunit](https://xunit.net/)
15 | * 📚 Detailed documentation to help you get started quickly.
16 | * 🔍 C# Based API
17 |
18 | ## Installing
19 |
20 | 1. Download a copy of the `addons` folder
21 | 2. Copy the `addons` folder from the downloaded file to your project directory
22 | 3. Activate the addon in the editor `Project -> Project Settings -> Plugins`
23 |
24 | ## Documentation
25 |
26 | More detailed API documentation can be found in the `docs` folder.
27 |
28 | ## Support and Contributions
29 |
30 | If you think you have found a bug or have a feature request, create new issue. Try to be as detailed as possible in your issue reports.
31 |
32 | If you are interested in contributing fixes or features, please read [contributors](docs/contributing.md) guide first.
33 |
--------------------------------------------------------------------------------
/addons/dotnetquestsystem/MainPage.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using System;
3 | using dotnetquestsystem;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 |
7 | [Tool]
8 | public partial class MainPage : Control
9 | {
10 | [Export]
11 | private NodePath QuestListPath = null!;
12 |
13 | [Export]
14 | private NodePath PanelViewPath = null!;
15 |
16 | [Export]
17 | private NodePath NamePath = null!;
18 |
19 | [Export]
20 | private NodePath DescriptionPath = null!;
21 |
22 | [Export]
23 | private NodePath ObjectivePath = null!;
24 |
25 | [Export]
26 | private NodePath RewardPath = null!;
27 |
28 | [Export]
29 | private NodePath DeleteQuestPath = null!;
30 |
31 | [Export]
32 | private NodePath FileDialogPath = null!;
33 |
34 | private ItemList _questList = null!;
35 | private Panel _panelView = null!;
36 | private LineEdit _nameQuest = null!;
37 | private TextEdit _descriptionQuest = null!;
38 | private TextEdit _objectiveQuest = null!;
39 | private RewardOption _reward = null!;
40 | private Button _deleteQuest = null!;
41 | private FileDialog _fileDialog = null!;
42 |
43 | private string? _saveQuestsPath = null;
44 | private Quest _currentQuest = null!;
45 | private ConfigFile configFile = new ConfigFile();
46 |
47 | public override void _Ready()
48 | {
49 | InitFields();
50 |
51 | // We do it once plugin activate
52 | try
53 | {
54 | if(_saveQuestsPath != null){
55 | QuestManager.instance.questDatabase.LoadQuests(_saveQuestsPath);
56 | }
57 | else{
58 | QuestManager.instance.questDatabase.LoadQuests();
59 | }
60 | }
61 | catch (System.IO.FileNotFoundException)
62 | {
63 | GD.Print("Quests json file not found");
64 | }
65 |
66 | foreach (var quest in QuestManager.instance.questDatabase.Quests)
67 | {
68 | _questList.AddItem(quest.Name);
69 | }
70 | }
71 |
72 | public void OnCreateNewQuestButtonPressed(){
73 | Quest newQuest = QuestManager.instance.questController
74 | .CreateQuest("New Quest", "New Description", "New Objective");
75 |
76 | int indexNewQuest = _questList.AddItem(newQuest.Name);
77 |
78 | // Since Select() does not trigger the item selection signal we emmit it directly
79 | _questList.Select(indexNewQuest);
80 | OnQuestItemSelect(indexNewQuest);
81 | }
82 |
83 | public void OnQuestItemSelect(int index){
84 | if(index < 0 || index >= QuestManager.instance.questDatabase.Quests.Count){
85 | GD.PushError("Quest index out of bounds, objects on scene created manually?");
86 | return;
87 | }
88 |
89 | _deleteQuest.Visible = true;
90 |
91 | _currentQuest = QuestManager.instance.questDatabase.Quests[index];
92 |
93 | _nameQuest.Text = _currentQuest.Name;
94 | _descriptionQuest.Text = _currentQuest.Description;
95 | _objectiveQuest.Text = _currentQuest.Objective;
96 | if(_currentQuest.Reward != null){
97 | SelectRewardByText(_currentQuest.Reward.GetType().Name.ToString()!.ToLower().Replace("reward", ""));
98 | SetRewardParamsValue(_currentQuest);
99 | _reward.attributeContainer.Visible = true;
100 | }
101 | else{
102 | _reward.optionRewards.Select(-1);
103 | _reward.attributeContainer.Visible = false;
104 | }
105 |
106 | _panelView.Visible = true;
107 | }
108 |
109 | public void OnSaveQuestButtonPressed(){
110 | if(_currentQuest == null){
111 | GD.PushError("Selected quest is null");
112 | return;
113 | }
114 |
115 | string newName = _nameQuest.Text.Trim();
116 | if(string.IsNullOrEmpty(newName)){
117 | GD.PushWarning("Quest name cannot be empty");
118 | return;
119 | }
120 |
121 | Quest? existsngQuest = QuestManager.instance.questDatabase.Quests.Find((q)=>q.Name == newName && q != _currentQuest);
122 | if(existsngQuest != null){
123 | GD.PushWarning("A quest with this name already exists");
124 | return;
125 | }
126 |
127 | int selectedRewardId = _reward.optionRewards.Selected;
128 | IReward? newReward = null;
129 |
130 | QuestManager.instance.questController.DeleteQuest(_currentQuest);
131 |
132 | if(selectedRewardId>=0){
133 | object[]? rewardParams = GetRewardParamsValue();
134 |
135 | newReward = Activator.CreateInstance(_reward.RewardableClass[selectedRewardId], rewardParams) as IReward;
136 | }
137 |
138 | if(newReward != null){
139 | _currentQuest = QuestManager.instance.questController.CreateQuest(
140 | name: newName,
141 | description: _descriptionQuest.Text,
142 | objective: _objectiveQuest.Text,
143 | reward: newReward
144 | );
145 | }
146 | else{
147 | _currentQuest = QuestManager.instance.questController.CreateQuest(
148 | name: newName,
149 | description: _descriptionQuest.Text,
150 | objective: _objectiveQuest.Text
151 | );
152 | }
153 |
154 | int selectedIndex = -1;
155 | int[] selectedItems = _questList.GetSelectedItems();
156 |
157 | if(selectedItems.Length > 0){
158 | selectedIndex = selectedItems[0];
159 | }
160 | if(selectedIndex >= 0){
161 | _questList.SetItemText(selectedIndex, newName);
162 | }
163 |
164 | GD.Print("Quest saved!");
165 | }
166 |
167 | public void OnDeleteQuestButtonPressed(){
168 | int[] selectedItems = _questList.GetSelectedItems();
169 | int selectedIndex = -1;
170 |
171 | if(selectedItems.Length > 0){
172 | selectedIndex = selectedItems[0];
173 | }
174 | if(selectedIndex >= 0){
175 | _questList.RemoveItem(selectedIndex);
176 | }
177 |
178 | QuestManager.instance.questController.DeleteQuest(_currentQuest);
179 |
180 | _deleteQuest.Visible = false;
181 | _panelView.Visible = false;
182 |
183 | GD.Print("Quest delete!");
184 | }
185 |
186 | public void OnSelectQuestPathButtonPressed(){
187 | _fileDialog.Visible = true;
188 | }
189 |
190 | public void OnQuestPathSelect(){
191 | _fileDialog.Visible = false;
192 | _saveQuestsPath = _fileDialog.CurrentPath + "Quests.json";
193 | GD.Print("Save quest path change to: "+ _saveQuestsPath);
194 | }
195 |
196 | private void SelectRewardByText(string text){
197 | int itemCount = _reward.optionRewards.ItemCount;
198 |
199 | for (int i = 0; i < itemCount; i++){
200 | if(_reward.optionRewards.GetItemText(i) == text){
201 | _reward.optionRewards.Select(i);
202 |
203 | // Select() method did not emmit OnSelect signal
204 | _reward.OnRewardTypeSelect(i);
205 | return;
206 | }
207 | }
208 |
209 | }
210 |
211 | private object[]? GetRewardParamsValue(){
212 | List