Keen:Research Quests
OFFICIAL CONTENT NOTICE |
---|
OFFICIAL CONTENT This article contains official content from or verified by the developers at Keen Software House. This information is intended to be accurate at the time it is posted, but may become obsolete over time. If you find errors in this article please describe the errors in the Discussion Page. |
In this guide we explain the quest system, how to make your own quests, and finally, for the scripters among you, how to create custom quest actions and conditions.
Version: | 0.6.1 |
Quest System Overview
With the release of 0.6 we have overhauled the quest system entirely, and now it is significantly more advanced than before. After observing how it worked in 0.5, all the ins-and-outs, our requirements from the system changed and so also the implementation of the system had to change.
First, we wanted to remove the notion of quest arcs, as they were a poor solution for tying together loose quest steps, rather, we chose to create a quest that contains all of the steps by itself, rather than reverse matching the whole arc together.
Additionally, with the introduction of the new research system, we needed some more advanced condition management for the whole quest. This helps solve the issue of people unlocking the research through a scroll, and then getting stuck in the middle of the quest.
We also wanted to be able to have short texts on the HUD and longer texts in the book, as well as support for images inside the book. Finally, for the tutorial, we wanted to add support for making sure it only triggers once, as well as highlight the stones and other environment items, to help improve the early playing experience.
All these requirements resulted in a complete redesign of the system. This means that all previous quests are no longer supported, which is fine because we were only using it for the tutorial previously anyway.
Creating A Quest
Creating a quest is an extensive project that will involve a lot of playtesting and tweaking, but the end result is a fantastic piece of gameplay that will keep people entertained for hours. The research quests will provide a lot of examples for the system, but we will explain the definitions here so you understand how it functions more accurately.
Core Definition
As all our other definitions, it starts with a <Definition>
tag, with the xsi:type=”MyObjectBuilder_QuestDefinition”
, and the Id that follows should be Type=”QuestDefinition”
and the Subtype is the identifier for your quest.
The result looks something like this
<Definition xsi:type="MyObjectBuilder_QuestDefinition">
<Id Type="QuestDefinition" Subtype="EconomicSmelting" />
...
</Definition>
Quest Information
Quests provide a couple of bits of information that is then used by the game to present it to the player. The six values of interest are:
- DisplayName
- This is the actual name of the quest as displayed in the game, either in the book or on the HUD tracker.
- Description
- This is a description of what the quest is about. This can be a short text. It’s currently not displayed anywhere in the game, but we plan to use this in the future for tooltips in the quest book, etc.
- Icon
- This is the image that appears at the top of the page in the book when the quest is selected by the player.
- Tag
- This is a bit of metadata explaining what the quest is about. Currently, we don’t use this, but in the future we may use this for tutorials or research quests.
- IsAbandonable
- The value is either true or false, and it indicates whether or not we want to allow the player to abandon the quest. If this is set to false the Abandon button is hidden.
- IsRepeatable
- The value is either true or false, and it indicates whether or not a player can do this quest again after completing it once.
Quest-Wide Actions and Conditions
Each quest can trigger a set of actions based on a set of conditions that are not tied to a specific step. You can see great examples of these actions and conditions in use in our research quests. Supported types of actions and conditions:
- FailCondition
- If the fail condition is met, the quest is failed and the fail actions are performed.
- FailAction
- When the quest is failed by either the fail condition, abandoned by the player, failed from the code, or failed by another quest action, the fail actions are performed.
- SuccessCondition
- If the success condition is met, the quest is completed and the success actions are performed.
- SuccessAction
- When the quest is completed, all the success actions are performed.
- StartAction
- When the quest is started, all the start actions are performed.
Quest Steps
A quest is made out of one or more steps. Each step comes with a FullDescription and ShortDescription, though if the ShortDescription is not specified, the FullDescription is used instead. It also supports a Condition and once the Condition is met, the Actions are performed.
Steps are always executed in the same order, from top to bottom, and when the last step is completed, the quest is considered completed.
Quest Actions
We currently support the following actions:
Start Quest
Starts another quest for the player running the current quest. If the quest was already started, or if the quest is not repeatable and has been completed in the past, this will not do anything.
Example:
<Action xsi:type="Quest.Actions.StartQuest" Subtype="SettlingDown" />
Complete Quest
Flags a quest as completed for the player running the current quest. If the quest is not active this will not do anything.
Example: <sousyntaxhighlightrce lang="xml"><Action xsi:type="Quest.Actions.CompleteQuest" Subtype="EconomicSmelting" /></syntaxhighlight>
Fail Quest
Flags a quest as failed for the player running the current quest. If the quest is not active this will not do anything.
Example:
<Action xsi:type="Quest.Actions.FailQuest" Subtype="EconomicSmelting" />
Unlock Research
Unlocks a specific research for the player running the current quest. It supports both unlocking of research definitions as a whole, or a specific item as an override.
Example:
<Action xsi:type="Quest.Actions.UnlockResearch" Type="ResearchDefinition" Subtype="EconomicSmelting" />
And
<Action xsi:type="Quest.Actions.UnlockResearch" Type="CraftingRecipeDefinition" Subtype="ShovelWood" />
Lock Research
Opposite of Unlock Research, locks a specific research for the player running the current quest. It supports both locking a whole research definition, or a specific item as an override.
Example:
<Action xsi:type="Quest.Actions.LockResearch" Type="ResearchDefinition" Subtype="EconomicSmelting" />
And
<Action xsi:type="Quest.Actions.LockResearch" Type="CraftingRecipeDefinition" Subtype="ShovelWood" />
Show Notification
Displays the big “Quest Completed” notification to the player, except it also allows the quest to have a different text instead, like “Knowledge Gained”, or whatever seems appropriate for the quest at hand. It also specifies the sound effect played.
Example:
<Action xsi:type="Quest.Actions.ShowNotification" Text="Description_Objective_Completed" SoundCueId="QuestStepCompleted" SoundDelay="300" />
Flag Tutorial Completed
Used in combination with the Tutorial Completed condition, this writes to the player’s local configuration file that the tutorial was completed.
Example:
<Action xsi:type="Quest.Actions.FlagTutorialCompleted" />
Quest Conditions
We currently support the following conditions:
Composite
Contains multiple conditions that are checked using a specified operator. Operators will be AND
and OR
.
OR operator: Returns not completed until at least 1 of its child conditions are completed.
Example:
Using composite conditions you can trigger on any condition’s completion using the OR operator. Additionally, by setting a condition with IsHidden=”true”, this condition will not show up on the HUD.
<Condition xsi:type="Quest.Conditions.Composite" Operator="OR">
<Condition xsi:type="Quest.Conditions.Craft" Type="CubeBlock" Subtype="TableCrafting" Amount="1" />
<Condition xsi:type="Quest.Conditions.Gather" Type="CubeBlock" Subtype="TableCrafting" Amount="1" IsHidden="true" />
</Condition>
Example 2:
Composite conditions can have an ActiveDescription and CompletedDescription which will then hide any of the child descriptions. It will act as if all are marked IsHidden=”true”
.
<Condition xsi:type="Quest.Conditions.Composite" Operator="OR" ActiveDescription="Make a pickaxe at the furnace." CompletedDescription="Made a pickaxe.">
<Condition xsi:type="Quest.Conditions.Craft" Type="HandItem" Subtype="PickaxeCopper" />
<Condition xsi:type="Quest.Conditions.Gather" Type="HandItem" Subtype="PickaxeCopper" />
<Condition xsi:type="Quest.Conditions.Craft" Type="HandItem" Subtype="PickaxeIron" />
<Condition xsi:type="Quest.Conditions.Gather" Type="HandItem" Subtype="PickaxeIron" />
</Condition>
AND
operator. If the composite uses the OR
operator the completion of any condition will complete the step. If the condition with the CompletedDescription is not nested, then it controls the completion of the step with the same result that the quest moves on before the CompletedDescription is displayed.Craft
Checks if the player crafts an amount of a specific item, will also allow tags.
Examples:
<Condition xsi:type="Quest.Conditions.Craft" Tag="Timber" Amount="3" />
<Condition xsi:type="Quest.Conditions.Craft" Type="HandItem" Subtype="AxeStone" Amount="1" />
Gather
Checks if the player somehow obtains an amount of a specific item, will also allow tags.
Example 1:
<Condition xsi:type="Quest.Conditions.Gather" Tag="ScrapWood" Amount="3" />
<Condition xsi:type="Quest.Conditions.Gather" Type="InventoryItem" Subtype="StoneSmall" Amount="1" />
Area inventory can also be included. This is useful when you want the player to gather a large amount of items that may be difficult to fit in the player's inventory.
Example 2:
<Condition xsi:type="Quest.Conditions.Gather" Tag="ScrapWood" Amount="3" AreaInventory="Included" />
<Condition xsi:type="Quest.Conditions.Gather" Type="InventoryItem" Subtype="StoneSmall" Amount="1" AreaInventory="Included" />
Build Block
Checks if the player fully constructs (100%) blocks of a specific type.
Example:
<Condition xsi:type="Quest.Conditions.BuildBlock" Type="CubeBlock" Subtype="TableCrafting" Amount="1" />
Place Block
Checks if the player places blocks of a specific type.
Example:
<Condition xsi:type="Quest.Conditions.PlaceBlock" Type="CubeBlock" Subtype="TableCrafting" Amount="1" />
Consume
Checks if the player consumes a consumable.
Example:
<Condition xsi:type="Quest.Conditions.Consume" Type="ConsumableItem" Subtype="Mushrooms" Amount="1" />
Change Character Color
Checks if the player changes their suit color.
Example:
<Condition xsi:type="Quest.Conditions.ChangeCharacterColor" />
Entity Interaction
Checks if the player interacts with a specific entity, such as handcranks, gatherables, etc.
Example:
<Condition xsi:type="Quest.Conditions.EntityInteraction" Type="CubeBlock" Subtype="Wardrobe" />
Tutorial Completed
Checks if the player already completed the tutorial in the past. Usually used alongside the Flag Tutorial Completed quest action.
Example:
<FailCondition xsi:type="Quest.Conditions.TutorialCompleted" />
Quest Completed
Checks if the player completes a quest successfully.
Example:
<Condition xsi:type="Quest.Conditions.QuestCompleted" Subtype="MilitaryEngineering_Palisades" />
Quest Failed
Checks if the player fails or abandons the quest.
Example:
<Condition xsi:type="Quest.Conditions.QuestFailed" Subtype="MilitaryEngineering_Palisades" />
Quest Started
Checks if the player starts a quest.
Example:
<Condition xsi:type="Quest.Conditions.QuestStarted" Subtype="MilitaryEngineering_Palisades" />
Research Unlocked
Checks if the player has certain research unlocked.
Example:
<Condition xsi:type="Quest.Conditions.ResearchUnlocked" Type="ResearchDefinition" Subtype="Carpentry" />
Prospecting
Performs a prospecting quest that guides the player to within 5 meters of the specified ores.
Example:
<Condition xsi:type="Quest.Conditions.Prospecting">
<WaypointDefinition Type="WaypointDefinition" Subtype="Ore" />
<MaxSearchDistance>1000</MaxSearchDistance>
<SupportedVoxelMaterial Type="VoxelMaterialDefinition" Subtype="IronOre_PoorYield" />
</Condition>
Equip
Triggers when the player equips (one of) the specified tools.
Example:
<Condition xsi:type="Quest.Conditions.Equip" Type="HandItem" Subtype="AxeStone">
<Item Type="HandItem" Subtype="AxeIron" />
</Condition>
Please be aware that a quest’s Success Condition, Fail Condition, Success Actions, Fail Actions and Start Actions are formatted the same way the regular actions are formatted, just with a different element name.
Custom Quest Actions and Conditions
Finally, creating your own actions and conditions is now possible as well. This will be C# heavy, but it’s not actually so complicated once you understand the process. For each type, you have to implement a couple of custom functions, and be mindful of server and client side operations.
Custom Quest Actions
Create the following four classes:
MyObjectBuilder_QuestActionBaseDefinition.cs
using System.Xml.Serialization;
using VRage.ObjectBuilders;
namespace Medieval.ObjectBuilders.Definitions.Quests.Actions
{
[MyObjectBuilderDefinition]
[XmlSerializerAssembly("MedievalEngineers.ObjectBuilders.XmlSerializers")]
[XmlType("Quest.Actions.ExampleAction")]
public class MyObjectBuilder_QuestActionExampleActionDefinition : MyObjectBuilder_QuestActionBaseDefinition
{
// All properties to be loaded by the quest definition are defined here.
}
}
MyObjectBuilder_QuestActionExampleAction.cs
using System.Xml.Serialization;
using VRage.ObjectBuilders;
namespace Medieval.ObjectBuilders.Components.Quests.Actions
{
[MyObjectBuilderDefinition]
[XmlSerializerAssembly("MedievalEngineers.ObjectBuilders.XmlSerializers")]
public class MyObjectBuilder_QuestActionExampleAction : MyObjectBuilder_QuestActionBase
{
// This class is actually not used for actions.
// It has to be there for engine support.
}
}
MyQuestActionExampleActionDefinition.cs
using Medieval.ObjectBuilders.Components.Quests.Actions;
using Medieval.ObjectBuilders.Definitions.Quests.Actions;
using VRage.ObjectBuilder;
namespace Medieval.Definitions.Quests.Actions
{
[MyQuestActionDefinitionType(typeof(MyObjectBuilder_QuestActionExampleActionDefinition))]
public class MyQuestActionExampleActionDefinition : MyQuestActionBaseDefinition
{
// This class contains all the properties parsed from the object builder into a game-ready format.
// See the entity component modding guide for an example on how to use definitions.
public override void Init(MyObjectBuilder_QuestActionBaseDefinition builder)
{
MyObjectBuilder_QuestActionExampleActionDefinition ob = builder as MyObjectBuilder_QuestActionExampleActionDefinition;
}
public override MyObjectBuilderType SerializedTypeId
{
get { return new MyObjectBuilderType(typeof(MyObjectBuilder_QuestActionExampleAction)); }
}
}
}
MyQuestActionExampleAction.cs
using Medieval.Definitions.Quests.Actions;
using Medieval.ObjectBuilders.Components.Quests.Actions;
namespace Medieval.Entities.Components.Quests.Actions
{
[MyQuestActionType(typeof(MyObjectBuilder_QuestActionExampleAction))]
public class MyQuestActionExampleAction : MyQuestActionBase
{
public override void Init(MyQuestActionBaseDefinition definition)
{
base.Init(definition);
MyQuestActionExampleActionDefinition def = m_definition as MyQuestActionExampleActionDefinition;
}
public override void PerformAction(MyQuestEntityComponent owner, MyQuest quest)
{
// Perform the logic linked to this action here.
// Be mindful that you probably want to run this logic on the server, so check for IsServer here.
}
}
}
And adjust the classes as required. As this is not a full tutorial on how to make a specific effect, we will leave the topic here. The intrepid programmers amongst you are surely able to figure it out from here!
Custom Quest Conditions
Similar to actions, you have to create four classes:
MyObjectBuilder_QuestConditionExampleConditionDefinition.cs
using System.Xml.Serialization;
using VRage.ObjectBuilders;
namespace Medieval.ObjectBuilders.Definitions.Quests.Conditions
{
[MyObjectBuilderDefinition]
[XmlSerializerAssembly("MedievalEngineers.ObjectBuilders.XmlSerializers")]
[XmlType("Quest.Conditions.ExampleCondition")]
public class MyObjectBuilder_QuestConditionExampleConditionDefinition : MyObjectBuilder_QuestConditionBaseDefinition
{
// All properties to be loaded by the quest definition are defined here.
}
}
MyObjectBuilder_QuestConditionExampleCondition.cs
using System.Xml.Serialization;
using VRage.ObjectBuilders;
namespace Medieval.ObjectBuilders.Components.Quests.Conditions
{
[MyObjectBuilderDefinition]
[XmlSerializerAssembly("MedievalEngineers.ObjectBuilders.XmlSerializers")]
public class MyObjectBuilder_QuestConditionExampleCondition : MyObjectBuilder_QuestConditionBase
{
// All data to be saved by the quest condition are defined here. This way you can track progress, for example.
}
}
MyQuestConditionExampleConditionDefinition.cs
using Medieval.ObjectBuilders.Components.Quests.Conditions;
using Medieval.ObjectBuilders.Definitions.Quests.Conditions;
using System.Collections.Generic;
using VRage.ObjectBuilder;
namespace Medieval.Definitions.Quests.Conditions
{
[MyQuestConditionDefinitionType(typeof(MyObjectBuilder_QuestConditionExampleConditionDefinition))]
public class MyQuestConditionExampleConditionDefinition : MyQuestConditionBaseDefinition
{
// This class contains all the properties parsed from the object builder into a game-ready format.
// See the entity component modding guide for an example on how to use definitions.
public override void Init(MyObjectBuilder_QuestConditionBaseDefinition builder)
{
base.Init(builder);
MyObjectBuilder_QuestConditionExampleConditionDefinition ob = builder as MyObjectBuilder_QuestConditionExampleConditionDefinition;
}
public override MyObjectBuilderType SerializedTypeId
{
get { return new MyObjectBuilderType(typeof(MyObjectBuilder_QuestConditionExampleCondition)); }
}
public override List<string> GetCompletedDescription()
{
var list = base.GetCompletedDescription();
list.Add("TODO: Implement GetCompletedDescription for MyQuestConditionExampleConditionDefinition");
return list;
}
}
}
MyQuestConditionExampleCondition.cs
using Medieval.Definitions.Quests.Conditions;
using Medieval.ObjectBuilders.Components.Quests.Conditions;
using System.Collections.Generic;
namespace Medieval.Entities.Components.Quests.Conditions
{
[MyQuestConditionType(typeof(MyObjectBuilder_QuestConditionExampleCondition))]
public class MyQuestConditionExampleCondition : MyQuestConditionBase
{
#region (De)serialization
public override void Deserialize(MyObjectBuilder_QuestConditionBase builder)
{
base.Deserialize(builder);
MyObjectBuilder_QuestConditionExampleCondition ob = builder as MyObjectBuilder_QuestConditionExampleCondition;
}
public override MyObjectBuilder_QuestConditionBase Serialize()
{
var builder = base.Serialize();
MyObjectBuilder_QuestConditionExampleCondition ob = builder as MyObjectBuilder_QuestConditionExampleCondition;
return builder;
}
#endregion (De)serialization
#region (De)activation
// Called when the quest or step is activated.
// Hook to game events here.
public override void Activate(MyQuestEntityComponent owner, MyQuest quest)
{
base.Activate(owner, quest);
MyQuestConditionExampleConditionDefinition definition = m_definition as MyQuestConditionExampleConditionDefinition;
}
// Called when the quest or step deactivates. For example, when the quest is completed, abandoned, the quest step completes or the player leaves the server.
// Unhook game events here.
public override void Deactivate()
{
base.Deactivate();
}
#endregion (De)activation
#region Description
// Return the active description here. This will be shown on the HUD.
public override List<string> GetDescription()
{
var list = base.GetDescription();
if (IsCompleted)
{
list.AddRange(m_definition.GetCompletedDescription());
}
else
{
MyQuestConditionExampleConditionDefinition definition = m_definition as MyQuestConditionExampleConditionDefinition;
list.Add("TODO: Implement in progress description for MyQuestConditionExampleConditionDefinition");
}
return list;
}
#endregion Description
}
}
And adjust the classes as required. As this is not a full tutorial on how to make a specific effect, we will leave the topic here. The intrepid programmers amongst you are surely able to figure it out from here!