• 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!

Lua Understanding NPC syntax (TFS 1.4)

BBQ Fanatic

New Member
Joined
Jul 10, 2022
Messages
13
Reaction score
3
Hi there.
I'd like to understand few rules about npc.lua syntax, because after doing a research i'm a bit confused.

I generally wanted to make some Quest NPCs, so I was searching through OtLand for some examples, but most of them were posted in 2009/10, so I'm not sure wether they're good or not.

As first I tried to make simple NPC, who wants an item from us and gives other as reward. I used this solution as a base. My attempt looks like this:
Code:
<?xml version="1.0" encoding="UTF-8"?>
<npc name="Beggar" script="beggar.lua" walkinterval="0" floorchange="0">
    <health now="150" max="150"/>
    <look type="148" head="40" body="45" legs="99" feet="79"/>
    <parameters>
        <parameter key="message_greet" value="Hi. Will you help me?"/>
    </parameters>
</npc>
Code:
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 = {}

function creatureSayCallback(cid, type, msg)
    if(not npcHandler:isFocused(cid)) then
        return false
    end
 
    local talkUser = NPCHANDLER_CONVBEHAVIOR == CONVERSATION_DEFAULT and 0 or cid
 
    if msgcontains(msg, 'yes') or msgcontains(msg, 'help') then
        if getPlayerStorageValue(cid, 5569) == 0 then
            npcHandler:say("I need small diamond. Do you have one?", cid)
            if msgcontains(msg, 'yes') then
                if getPlayerItemCount(cid, 2145) >= 1 then
                    doPlayerRemoveItem(cid, 2145, 1)
                    doPlayerAddExp(cid, 300)
                    doPlayerAddItem(cid, 2159, 1)
                    npcHandler:say('Thank you.', cid)
                    setPlayerStorageValue(cid, 5569, 1)
                else
                    npcHandler:say("Liar! You don't have it!", cid)
                end
            else
                npcHandler:say("Come back when you get it.", cid)
            end
        else
            npcHandler:say("You've already helped me.", cid)
        end
    else
        npcHandler:say("Nevermind.", cid)
    end
    return true
end
npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback)
npcHandler:addModule(FocusModule:new())

But the entire storage thing seems to not working. No storage is applied at all, and dialogue always looks like that:
19:11 God [999]: hi
19:11 Beggar: Hi. Will you help me?
19:11 God [999]: yes
19:11 Beggar: You've already helped me.

So I've been looking at NPC scripts in Resources board, then I discovered a different(?) ways to NPC syntax.
  • Sometimes it's selfSay, sometimes npcHandler:say.
  • What is the purpose of talkState? I feel like this is the thing that's missing in my npc.
  • Default NPCs like The Oracle or Bless has a completely different syntax, which I don't understand at all. I also saw few community NPC scripts with that kind of syntax.
  • npcHandler.topic[cid] - what is this? It looks like different wany to type talkState, but dunno.

Just to be clear, I don't want you to fix my NPC (but solution + explaination will be appreciated), because I'm gonna have plenty of them, different each. I'd like to understand these few things I don't right now, to be able to create custom NPC by myself.
 
here you can find a lot of npcs to use as example:

for example simon the beggar:

Sometimes it's selfSay, sometimes npcHandler:say.

first one is the old way of doing it and was kept as backward compatibility

What is the purpose of talkState? I feel like this is the thing that's missing in my npc.
npcHandler.topic[cid] - what is this? It looks like different wany to type talkState, but dunno.
talkstate and npcHandler.topic do the "same thing", but nowadays we stick to using npcHandler.topic and it works as a "storage" to keep track of what topic you are interacting with that npc (for example you set topic 1 and then check if player said "yes" and topic = 1), and that is what is missing in your npc

look how @Znote made it more clear at banker npc here

