Modding:Conversations: Difference between revisions

From Caves of Qud Wiki
Jump to navigation Jump to search
m (not quite time yet)
(Add delegate table)
Line 128: Line 128:
== Delegates ==
== Delegates ==
Unique to conversations are their delegate attributes such as <code>IfHaveQuest="What's Eating the Watervine?"</code> or <code>GiveItem="Joppa Recoiler"</code>.<br/>
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: Predicates which control whether an element is accessible, and Actions which perform some task when the element is selected.<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.
After the Deep Jungle update these are now for the most part agnostic as to what their parent element is.


Line 228: Line 228:


==Tables==
==Tables==
Below will be some non-exhaustive tables with descriptions of existing parts, events and delegates if you check back in a bit.
Below are non-exhaustive tables of existing parts, events and delegates.
===Delegates===
An Inverse predicate can be invoked with <code>IfNot</code> to negate its value.<br />
A Speaker delegate can be invoked with <code>IfSpeaker</code> to target the speaker game object.<br />
If both are applicable then it can also be invoked with <code>IfNotSpeaker</code>.<br />
{| class="wikitable"
|-
! Name
! Type
! Description
! Inverse
! Speaker
|-
| IfHaveQuest
| Predicate
| Continue if the player has an active or finished quest by specified ID.
| Yes
| No
|-
| IfHaveActiveQuest
| Predicate
| Continue if the player has an active quest by specified ID.
| Yes
| No
|-
| IfFinishedQuest
| Predicate
| Continue if the player has a finished quest by specified ID.
| Yes
| No
|-
| IfFinishedQuestStep
| Predicate
| Takes a '~' separated value of "Quest ID~Step ID" and checks if the step is completed.
| Yes
| No
|-
| IfHaveObservation
| Predicate
| Continue if the player knows of any gossip or lore with the specified ID.
| Yes
| No
|-
| IfHaveObservationWithTag
| Predicate
| Continue if the player knows of any gossip or lore with the specified tag.
| Yes
| No
|-
| IfHaveSultanNoteWithTag
| Predicate
| Continue if the player knows any history of a sultan with the specified tag.
| Yes
| No
|-
| IfHaveVillageNote
| Predicate
| Continue if the player knows any history of a village with the specified ID.
| Yes
| No
|-
| IfHaveState
| Predicate
| Continue if any global game state has been set by specified ID.
| Yes
| No
|-
| IfTestState
| Predicate
| Evaluates an expression of format "ID Operator Value", for example "SlynthSettlementFaction = Joppa", comparing the global game state to the specified value.
| Yes
| No
|-
| IfLastChoice
| Predicate
| Continue if the last selected choice in this conversation has the specified ID.
| Yes
| No
|-
| IfCommand
| Predicate
| Fires an event on the element with the specified value as its command, continue if the result is set true by a consuming part.
| Yes
| No
|-
| IfReputationAtLeast
| Predicate
| Continue if the player has the specified reputation level or higher, valid entries are "Loved", "Liked", "Indifferent", "Disliked", and "Hated".
| Yes
| No
|-
| IfIn100
| Predicate
| Continues if the randomly rolled value is below or equal to the specified value.
| No
| No
|-
| IfGenotype
| Predicate
| Continues if the target is of the specified genotype.
| Yes
| Yes
|-
| IfSubtype
| Predicate
| Continues if the target is of the specified genotype.
| Yes
| Yes
|-
| IfTrueKin
| Predicate
| Continues if the target counts as a true kin and can implant cybernetics.
| Yes
| Yes
|-
| IfMutant
| Predicate
| Continues if the target counts as a mutant and can gain mutations.
| Yes
| Yes
|-
| IfHaveItem
| Predicate
| Continues if the target has an item of the specified blueprint in their inventory or equipped on their body.
| Yes
| Yes
|-
| IfWearingBlueprint
| Predicate
| Continues if the target has an item of the specified blueprint equipped on their body.
| Yes
| Yes
|-
| IfHaveBlueprint
| Predicate
| Continues if the target has an item of the specified blueprint in their inventory.
| Yes
| Yes
|-
| IfHavePart
| Predicate
| Continues if the target has a part by the specified class name. Mutations are a variant of a part that this is applicable to.
| Yes
| Yes
|-
| IfHaveTag
| Predicate
| Continues if the target's blueprint has the specified tag.
| Yes
| Yes
|-
| IfHaveProperty
| Predicate
| Continues if the target game object has the specified property.
| Yes
| Yes
|-
| IfHaveTagOrProperty
| Predicate
| Continues if the target has the specified tag or property.
| Yes
| Yes
|-
| IfLevelLessOrEqual
| Predicate
| Continues if the target is at or below the specified value.
| Yes
| Yes
|-
| FinishQuest
| Action
| Marks a quest by the specified ID as finished. This will not complete all the quest's steps.
| No
| No
|-
| FireEvent
| Action
| Takes a comma separated list of "EventID,Parameter1,Parameter2,Para..." and fires a constructed event on the target game object.
| No
| Yes
|-
| FireSystemsEvent
| Action
| Takes a comma separated list of "EventID,Parameter1,Parameter2,Para..." and fires a constructed event to all game systems.
| No
| No
|-
| SetStringState
| Action
| Takes a comma separated list of "GameStateID,Value" and sets the global game state to the specified value. If no value is specified the state is removed.
| No
| No
|-
| SetIntState
| Action
| Takes a comma separated list of "GameStateID,Value" and sets the global game state to the specified value. If no value is specified the state is removed.
| No
| No
|-
| AddIntState
| Action
| Takes a comma separated list of "GameStateID,Value" and adds the specified value to the global game state's value.
| No
| No
|-
| SetBooleanState
| Action
| Takes a comma separated list of "GameStateID,Value" and sets the global game state to the specified value. If no value is specified the state is removed.
| No
| No
|-
| ToggleBooleanState
| Action
| Toggles the boolean value of the global game state by specified ID.
| No
| No
|-
| SetStringProperty
| Action
| Sets a string property on the target game object.
| No
| Yes
|-
| SetIntProperty
| Action
| Sets an int property on the target game object.
| No
| Yes
|-
| RevealObservation
| Action
| Reveals a piece of gossip or lore by the specified ID.
| No
| No
|-
| StartQuest
| Part Generator
| Adds a QuestHandler part to the parent element with the Start action and specified QuestID.
| No
| No
|-
| CompleteQuestStep
| Part Generator
| Adds a QuestHandler part to the parent element with the Step action and specified "QuestID~StepID".
| No
| No
|-
| GiveItem
| Part Generator
| Adds a ReceiveItem part to the parent element with the specified comma separated blueprints.
| No
| No
|-
| TakeItem
| Part Generator
| Adds a TakeItem part to the parent element with the specified comma separated blueprints.
| No
| No
|}
 
 
{{Modding Navbox}}

