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

Solved [NPC] Quest NPC, Requesting help or direction

  • Thread starter Thread starter Xikini
  • Start date Start date
X

Xikini

Guest
An answer was provided by Ninja here.
Feel free to read through the discussion. :oops:

TL : DR I need to know how to create timed storyline within an npc, along with advancement in conversation.
Also knowing what the local's are defining.

I have been editing some npc's for my friends server, and for the most part it's pretty easy.

In every single one of his npc's there is no dialogue.

example:
Player: Hello
Npc: What's up?
Player: quest!
Npc: I might have something for you.
Player: something?
Npc: Something. Everything! Goblins everywhere!
idle timer, 2 seconds.
Npc: Are you up for that kind of Challenge?
Player: Yes!
Npc: Great! I'll be here when you get back!

else
shame. I needed help.
Come back if you want to help me.

I can easily assume that talkState = {} would be useful in advancing npc conversation, however I am unfamiliar with it's use. I also do not see how to create an idle timer, or a lull in conversation before the npc speaks again.

I crafted this *masterpeice* from the scripts he has.
The npc will *ask* (being a loose term at the moment) for help, and give you an item as a reward, along with experience.
If you wish to repeat the mission, he will give you gold and experience, every time thereafter.

Code:
local keywordHandler = KeywordHandler:new()
local npcHandler = NpcHandler:new(keywordHandler)
NpcSystem.parseParameters(npcHandler)
local talkState = {}
function onThink()           npcHandler:onThink()           end

local playerID = 0
function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end
function onCreatureDisappear(cid)     npcHandler:onCreatureDisappear(cid)  

   end
  
-- Npc Name That this Quest is Used with --

-- Monk Tonir --

-- Other Npc's --


function onCreatureSay(cid, type, msg)
   npcHandler:setMessage(MESSAGE_FAREWELL, "Wish I could get rid of those goblins as easy as I {kill} people.") -- generic end of conversation
   if (getPlayerStorageValue(cid,45007) < 1) then
     npcHandler:setMessage(MESSAGE_GREET, "Hey there.") -- Generic Greeting
   elseif (getPlayerStorageValue(cid,45007) == 1) then
     npcHandler:setMessage(MESSAGE_GREET, "Please kill off some of those goblins or I'll never get rid of them all!") -- Started quest for First Reward
   elseif (getPlayerStorageValue(cid,45007) == 2) then
     npcHandler:setMessage(MESSAGE_GREET, "I still need a few more {troublemakers} taken care of!") -- Ended Starting Quest, Now Repeatable quest
   elseif (getPlayerStorageValue(cid,45007) == 3) then
     npcHandler:setMessage(MESSAGE_GREET, "The more goblins you kill the richer I'll make you!") -- Ended Starting Quest, Now Repeatable quest
   end
   npcHandler:onCreatureSay(cid, type, msg)  
end

