• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

NPC [TFS 1.2] NPC Dialogue Tree

Karain

Jack of all trades
Joined
Jul 9, 2009
Messages
338
Solutions
3
Reaction score
147
Location
Egypt
Hello again wonderful people of otland, i have a small gift for those who would like to see something more interactive when talking to NPCs. i personally liked the idea of manually talking to NPCs, but with my OT experience, sometimes they aren't that simple or it breaks the immersion as you struggle to find the right keyword for them to respond after a good couple of minutes of guessing. so why not make premade options without making the NPC do all the talking.

introducing! NPC Dialogue Tree! bringing modern gaming concepts into tibia!

with this script, talking with NPCs will be much more friendlier and it's still just as customizable as regular NPCs. BUT MUCH EASIER!

d97b824e2143023e0064dfef4c8b4451.png

How to install:



data/creaturescripts/creaturescripts.xml:
Code:
<event type="modalWindow" name="Dialogue" script="dialogue.lua"/>

data/creaturescripts/scripts/login.lua:

add this before "return true"
Code:
player:registerEvent("Dialogue")

data/creaturescripts/scripts/dialogue.lua:

Code:
player_choices = {}
defaultButtons = {{id = 0x00, text = "Select", enter = true, escape = false}, {id = 0x01, text = "End", enter = false, escape = true}}

function Player:getChoiceText(choice_id)
if player_choices and player_choices[self:getId()] then
return player_choices[self:getId()][choice_id].text
else
return false
end
end

function Player:createDialogueWindowWithButtons(modalWindowId, headerText, bodyText, buttonTable, choiceTable, sendToPlayer, priority)
local var = ModalWindow(modalWindowId, headerText, bodyText)
for i = 1, #buttonTable do
var:addButton(buttonTable[i].id, buttonTable[i].text)
if buttonTable[i].enter then
var:setDefaultEnterButton(buttonTable[i].id)
end
if buttonTable[i].escape then
var:setDefaultEscapeButton(buttonTable[i].id)
end
end
player_choices[self:getId()] = choiceTable
for i = 0, #choiceTable do
if choiceTable[i] ~= nil and (choiceTable[i].storage == false or self:getStorageValue(choiceTable[i].storage[1]) == choiceTable[i].storage[2]) then
var:addChoice(i, choiceTable[i].text)
end
end
if not priority then
var:setPriority(false)
end
if sendToPlayer then
var:sendToPlayer(self)
end
end

function onModalWindow(player, modalWindowId, buttonId, choiceId) -- be careful here if you have other modalwindow scripts
if buttonId == 0x00 then
player:say(player:getChoiceText(choiceId),TALKTYPE_SAY)
elseif buttonId == 0x01 then
player:say("Good Bye.",TALKTYPE_SAY)
end
return true
end

data/npc/scripts/dialogue.lua:

Code:
local npc_dialogue = {
[1] = {
message="This is the message that shows up before the choices, make sure it's long enough if you are having long choices.",
choices= {
[1]={text="Choice 1", storage=false, dialogue=1, script="end"},
[2]={text="Choice 2",storage=false, dialogue=2, script="script1"},
[3]={text="Choice 3",storage={1234,1}, dialogue=3, script="trade"},
[4]={text="Choice 4",storage=false, dialogue=3, script="quest"},
[5]={text="Choice 5",storage=false, dialogue=false, script="quest2"}}},
[2] = {
message="Bla bla bla bla bla bla bla.",
choices= {
[1]={text="Choice 1",storage=false, dialogue=1, script="end"},
[2]={text="Choice 2",storage=false, dialogue=2, script="end"},
[3]={text="Choice 3",storage={1234,1}, dialogue=false, script="end"},
[4]={text="Choice 4",storage=false, dialogue=false, script="end"},
[5]={text="Choice 5",storage=false, dialogue=3, script="end"}}},
[3] = {
message="Brought to you by Matt Shadowwing.",
choices= {
[1]={text="Choice 1",storage={1245,2}, dialogue=false, script="end"},
[2]={text="Choice 2",storage=false, dialogue=false, script="end"},
[3]={text="Choice 3",storage={1234,1}, dialogue=false, script="end"},
[4]={text="Choice 4",storage=false, dialogue=1, script="end"},
[5]={text="Choice 5",storage=false, dialogue=2, script="end"}}}
}

local keywordHandler = KeywordHandler:new()
local npcHandler = NpcHandler:new(keywordHandler)
NpcSystem.parseParameters(npcHandler)

function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end
function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end
function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end
function onThink() npcHandler:onThink() end
local talkstate = {}
local function greetCallback(cid)
local player = Player(cid)
local npc = Npc(getNpcCid())
  npcHandler:setMessage(MESSAGE_GREET, npc_dialogue[1].message)
player:createDialogueWindowWithButtons(1, npc:getName(), npc_dialogue[1].message, defaultButtons, npc_dialogue[1].choices, true, false)
talkstate[player:getId()] = 1
  return true