Revision as of 03:23, 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 are non-exhaustive tables of existing parts, events and delegates.

Delegates

An Inverse predicate can be invoked with IfNot to negate its value.
A Speaker delegate can be invoked with IfSpeaker to target the speaker game object.
If both are applicable then it can also be invoked with IfNotSpeaker.

Name Type Description Inverse Speaker
IfHaveQuest Predicate Continue if the player has an active or finished quest by specified ID. Yes No
IfHaveActiveQuest Predicate Continue if the player has an active quest by specified ID. Yes No
IfFinishedQuest Predicate Continue if the player has a finished quest by specified ID. Yes No
IfFinishedQuestStep Predicate Takes a '~' separated value of "Quest ID~Step ID" and checks if the step is completed. Yes No
IfHaveObservation Predicate Continue if the player knows of any gossip or lore with the specified ID. Yes No
IfHaveObservationWithTag Predicate Continue if the player knows of any gossip or lore with the specified tag. Yes No
IfHaveSultanNoteWithTag Predicate Continue if the player knows any history of a sultan with the specified tag. Yes No
IfHaveVillageNote Predicate Continue if the player knows any history of a village with the specified ID. Yes No
IfHaveState Predicate Continue if any global game state has been set by specified ID. Yes No
IfTestState Predicate Evaluates an expression of format "ID Operator Value", for example "SlynthSettlementFaction = Joppa", comparing the global game state to the specified value. Yes No
IfLastChoice Predicate Continue if the last selected choice in this conversation has the specified ID. Yes No
IfCommand Predicate Fires an event on the element with the specified value as its command, continue if the result is set true by a consuming part. Yes No
IfReputationAtLeast Predicate Continue if the player has the specified reputation level or higher, valid entries are "Loved", "Liked", "Indifferent", "Disliked", and "Hated". Yes No
IfIn100 Predicate Continues if the randomly rolled value is below or equal to the specified value. No No
IfGenotype Predicate Continues if the target is of the specified genotype. Yes Yes
IfSubtype Predicate Continues if the target is of the specified genotype. Yes Yes
IfTrueKin Predicate Continues if the target counts as a true kin and can implant cybernetics. Yes Yes
IfMutant Predicate Continues if the target counts as a mutant and can gain mutations. Yes Yes
IfHaveItem Predicate Continues if the target has an item of the specified blueprint in their inventory or equipped on their body. Yes Yes
IfWearingBlueprint Predicate Continues if the target has an item of the specified blueprint equipped on their body. Yes Yes
IfHaveBlueprint Predicate Continues if the target has an item of the specified blueprint in their inventory. Yes Yes
IfHavePart Predicate Continues if the target has a part by the specified class name. Mutations are a variant of a part that this is applicable to. Yes Yes
IfHaveTag Predicate Continues if the target's blueprint has the specified tag. Yes Yes
IfHaveProperty Predicate Continues if the target game object has the specified property. Yes Yes
IfHaveTagOrProperty Predicate Continues if the target has the specified tag or property. Yes Yes
IfLevelLessOrEqual Predicate Continues if the target is at or below the specified value. Yes Yes
FinishQuest Action Marks a quest by the specified ID as finished. This will not complete all the quest's steps. No No
FireEvent Action Takes a comma separated list of "EventID,Parameter1,Parameter2,Para..." and fires a constructed event on the target game object. No Yes
FireSystemsEvent Action Takes a comma separated list of "EventID,Parameter1,Parameter2,Para..." and fires a constructed event to all game systems. No No
SetStringState Action Takes a comma separated list of "GameStateID,Value" and sets the global game state to the specified value. If no value is specified the state is removed. No No
SetIntState Action Takes a comma separated list of "GameStateID,Value" and sets the global game state to the specified value. If no value is specified the state is removed. No No
AddIntState Action Takes a comma separated list of "GameStateID,Value" and adds the specified value to the global game state's value. No No
SetBooleanState Action Takes a comma separated list of "GameStateID,Value" and sets the global game state to the specified value. If no value is specified the state is removed. No No
ToggleBooleanState Action Toggles the boolean value of the global game state by specified ID. No No
SetStringProperty Action Sets a string property on the target game object. No Yes
SetIntProperty Action Sets an int property on the target game object. No Yes
RevealObservation Action Reveals a piece of gossip or lore by the specified ID. No No
StartQuest Part Generator Adds a QuestHandler part to the parent element with the Start action and specified QuestID. No No
CompleteQuestStep Part Generator Adds a QuestHandler part to the parent element with the Step action and specified "QuestID~StepID". No No
GiveItem Part Generator Adds a ReceiveItem part to the parent element with the specified comma separated blueprints. No No
TakeItem Part Generator Adds a TakeItem part to the parent element with the specified comma separated blueprints. No No