function creatureSayCallback(cid, type, msg)
   if(not npcHandler:isFocused(cid)) then
     return FALSE
   end
   playerID = cid
   if (getPlayerStorageValue(cid,45007) < 1) then
     if msgcontains(msg, 'kill goblins') then
       if (getPlayerStorageValue(cid,45007) < 1) then
         npcHandler:say("Great! Please kill 50 {goblins} and I'll give you an awesome reward!",cid)
         setPlayerStorageValue(cid,45007,1) -- start killing monsters
       else
         npcHandler:say("What was that?",cid)
       end
     end
   elseif (getPlayerStorageValue(cid,45007) == 1) then -- if player has started killing monster
     if msgcontains(msg, "goblins") then
       if (getPlayerStorageValue(cid,45008) == 50) then -- If player has killed 50 monsters  
           npcHandler:say("Thank you.. here's an awesome reward! If you want some extra money and experience ask me about those {troublemakers}.",cid)
           doPlayerAddItem(cid, 8305, 1) -- whatever item it is
           local exp = getExperienceStage(getPlayerLevel(cid), getVocationInfo(getPlayerVocation(cid)).experienceMultiplier) -- checks if they have bonus exp on
           doPlayerAddExperience(cid, 2000 * exp) -- Gives player exp depending on level and current exp rate
           setPlayerStorageValue(cid,45007,2) -- start repeatable
           setPlayerStorageValue(cid,45008,0) -- restart monster count
       else
         npcHandler:say("If you can kill 50 goblins I'll reward you greatly!",cid)
         end
     end
   end

   if (getPlayerStorageValue(cid,45007) == 2) then
     if msgcontains(msg, 'troublemakers') then
       if (getPlayerStorageValue(cid,45007) == 2) then
         npcHandler:say("Please kill more {goblins} and I'll give you 300 gold and some experience points!",cid)
         setPlayerStorageValue(cid,45007,3) -- start killing monsters
       else
         npcHandler:say("What was that?",cid)
       end
     end
   elseif (getPlayerStorageValue(cid,45007) == 3) then -- if player has started killing goblins
     if msgcontains(msg, "goblins") then
       if (getPlayerStorageValue(cid,45008) == 50) then -- If player has killed 30 goblins  
           npcHandler:say("As promised here's your reward. If you want to help me again just ask about those troublemakers.",cid)
           doPlayerAddItem(cid, 2152, 3) -- platinum coins
           local exp = getExperienceStage(getPlayerLevel(cid), getVocationInfo(getPlayerVocation(cid)).experienceMultiplier) -- checks if they have bonus exp on
           doPlayerAddExperience(cid, 2000 * exp) -- Gives player exp depending on level and current exp rate
           setPlayerStorageValue(cid,45007,2) -- restart repeatable
           setPlayerStorageValue(cid,45008,0) -- restart monster count
       else
         npcHandler:say("If you can kill 50 goblins I'll reward you with my rapidly depleting wealth!",cid)
         end
     end
   return true


end
end

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

One more thing I'd like to reason out.. How do you create general question and answers?

like this?

Code:
if msgcontains(msg, 'job') then
        npcHandler:say("I am a farmer!",cid)
end

if msgcontains(msg, 'monk tonir') then
        npcHandler:say("He is a monk!",cid)
end
..
Assuming once again to put it near end of script, so it doesn't interfere with quest checks.

So all in all I guess I just want an example of everything, and some information so I can actually make a decent storyline and interactive npc's.