end

local function creatureSayCallback(cid, type, msg)
local player = Player(cid)
local npc = Npc(getNpcCid())
if not npcHandler:isFocused(cid) then
return false
elseif talkstate[player:getId()] then
for _, v in pairs(npc_dialogue[talkstate[player:getId()]].choices) do
if msgcontains(msg, v.text) and (v.storage == false or player:getStorageValue(v.storage[1]) == v.storage[2]) then
if v.script == "end" then
talkstate[player:getId()] = v.dialogue
npcHandler:say(npc_dialogue[v.dialogue].message, cid)
player:createDialogueWindowWithButtons(1, npc:getName(), npc_dialogue[v.dialogue].message, defaultButtons, npc_dialogue[v.dialogue].choices, true, false)
elseif v.script == "trade" then
-- trading script here
talkstate[player:getId()] = v.dialogue
npcHandler:say(npc_dialogue[v.dialogue].message, cid)
player:createDialogueWindowWithButtons(1, npc:getName(), npc_dialogue[v.dialogue].message, defaultButtons, npc_dialogue[v.dialogue].choices, true, false)
elseif v.script == "quest" then
-- quest script here
talkstate[player:getId()] = v.dialogue
npcHandler:say(npc_dialogue[v.dialogue].message, cid)
player:createDialogueWindowWithButtons(1, npc:getName(), npc_dialogue[v.dialogue].message, defaultButtons, npc_dialogue[v.dialogue].choices, true, false)
end
end
end
end
  return true
end

npcHandler:setCallback(CALLBACK_GREET, greetCallback)
npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback)
npcHandler:addModule(FocusModule:new())

data/npc/lib/npcsystem/npchandler.lua:

change all
Code:
TALKTYPE_PRIVATE_PN
to
Code:
TALKTYPE_SAY

And then create your own npc.xml with this script attached to it :)

Please let me know if you have any feedback, comments or problems, i hope you have fun.

Thank you!
 
Last edited:
You might also point to a function on the script field, instead of your string approach :p I think it would be cleaner.
 
You might also point to a function on the script field, instead of your string approach :p I think it would be cleaner.
that's a brilliant plan, but how will I pass parameters from the config tho?

also forgot to add in a change regarding npchandler.lua
it's in now.
 
Last edited:
I did something like this as well, for trading with NPCs. The only thing I found an issue with is that you cannot close the modalwindow unless you return false when executing an action on it, so the NPC losing focus does not close the window, and cannot close the window until the player attempts to use an action. Are you aiming to have extended focus time or remove the time limit entirely? Or just allowing the NPC to lose focus?
 
I did something like this as well, for trading with NPCs. The only thing I found an issue with is that you cannot close the modalwindow unless you return false when executing an action on it, so the NPC losing focus does not close the window, and cannot close the window until the player attempts to use an action. Are you aiming to have extended focus time or remove the time limit entirely? Or just allowing the NPC to lose focus?

This is really good troubleshooting. I wanted to implement a system similar to this because it would allow for international players that aren't proficient with English to basically choose the term(s) they may be familiar with.
 
in Whi World my npc's have modal window too. it triggers with on: onLook function. Choices are automatically generated, depending on gamestate. (missions,tasks,items using, skills, levels, quests, etc)
You might get ideas or improve your idea after you have seen how it looks like. (I'm not gona make pictures, too troublesome for a post xD)
 
currently i have them just lose focus after the default amount of time. even if they did choose/say something after the npc loses focus, it will not cause any problems. Just a mild inconvenience imo.
 
Yeah, I was thinking about resetting focus to full on every action within the modal window and I wanted to make it close if they lost focus but there's no way to close it because it's client side. I plan to rework the protocol in otclient to allow closing it externally but I haven't got around to it because I'm not focusing on the npc system right now
 
Lua:
Lua Script Error: [Npc interface]
data/npc/scripts/dialog.luanCreatureSay
data/npc/scripts/dialog.lua:41: attempt to call method 'createDialogueWindowWithButtons' (a nil value)
stack traceback:
        [C]: in function 'createDialogueWindowWithButtons'
        data/npc/scripts/dialog.lua:41: in function 'callback'
        data/npc/lib/npcsystem/npchandler.lua:340: in function 'greet'
        data/npc/lib/npcsystem/npchandler.lua:519: in function 'onGreet'
        data/npc/lib/npcsystem/modules.lua:220: in function 'callback'
        data/npc/lib/npcsystem/keywordhandler.lua:26: in function 'processMessage'
        data/npc/lib/npcsystem/keywordhandler.lua:136: in function 'processNodeMessage'
        data/npc/lib/npcsystem/keywordhandler.lua:104: in function 'processMessage'
        data/npc/lib/npcsystem/npchandler.lua:408: in function 'onCreatureSay'
        data/npc/scripts/dialog.lua:34: in function <data/npc/scripts/dialog.lua:34>
 
Back
Top