Modding:Conversations: Difference between revisions

6,090 bytes added ,  02:49, 1 June 2019
no edit summary
(methods for ConversationNode added)
No edit summary
Line 15: Line 15:
   </object>
   </object>
</syntaxhighlight>
</syntaxhighlight>
== ConversationScript configuration ==
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 wether 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()
== XRL.UI.ConversationUI.HaveConversation() ==
This is the main loop that handles the conversation start / end and setup.
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.
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>
Then 3 events will fire in a row - all 3 will cancel the conversation if "false" is returned.
=== Event: <code>BeginConversation</code> ===
Fired on: "Speaker" (not the player)
Parameters: <Conversation>"Conversation" and <GameObject>"With" pointing to the player.
=== Event: <code>PlayerBeginConversation</code> ===
Fired on: "Player"
Parameters: <Conversation>"Conversation" and <GameObject>"Speaker" pointing to the person to be talked to.
=== Event: <code>ObjectTalking</code> ===
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 some time be fired after.
=== Core Conversation Loop ===
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.
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.
==== Event: ShowConversationChoices ====
Fired on "Speaker"
Parameters:
* <code><List<ConversationChoice>>Choices</code> - can set this parameter as well as read to "extend" the choices available in a node.
* <code><ConversationNode>CurrentNode</code> - the current conversation node.
* <code><ConversationNode>firstNode</code> - the start node for conversation. (lowercase f intentional)
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>.
==== Building the message ====
After this event fires, the current menu will render its text.  The conversation itself has a <code>Conversation.Introduction</code> which will prepend the nodes message if it is set, and after it is displayed, the value will be reset to <code>""</code> - showing the "intro" only once.  We take the <code>ConversationNode.Text</code> and select from it's random selections (<code>~</code> separated) and apply the Variable Replacement to it, then prepend intro, and affix a TradeNote (if enabled on the node)  This is the conversation's content and the choices are displayed.
=== Picking a Choice ===
==== Call: ConversationChoice.Visit(Speaker, Player) ====
When the choice is selected, we call ConversationChoice.Visit() -- it can to 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) ====
<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 ====
Fired on: Speaker
No Parameters


== Conversation Classes and XML properties ==
== Conversation Classes and XML properties ==
Line 20: Line 105:
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.
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.


=== Nodes: &lt;conversation> Deserialized: XRL.World.Conversation ===
=== XRL.World.Conversation XML Node: &lt;conversation> ===
{| class="wrapped wikitable tablesorter tablesorter-default stickyTableHeaders" role="grid" resolved="" style="padding: 0px;"
{| class="wrapped wikitable tablesorter tablesorter-default stickyTableHeaders" role="grid" resolved="" style="padding: 0px;"
|-
|-
Line 53: Line 138:
| colspan="1" class="confluenceTd"|ID
| colspan="1" class="confluenceTd"|ID
| colspan="1" class="confluenceTd"|string / required / "key"
| 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 other boolean filter parameters.
| 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"
|- role="row"
| colspan="1" class="confluenceTd"|TradeNote
| colspan="1" class="confluenceTd"|TradeNote
Line 62: Line 147:
| colspan="1" class="confluenceTd"|boolean
| 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.
| 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"
|- role="header"
! colspan="3" class="confluenceTd"|Visit node triggers
! colspan="3" class="confluenceTd"|Visit node triggers
Line 145: Line 238:
* <code>IsMapNoteRevealed:MapNoteID</code> : <code>Qud.API.JournalAPI.IsMapOrVillageNoteRevealed( SpecialRequirement.Split(':')[1] )</code>
* <code>IsMapNoteRevealed:MapNoteID</code> : <code>Qud.API.JournalAPI.IsMapOrVillageNoteRevealed( SpecialRequirement.Split(':')[1] )</code>
* <code>!IsMapNoteRevealed:MapNoteID</code> : NOT of the above
* <code>!IsMapNoteRevealed:MapNoteID</code> : NOT of the above
|- role="row"
| colspan="1" class="confluenceTd"|Choices
| colspan="1" class="confluenceTd"|List<ConversationChoice> (C# only)
| colspan="1" class="confluenceTd"|The `<choice>` nodes below this node from the XML.
|- role="header"
|- role="header"
! colspan="3" class="confluenceTd"|C# Methods
! colspan="3" class="confluenceTd"|C# Methods