and also a few more npcs: forgottenserver/data/npc/scripts at master · otland/forgottenserver (https://github.com/otland/forgottenserver/tree/master/data/npc/scripts)
 
Lua:
function creatureSayCallback(cid, type, msg)
    if(not npcHandler:isFocused(cid)) then
        return false
    end
 
    local talkUser = NPCHANDLER_CONVBEHAVIOR == CONVERSATION_DEFAULT and 0 or cid -- useless
   
    if npcHandler[cid] == 0 then -- this check if its beggining of the whole dialog
       if msgcontains(msg, 'yes') or msgcontains(msg, 'help') then
          if getPlayerStorageValue(cid, 5569) > 0 then
             npcHandler:say("You've already helped me.", cid)
             return false
          end
          npcHandler:say("I need small diamond. Do you have one?", cid)
          npcHandler.topic[cid] = 1  --- we move the topic index from 0 to 1, so now npc will check next topic
       else
          npcHandler:say("Nevermind.", cid)
       end
    elseif npcHandler[cid] == 1 then  -- here its the check where in dialogues player is, in this case npc already asked him about required item
       if msgcontains(msg, 'yes') or msgcontains(msg, 'help') then
          if getPlayerItemCount(cid, 2145) < 1 then
             npcHandler:say("Liar! You don't have it!", cid)
             return false
          end
          doPlayerRemoveItem(cid, 2145, 1)
          doPlayerAddExp(cid, 300)
          doPlayerAddItem(cid, 2159, 1)
          npcHandler:say('Thank you.', cid)
          setPlayerStorageValue(cid, 5569, 1)
          npcHandler.topic[cid] = 0 -- not sure if its required or not honestly.
       else
          npcHandler:say("Come back when you get it.", cid)
       end
    end
    return true
end

npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback)
npcHandler:addModule(FocusModule:new())
added some comments idk if its understandable xd
 
Lua:
          npcHandler.topic[cid] = 0 -- not sure if its required or not honestly.
Definitely required. You want to set your npc back to default state whenever you can, otherwise..
If you don't reset, then the player could trade in diamonds over and over for 300 experience. (in this specific npc scenario, and also obtain a bunch of item 2159.. whatever that is)
 
here you can find a lot of npcs to use as example:
Yay, that's gonna be so useful, thank you.

