Coding Help with Conversation System

Twine Version: 2.36.1

Hi I’m trying to build a ‘conversation’ system that, when the player clicks on someone to talk to, displays a random conversation chosen from an array. The problem I’m having, is that I’d like to implement variables into the system so that some conversations are only available if those variables are true, e.g., if the $time is “morning” or relationship levels are high enough.

I’d like it to either not be available to have the conversation at all (so the player cannot click the link to talk) or re-randomise to have a different, valid conversation from the array (if there are no conversations left in the array then show a generic message).

Here is my current code:
In StoryInit:

<<set $james to {
  relationship: 5,
  talk: ["james talk 1", "james talk 2"]
 }
>>

In Passage:

<<if $james.talk.length > 0>>
<<set _conversation to $james.talk.random()>>

<<if _conversation is "james talk 1">>
Blah blah blah...
<<set $james.talk.delete("james talk 1">>
<</if>>

<<if _conversation is "james talk  2">>
Blah blah blah...
<<set $james.talk.delete("james talk 2">>
<</if>>

That all works well enough, but here is the code where I try to implement variables:

<<if _conversation is "james talk 1" and $time is "morning">>
...
<<set $james.talk.delete("james talk 1")>>

<<elseif _conversation is "james talk 1" and $time isnot "morning>>
<<set _conversation to $james.talk.random()>>
<<goto "talk to james">>

<</if>>

<<if _conversation is "james talk 2" and $james.relationship gte 7>>
...
<<set $james.talk.delete("james talk 2")>>

<<elseif _conversation is "james talk 2" and $james.relationship lt 7>>
<<set _conversation to $james.talk.random()>>
<<goto "talk to james">>

<</if>>

These two don’t work, but I’m not sure how to achieve what I want.

Any help is greatly appreciated!

1 Like

Don’t forget to close the parenthesis :wink:

<<set $james.talk.delete("james talk 1")>>

Also, in your first example, you are missing a closing <</if>>

As well, you have an extra space in _conversation is "james talk 2", between talk and 2, which breaks the conditional statement.

If you set a temporary variable and move the player to a new passage, the temporary variable will delete itself (because it’s temporary, it doesn’t save between passages like a regular variable). If you change _ for $ it should work.

Don’t forget the closing quotes here :wink:

1 Like

Oh wow, I had to rewrite the code here to remove alot of the extra stuff, didn’t realise I made so many mistakes :sweat_smile:

I changed _conversation to $conversation, but it doesn’t seem to be working. It just shows a blank page :cry:

1 Like

Do you mean the passage called talk to james or the one with this codeblock:

Are you certain $time is set properly? otherwise nothing will appear)

1 Like

I just tried pasting this into a new twine and it worked fine with these notes:

  • the passage will be blank if you don’t have <<set $time to "morning">> or similar, or up the “relationship” stat to 7 before this passage because none of the if conditions are fulfilled;
  • it’s redundant to check $time is not “morning” in the first elseif, or the relationship stat in the second;
  • since you re-set _conversation at the top of the passage you don’t need to do so before the gotos.

IE if you write as below you do get randomly one or the other, and you can close in options by changing the settings of relationship or $time.

:: StoryInit
<<set $james to {
  relationship: 7,
  talk: ["james talk 1", "james talk 2"]
 }
>>

<<set $time to "morning">>

:: talk to james
<<if $james.talk.length > 0>>
<<set _conversation to $james.talk.random()>>
<</if>>

<<if _conversation is "james talk 1" and $time is "morning">>
James talk 1 - it's morning
<<set $james.talk.delete("james talk 1")>>
<<elseif _conversation is "james talk 1">>
<<goto "talk to james">>
<</if>>

<<if _conversation is "james talk 2" and $james.relationship gte 7>>
James talk 2 - relationship gte 7
<<set $james.talk.delete("james talk 2")>>
<<elseif _conversation is "james talk 2">>
<<goto "talk to james">>
<</if>>

I have a slightly more sophisticated version of this kind of conversation system which someone on the Twine discord shared with me - I’ll dig it out later or you could ask on there.

1 Like

The talk to james passage is the same passage with the codeblock, and I’m pretty sure everything is set properly. It seems like it keeps looping over and over again, as the passage history on the bottom right keeps on re-visiting the passage.

1 Like

It does work when $time is set to morning, but if it’s anything else, like afternoon or night (and there are no other choices left in the array), then it does that infinity looping back to the passage I described above. I’m not sure what’s wrong! :cry:

And that other version you mentioned sounds great. Even if I’m unable to understand it (I’m very new of course :sweat_smile:) it would be nice to see a fully functioning system. If you’re able to find it or link it to me, I’m very interested! Thank you.

1 Like

You need to have <<if $time is…>> etc in there. As it stands, you get an infinite loop if none of the if statements are true, because the passage reloads. (goto itself)

Here’s the code I mentioned it’s by user SleepyFool on the twine discord. I use a version of this in my game Sonnet. It has some notes and I can try to come back this evening and explain -

:: farmRoad
A strange man is pantomiming in front of an elderly couple who seem very annoyed.

<!-- stores things spoken about -->
<<set $topics = []>>
<!-- output for conversation -->
<div id="converse_output"></div>
<!-- links -->
<div id="converse_links"><<converse_start>></div>


:: widget [widget]
<!-- widget, outputs people links -->
<<widget "converse_start">>
  <!-- link to speak w/ mime -->
  <<link "Speak w/ the mime">>
    <!-- update convo links to mime links -->
    <<replace "#converse_links">><<converse_mime>><</replace>>
  <</link>>
  <!-- link to speak w/man -->
  <<link "Speak w/ the man">>
    <<replace "#converse_links">><<converse_man>><</replace>>
  <</link>>
  <!-- link to speak w/ woman-->
  <<link "Speak w/ the woman>>
    <<replace "#converse_links">><<converse_woman>><</replace>>
  <</link>>
<</widget>>

<!-- widget, outputs mime links -->
<<widget "converse_mime">>
  <!-- topic link, "roadblock" used as identifier, hidden if selected before -->
  <<if ! $topics.includes("roadblock")>>
    <<link "Ask the mime why he's blocking the road">>
      <!-- update conversation tracker, $topics -->
      <<run $topics.pushUnique("roadblock")>>
      <!-- add what happens when you select this option -->
      <<append "#converse_output">><<include "roadblock">><</append>>
      <!-- update links, replace container w/ itself -->
      <<replace "#converse_links">><<converse_mime>><</replace>>
    <</link>>
  <</if>>
  <!-- next topic link, "clothing" used as identifier -->
  <<if ! $topics.includes("clothing")>>
    ...same as above, except using "clothing"
  <</if>>
  <!-- finally, include link to return to other people -->
  <<link "Back">>
    <!-- replace links w/ conversation start again -->
    <<replace "#converse_links">><<converse_start>><</replace>>
  <</link>>
<</widget>>
1 Like

Hi thank you for the code. I’ve tried going through it and… yea those notes would be very helpful :sweat_smile: In the meantime though, could you please tell me where to specifically have another <<if $time is…>> in my code? I’m not sure where or what to do as it keeps looping still…

Or if anyone else sees this, any help is appreciated!

So thinking about this passage of your code in my slightly edited version:


:: talk to james
<<if $james.talk.length > 0>>
<<set _conversation to $james.talk.random()>>
<</if>>

<<if _conversation is "james talk 1" and $time is "morning">>
James talk 1 - it's morning
<<set $james.talk.delete("james talk 1")>>
<<elseif _conversation is "james talk 1">>
<<goto "talk to james">>
<</if>>

<<if _conversation is "james talk 2" and $james.relationship gte 7>>
James talk 2 - relationship gte 7
<<set $james.talk.delete("james talk 2")>>
<<elseif _conversation is "james talk 2">>
<<goto "talk to james">>
<</if>>

The first block sets _conversation to either “james talk 1” or “james talk 2”.

The next block, starting <<if _conversation is "james talk 1" and $time is "morning">>, resolves as true if the first block has set _conversation to “james talk 1” AND $time is set to “morning”, and if so it then produces whatever text is here (currently “James talk 1 - it’s morning”), deletes “james talk 1” as an option for _conversation (so you won’t get the same piece twice) and then re-runs the passage.
The elseif option here just reruns the passage if $time is not “morning”.

The third block, similarly, is true if _conversation is “james talk 2” AND $james.relationship is 7 or more, and so on.

As it stands, if $time is “morning” and $james.relationship is 6 (for example) it will eventually produce the first outcome because it keeps re-running the random selection until it gets “james talk 1”. If $time is not morning and $james.relationship is 7, you will eventually get the second outcome.

If both are true - for example, if $time is “morning” and $james.relationship is 8 - then it is random - you’ll get block one if _conversation randomises to “james talk 1” and block two if “james talk 2”.

BUT, if neither of these can be true - for example, if $time is “evening” and $james.relationship is 5 - then the passage will keep re-running forever producing no text because neither block can resolve, and both re-run the passage if they don’t.

Does that make sense?

If you want it to work when $time is “evening”, you need something to happen when that’s the case, so for example, you could add a new block:

<<if _conversation is "james talk 1" and $time is "evening">>
James talk 1 - it's evening
<<set $james.talk.delete("james talk 1")>>
<<elseif _conversation is "james talk 1">>
<<goto "talk to james">>
<</if>>

If you want to have random options even if $time is “evening” you can do that too, one way would be just to add more code eg

<<if _conversation is "james talk 2" and $time is "evening">>
James talk 2 - it's evening
<<set $james.talk.delete("james talk 2")>>
<<elseif _conversation is "james talk 2">>
<<goto "talk to james">>
<</if>>

As ever there are lots of different ways of coding this - this is just one option and definitely not the most concise one. But it is quite easy to read and see what’s going on.

As an aside, if I was writing this I wouldn’t put the output text in the main passage like this - instead of having whatever would go in place of “James talk 2 - it’s evening” in the main passage, I’d have something like <<include "jamesTalk2Evening">> and then put the text in a new :: jamesTalk2Evening passage.

In the SleepyFool code, the first widget is about choosing which of two NPCs present to talk to so if that’s not something you’re dealing with just ignore that part. The second basically makes a widget that sorts conversation topics for you, so you can make it produce a list of topics to talk to James about, which can be dependent on other factors (like $time etc), and which will be knocked off the list once they’re chosen so they aren’t repeated. It might not be appropriate at all for what you’re doing (it’s not set up to produce random conversation elements, although it could be adapted to that), or it might need significant tweaking, it’s just an example of how to compress the code so you don’t have chains of different IF options, or conversation topic links.