PS: how do you disable smilies! Lol :(
 
Last edited by a moderator:
I should also note that the current NPC file works fine.
It uses a creature script to count the killed monsters.
I'll release the full NPC/creature script once it's completed if anyone wants it!

Any help is appreciated.
 
Depending on how good you want it to be, making an NPC that can simulate a conversation could be a lot of work.

1. I'd suggest you take a look at Lua's string functions. I don't think msgcontains() will be enough.
2. Start with the NPC's answers - i.e. design by topic, rather than from a player perspective.
3. You'll probably want support for synonyms and variations on words (job/profession/vocation/work, ....). If so design the data structure and code the search functions for that first.
4. If you design a conversation from the NPC side, with built-in progression, tracking the state will be easy enough.
5. Natural Language parsing is really difficult. e.g. Player: "This quest is really hard work." NPC: "I'm a gem merchant". I'd suggest you don't do anything in that area until after you have a keyword-based system running.


BTW: here's a brief look at the design/documentation process:
http://en.wikipedia.org/wiki/State_machine_diagram

The first cut should be a draft scenario (some detail, but not too much), then a diagram with the exit criteria identified (don't worry about stuff like entry actions yet). Making the diagram helps get the states right, and also helps identify if there are alternative paths, and cases where you want to go back to an earlier state. You should have few of those, ideally none, but unless you're absolutely certain you'll never have either one, you need to allow for them in your coding spec.
 
Last edited:
Simulating a conversation would make me cry on the inside I think!

I am still learning, and wish to learn, either through trial and error, or through instruction/demonstration.
My goal is to simply have a base working npc, with most basic functions an npc should have.

Being able to answer general answers such as 'job' 'quest' 'name' 'monk tonir' <aka different Npc's around town or famous hero's within a storyline, like ferumbrus>
Being able to continue a conversation, such as in a quest. p:quest, npc:Would you like to start this quest?, p:yes, npc:Great!, else:That's too bad.

I understand that talkState = {} is loaded with the npc already.
Do you happen to know the parameter's this function uses?
Would it work something like this?

Code:
if (getPlayerStorageValue(cid,45018) < 1) then
    if msgcontains(msg, 'story') then
        npcHandler:say("Would you like to hear my story?",cid)
        setPlayertalkState(cid,1)
                if msgcontains(msg, 'yes') then
                        npcHandler:say("My story is amazing.",cid)
                        npcHandler:say("It could continue for while.",cid)
                        npcHandler:say("But this is all. Would you like to accept a quest?",cid)
                        setPlayertalkState(cid,2)
                               if msgcontains(msg, 'yes') then
                                       npcHandler:say("Great! Please go see Monk Tonir for further instructions.",cid)
                                       setPlayerStorageValue(cid,45018,1)
                                       setPlayertalkState(cid,0)
                               else
                                      npcHandler:say("That is a shame.",cid)
                                      setPlayertalkState(cid,0)
                 else
                          npcHandler:say("Another time then.",cid)
                          setPlayertalkState(cid,0)
                               end
                 end
   elseif (getPlayerStorageValue(cid,45018) == 1) then
           npcHandler:say("My story is a sad one. If you still wish to help see Monk Tonir for further instructions.",cid)
   end
   return true
end
 
It's near the top of the first script posted.

It defines it as
Code:
local talkState = {}

I just assumed that it was a built in function to elevate the player so that the script will stop until next input from player.

For the record, I just scripted that, and never tested, as it's my friends server, and he only restarts it once or twice a day.
 
get talkState
Code:
if msgcontains(msg, "yes") and talkState[talkUser] == 1 then
set talkState
Code:
talkState[talkUser] = 1

I would like to suggest that you go through this tutorial, might help you a little.
 
That first script is actually reacting to:
getPlayerStorageValue(cid,45007)

That local array doesn't seem to be used at all.

Player centric "memory" isn't the only option though. You could also make it work with an NPC-centric memory, or a mixture of the two.
Except there doesn't seem to be an API for creature- or NPC-centric storage.
I found these:
Game.getStorageValue(key)
Game.setStorageValue(key, value)

but you'd need more than an integer key to handle quests nicely.

Do you know everything about Storage?

One good approach would be to have a unique id for each quest, pointing to all the players currently interacting with that quest, then another structure indexed by player for that particular quest which could hold a small set of variables tracking their quest status.

That would get away from the hassle of getting managing free slots in Player storage, and work well with generic NPCs (e.g. you could easily have a chain of NPC's handling different parts of the same quest).

It could be done with a custom DB able too, and maybe even with global variables. The key requirements are isolated persistent storage that player-centric scripts can't screw up, and a way to store (or simulate) Tables.
 
get talkState
Code:
if msgcontains(msg, "yes") and talkState[talkUser] == 1 then
set talkState
Code:
talkState[talkUser] = 1

I would like to suggest that you go through this tutorial, might help you a little.
This is just using the local Table directly though?

It's definitely sufficient for a simple conversation, and likely a lot faster than a Storage, but IMO it's not enough for a generic NPC/Quest system.

BTW - nice tutorial - thanks for the link.

@Xikini
I'm not really clear on what you're trying to do, and I might be aiming too high.

A generic system for Quests and NPC's, including NPC conversations, is definitely possible, but requires some data design to be done up front. If, OTOH, you're mainly interested in managing conversations, that tutorial has enough to go with.
 
Last edited:
For what I intend, the talkstate, and player storage values are sufficient.

When using the storage values to continue a quest, I've simply been increasing it

Code:
setPlayerStorageValue(cid,45007,0)
setPlayerStorageValue(cid,45007,1)
setPlayerStorageValue(cid,45007,2)

Placing the same storage value in another NPC can continue the quest, and is unreachable with keywords unless you've completed the missions thus far.
Or as far as I've seen anyways. :p

I'm sure there is other ways, probably better, but this works for me at the moment. :D
 
Player-centric "memory" will work fine for "hard-coded" NPCs.

Not so well for a generic NPC though. OTOH you'd need to have a lot of quests and NPCs before a generic NPC would be worth the effort.
 
Back
Top