Modding:Conversations: Difference between revisions
Jump to navigation
Jump to search
m (fix typos) |
(Conversation modding for 203.11) |
||
Line 1: | Line 1: | ||
{{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. | |||
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. | |||
== 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. | |||
== | A barebones definition might look like this for a lovely snapjaw. | ||
<syntaxhighlight lang="xml"> | |||
<!-- 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> | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== | === XML Tags === | ||
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. | |||
|- | |||
|} | |||
== | === Merging === | ||
{{Betamoddingcontent}} | |||
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> | |||
== | <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> | |||
</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> | |||
=== | <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> | |||
</syntaxhighlight> | |||
==== | === Distribution === | ||
{{Betamoddingcontent}} | |||
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> | |||
==== | == 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/> | |||
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. | |||
=== | <syntaxhighlight lang="xml"> | ||
<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> | |||
=== 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. | |||
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. | |||
<syntaxhighlight lang="C#"> | |||
[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); | |||
} | |||
} | |||
</syntaxhighlight> | |||
==== | == Parts == | ||
{{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 | |||
; | |||
} | |||
public override bool HandleEvent(PrepareTextEvent E) | |||
{ | |||
E.Text.Append("\n\nehehehehe!"); | |||
return base.HandleEvent(E); | |||
} | |||
} | |||
</syntaxhighlight> | |||
=== | === Events === | ||
{{Betamoddingcontent}} | |||
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. | |||
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. | |||
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. | |||
<syntaxhighlight lang="xml"> | <syntaxhighlight lang="xml"> | ||
<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> | |||
<text> | |||
</text> | |||
< | |||
</syntaxhighlight> | </syntaxhighlight> | ||
==Tables== | |||
Below will be some non-exhaustive tables with descriptions of existing parts, events and delegates if you check back in a bit. |