Modding:Conversations: Difference between revisions

From Caves of Qud Wiki
Jump to navigation Jump to search
m (fix typos)
(Conversation modding for 203.11)
Line 1: Line 1:
[[Category:Modding]]{{Modding Info}}
{{Modding Info}}
Conversations are trees of XML loaded from <code>Conversations.xml</code> and usually executed from a <code>ConversationScript</code> part on a game object.
The most common elements are the Node and the Choice: a node is a piece of text spoken by the creature you're interacting with, coupled with a list of choices for the player to respond with.
This usually takes you to another node where the cycle repeats.


Conversations are loaded as templates from <code>Conversations.xml</code> - The base conversations can not be extended or modified through the XML, only overwritten. However there are events that will allow dynamically editing conversations as they happen, and mod authors can define their own conversation templates in their xml.
For extensive conversation design in mods that use a lot of conversations, some modders have recommended using a tool such as [https://twinery.org/ Twine] to map out your conversation logic.


For extensive conversation design in mods that use a lot of conversations, some Qud modders have recommended using a tool such as [https://twinery.org/ Twine] to map out your conversation logic.
== Adding a Conversation ==
In order to be conversable, an object should have a <code>ConversationScript</code> part and define a <code>ConversationID</code> which references a conversation template of the same <code>ID</code> in Conversations.xml.


== Conversation Object Parts ==
A barebones definition might look like this for a lovely snapjaw.
 
<syntaxhighlight lang="xml">
In order to be conversable, an object should use the <code>ConversationScript</code> part and attach a <code>ConversationID</code> which references a conversation template from Conversations.xml.
<!-- ObjectBlueprints.xml-->
<object Name="Snapjaw Pal" Inherits="Snapjaw">
  <part Name="ConversationScript" ConversationID="FriendlySnapjaw" />
</object>


'''Snippet of ObjectBlueprints.xml that links Mehmet's Conversation to "JoppaMehmet"'''
<!-- Conversations.xml-->
<syntaxhighlight lang="xml">
<conversation ID="FriendlySnapjaw">
   <object Name="Mehmet" Inherits="NPC">
   <start ID="Welcome">
    <part Name="ConversationScript" ConversationID="JoppaMehmet" />
    <text>ehekehe. gn. welcom.</text>
   </object>
    <choice Target="LibDink">Thank you.</choice>
  </start>
  <node ID="LibDink">
    <text>hrffff... lib? dink?</text>
    <text>nyeh. heh! friemd?</text>
    <choice Target="End">Live and drink.</choice>
   </node>
</conversation>
</syntaxhighlight>
</syntaxhighlight>


=== ConversationScript configuration ===
=== XML Tags ===
Skipping over a small section of pre-conversation / checks / etc handled inside the "ConversationScript" part (for now, documentation TODO here). ConversationScript should definitely be used because it checks all sorts of parameters about whether or not the player is capable of speaking with the object. Telepathy and other special conversation choices are considered when ConversationScript is the way into the ConversationUI.HaveConversation()
These are the basic tags supported by conversations, not including any custom elements used by parts.
{| class="wikitable"
! XML Tag
! Description
|-
| <code><nowiki><conversation></nowiki></code>
| Single conversation template typically containing <code><nowiki><node></nowiki></code> and <code><nowiki><start></nowiki></code> elements, linked to a <code>ConversationScript</code> via its <code>ID</code>.
|-
| <code><nowiki><node></nowiki></code>
| Collection of <code><nowiki><text></nowiki></code> from the Speaker's point of view, along with a range of <code><nowiki><choice></nowiki></code> for the Player to respond with.
|-
| <code><nowiki><start></nowiki></code>
| Special variant of <code><nowiki><node></nowiki></code> that can be selected when starting a conversation. <br />For backwards compatibility, a <code><nowiki><node></nowiki></code> with an ID of "Start" will behave similarly.
|-
| <code><nowiki><choice></nowiki></code>
| Collection of <code><nowiki><text></nowiki></code> from the Player's point of view, commonly defines a <code>Target</code> <code><nowiki><node></nowiki></code> to navigate to if selected. <br />The <code>Target</code> attribute has two special values: <code>Start</code> and <code>End</code>, which will return to the beginning of the conversation or end it, respectively. <br />For backwards compatibility, the <code>GotoID</code> attribute will behave similarly to <code>Target</code>.
|-
| <code><nowiki><text></nowiki></code>
| Contains a block of text to display for an element, multiple of these can be defined and randomly selected from if valid.
|-
| <code><nowiki><part></nowiki></code>
| Reference to a C# class that inherits from <code>IConversationPart</code>. <br />Any attributes defined here will be inserted into the fields & properties of the part, if possible. <br />Anything defined as a child element of the part can be loaded with custom C# behavior.
|-
|}


== XRL.UI.ConversationUI.HaveConversation() ==
=== Merging ===
This is the main loop that handles the conversation start / end and setup.
{{Betamoddingcontent}}
First, it deep copies the Conversation object as an original template, allowing mods and parts responding to the following events to mutate the nodes as much as they want.
If multiple elements with the same <code>ID</code> are defined within the same scope, a merge will occur by default where the properties of the later element overwrite the former.<br />
If an explicit ID isn't defined, one will be created based on other attributes.<br/>
You can alter the conflict behavior of an element by setting a <code>Load</code> attribute with valid values of: "Merge", "Replace", "Add", or "Remove".
<syntaxhighlight lang="xml">
<conversation ID="FriendlySnapjaw">
  <node ID="SnappyNoise">
    <text>gnnnnnnn.</text> <!-- ID is "Text" -->
    <text>beh. mmmf.</text> <!-- ID is "Text2" -->
    <text>mmnnn!</text> <!-- ID is "Text3" -->
    <choice Target="LibDink">Thank you.</choice>  <!-- ID is "LibDinkChoice" -->
  </node>
</conversation>


=== Starting Conversation: Pick Start Node ===
<conversation ID="FriendlySnapjaw"> <!-- Will merge with above conversation -->
Then, it checks in order <code>Conversation.StartNodes</code> looking for the first node which passes the <code>ConversationNode.Test()</code> and sets it as <code>Conversation.NodesByID["Start"]</code>
  <node ID="SnappyNoise">  <!-- Will merge with "SnappyNoise" node -->
    <text>gra! gra! gra!</text> <!-- ID is "Text" and will merge -->
    <text Cardinal="3">gra! gra! gra!</text> <!-- ID is "Text3" and will merge -->
    <choice Target="End">Live and drink.</choice> <!-- ID is "EndChoice" and will not merge -->
  </node>
</conversation>
</syntaxhighlight>


=== Inheritance ===
{{Betamoddingcontent}}
In cases where you'd like your elements to appear in multiple places, you can inherit their properties with the <code>Inherits</code> attribute.<br />
The attribute can also take a comma separated list, meaning you can inherit and merge the properties of multiple parent elements together.<br />
Unlike merging, the properties of the current element have precedence over those it is inheriting from.
<syntaxhighlight lang="xml">
<conversation ID="FriendlySnapjaw">
  <start ID="SnappyNoise">
    <text>gnnnnnnn.</text>
    <choice Target="LibDink">Thank you.</choice>
  </start>
</conversation>


=== Modding and Event Hooks ===
<conversation ID="ExcitedSnapjaw" Inherits="FriendlySnapjaw"> <!-- Inherits SnappyNoise -->
Then 3 events will fire in a row - all 3 will cancel the conversation if "false" is returned.
  <node ID="SnappyBye">
    <text>gra! gra! gra!</text>
    <choice Target="End">Live and drink.</choice>
  </node>
</conversation>


==== Event: <code>BeginConversation</code> ====
<conversation ID="AngryArconaut">
Fired on: "Speaker" (not the player)
  <start ID="Grumpy">
Parameters: <Conversation>"Conversation" and <GameObject>"With" pointing to the player.
    <text>I hate things.</text>
    <choice Inherits="ExcitedSnapjaw.SnappyBye.EndChoice" /> <!-- Inherits "Live and drink." -->
  </start>
</conversation>
</syntaxhighlight>


==== Event: <code>PlayerBeginConversation</code> ====
=== Distribution ===
Fired on: "Player"
{{Betamoddingcontent}}
Parameters: <Conversation>"Conversation" and <GameObject>"Speaker" pointing to the person to be talked to.
An alternative to explicitly inheriting elements where you'd like them repeated is distribution, where you specify directly on the element where it should propagate.<br/>
The <code>Distribute</code> attribute normally takes a list of element types, but if <code>Qualifier="ID"</code> is specified, a list of IDs can be provided.<br/>
Choices that are defined as children under a conversation will propagate to all start nodes by default.
<syntaxhighlight lang="xml">
<conversation ID="FriendlySnapjaw">
  <start ID="SnappyHello">
    <text>heeeelo!</text>
  </start>
  <start ID="SnappyNoise">
    <text>gnnnnnnn.</text>
  </start>
  <choice Target="End">Live and drink.</choice> <!-- Added to both start nodes -->
  <choice GiveItem="Dagger" Distribute="SnappyNoise" Qualifier="ID">It is time to grill cheese.</choice>
</conversation>
</syntaxhighlight>


==== Event: <code>ObjectTalking</code> ====
== Delegates ==
Is fired on the speaker when asked to check object talking in the HaveConversation() parameters. This event would normally be checked before getting this far if you enter via ConversationScript, so before these other events, but can sometimes be fired after.
Unique to conversations are their delegate attributes such as <code>IfHaveQuest="What's Eating the Watervine?"</code> or <code>GiveItem="Joppa Recoiler"</code>.<br/>
These are distinguished between two types (there's a secret third option explained later): Predicates which control whether an element is accessible, and Actions which perform some task when the element is selected.<br/>
After the Deep Jungle update these are now for the most part agnostic as to what their parent element is.


=== Core Conversation Loop ===
<syntaxhighlight lang="xml">
After the pre-conversation events above, the conversation has started, and enters at the selected "Start" node.  This could have been mutated during the previous events as well, so is checked once as we begin the loop.
<conversation ID="FriendlySnapjaw">
  <start ID="FurFriend" IfHavePart="ThickFur"> <!-- Hidden if player doesn't have thick fur -->
    <text>ooohh. pretty...</text>
    <text IfReputationAtLeast="Loved">deheh. like you. hohohoho.</text> <!-- Hidden if not Loved by speaker's faction -->
    <choice Target="End" IfReputationAtLeast="Loved" GiveItem="Dagger">I like you too.</choice> <!-- Gives the player a dagger if selected-->
    <choice Target="End">Thank you.</choice>
  </start>
</conversation>
</syntaxhighlight>


During each step of the loop, we call <code>ConversationNode.Visit(Speaker, Player)</code>, to trigger any "on visit" effects in the node. We then sort the <code>ConversationNode.Choices</code> using the <code>ConversationChoice.Sorter</code> which uses the Choices's "Ordinal" property to sort, but also automatically handles any "End" nodes with <code>ConversationChoice.END_SORT_ORDINAL</code>.  To sort an option after an End node, you can set it to END_SORT_ORDINAL + 1.
=== Custom Delegates ===
{{Betamoddingcontent}}
It's possible to add your own delegates for you to use in XML by adding a <code>[ConversationDelegate]</code> attribute to a static method in C#.<br/>
Depending on the return type it will either be registered as a predicate or action, and variants of the delegate will automatically be created.


==== Event: ShowConversationChoices ====
For example the below delegate will automatically create the inversion <code>IfNotHaveItem</code>, and because we set the <code>Speaker</code> attribute parameter, another two (<code>IfSpeakerHaveItem</code>, <code>IfSpeakerNotHaveItem</code>) where <code>Context.Target</code> holds the Speaker instead of the Player.
Fired on "Speaker"
<syntaxhighlight lang="C#">
Parameters:
[HasConversationDelegate] // This is required on the surrounding class to reduce the search complexity.
* <code><List<ConversationChoice>>Choices</code> - can set this parameter as well as read to "extend" the choices available in a node.
public static class DelegateContainer
* <code><ConversationNode>CurrentNode</code> - the current conversation node.
{
* <code><ConversationNode>firstNode</code> - the start node for conversation. (lowercase f intentional)
  // A predicate that receives a DelegateContext object with our values assigned, this to protect mods from signature breaks.
  [ConversationDelegate(Speaker = true)]
  public static bool IfHaveItem(DelegateContext Context)
  {
    // Context.Value holds the quoted value from the XML attribute.
    // Context.Target holds the game object.
    // Context.Element holds the parent element.
    return Context.Target.HasObjectInInventory(Context.Value);
  }
}
</syntaxhighlight>


This event is passed the sorted choices and gets one last chance to pass back additional choices / resort / or whatever it wants to do.  We suggest setting the response to a <code>new List<ConversationChoice>(Choices)</code> before mutating it as changes made to the original "Choices" here will persist throught the current conversation, but the choices used are not the Node.Choices, it is the response from this event's <code>Event.GetObjectParameter("Choices") as List<ConversationChoice></code>.


==== Peek at result nodes ConversationChoice.Goto(Speaker, peekOnly: true) ====
== Parts ==
We use this information to mark if the choice is visited or not, but each choice will have "goto" called with peekOnly to ask to resolve the node it will visit.
{{Betamoddingcontent}}
For more advanced or specific logic not easily reduced to a generally accessible delegate, a custom part is preferred.<br/>
Similar to their equivalent for objects in `ObjectBlueprints.xml`, parts define custom behaviour for elements within and can be attached to most any element just like delegates.<br/>
If you use a period within the part's name, it's assumed you are specifying your own namespace and won't be required to place your part within <code>XRL.World.Conversations.Parts</code>.
<syntaxhighlight lang="xml">
<conversation ID="JoppaZealot">
  <part Name="SpiceContext" />
  <start ID="OrphanOfTheSalt">
    <text>
      Blah! Orphan of the salt! Blooh!
      <part Name="TextInsert" Spoken="false" NewLines="2" Text="[Press Tab or T to open trade]" />
    </text>
    <choice Target="End">
      <text>You intrigue me. I will go to the Six Day Stilt for no particular reason.</text>
      <part Name="QuestHandler" QuestID="O Glorious Shekhinah!" Action="Start" />
    </choice>
  </start>
</conversation>
</syntaxhighlight>


A very basic C# implementation of a part that adds a laugh to any text it's added to might look like this.
<syntaxhighlight lang="C#">
public class SnapjawLaugh : IConversationPart
{
    public override bool WantEvent(int ID, int Propagation)
    {
        return base.WantEvent(ID, Propagation)
              || ID == PrepareTextEvent.ID
            ;
    }


==== Building the message ====
    public override bool HandleEvent(PrepareTextEvent E)
After this event fires, the current menu will render its text.  The conversation itself has a <code>Conversation.Introduction</code> which will prepend the node's message if it is set, and after it is displayed, the value will be reset to <code>""</code> &mdash; showing the "intro" only once.  We take the <code>ConversationNode.Text</code> and select from its random selections (<code>~</code> separated) and apply Variable Replacement to it, then prepend the intro, and affix a TradeNote if enabled on the node.  This is the conversation's content and the choices are displayed.
    {
 
        E.Text.Append("\n\nehehehehe!");
=== Picking a Choice ===
        return base.HandleEvent(E);
 
    }
==== Call: ConversationChoice.Visit(Speaker, Player) ====
}
 
</syntaxhighlight>
When the choice is selected, we call ConversationChoice.Visit() &mdash; it can return false to abort following through with the choice.  The default implementation handles checks for things like "GiveBook".
 
==== Event: LeaveConversationNode ====
Fired on "Speaker"
Parameters:
* <code><ConversationNode>CurrentNode</code> - the current conversation node.
* <code><string>GotoID</code> - the goto id of the node WE ARE LEAVING (not the one we are going to!!!)
 
==== Call: ConversationChoice.Goto(Speaker, peekOnly: false) ====
<code>ConversationChoice.Goto(Speaker)</code> is responsible for telling us the next node in the conversation.  By default it resolves GotoID or other special GotoIDs.
 
==== Optional Event: GetConversationNode ====
In the default handling of ConversationChoice.Goto() it will resolve any GotoID starting with <code>*</code> into a ConversationNode by broadcasting a <code>GetConversationNode</code> on the speaker.
 
Parameters:
* <string> GotoID - Input the "GotoID" including a * - I.E. <code>*waterritual</code>
* <ConversationNode> ConversationNode - This is "output" from the event - we read this to get the result node.  Check the <code>GivesRep</code> part for an example of returning the water ritual.
 
==== Exit loop if ConversationNode is null now ====
 
==== Event: VisitConversationNode ====
Fired on "Speaker"
Parameters:
* <code><ConversationNode>CurrentNode</code> - the new conversation node.
* <code><string>GotoID</code> - the goto id of the node
 
==== Call: ConversationNode.Enter(ConversationNode previous, GameObject speaker) ====
This virtual method can return even yet another new node if it wants,
 
==== Exit loop if ConversationNode is null now ====
 
=== Exiting the Conversation Loop ===


==== Event: AfterConversation ====
=== Events ===
Fired on: Speaker
{{Betamoddingcontent}}
No Parameters
Conversations have their own set of events to handle, but should be immediately familiar to anyone that has tampered with the [[Modding:Events#Minimal_Events|Minimal Events]] of game objects.


== Conversation Classes and XML properties ==
Unlike min events which cascade down, conversation events will propagate up the element tree from where it was fired (See [https://en.wikipedia.org/wiki/Event_bubbling event bubbling]). This means an event fired on a choice will first be handled by parts on the choice itself, then its parent node, last the node's conversation.


The xml file for conversation consists of a single <conversations> node with multiple <conversation> nodes underneath.  It does not currently support extending the base game dialog via <code>Load="Merge"</code> or the other similar tricks used for many of the XMLs.  Creating a Conversation.xml file for your mod would be done when you want to add a new specific conversation / template to be used in the game.
Finally the propagation is separated by perspective, Speaker and Listener (the listener being you, the player). In most cases when you attach a part that modifiers the text of a node, you do not want to also modify the text of its underlying choices since those are spoken by a different entity. By default parts will register for the perspective they are placed in, but can be overriden with the <code>Register</code> attribute.
 
=== XRL.World.Conversation XML Node: &lt;conversation> ===
{| class="wrapped wikitable tablesorter tablesorter-default stickyTableHeaders" role="grid" resolved="" style="padding: 0px;"
|-
! colspan="1" class="confluenceTd"|property
! colspan="1" class="confluenceTd"|details
! colspan="1" class="confluenceTd"|Description
|- role="row"
| colspan="1" class="confluenceTd"|ID
| colspan="1" class="confluenceTd"|string / required / "key"
| colspan="1" class="confluenceTd"|The conversation ID used to reference this conversation template via a <code>&lt;part Name="ConversationScript" ConversationID="...."></code>
|- role="header"
! colspan="3" class="confluenceTd"|C# Properties
|- role="row"
| colspan="1" class="confluenceTd"|StartNodes
| colspan="1" class="confluenceTd"|List<ConversationNode>
| colspan="1" class="confluenceTd"|C# only.  Represents the children <code>&lt;node ID="Start"></code> (of which there can be multiple)
|- role="row"
| colspan="1" class="confluenceTd"|NodesByID
| colspan="1" class="confluenceTd"|Dictionary<string, ConversationNode>
| colspan="1" class="confluenceTd"|C# only.  Represents the children <code>&lt;node></code> that are not "Start" - the "Start" node will be added here when conversation begins and it is chosen.
|}
 
Conversation's have children <code>&lt;node></code> nodes:
 
=== XRL.World.ConversationNode - XML Node: &lt;node> ===
{| class="wrapped wikitable tablesorter tablesorter-default stickyTableHeaders" role="grid" resolved="" style="padding: 0px;"
|-
! colspan="1" class="confluenceTd"|property
! colspan="1" class="confluenceTd"|details
! colspan="1" class="confluenceTd"|Description
|- role="row"
| colspan="1" class="confluenceTd"|ID
| colspan="1" class="confluenceTd"|string / required / "key"
| colspan="1" class="confluenceTd"|The node ID used in other "GotoID" properties, etc.  Can contain multiple "Start" nodes, one of which will be selected given the boolean filter parameters.
|- role="row"
| colspan="1" class="confluenceTd"|TradeNote
| colspan="1" class="confluenceTd"|boolean
| colspan="1" class="confluenceTd"|<code>TradeNote="show"</code> in XML will set this to true, it tells the conversation engine to display the [Press T or Tab to trade] after the conversation text.
|- role="row"
| colspan="1" class="confluenceTd"|bCloseable
| colspan="1" class="confluenceTd"|boolean
| colspan="1" class="confluenceTd"|Defaults to true, but <code>Closable="false"</code> in XML will set this to false, telling the conversation to not allow "escape" to get out of it.
|- role="row"
| colspan="1" class="confluenceTd"|Text
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|The text content of the <code>&lt;text></code> node. Uses [[Modding: Text Replacement|Text Replacement]] strings.
|- role="row"
| colspan="1" class="confluenceTd"|Choices
| colspan="1" class="confluenceTd"|List<ConversationChoice> (C# only)
| colspan="1" class="confluenceTd"|The <code>&lt;choice></code> nodes below this node from the XML.
|- role="header"
! colspan="3" class="confluenceTd"|Visit node triggers
|- role="row"
| colspan="1" class="confluenceTd"|CompleteQuestStep
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A comma separated list of <code>QuestID~StepID</code> that will Complete the given quest step when the node is entered (awarding XP, etc)
|- role="row"
| colspan="1" class="confluenceTd"|GiveItem
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A comma separated list of <code>BlueprintID</code> that will give items to the player whenever this node is entered.
|- role="row"
| colspan="1" class="confluenceTd"|SetStringState
| colspan="1" class="confluenceTd"|string "name,value"
| colspan="1" class="confluenceTd"|Sets the global string game state designated by <code>name</code> to <code>value</code>. If <code>value</code> is empty, removes the string game state designated by <code>name</code>.
|- role="row"
| colspan="1" class="confluenceTd"|SetIntState
| colspan="1" class="confluenceTd"|string "name,value"
| colspan="1" class="confluenceTd"|Sets the global int game state designated by <code>name</code> to <code>value</code>, which should be a valid string representation of a 32-bit integer. If <code>value</code> is empty, removes the int game state designated by <code>name</code>.
|- role="row"
| colspan="1" class="confluenceTd"|AddIntState
| colspan="1" class="confluenceTd"|string "name,value"
| colspan="1" class="confluenceTd"|Adds <code>value</code>, which should be a valid string representation of a 32-bit integer, to the global int game state designated by <code>name</code>.
|- role="row"
| colspan="1" class="confluenceTd"|SetBooleanState
| colspan="1" class="confluenceTd"|string "name,value"
| colspan="1" class="confluenceTd"|Sets the global boolean game state designated by <code>name</code> to <code>value</code>, which should be "true" or "false" or empty. If <code>value</code> is empty, removes the boolean game state designed by <code>name</code>.
|- role="row"
| colspan="1" class="confluenceTd"|ToggleBooleanState
| colspan="1" class="confluenceTd"|string "name"
| colspan="1" class="confluenceTd"|Toggles the global boolean game state designated by <code>name</code>.
|- role="header"
! colspan="3" class="confluenceTd"|Filter / Test properties for Start nodes
|- role="row"
| colspan="1" class="confluenceTd"|IfWearingBlueprint
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A single <code>BlueprintID</code> that the player must have equipped to see this start node.
<code>XRLCore.Core.Game.Player.Body.HasObjectEquipped(IfWearingBlueprint)</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfHasBlueprint
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A single <code>BlueprintID</code> that the player must in their Inventory to see this start node.
<code>XRL.Core.XRLCore.Core.Game.Player.Body.GetPart<Parts.Inventory>().FireEvent(Event.New("HasBlueprint", "Blueprint", IfHasBlueprint))</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfLevelLessOrEqual
| colspan="1" class="confluenceTd"|string (of a number)
| colspan="1" class="confluenceTd"|A string representation of the level the character must be less than or equal to to see this node.
<code>XRL.Core.XRLCore.Core.Game.Player.Body.Statistics["Level"].Value <= Convert.ToInt32(IfLevelLessOrEqual))</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfHaveQuest
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A string Quest ID the player must have to get this start node.
<code>XRL.Core.XRLCore.Core.Game.HasQuest(IfHaveQuest)</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfNotHaveQuest
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A string Quest ID the player must *NOT* have to get this start node.
<code>!XRL.Core.XRLCore.Core.Game.HasQuest(IfNotHaveQuest)</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfFinishedQuest
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A string Quest ID the player must have completed get this start node.
<code>XRL.Core.XRLCore.Core.Game.FinishedQuest(IfFinishedQuest)</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfNotFinishedQuest
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A string Quest ID the player must *NOT* have completed get this start node.
<code>!XRL.Core.XRLCore.Core.Game.FinishedQuest(IfNotFinishedQuest)</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfFinishedQuestStep
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A string Quest Step ID the player must have completed get this start node.
<code>XRL.Core.XRLCore.Core.Game.FinishedQuestStep(IfFinishedQuestStep)</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfNotFinishedQuestStep
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A string Quest Step ID the player must *NOT* have completed get this start node.
<code>!XRL.Core.XRLCore.Core.Game.FinishedQuestStep(IfNotFinishedQuestStep)</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfHaveObservation
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A string Observation ID the player must have to get this start node.
<code>Qud.API.JournalAPI.HasObservation(IfHaveObservation)</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfHaveState
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A string state flag the game must have to get this start node.
<code>XRL.Core.XRLCore.Core.Game.HasGameState(IfHaveState)</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfNotHaveState
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A string state flag the game must *NOT* have to get this start node.
<code>!XRL.Core.XRLCore.Core.Game.HasGameState(IfNotHaveState)</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfTestState
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|Defines a test condition that must pass on global game state in order for this node to be enabled.
Note all test pattern formats are exact. Extra spaces or missing spaces will make the test fail.
{| class="wrapped wikitable stickyTableHeaders" role="grid" resolved="" style="padding: 0px;"
|-
! colspan="1" class="confluenceTd"|test pattern
! colspan="1" class="confluenceTd"|type of game state
! colspan="1" class="confluenceTd"|description
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name = value"</code>
| colspan="1" class="confluenceTd"|string, int, int64, boolean
| colspan="1" class="confluenceTd"|Tests for a defined game state <code>name</code> equal to <code>value</code>.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name != value"</code>
| colspan="1" class="confluenceTd"|string, int, int64, boolean
| colspan="1" class="confluenceTd"|Tests for absence of a defined game state <code>name</code> equal to <code>value</code>.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name > value"</code>
| colspan="1" class="confluenceTd"|int, int64
| colspan="1" class="confluenceTd"|Tests for a defined game state <code>name</code> greater than <code>value</code>.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name !> value"</code>
| colspan="1" class="confluenceTd"|int, int64
| colspan="1" class="confluenceTd"|Tests for absence of a defined game state <code>name</code> greater than <code>value</code>.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name >= value"</code>
| colspan="1" class="confluenceTd"|int, int64
| colspan="1" class="confluenceTd"|Tests for a defined game state <code>name</code> greater than or equal to <code>value</code>.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name !>= value"</code>
| colspan="1" class="confluenceTd"|int, int64
| colspan="1" class="confluenceTd"|Tests for absence of a defined game state <code>name</code> greater than or equal to <code>value</code>.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name < value"</code>
| colspan="1" class="confluenceTd"|int, int64
| colspan="1" class="confluenceTd"|Tests for a defined game state <code>name</code> less than <code>value</code>.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name !< value"</code>
| colspan="1" class="confluenceTd"|int, int64
| colspan="1" class="confluenceTd"|Tests for absence of a defined game state <code>name</code> less than <code>value</code>.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name <= value"</code>
| colspan="1" class="confluenceTd"|int, int64
| colspan="1" class="confluenceTd"|Tests for a defined game state <code>name</code> less than or equal to <code>value</code>.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name !<= value"</code>
| colspan="1" class="confluenceTd"|int, int64
| colspan="1" class="confluenceTd"|Tests for absence of a defined game state <code>name</code> less than or equal to <code>value</code>.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name % value"</code>
| colspan="1" class="confluenceTd"|int, int64
| colspan="1" class="confluenceTd"|Tests for a defined game state <code>name</code> whose modulus with respect to <code>value</code> is 0.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name !% value"</code>
| colspan="1" class="confluenceTd"|int, int64
| colspan="1" class="confluenceTd"|Tests for absence of a defined game state <code>name</code> whose modulus with respect to <code>value</code> is 0.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name & value"</code>
| colspan="1" class="confluenceTd"|int, int64
| colspan="1" class="confluenceTd"|Tests for a defined game state <code>name</code> in which all the bits in <code>value</code> are set.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name !& value"</code>
| colspan="1" class="confluenceTd"|int, int64
| colspan="1" class="confluenceTd"|Tests for absence of a defined game state <code>name</code> in which all the bits in <code>value</code> are set.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name ~ value"</code>
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|Tests for a defined game state <code>name</code> that is equal to <code>value</code>, case insensitive.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name !~ value"</code>
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|Tests for absence of a defined game state <code>name</code> that is equal to <code>value</code>, case insensitive.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name contains value"</code>
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|Tests for a defined game state <code>name</code> that contains <code>value</code>.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name !contains value"</code>
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|Tests for absence of a defined game state <code>name</code> that contains <code>value</code>.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name ~contains value"</code>
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|Tests for a defined game state <code>name</code> that contains <code>value</code>, case insensitive.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name !~contains value"</code>
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|Tests for absence of a defined game state <code>name</code> that contains <code>value</code>, case insensitive.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name isin value"</code>
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|Tests for a defined game state <code>name</code> that is contained in <code>value</code>.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name !isin value"</code>
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|Tests for absence of a defined game state <code>name</code> that is contained in <code>value</code>.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name ~isin value"</code>
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|Tests for a defined game state <code>name</code> that is contained in <code>value</code>, case insensitive.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name !~isin value"</code>
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|Tests for absence of a defined game state <code>name</code> that is contained in <code>value</code>, case insensitive.
|- role="row"
| colspan="1" class="confluenceTd"|<code>"name"</code>
| colspan="1" class="confluenceTd"|boolean
| colspan="1" class="confluenceTd"|Tests for a defined game state <code>name</code> that is true.
|}
<code>XRL.Core.XRLCore.Core.Game.TestGameState(IfTestState)</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfHaveItemWithID
| colspan="1" class="confluenceTd"|string (C# ONLY)
| colspan="1" class="confluenceTd"|A specific item ID that must be in the players inventory.  This is a C# only property because Obejct ID doesn't exist until the object is created from the blueprint, making this option only useful on dynamic conversation nodes.
|- role="row"
| colspan="1" class="confluenceTd"|SpecialRequirement
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|Other special requirements.
* <code>LovedByConsortium</code> : <code>XRLCore.Core.Game.PlayerReputation.get("Consortium") < World.Reputation.lovedRep</code>
* <code>IsMapNoteRevealed:MapNoteID</code> : <code>Qud.API.JournalAPI.IsMapOrVillageNoteRevealed( SpecialRequirement.Split(':')[1] )</code>
* <code>!IsMapNoteRevealed:MapNoteID</code> : NOT of the above
|- role="header"
! colspan="3" class="confluenceTd"|C# Methods
|- role="row"
| colspan="1" class="confluenceTd"|Copy(ConversationNode source)
| colspan="1" class="confluenceTd"|void
| colspan="1" class="confluenceTd"|Copies all the properties and makes a copy of each Choice in the Choices list
|- role="row"
| colspan="1" class="confluenceTd"|Test()
| colspan="1" class="confluenceTd"|virtual bool
| colspan="1" class="confluenceTd"|Tests the various If* and other requirements properties to see if the node should be chosen for a start node.
|- role="row"
| colspan="1" class="confluenceTd"|Visit(GameObject speaker, GameObject player)
| colspan="1" class="confluenceTd"|virtual void
| colspan="1" class="confluenceTd"|Tells the node that it was visited, is used to handle GiveItem and CompleteQuestStep and tracking "Visited" status.
 
|- role="header"
! colspan="3" class="confluenceTd"|Unused/Unimplemented properties
|- role="row"
| colspan="1" class="confluenceTd"|Filter
| colspan="1" class="confluenceTd"|string / deprecated
| colspan="1" class="confluenceTd"|A potentailly unused property, it seems to have no code paths calling it
|- role="row"
| colspan="1" class="confluenceTd"|GiveOneItem
| colspan="1" class="confluenceTd"|string / deprecated
| colspan="1" class="confluenceTd"|A seemingly unused string (on node), no code paths in base CoQ reference it
|- role="row"
| colspan="1" class="confluenceTd"|StartQuest
| colspan="1" class="confluenceTd"|string / deprecated
| colspan="1" class="confluenceTd"|A seemingly unused string (on node), no code paths in base CoQ reference it
|- role="row"
| colspan="1" class="confluenceTd"|RevealMapNoteId
| colspan="1" class="confluenceTd"|string / deprecated
| colspan="1" class="confluenceTd"|A seemingly unused string (on node), no code paths in base CoQ reference it
|- role="row"
| colspan="1" class="confluenceTd"|TakeItem
| colspan="1" class="confluenceTd"|string / deprecated
| colspan="1" class="confluenceTd"|A seemingly unused string (on node), no code paths in base CoQ reference it
|- role="row"
| colspan="1" class="confluenceTd"|ClearOwner
| colspan="1" class="confluenceTd"|string / deprecated
| colspan="1" class="confluenceTd"|A seemingly unused string (on node), no code paths in base CoQ reference it
 
|}
 
=== XRL.World.ConversationChoice - XML Node: &lt;choice> ===
{| class="wrapped wikitable tablesorter tablesorter-default stickyTableHeaders" role="grid" resolved="" style="padding: 0px;"
|-
! colspan="1" class="confluenceTd"|property
! colspan="1" class="confluenceTd"|details
! colspan="1" class="confluenceTd"|Description
|- role="row"
| colspan="1" class="confluenceTd"|Ordinal
| colspan="1" class="confluenceTd"|int
| colspan="1" class="confluenceTd"|Determines the choice sort order.
|- role="row"
| colspan="1" class="confluenceTd"|Text
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|The line of text rendered for the choice in the UI.
|- role="row"
| colspan="1" class="confluenceTd"|GotoID
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|The conversation node that will be navigated to if this choice is selected. (Reserved: "End" "EndFight")
|- role="row"
| colspan="1" class="confluenceTd"|ParentNode
| colspan="1" class="confluenceTd"|ConversationNode
| colspan="1" class="confluenceTd"|The parent conversation node.
|- role="header"
! colspan="3" class="confluenceTd"|Consts
|- role="row"
| colspan="1" class="confluenceTd"|WATER_RITUAL_ORDINAL
| colspan="1" class="confluenceTd"|980
| colspan="1" class="confluenceTd"|Ordinal for water ritual nodes
|- role="row"
| colspan="1" class="confluenceTd"|TRADE_ORDINAL
| colspan="1" class="confluenceTd"|990
| colspan="1" class="confluenceTd"|Ordinal for trade nodes
|- role="row"
| colspan="1" class="confluenceTd"|END_SORT_ORDINAL
| colspan="1" class="confluenceTd"|999999
| colspan="1" class="confluenceTd"|Ordinal for end nodes
|- role="header"
! colspan="3" class="confluenceTd"|Visit node triggers
|- role="row"
| colspan="1" class="confluenceTd"|Achievement
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|If this choice is selected an achievement with this ID will be awarded.
|- role="row"
| colspan="1" class="confluenceTd"|StartQuest
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|Contains a Quest ID. This quest will start when the node is selected.
|- role="row"
| colspan="1" class="confluenceTd"|RevealMapNoteID
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|Contains a Map Note ID. This map note will be revealed when the node is selected.
|- role="row"
| colspan="1" class="confluenceTd"|CompleteQuestStep
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A comma separated list of <code>QuestID~StepID</code> that will Complete the given quest step when the node is entered (awarding XP, etc)
|- role="row"
| colspan="1" class="confluenceTd"|Execute
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A reference to a static function, in the format <code>MyNamespace.MyClass:MyStaticFunction</code> which will be called when this node is selected.
|- role="row"
| colspan="1" class="confluenceTd"|CallScript
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A reference to a static function, in the format <code>MyNamespace.MyClass.MyStaticFunction</code> which will be called when this node is selected.
|- role="row"
| colspan="1" class="confluenceTd"|IdGift
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|Creates, identifies and gives to the player an object with the specified blueprint.
|- role="row"
| colspan="1" class="confluenceTd"|GiveItem
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A comma separated list of <code>BlueprintID</code> that will give items to the player whenever this node is entered.
|- role="row"
| colspan="1" class="confluenceTd"|GiveOneItem
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A comma separated list of <code>BlueprintID</code>. The player will be given the option to choose one of these items.
|- role="row"
| colspan="1" class="confluenceTd"|TakeItem
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A comma separated list of <code>BlueprintID</code> which can optionall include <code>[destroy],...</code> and/or <code>[byid],...</code>. Will take all instances of an item blueprint (or items with the specified id if <code>[byid]</code> is included.) from the player. Items will be taken into the speakers inventory or destroyed if the <code>[destroy]</code> spec is included.
|- role="row"
| colspan="1" class="confluenceTd"|onAction
| colspan="1" class="confluenceTd"|Func<bool> (delegate)
| colspan="1" class="confluenceTd"|Called when a choice is selected, if false is returned the GotoID won't be followed and the current node will be re-rendered.
|- role="header"
! colspan="3" class="confluenceTd"|Filter / Test properties for Conversation Choices
|- role="row"
| colspan="1" class="confluenceTd"|IfWearingBlueprint
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A single <code>BlueprintID</code> that the player must have equipped to see this choice.
<code>XRLCore.Core.Game.Player.Body.HasObjectEquipped(IfWearingBlueprint)</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfHasBlueprint
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A single <code>BlueprintID</code> that the player must in their Inventory to see this choice.
<code>XRL.Core.XRLCore.Core.Game.Player.Body.GetPart<Parts.Inventory>().FireEvent(Event.New("HasBlueprint", "Blueprint", IfHasBlueprint))</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfHaveQuest
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A string Quest ID the player must have to get this choice.
<code>XRL.Core.XRLCore.Core.Game.HasQuest(IfHaveQuest)</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfNotHaveQuest
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A string Quest ID the player must *NOT* have to get this choice.
<code>!XRL.Core.XRLCore.Core.Game.HasQuest(IfNotHaveQuest)</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfFinishedQuest
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A string Quest ID the player must have completed get this choice.
<code>XRL.Core.XRLCore.Core.Game.FinishedQuest(IfFinishedQuest)</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfNotFinishedQuest
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A string Quest ID the player must *NOT* have completed get this choice.
<code>!XRL.Core.XRLCore.Core.Game.FinishedQuest(IfNotFinishedQuest)</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfFinishedQuestStep
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A string Quest Step ID the player must have completed get this choice.
<code>XRL.Core.XRLCore.Core.Game.FinishedQuestStep(IfFinishedQuestStep)</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfNotFinishedQuestStep
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A string Quest Step ID the player must *NOT* have completed get this choice.
<code>!XRL.Core.XRLCore.Core.Game.FinishedQuestStep(IfNotFinishedQuestStep)</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfHaveObservation
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A string Observation ID the player must have to get this choice.
<code>Qud.API.JournalAPI.HasObservation(IfHaveObservation)</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfHaveState
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A string state flag the game must have to get this choice.
<code>XRL.Core.XRLCore.Core.Game.HasGameState(IfHaveState)</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfNotHaveState
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|A string state flag the game must *NOT* have to get this choice.
<code>!XRL.Core.XRLCore.Core.Game.HasGameState(IfNotHaveState)</code>
|- role="row"
| colspan="1" class="confluenceTd"|IfHaveItemWithID
| colspan="1" class="confluenceTd"|string (C# ONLY)
| colspan="1" class="confluenceTd"|A specific item ID that must be in the players inventory.  This is a C# only property because Obejct ID doesn't exist until the object is created from the blueprint, making this option only useful on dynamic conversation nodes.
|- role="row"
| colspan="1" class="confluenceTd"|IfDelegate
| colspan="1" class="confluenceTd"|Func<bool>
| colspan="1" class="confluenceTd"|An arbitrary If delegate.
|- role="row"
| colspan="1" class="confluenceTd"|SpecialRequirement
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|Other special requirements.
* <code>LovedByConsortium</code> : <code>XRLCore.Core.Game.PlayerReputation.get("Consortium") < World.Reputation.lovedRep</code>
* <code>IsMapNoteRevealed:MapNoteID</code> : <code>Qud.API.JournalAPI.IsMapOrVillageNoteRevealed( SpecialRequirement.Split(':')[1] )</code>
* <code>!IsMapNoteRevealed:MapNoteID</code> : NOT of the above
|- role="header"
! colspan="3" class="confluenceTd"|C# Methods
|- role="row"
| colspan="1" class="confluenceTd"|Copy(ConversationChoice source)
| colspan="1" class="confluenceTd"|void
| colspan="1" class="confluenceTd"|Copies all the properties.
|- role="row"
| colspan="1" class="confluenceTd"|TestSpecialRequirement()
| colspan="1" class="confluenceTd"|virtual bool
| colspan="1" class="confluenceTd"|Tests the SpecialRequirement member and returns true or false.
|- role="row"
| colspan="1" class="confluenceTd"|TestHaveItemWithID()
| colspan="1" class="confluenceTd"|virtual bool
| colspan="1" class="confluenceTd"|Tests the IfHaveItemWithID member and returns true or false.
|- role="row"
| colspan="1" class="confluenceTd"|Test()
| colspan="1" class="confluenceTd"|virtual bool
| colspan="1" class="confluenceTd"|Tests the various If* and other requirements properties to see if the choice should be accepted or the current node should re-render.  Called during Visit()
|- role="row"
| colspan="1" class="confluenceTd"|Goto(GameObject speaker, bool peekOnly=false)
| colspan="1" class="confluenceTd"|virtual ConversationNode
| colspan="1" class="confluenceTd"|Called on the conversation node when the choice is rendered or selected to gather information about the node that will be navigated to. Returns the node that will be navigated to.  peekOnly is set to true when rendering choices, and to false when actually navigating.
|- role="row"
| colspan="1" class="confluenceTd"|GetDisplayText()
| colspan="1" class="confluenceTd"|virtual string
| colspan="1" class="confluenceTd"|Called when the conversation node is rendered, returns the final line of text that will be displayed.
|- role="row"
| colspan="1" class="confluenceTd"|CheckSpecialRequirements(GameObject Speaker, GameObject Player)
| colspan="1" class="confluenceTd"|virtual bool
| colspan="1" class="confluenceTd"|Called when the choice is selected. Perfoms a variety of hard-coded interactions.
|- role="row"
| colspan="1" class="confluenceTd"|Visit(GameObject Speaker, GameObject Player)
| colspan="1" class="confluenceTd"|virtual bool
| colspan="1" class="confluenceTd"|Called when the choice is selected. If false is returned, the GotoID will not be respected and the currently selected choice will re-render.
 
 
|- role="header"
! colspan="3" class="confluenceTd"|Unused/Unimplemented properties
|- role="row"
| colspan="1" class="confluenceTd"|ID
| colspan="1" class="confluenceTd"|string
| colspan="1" class="confluenceTd"|Currently unused, but can be referenced.
|- role="row"
| colspan="1" class="confluenceTd"|Filter
| colspan="1" class="confluenceTd"|string / deprecated
| colspan="1" class="confluenceTd"|Should not be used. Does nothing!
|- role="row"
| colspan="1" class="confluenceTd"|ClearOwner
| colspan="1" class="confluenceTd"|string / deprecated
| colspan="1" class="confluenceTd"|A seemingly unused string (on node), no code paths in base CoQ reference it
|- role="row"
| colspan="1" class="confluenceTd"|TakeBlueprint
| colspan="1" class="confluenceTd"|string / deprecated
| colspan="1" class="confluenceTd"|A seemingly unused string (on node), no code paths in base CoQ reference it
|- role="row"
| colspan="1" class="confluenceTd"|GetScriptClassName
| colspan="1" class="confluenceTd"|method
| colspan="1" class="confluenceTd"|Returns a unique script ID. Unused.
 
|}
 
 
'''Snippet of Conversation.xml with Mehmet's script'''
<syntaxhighlight lang="xml">
<syntaxhighlight lang="xml">
<?xml version="1.0" encoding="utf-8"?>
<conversation ID="EventfulSnapjaw">
<conversations>
  <part Name="SpiceContext" Register="All" /> <!-- Registers for both Speaker events by default, but overrides it -->
  <conversation ID="JoppaMehmet">
  <start ID="TasterOfTheSalt">
    <node ID="Start" IfNotHaveQuest="What's Eating the Watervine?">
     <part Name="SnapjawLaugh" /> <!-- Registers for Speaker events -->
      <text>
     <text>mmmg. salt.</text>
Live and drink, =player.formalAddressTerm=. May you find shade in Joppa.</text>
     <text>tasty.</text>
      <choice GotoID="AboutJoppa1">What can you tell me about Joppa?</choice>
    <choice Target="End">
      <choice GotoID="LookingForWork1">I am in search of work.</choice>
       <text>Salt responsibly, friend.</text>
      <choice GotoID="End">Live and drink.</choice>
       <part Name="ReceiveItem" Blueprints="EmptyWaterskin" /> <!-- Registers for Listener events -->
    </node>
     </choice>
     <node ID="Start" IfHaveQuest="What's Eating the Watervine?" IfNotFinishedQuest="What's Eating the Watervine?">
  </start>
      <text>
</conversation>
Live and drink, =player.formalAddressTerm=. Have you tidings from Red Rock?
      </text>
      <choice GotoID="FinishExploringRedrock1" IfHasBlueprint="Girshling Corpse">Yes. I found bits of gnawed watervine and slew a white spiderling. I carry its corpse with me.</choice>
      <choice GotoID="End">I'm working on it, =pronouns.personTerm=! Live and drink.</choice>
    </node>
     <node ID="Start" IfHaveQuest="What's Eating the Watervine?" IfFinishedQuest="What's Eating the Watervine?">
      <text>
Live and drink, =player.formalAddressTerm=. May you find shade in Joppa.</text>
      <choice GotoID="AboutJoppa2">What can you tell me about Joppa?</choice>
      <choice GotoID="End">Live and drink.</choice>
    </node>
     <node ID="FinishExploringRedrock1">
      <text>
What a hideous thing! I dread the horrors its presence portends. Bring the
corpse to Elder Irudad's hut for the Elder to examine.
      </text>
      <choice GotoID="End">As you say.</choice>
    </node>
    <node ID="LookingForWork1">
       <text>
Some critters are eating our watervine. Faarooq claims he saw one slinking around a vine patch. Ugly little thing, he says; pale white, eight legs, an ear-splitting whine.
 
I noticed a bit of red dirt in the watervine pool, the same we find in the soil at a nearby &amp;Ycave to the north&amp;y we call &amp;rRed Rock.&amp;y
 
Travel to &amp;rRed Rock&amp;y and kill as many of these critters as you can. Bring back the corpse of one, too. &amp;GElder Irudad&amp;y will reward your efforts.
</text>
       <choice GotoID="End" StartQuest="What's Eating the Watervine?">I will do as you ask.</choice>
      <choice GotoID="Start">I will perform no such peasant's task.</choice>
    </node>
    <node ID="AboutJoppa1">
      <text>
You would be wise to speak with &amp;GElder Irudad.&amp;y Look for his hut to the north.
    </text>
      <choice GotoID="LookingForWork1" IfNotFinishedQuest="What's Eating the Watervine?">I am in search of work.</choice>
      <choice GotoID="End">Live and drink.</choice>
    </node>
     <node ID="AboutJoppa2">
      <text>
You would be wise to speak with &amp;GElder Irudad.&amp;y Look for his hut to the north.
</text>
      <choice GotoID="End">Live and drink.</choice>
    </node>
  </conversation>
</conversations>
</syntaxhighlight>
</syntaxhighlight>


{{Modding Navbox}}
==Tables==
Below will be some non-exhaustive tables with descriptions of existing parts, events and delegates if you check back in a bit.

Revision as of 00:59, 5 February 2022

This page is about modding. See the modding overview for an abstract on modding.
This page is about modding. See the modding overview for an abstract on modding.

Conversations are trees of XML loaded from Conversations.xml and usually executed from a ConversationScript part on a game object. The most common elements are the Node and the Choice: a node is a piece of text spoken by the creature you're interacting with, coupled with a list of choices for the player to respond with. This usually takes you to another node where the cycle repeats.

For extensive conversation design in mods that use a lot of conversations, some modders have recommended using a tool such as Twine to map out your conversation logic.

Adding a Conversation

In order to be conversable, an object should have a ConversationScript part and define a ConversationID which references a conversation template of the same ID in Conversations.xml.

A barebones definition might look like this for a lovely snapjaw.

<!-- ObjectBlueprints.xml-->
<object Name="Snapjaw Pal" Inherits="Snapjaw">
  <part Name="ConversationScript" ConversationID="FriendlySnapjaw" />
</object>

<!-- Conversations.xml-->
<conversation ID="FriendlySnapjaw">
  <start ID="Welcome">
    <text>ehekehe. gn. welcom.</text>
    <choice Target="LibDink">Thank you.</choice>
  </start>
  <node ID="LibDink">
    <text>hrffff... lib? dink?</text>
    <text>nyeh. heh! friemd?</text>
    <choice Target="End">Live and drink.</choice>
  </node>
</conversation>

XML Tags

These are the basic tags supported by conversations, not including any custom elements used by parts.

XML Tag Description
<conversation> Single conversation template typically containing <node> and <start> elements, linked to a ConversationScript via its ID.
<node> Collection of <text> from the Speaker's point of view, along with a range of <choice> for the Player to respond with.
<start> Special variant of <node> that can be selected when starting a conversation.
For backwards compatibility, a <node> with an ID of "Start" will behave similarly.
<choice> Collection of <text> from the Player's point of view, commonly defines a Target <node> to navigate to if selected.
The Target attribute has two special values: Start and End, which will return to the beginning of the conversation or end it, respectively.
For backwards compatibility, the GotoID attribute will behave similarly to Target.
<text> Contains a block of text to display for an element, multiple of these can be defined and randomly selected from if valid.
<part> Reference to a C# class that inherits from IConversationPart.
Any attributes defined here will be inserted into the fields & properties of the part, if possible.
Anything defined as a child element of the part can be loaded with custom C# behavior.

Merging

This section contains modding information that is only applicable to the in-development beta branch of Caves of Qud on Steam.

If multiple elements with the same ID are defined within the same scope, a merge will occur by default where the properties of the later element overwrite the former.
If an explicit ID isn't defined, one will be created based on other attributes.
You can alter the conflict behavior of an element by setting a Load attribute with valid values of: "Merge", "Replace", "Add", or "Remove".

<conversation ID="FriendlySnapjaw">
  <node ID="SnappyNoise">
    <text>gnnnnnnn.</text> <!-- ID is "Text" -->
    <text>beh. mmmf.</text> <!-- ID is "Text2" -->
    <text>mmnnn!</text> <!-- ID is "Text3" -->
    <choice Target="LibDink">Thank you.</choice>  <!-- ID is "LibDinkChoice" -->
  </node>
</conversation>

<conversation ID="FriendlySnapjaw"> <!-- Will merge with above conversation -->
  <node ID="SnappyNoise">  <!-- Will merge with "SnappyNoise" node -->
    <text>gra! gra! gra!</text> <!-- ID is "Text" and will merge -->
    <text Cardinal="3">gra! gra! gra!</text> <!-- ID is "Text3" and will merge -->
    <choice Target="End">Live and drink.</choice> <!-- ID is "EndChoice" and will not merge -->
  </node>
</conversation>

Inheritance

This section contains modding information that is only applicable to the in-development beta branch of Caves of Qud on Steam.

In cases where you'd like your elements to appear in multiple places, you can inherit their properties with the Inherits attribute.
The attribute can also take a comma separated list, meaning you can inherit and merge the properties of multiple parent elements together.
Unlike merging, the properties of the current element have precedence over those it is inheriting from.

<conversation ID="FriendlySnapjaw">
  <start ID="SnappyNoise">
    <text>gnnnnnnn.</text>
    <choice Target="LibDink">Thank you.</choice>
  </start>
</conversation>

<conversation ID="ExcitedSnapjaw" Inherits="FriendlySnapjaw"> <!-- Inherits SnappyNoise -->
  <node ID="SnappyBye">
    <text>gra! gra! gra!</text>
    <choice Target="End">Live and drink.</choice>
  </node>
</conversation>

<conversation ID="AngryArconaut">
  <start ID="Grumpy">
    <text>I hate things.</text>
    <choice Inherits="ExcitedSnapjaw.SnappyBye.EndChoice" /> <!-- Inherits "Live and drink." -->
  </start>
</conversation>

Distribution

This section contains modding information that is only applicable to the in-development beta branch of Caves of Qud on Steam.

An alternative to explicitly inheriting elements where you'd like them repeated is distribution, where you specify directly on the element where it should propagate.
The Distribute attribute normally takes a list of element types, but if Qualifier="ID" is specified, a list of IDs can be provided.
Choices that are defined as children under a conversation will propagate to all start nodes by default.

<conversation ID="FriendlySnapjaw">
  <start ID="SnappyHello">
    <text>heeeelo!</text>
  </start>
  <start ID="SnappyNoise">
    <text>gnnnnnnn.</text>
  </start>
  <choice Target="End">Live and drink.</choice> <!-- Added to both start nodes -->
  <choice GiveItem="Dagger" Distribute="SnappyNoise" Qualifier="ID">It is time to grill cheese.</choice>
</conversation>

Delegates

Unique to conversations are their delegate attributes such as IfHaveQuest="What's Eating the Watervine?" or GiveItem="Joppa Recoiler".
These are distinguished between two types (there's a secret third option explained later): Predicates which control whether an element is accessible, and Actions which perform some task when the element is selected.
After the Deep Jungle update these are now for the most part agnostic as to what their parent element is.

<conversation ID="FriendlySnapjaw">
  <start ID="FurFriend" IfHavePart="ThickFur"> <!-- Hidden if player doesn't have thick fur -->
    <text>ooohh. pretty...</text>
    <text IfReputationAtLeast="Loved">deheh. like you. hohohoho.</text> <!-- Hidden if not Loved by speaker's faction -->
    <choice Target="End" IfReputationAtLeast="Loved" GiveItem="Dagger">I like you too.</choice> <!-- Gives the player a dagger if selected-->
    <choice Target="End">Thank you.</choice>
  </start>
</conversation>

Custom Delegates

This section contains modding information that is only applicable to the in-development beta branch of Caves of Qud on Steam.

It's possible to add your own delegates for you to use in XML by adding a [ConversationDelegate] attribute to a static method in C#.
Depending on the return type it will either be registered as a predicate or action, and variants of the delegate will automatically be created.

For example the below delegate will automatically create the inversion IfNotHaveItem, and because we set the Speaker attribute parameter, another two (IfSpeakerHaveItem, IfSpeakerNotHaveItem) where Context.Target holds the Speaker instead of the Player.

[HasConversationDelegate] // This is required on the surrounding class to reduce the search complexity.
public static class DelegateContainer
{
  // A predicate that receives a DelegateContext object with our values assigned, this to protect mods from signature breaks.
  [ConversationDelegate(Speaker = true)]
  public static bool IfHaveItem(DelegateContext Context)
  {
    // Context.Value holds the quoted value from the XML attribute.
    // Context.Target holds the game object.
    // Context.Element holds the parent element.
    return Context.Target.HasObjectInInventory(Context.Value);
  }
}


Parts

This section contains modding information that is only applicable to the in-development beta branch of Caves of Qud on Steam.

For more advanced or specific logic not easily reduced to a generally accessible delegate, a custom part is preferred.
Similar to their equivalent for objects in `ObjectBlueprints.xml`, parts define custom behaviour for elements within and can be attached to most any element just like delegates.
If you use a period within the part's name, it's assumed you are specifying your own namespace and won't be required to place your part within XRL.World.Conversations.Parts.

<conversation ID="JoppaZealot">
  <part Name="SpiceContext" />
  <start ID="OrphanOfTheSalt">
    <text>
      Blah! Orphan of the salt! Blooh!
      <part Name="TextInsert" Spoken="false" NewLines="2" Text="[Press Tab or T to open trade]" />
    </text>
    <choice Target="End">
      <text>You intrigue me. I will go to the Six Day Stilt for no particular reason.</text>
      <part Name="QuestHandler" QuestID="O Glorious Shekhinah!" Action="Start" />
    </choice>
  </start>
</conversation>

A very basic C# implementation of a part that adds a laugh to any text it's added to might look like this.

public class SnapjawLaugh : IConversationPart
{
    public override bool WantEvent(int ID, int Propagation)
    {
        return base.WantEvent(ID, Propagation)
               || ID == PrepareTextEvent.ID
            ;
    }

    public override bool HandleEvent(PrepareTextEvent E)
    {
        E.Text.Append("\n\nehehehehe!");
        return base.HandleEvent(E);
    }
}

Events

This section contains modding information that is only applicable to the in-development beta branch of Caves of Qud on Steam.

Conversations have their own set of events to handle, but should be immediately familiar to anyone that has tampered with the Minimal Events of game objects.

Unlike min events which cascade down, conversation events will propagate up the element tree from where it was fired (See event bubbling). This means an event fired on a choice will first be handled by parts on the choice itself, then its parent node, last the node's conversation.

Finally the propagation is separated by perspective, Speaker and Listener (the listener being you, the player). In most cases when you attach a part that modifiers the text of a node, you do not want to also modify the text of its underlying choices since those are spoken by a different entity. By default parts will register for the perspective they are placed in, but can be overriden with the Register attribute.

<conversation ID="EventfulSnapjaw">
  <part Name="SpiceContext" Register="All" /> <!-- Registers for both Speaker events by default, but overrides it -->
  <start ID="TasterOfTheSalt">
    <part Name="SnapjawLaugh" /> <!-- Registers for Speaker events -->
    <text>mmmg. salt.</text>
    <text>tasty.</text>
    <choice Target="End">
      <text>Salt responsibly, friend.</text>
      <part Name="ReceiveItem" Blueprints="EmptyWaterskin" /> <!-- Registers for Listener events -->
    </choice>
  </start>
</conversation>

Tables

Below will be some non-exhaustive tables with descriptions of existing parts, events and delegates if you check back in a bit.