Difference between revisions of "Modding Tutorials/Quests"

From RimWorld Wiki
Jump to navigation Jump to search
(There is still so much to be done here... The RunTime stuff needs heavy extension and everything needs examples)
m (Update style to use <code> instead of '' for code references)
Line 5: Line 5:
 
Who this is not for: Beginners that have not invested a large amount of time into how the games basic components work
 
Who this is not for: Beginners that have not invested a large amount of time into how the games basic components work
  
Making or modifying quests is a complicated endeavor. Ludeon itself appears to have given up on using the ''QuestScriptDef'' "properly" with the new quests from Ideology, and some modders have followed their example. This tutorial is for the '''proper''' Quest system that was implemented with Royalty and offers the most extendibility (for what it's worth, the quests are still extremely difficult to modify or extend).
+
Making or modifying quests is a complicated endeavor. Ludeon itself appears to have given up on using the <code>QuestScriptDef</code> "properly" with the new quests from Ideology, and some modders have followed their example. This tutorial is for the '<code>proper</code>' Quest system that was implemented with Royalty and offers the most extendibility (for what it's worth, the quests are still extremely difficult to modify or extend).
  
"Modern" quests simply have one single bloated ''QuestNode'' generating all ''QuestPart''s, ignoring the ''Slate'' and compartmentalized ''QuestNode'' approach entirely. This is certainly a functional way of creating a ''Quest'', but far away from the potential extendibility of "proper" quest logic.
+
"Modern" quests simply have one single bloated <code>QuestNode</code> generating all <code>QuestPart</code>s, ignoring the <code>Slate</code> and compartmentalized <code>QuestNode</code> approach entirely. This is certainly a functional way of creating a <code>Quest</code>, but far away from the potential extendibility of "proper" quest logic.
  
 
=Types=
 
=Types=
Line 13: Line 13:
 
These types are normally restricted to the generation time of a given quest. This means these are instanced and not scribed at any point, acting only as the builders for the later generated quest objects.
 
These types are normally restricted to the generation time of a given quest. This means these are instanced and not scribed at any point, acting only as the builders for the later generated quest objects.
 
===QuestScriptDef===
 
===QuestScriptDef===
The ''QuestScriptDef'' is the root container for any given quest. It contains the ''root'' ''QuestNode'', which determines the behaviour of the generated ''Quest''.
+
The <code>QuestScriptDef</code> is the root container for any given quest. It contains the <code>root</code> <code>QuestNode</code>, which determines the behaviour of the generated <code>Quest</code>.
  
 
===QuestNode===
 
===QuestNode===
''QuestNode''s are types meant to pass or modify slate data and create ''QuestPart''s. I personally like to divide the "responsibility" of ''QuestNode''s into two categories: ''Slate''-Modifiers and ''QuestPart''-Generators.  
+
<code>QuestNode</code>s are types meant to pass or modify slate data and create <code>QuestPart</code>s. I personally like to divide the "responsibility" of <code>QuestNode</code>s into two categories: <code>Slate</code>-Modifiers and <code>QuestPart</code>-Generators.  
  
 
====Slate-Modifiers====
 
====Slate-Modifiers====
Responsible for manipulating data in the ''Slate'', so that later ''QuestNode''s (most likely ''QuestPart''-Generators) can consume it.
+
Responsible for manipulating data in the <code>Slate</code>, so that later <code>QuestNode</code>s (most likely <code>QuestPart</code>-Generators) can consume it.
  
 
Here you retrieve pawns to affect, factions to interact with, maps to use, basically the context-data for your actual moving parts.
 
Here you retrieve pawns to affect, factions to interact with, maps to use, basically the context-data for your actual moving parts.
  
 
====QuestPart-Generators====
 
====QuestPart-Generators====
Create ''QuestPart'' instances and append it to the Quest instance that is currently being generated.
+
Create <code>QuestPart</code> instances and append it to the Quest instance that is currently being generated.
  
Following methods in ''QuestNode'' are of note:
+
Following methods in <code>QuestNode</code> are of note:
 
====TestRunInt()====
 
====TestRunInt()====
This method is called when the quest is being considered for generation. It is responsible for setting up slate data for other TestRunInt() calls of other nodes, emulating the (later executed) normal RunInt() call, '''without actually having a real effect'''. The idea being that the ''QuestNode'' performs a dry run, only modifying other generation time values and not actually modifying anything on the players map.
+
This method is called when the quest is being considered for generation. It is responsible for setting up slate data for other <code>TestRunInt()</code> calls of other nodes, emulating the (later executed) normal <code>RunInt()</code> call, '''without actually having a real effect'''. The idea being that the <code>QuestNode</code> performs a dry run, only modifying other generation time values and not actually modifying anything on the players map.
  
The TestRunInt() has the temporary ''Slate'' provided as an argument, which contains the other Test-Run slate modifications instead of the global static Slate.
+
The TestRunInt() has the temporary <code>Slate</code> provided as an argument, which contains the other Test-Run slate modifications instead of the global static Slate.
  
 
====RunInt()====
 
====RunInt()====
The "real" execution of the ''QuestNode'', modifying ''Slate''data and/or creating ''QuestPart'' instances.
+
The "real" execution of the <code>QuestNode</code>, modifying <code>Slate</code>data and/or creating <code>QuestPart</code> instances.
  
 
===Slate===
 
===Slate===
The ''Slate'' is basically a ''Dictionary<string, object>'', being used as the main transport medium for data related to quest generation - like pawns, maps, points to use for threats, etc.
+
The <code>Slate</code> is basically a <code>Dictionary<string, object></code>, being used as the main transport medium for data related to quest generation - like pawns, maps, points to use for threats, etc.
Access to the slate can be done manually in-code with ''slate.Get<T>()'', or by using the ''SlateRef'' type.
+
Access to the slate can be done manually in-code with <code>slate.Get<T>()</code>, or by using the <code>SlateRef</code> type.
  
 
===SlateRef===
 
===SlateRef===
Controls the access to the slate, allowing the ''QuestScriptDef''s XML to provide the key names to use.
+
Controls the access to the slate, allowing the <code>QuestScriptDef</code>s XML to provide the key names to use.
  
 
==Run time==
 
==Run time==
 
The produced objects from the generation time quest types.
 
The produced objects from the generation time quest types.
 
===Quest===
 
===Quest===
The produced result of a ''QuestScriptDef'', containing a list of quest parts. This is the thing you see listed in the Quests tab of the game.
+
The produced result of a <code>QuestScriptDef</code>, containing a list of quest parts. This is the thing you see listed in the Quests tab of the game.
  
 
===QuestPart===
 
===QuestPart===
''QuestPart''s are the logic blocks making up a ''Quest'', the main hook being based on receiving quest related signals and acting upon them.
+
<code>QuestPart</code>s are the logic blocks making up a <code>Quest</code>, the main hook being based on receiving quest related signals and acting upon them.
  
 
===Signal===
 
===Signal===
''Signal''s are basically a simple string, sometimes accompanied with additional data called ''NamedArgument'', which act as keyed object types, containing any instance the signal creator inserts into it.
+
<code>Signal</code>s are basically a simple string, sometimes accompanied with additional data called <code>NamedArgument</code>, which act as keyed object types, containing any instance the signal creator inserts into it.
  
 
=How quest generation works=
 
=How quest generation works=
 
These steps may not be 100% accurate, as far as I can tell there is not a lot of modders that have dug this deep in the guts of the quest generation logic, so if some things are off, I sincerely apologize, but this is the best you'll get.
 
These steps may not be 100% accurate, as far as I can tell there is not a lot of modders that have dug this deep in the guts of the quest generation logic, so if some things are off, I sincerely apologize, but this is the best you'll get.
# When the game wants to generate a random quest (so-called natural quests) it considers all of the ''QuestScriptDef''s loaded into the ''DefDatabase'', picking a random one by weight determined by the ''QuestScriptDef.rootSelectionWeight''.
+
# When the game wants to generate a random quest (so-called natural quests) it considers all of the <code>QuestScriptDef</code>s loaded into the <code>DefDatabase</code>, picking a random one by weight determined by the ''QuestScriptDef.rootSelectionWeight''.
# It then checks if the ''root'' ''QuestNode'' of the ScriptDef successfully passes the ''TestRunInt()'' with an emulated slate
+
# It then checks if the <code>root</code> <code>QuestNode</code> of the ScriptDef successfully passes the ''TestRunInt()'' with an emulated slate
# Once the ''QuestScriptDef'' is considered valid, the static ''QuestGen'' class comes into play, receiving the ''Generate()'' call with the chosen ''QuestScriptDef''
+
# Once the <code>QuestScriptDef</code> is considered valid, the static <code>QuestGen</code> class comes into play, receiving the ''Generate()'' call with the chosen <code>QuestScriptDef</code>
# It now populates the ''QuestGen.slate'' with the initial ''Slate'' values, which is normally ''Map map'' (the target for the quest) and ''int points'' (the threat/reward points to use)
+
# It now populates the ''QuestGen.slate'' with the initial <code>Slate</code> values, which is normally ''Map map'' (the target for the quest) and ''int points'' (the threat/reward points to use)
# The main call-stack that calls all the ''QuestNode'' ''RunInt()'' looks as follows: ''QuestGen.Generate()''->''(QuestScriptDef)root.Run()''->''(QuestNode)root.Run()''->''(QuestNode)this.RunInt()''
+
# The main call-stack that calls all the <code>QuestNode</code> ''RunInt()'' looks as follows: ''QuestGen.Generate()''->''(QuestScriptDef)root.Run()''->''(QuestNode)root.Run()''->''(QuestNode)this.RunInt()''
# Once the ''QuestPart''s are populated, the ''Quest'' is fully generated and made available
+
# Once the <code>QuestPart</code>s are populated, the <code>Quest</code> is fully generated and made available
  
 
[[Category:Modding tutorials]]
 
[[Category:Modding tutorials]]

Revision as of 09:23, 18 November 2022

Preamble

Who this is for: Brave modders that want to tackle the quest system

Who this is not for: Beginners that have not invested a large amount of time into how the games basic components work

Making or modifying quests is a complicated endeavor. Ludeon itself appears to have given up on using the QuestScriptDef "properly" with the new quests from Ideology, and some modders have followed their example. This tutorial is for the 'proper' Quest system that was implemented with Royalty and offers the most extendibility (for what it's worth, the quests are still extremely difficult to modify or extend).

"Modern" quests simply have one single bloated QuestNode generating all QuestParts, ignoring the Slate and compartmentalized QuestNode approach entirely. This is certainly a functional way of creating a Quest, but far away from the potential extendibility of "proper" quest logic.

Types

Generation time

These types are normally restricted to the generation time of a given quest. This means these are instanced and not scribed at any point, acting only as the builders for the later generated quest objects.

QuestScriptDef

The QuestScriptDef is the root container for any given quest. It contains the root QuestNode, which determines the behaviour of the generated Quest.

QuestNode

QuestNodes are types meant to pass or modify slate data and create QuestParts. I personally like to divide the "responsibility" of QuestNodes into two categories: Slate-Modifiers and QuestPart-Generators.

Slate-Modifiers

Responsible for manipulating data in the Slate, so that later QuestNodes (most likely QuestPart-Generators) can consume it.

Here you retrieve pawns to affect, factions to interact with, maps to use, basically the context-data for your actual moving parts.

QuestPart-Generators

Create QuestPart instances and append it to the Quest instance that is currently being generated.

Following methods in QuestNode are of note:

TestRunInt()

This method is called when the quest is being considered for generation. It is responsible for setting up slate data for other TestRunInt() calls of other nodes, emulating the (later executed) normal RunInt() call, without actually having a real effect. The idea being that the QuestNode performs a dry run, only modifying other generation time values and not actually modifying anything on the players map.

The TestRunInt() has the temporary Slate provided as an argument, which contains the other Test-Run slate modifications instead of the global static Slate.

RunInt()

The "real" execution of the QuestNode, modifying Slatedata and/or creating QuestPart instances.

Slate

The Slate is basically a Dictionary<string, object>, being used as the main transport medium for data related to quest generation - like pawns, maps, points to use for threats, etc. Access to the slate can be done manually in-code with slate.Get<T>(), or by using the SlateRef type.

SlateRef

Controls the access to the slate, allowing the QuestScriptDefs XML to provide the key names to use.

Run time

The produced objects from the generation time quest types.

Quest

The produced result of a QuestScriptDef, containing a list of quest parts. This is the thing you see listed in the Quests tab of the game.

QuestPart

QuestParts are the logic blocks making up a Quest, the main hook being based on receiving quest related signals and acting upon them.

Signal

Signals are basically a simple string, sometimes accompanied with additional data called NamedArgument, which act as keyed object types, containing any instance the signal creator inserts into it.

How quest generation works

These steps may not be 100% accurate, as far as I can tell there is not a lot of modders that have dug this deep in the guts of the quest generation logic, so if some things are off, I sincerely apologize, but this is the best you'll get.

  1. When the game wants to generate a random quest (so-called natural quests) it considers all of the QuestScriptDefs loaded into the DefDatabase, picking a random one by weight determined by the QuestScriptDef.rootSelectionWeight.
  2. It then checks if the root QuestNode of the ScriptDef successfully passes the TestRunInt() with an emulated slate
  3. Once the QuestScriptDef is considered valid, the static QuestGen class comes into play, receiving the Generate() call with the chosen QuestScriptDef
  4. It now populates the QuestGen.slate with the initial Slate values, which is normally Map map (the target for the quest) and int points (the threat/reward points to use)
  5. The main call-stack that calls all the QuestNode RunInt() looks as follows: QuestGen.Generate()->(QuestScriptDef)root.Run()->(QuestNode)root.Run()->(QuestNode)this.RunInt()
  6. Once the QuestParts are populated, the Quest is fully generated and made available