talkstate and npcHandler.topic do the "same thing", but nowadays we stick to using npcHandler.topic and it works as a "storage" to keep track of what topic you are interacting with that npc (for example you set topic 1 and then check if player said "yes" and topic = 1), and that is what is missing in your npc
You mean thats the only thing missing there? I'm asking, because...
After quick brainstorm in my head, I remembered that Sweaty Cyclops might has a code i need in this specific case (firstly I tried with Gamel, because he was giving life ring for life crystal, but I couldn't find this part in his gamel.lua from your repository).
So I copy-pasted & edited code this way:
Code:
function creatureSayCallback(cid, type, msg)
    if not npcHandler:isFocused(cid) then
        return false
    end

    local player = Player(cid)
    if msgcontains(msg, "help") then
        npcHandler:say("I need small diamond. Do you have one?", cid)
        npcHandler.topic[cid] = 10
    elseif msgcontains(msg, "yes") and npcHandler.topic[cid] == 10 then
        if player:removeItem(2145,1) then
            npcHandler:say("Thank you!!!", cid)
            player:addItem(2250, 1)
        else
            npcHandler:say("You don't have it, liar!", cid)
        end
        npcHandler.topic[cid] = 0
    end
end

And this seems to be working. Almost. I can't duplicate "yes" (there has to be "help" not "yes" or "help") because second one is treated like first, then dialogue goes like this:
Code:
18:56 Beggar: Hi. Will you help me?
18:56 God [999]: yes
18:56 Beggar: I need small diamond. Do you have one?
18:56 God [999]: yes
18:56 Beggar: I need small diamond. Do you have one?

I assume I can solve this using npcHandler.topic, but don't know how to use it yet. Btw. I guess topics ids above might be wrong, because it's blindly copypasted from Sweat Cyclops, which has a lot of topics.
The other problem comes when I try to put a storage there. Those Elseifs make me lost, have no idea where should I put if getPlayerStorageValue(cid, 5569) > 0 then statement. I tried adding another elseif:
Code:
    if msgcontains(msg, "help") then
        npcHandler:say("I need small diamond. Do you have one?", cid)
        npcHandler.topic[cid] = 10
    elseif msgcontains(msg, "yes") and npcHandler.topic[cid] == 10 then
        if player:removeItem(2145,1) then
            npcHandler:say("Thank you!!!", cid)
            player:addItem(2250, 1)
            setPlayerStorageValue(cid, 5569, 1)
        else
            npcHandler:say("You don't have it, liar!", cid)
        end
    elseif msgcontains(msg, "help") and getPlayerStorageValue(cid, 5569) > 0 then
        npcHandler:say("You already helped me!", cid)   
        npcHandler.topic[cid] = 0
    end
end
But doesn't work, no surprise.

added some comments idk if its understandable xd
Your code doesnt work at all to me. NPC doesn't react at all, no errors in console.
 
The other problem comes when I try to put a storage there. Those Elseifs make me lost, have no idea where should I put if getPlayerStorageValue(cid, 5569) > 0 then statement. I tried adding another elseif:
Code:
    if msgcontains(msg, "help") then
        npcHandler:say("I need small diamond. Do you have one?", cid)
        npcHandler.topic[cid] = 10
    elseif msgcontains(msg, "yes") and npcHandler.topic[cid] == 10 then
        if player:removeItem(2145,1) then
            npcHandler:say("Thank you!!!", cid)
            player:addItem(2250, 1)
            setPlayerStorageValue(cid, 5569, 1)
        else
            npcHandler:say("You don't have it, liar!", cid)
        end
    elseif msgcontains(msg, "help") and getPlayerStorageValue(cid, 5569) > 0 then
        npcHandler:say("You already helped me!", cid)  
        npcHandler.topic[cid] = 0
    end
end
But doesn't work, no surprise.
While is not the proper way to do the check, it does not work due the order of the if statements

One way would be:
Lua:
if msgcontains(msg, "help") then
    if player:getStorageValue(5569) > 0 then
        return npcHandler:say("You already helped me.", cid)
    end
    npcHandler:say("I need small diamond. Do you have one?", cid)
    npcHandler.topic[cid] = 10
 
So I copy-pasted & edited code this way:
Code:
function creatureSayCallback(cid, type, msg)
    if not npcHandler:isFocused(cid) then
        return false
    end

    local player = Player(cid)
    if msgcontains(msg, "help") then
        npcHandler:say("I need small diamond. Do you have one?", cid)
        npcHandler.topic[cid] = 10
    elseif msgcontains(msg, "yes") and npcHandler.topic[cid] == 10 then
        if player:removeItem(2145,1) then
            npcHandler:say("Thank you!!!", cid)
            player:addItem(2250, 1)
        else
            npcHandler:say("You don't have it, liar!", cid)
        end
        npcHandler.topic[cid] = 0
    end
end

And this seems to be working. Almost. I can't duplicate "yes" (there has to be "help" not "yes" or "help") because second one is treated like first, then dialogue goes like this:
Code:
18:56 Beggar: Hi. Will you help me?
18:56 God [999]: yes
18:56 Beggar: I need small diamond. Do you have one?
18:56 God [999]: yes
18:56 Beggar: I need small diamond. Do you have one?
Here are 2 different ways to get it working with "yes" and "help" at the same time. Using either or.

Lua:
function creatureSayCallback(cid, type, msg)
    if not npcHandler:isFocused(cid) then
        return false
    end
    
    local player = Player(cid)
    
    if npcHandler.topic[cid] == 0 and (msgcontains(msg, "yes") or msgcontains(msg, "help")) then
        npcHandler:say("I need small diamond. Do you have one?", cid)
        npcHandler.topic[cid] = 1
        
    elseif npcHandler.topic[cid] == 1 and (msgcontains(msg, "yes") or msgcontains(msg, "help")) then
        if player:getItemCount(2145) < 1 then
            npcHandler:say("You don't have it, liar!", cid)
            npcHandler.topic[cid] = 0
            return true
        end
        player:removeItem(2145, 1)
        player:addItem(2250, 1)
        npcHandler:say("Thank you!!!", cid)
        npcHandler.topic[cid] = 0
    end
    return true
end

Lua:
function creatureSayCallback(cid, type, msg)
    if not npcHandler:isFocused(cid) then
        return false
    end
    
    local player = Player(cid)
    
    if msgcontains(msg, "yes") or msgcontains(msg, "help") then
        if npcHandler.topic[cid] == 0 then
            npcHandler:say("I need small diamond. Do you have one?", cid)
            npcHandler.topic[cid] = 1
        elseif npcHandler.topic[cid] == 1 then
            if player:getItemCount(2145) < 1 then
                npcHandler:say("You don't have it, liar!", cid)
                npcHandler.topic[cid] = 0
                return true
            end
            player:removeItem(2145, 1)
            player:addItem(2250, 1)
            npcHandler:say("Thank you!!!", cid)
            npcHandler.topic[cid] = 0
        end
    end
    return true
end
 
Last edited:
Back
Top