I’ve found a critical flaw in the way you’ve implemented conditionals in this dialog system that makes it impossible to have conditions that are not pure functions (such as a condition that randomly selects the next prompt). It’s been causing a strange softlock in our game, Crown Academy, that I’ve been unable to pinpoint until I saw @XYZLambda’s reply.
The issue stems from the fact that conditions are treated as inputs to a node indicating whether it should run or not, and not as part of the main pathway that actually chooses the next node in the sequence.
function Condition.Run()
return math.random(1, 2)
end
Then you are opening yourself up to a potential softlock. Let’s run through the tree and see why:
We find the DialogueRoot of this tree and see it has 2 outputs: Greetings and Prompt
We traverse to Greetings, see it has a condition, and run the condition logic. Let’s say it returns 2. That doesn’t match the priority of Greetings, so we don’t run node Greetings
We traverse backwards and try node Prompt, see it has a condition, and run the condition logic. Let’s say this time it returns 1. This again doesn’t match the priority of our node, 2, so we don’t run node Prompt either.
We now softlock because we have no node to run.
This is also incredibly inefficient because you may end up running your condition logic n times, where n is the number of nodes you have, instead of just once. Not to mention it also clutters the node editor and makes the tree more difficult to follow.
So let’s run through the tree with alternative condition behavior, where nodes can output to a condition, the condition will run some logic, then select the next node in the pathway based on the result.
We find the DialogueRoot of this tree and see it has 1 output: Condition
We run the condition logic and it returns 1. We select the first output of Condition that matches this priority (Greetings).
We run node Greetings.
Much better!
I was originally wary of the way you implemented conditionals with this system but overlooked it because I couldn’t figure out why. Now I realize why, and would recommend updating your system.
That is most likely a bug. I’ll try to get it fixed whenever I have time. In the meantime, I recommend not relying on that node setup unless you want to edit the code by hand or find some “hacky” solution. I aim do some big patch sometime in the future to make the entire system more robust. Thanks for bringing this to my attention!
This is indeed a major flaw and something I definitely overlooked when I was developing the system. Looks like I gotta squeeze the development of the patch to this week’s schedule. I will be updating this post and leaving a patch note hopefully sometime this week.
Thank you for bringing this to my attention and for providing me a detailed explanation.
Actually, it turns out the fix for this is really simple. All it requires is removal of the CheckForCondition function and the addition of this code into the LoadNode function’s type checking logic:
elseif Type == "Condition" then
local Module = require(Node:FindFirstChildWhichIsA("ModuleScript"))
local Result: number = Module.Run()
for _, OutputNode in pairs(GetOutputNodes(Node)) do
local Priority = OutputNode:GetAttribute("Priority")
if Priority == Result then
self:LoadNode(OutputNode)
break
end
end
The only other things it’ll require is changing the default condition type to accept inputs and adjusting existing dialog trees to work with the new condition structure.
Glad you found a fix for it already, I figured it would be pretty straight forward.
Now that I’ve slowly picked up my development pace again, I think it’s time I give this entire system another look. That would mean some sort of rewrite of the system without changing the general behavior of the nodes to allow for more flexibility, clarity, and to just make the entire thing more robust. Also doing some quality of life changes like adding the input path for the condition node (Which btw was originally planned but scrapped for reasons I don’t remember)
There’s just one problem: I assume you guys have edited the code quite a bit so changing most of- if not all of it would be quite a hassle for you guys. So before I start working on any rewrites, I would like to hear what kind of changes you guys have made and if you’d like me to make them native for the system.
We actually didn’t change the core of it all that much since we found it to be mostly complete for what we wanted. Among the things we added were the ability to skip the typewriter animation with arbitrary input and having text prompts wait for user interaction before moving onto the next one (meaning we can have two back-to-back text prompts that aren’t separated by responses, something the base system does not support very well).
If you are considering rewriting the whole system again, I would suggest a couple things:
Try to make the system as UI-agnostic as possible. This system should ideally only handle the internal logic of traversing through the dialog tree. Users of the system should be able to put whatever UI they want on top of it without having to go through and edit the internals. This could be done most simply with events.
Move the node editor into a dock widget instead of it being rendered on top of the main viewport.
Polish up the node editor a bit more. Allow selecting and dragging multiple nodes at once (not having this makes reorganizing trees a pain), align nodes to a grid so they aren’t just everywhere, improve zoom and pan behavior, and improve the inconsistent node connecting behavior.
Integrate the node presets directly into the editor and add a widget to customize them instead of littering every single one of my places with an empty “NodePresets” folder.
is there a way to control how long a prompt would stay on the screen before vanishing, if there is a long prompt at the end, or a string of prompts, they transition too fast, forcing you to read fast.
There is no native feature to change it per prompt but that shouldn’t be hard to add. There should be a wait function somewhere in the script after all of the text has appeared. If you find that you can easily change it to whatever you want. I can try and make a quick edit of the script and send it your way sometime tomorrow.
ill add it myself, i can send an edited version of it back if you want to look at it and fully add it, then again not too hard, ill probably make it read from the prompt itself via attribute, as it is already
Edit: was really easy lol, i move the wait time intot he if prompt, and it reads from a value inside the folder, it even shows up in your little plugin ui, so, nice and easy to change. SmolChange.rbxm (16.0 KB)
I updated the test one to include it, nothing else was changed
Check line 257 to see what I did
.1 is instant, but I set it to 1 in there, feel free to mess with it.
If you do use it, don’t care if you add a thanks or not, was a simple mod lol.
I haven’t used this plugin in a long time since I switched to the other dialogue editor, but I’m fairly certain my addition works I can’t be too certain since its been updated since I posted.
Ah, I see. I’m trying to find the problem with me and it fails here:
if s and e then
--Doesn't reach this
local before = string.sub(textValue,1,s-1)
local middle
if typeof(val) == 'function' then
local ret = val()
middle = before..""..ret
else
middle = before..""..val
end
local after = middle..string.sub(textValue,e+1,string.len(textValue))
textValue = after
end
maybe I can figure it out.
edit: Figured it out.
add
v.Value = textValue
after
local after = middle..string.sub(textValue,e+1,string.len(textValue))
textValue = after
so
local after = middle..string.sub(textValue,e+1,string.len(textValue))
textValue = after
v.Value = textValue
This has really aided in my game’s workflow. I planned to make a basic dialogue system but with this I can really do a lot more in terms of secret dialogue, quest systems, cutscenes, etc etc. Thank you, friend.
Here is what managed to create with this system you made.
I’m trying to capture the player’s response when a button is activated, but when i loop through the responses it captures the first response twice, and the second response once. It never captures the third response text.
This code is inside a conditional linked to the three responses… it always works with the first two just not the third. and ideas on how to fix this? I also tried it in a command prompt but .Activated didnt work at all there. plz help thank youu
local MainFrame = Player.PlayerGui.DialogueUI.MainFrame
local ResponseFrame = MainFrame.ResponseFrame
local responses = ResponseFrame:GetChildren()
for index, item in pairs(responses) do
if item:IsA("TextButton") and item.Name ~= "SampleResponse" then
print(item.Text)
item.Activated:Connect(function()
-- code stuff here
-- (last response always never captured/activated)
end)
end
end