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

Enhanced NPC System

What do you think?


  • Total voters
    45

jeybi

New Member
Joined
Oct 6, 2008
Messages
39
Reaction score
1
VERSION .01ALPHA RELEASED SEE http://otland.net/f83/enhanced-npc-system-0-01alpha-released-143943/#post1384937
~~~~~~~~~~~~~~ o ~~~~~~~~~~~~~~ End of Edit

Hello, I'm about to release a complete library for NPC coding, I started this library because the current facilities that are included by default in TFS/Otserv did not fulfill our team needs for simplicity and ease of extension.

An example (The Oracle.lua):
Lua:
local Oracle = AI.SocialNpc:New()

-- Lots of configuration options skipped for simplicity (hope)

-- This is just a specific table to store some data, we might use a local table aswell
-- The Npc class reserves all properties/methods in the object that start with a Upercase letter
-- you can add your own fields as long as they start with lowercase
Oracle.config = {
	minLevel = 8,
	cities = {
		['Rhyves'] = { town = 1, pos={x=242, y=429, z=12} }, 
		['Varak'] = { town = 2, pos={x=159, y=387, z=6} },  
		['Jorvik'] = { town = 3, pos={x=469, y=172, z=7} }		
	},
	vocations = {
		[1] = 'So, you wish to be a powerful magician? Are you sure about that?',
		[2] = 'Are you sure that a druid is what you wish to become?',
		[3] = 'A ranged marksman. Are you sure?',
		[4] = 'A mighty warrior. Is that your final decision?'
	},
	vocIdMap = {}
}

-- Do this one time
Oracle.config.citiesList = AI.CreateOptionList(Oracle.config.cities) -- outputs: "{Rhyves}, {Varak} or {Jorvik}"
Oracle.config.vocationsList = AI.CreateOptionList(Oracle.config.vocations, function(vocid) -- outputs: "{Sorcrer}, {Knight}, {Paladin} or {Druid}"
	local vocName = getVocationInfo(vocid).name
	Oracle.config.vocIdMap[vocName] = vocid -- Some mapping for later use
	return vocName
end)

-- This is called when user replies positive to our question (see line 85)
Oracle.dudeIsPrepared = function(talk)
	-- This outputs the message is passed, extracts the keywords (all words enclosed in {}) and
	-- later, if the players answers with any of these keywords, the response function
	-- (see below) is called
	talk:Ask('What city do you wish to live in? ' .. Oracle.config.citiesList .. '?')
	-- Response to all valid cities
	-- city = the city the player answered (with no case modification, so if player said
	-- "ThAiS" city will be "Thais"(the orignal keyword on the talk:Ask parsed parameters list)
	talk:Response(function(talk, city)	
	
		talk:Ask(city .. ', eh? So what vocation do you wish to become? ' .. Oracle.config.vocationsList .. '?')
		 -- Resonse functions are only called when the player replies with a valid answer
		 -- Theres a equivalent talk:BadResponse() function for when the player says a nonsense
		talk:Response(function(talk, vocation)
		
			talk:AskAgreement(Oracle.config.vocations[Oracle.config.vocIdMap[vocation]] .. ' This decision is irreversible!',
			function() -- yes					
				-- Start of default oracle code
				if(getPlayerVocation(talk.player) > 0) then
					talk:Say('Sorry, You already have a vocation!')
				else
					local cityPos =  Oracle.config.cities[city].pos
					doPlayerSetVocation(talk.player, Oracle.config.vocIdMap[vocation])
					doPlayerSetTown(talk.player, Oracle.config.cities[city].town)
					
					local tmp = getCreaturePosition(talk.player)
					doTeleportThing(talk.player, cityPos)
					doSendMagicEffect(tmp, CONST_ME_POFF)
					doSendMagicEffect(cityPos, CONST_ME_TELEPORT)
				end		
				-- End of default oracle code
				
				return talk:SilentEnd() -- Player is gone or we already said "You already have a voc.."
			end,
				
			function() -- no
				talk:Say('Then what vocation do you want to become?')
				talk:GoUp(1) -- Go up to vocation selection
			end)
			
		end)
	end)		
end


-- All replies can be either a function or a string 
Oracle.GreetReply = function(talk) -- a Talk object represent a conversation with a player
	if(getPlayerLevel(talk.player) < Oracle.config.minLevel) then
		talk:Say('COME BACK WHEN YOU GROW UP, CHILD!')
		talk:SilentEnd() -- Unfocus this player without saying anything
	else
		-- This output the question and waits for a positive or negative answer,
		-- calling the appropriate passed function once the player replies
		talk:AskAgreement('Hello |PLAYERNAME|. Are you prepared to face your destiny?', 
			Oracle.dudeIsPrepared, -- yes
			nil) -- no, since its nil(you can leave it empty) if players dont agree, the talk ends)
	end
end

-- String reply
Oracle.FarewellReply = 'Then come back when you are ready.'

-- This registers the default OnCreatureSay, OnCreatureDissapear, etc
-- to this enviroment
Oracle:Register()
(50% Less code (comments stripped) than the oracle.lua in npcs/scripts folder)

The system is based on events, like the Jiddo's system, but much less coupled, with this system will be possible (and not extremely difficult) to create npcs that follow walk routes, speak with several players in private at the same time they speak in public, maintaining individual/shared states and in general more deeply conversations/interactions. A very notorious change is that some of the things that are configurable in Jiddo's npc system, are per server, that means that, for example, you cant change say NPCHANDLER_TALKDELAY or KEYWORD_BEHAVIOR for two given npcs. Wait a bit to see this library, there's a lot of new things.


The conversation system (Talk) is very simple, is based on a stack of topics (as opposed to Jidds's system, which is based on a tree of topics), I found that with the stack approach is easier to create normal npcs and serves for most of the complexes npcs you will ever create. Either way, the system that handles the conversation is completely detached from the npc, so is 100% customizable.

I'm 99% done with the base (1.1k lines in 5 files so far), I just need to add "default" templates for npcs (shop, travel, storage id handling) and make documentation a bit more consistent.

Any suggestions or criticism are welcome.

P.S: Of course, it will be realeased to the public :) (Soon)

Edit:
A very early version of an editor im working on
editorpreview.png
 
Last edited:
looks very nice indeed, cleans up in the npc folder as well as make it very nice and neat, good job!

also, as you seem to have a lot of knowledge in the area, isn't it possible to make npc's sell items with extra attributes? I know I can add the items to a selling list, but I'd like for them to show up in the npc shop-menu to the right if you know what I mean! Just so you can preview the items before buying them.
 
@findus
Thank you for your feedback, about your question.. AFAIK the onlook in trade is handled entirely inside the server, it wont even fire a OnLook event. I think this is done because the server only knows the item type at the look time, so in theory is impossible (without source changes) to change that behaviour. For that to work you might need an actual item before selling it, then for every sell, clone the item, just an idea tho.

@thread
I forgot to say that I'm looking for suggestions or things you would like to see in a enhanced NPC system
 
Might make it easier for some, but I'm used to the good old CALLBACK_CREATURE_SAY:p:p
 
@up
You can register callbacks in this system too:

Lua:
MyOwnNpc:AddCallback(AI.Events.OnHear, myFn)

You can also create your own events, register them with Npc:RegisterEvent(eventid) and dispatch an event with Npc:Dispatch(EventData).For example, a SocialNpc (an extension of 2 other npc classes(BasicNpc and FocusNpc), registers it owns events (OnGreet, OnFarewell, etc), so basically your npc is going to be dispatching only the events it truly use (i.e there's not a set fixed of events, each "module" defines it owns events/callbacks).

@thread
I'm going to post some more "complex" examples, npcs that will be incredible difficult to create by hand/jiddos.
 
Last edited:
Im currently adding a declarative syntax for conversations, like the one in revscriptsys. That's of course, in addition to the current closure/callback based syntax.
 
Bump, edited the main post with a shot of my soon to be npc editor.
 
wow reeally great work. I never really feel in love with jiddos NPC system. im happy to finally see a remake/update. Im looking forward to seeing a few more example. If you havent already you should go post this at OTfans. if the devs like it enough i think it has the ability to replace the current jiddos system.
 
@up
Thanks for the feedback, yea ill post it on otfans once I finish some last improvements.

@topic
Currently fixing bugs and adding new features, declarative conversation parsing code is working 100%, you can now do things like:

Lua:
Oracle.Conversation = {
	{{NotFocused, Oracle.GreetKeywords} = { 
		{LessThan(PlayerLevel, 8) = "CHILD"},
		{Default = {"Are you ready?", setTopic('playerReady')}}
	}},
	
	{isTopic('playerReady') = {
		{Oracle.AgreementKeywords = {"What city? A|B|C", setTopic('city')}}
		{Default = "COME BACK WHEN YOU READY!"}
	}},
	
	{isTopic('city') = {
		{"A|B|C" = {setTopic('voc'), function(talk, city)
			return city .. ", eh? What voc do u want X|Z|Y?"
		end}},
		{Default = "A|B|C?"}
	}},
	
	{isTopic('voc') = {
		{"X" = {"A mighty X eh? u sure?", setTopic('sure')}},
		{"Z" = {"An awesome Z, u sure?", , setTopic('sure')}},
	}},
	
	{isTopic('sure') = {
		{Oracle.AgreementKeywords = function(talk)
			if getVocation(talk.Player) ~= 0 then
				return "Sorry u have voc lrdy"
			end
			
			doTeleportThing(talk.Player, getCityPos(talk:Var('city')))		
		end},
		{Oracle.DisagreementKeywords = {"Then, what voc do u want?", setTopic('voc')}}
	}}
}

Its a hand-made code I do the last night, I dont even think it parse correctly but It's more or less how its gonna look in the end. In the Enhanced NPC Editor i'm working on, you will be able to create the dialogs in a friendly manner and later export it as a declarative conversation. The declarative conversation module is provided for basic npc conversations, its quite powerful actually, you might create real complex dialogs in it, altough the code ugliness grows as the dialog complexity grows. For advanced usage the imperative Talk (first post) module is provided.

ETA Coming closer!
 
I like, and appreciate that you're trying to make a new NPC system (god forbid it should have been done months ago). However, I don't like how the code looks and operates - it seems more complicated (in a sense). Perhaps spending some time getting used to it might be worth while, but at a first glance it doesn't appear to be "user-friendly." I suppose I am "too tied in" with the current system. Good luck with the code though, I hope to at least try it out.

Sincerely,
TibiaWR
 
@TibiaWR
It's hard to find a balance between user friendliness and versatility. I know for sure, that if a system doesn't look "intuitive" at a first glance, it's going to be hard for people to adopt it, but lets see why it works they way it does.

The library was first designed based on my team needs, we could've developed some adhoc solution, but after a few prototypes I decided it might be useful to elaborate on a more complex alternative that will benefit us in the future. While working on it, I recognized that other people could use it too, due the fact the library is totally decoupled from our script infrastructure. That said, I'm not aiming to replace the default NPC System, but to provide an alternative (i.e is not an update to the current npc system), so if people feel that Jiddo's system works fine for their needs, there's no reason to learn this.

Also, the GUI I'm working on will help scripters to get used to the library, it won't do everything for you though, since the main purpose of the tool is for my team internal usage, and we are ok having to hand-code stuff. I'm planing to release a extensive tutorial alongside with the first version of the library.

Finally, the only purpose of this release, is to give something back to the OT community, and keeping it alive.

@thread
I'm almost done with the first milestone, expect a release soon!
 
First beta version going to be released tomorrow monday. Just fixing documentation atm, heres another preview (Working 100%):
Lua:
George.Conversation = {
	{["ass%"]= function(talk, match, msg) -- Matchs ass/asshole, but no badass/noobass
		talk:Say("You called me an " .. match)
		talk:Say("You must die")
		
		local m = talk:Listen() -- Stops the execution until new data from player arrives (this does not stop the npc from moving, or talking to other players)
		
		talk:End("And now you called me a " .. m .. "! Bye |PLAYERNAME|")
	end},
	
	{["job|occupation"] = "My job is to complete the turing test ASAP!"}, -- Matchs either job OR occupation
	{["mission|quest"] = "I have no missions for you son"},
	{["heal"] = { 
		{[lessThan(playerHealth, 50)] = function(talk) -- Executes if player health is below 50
			doCreatureAddHealth(talk.player, 200)
			talk:Say("You have been healed!")
		end},
		{[Default] = "Sorry ur not wounded enough!"} -- Executes if the above wasnt true (i.e player health >= 50)
	}}
}

A bit ugly I must say, but for basic "text" npcs, this will look nice enough. (For more complex npcs, the Talk object will provide the needed tools, though is very basic in this first version)
 
Back
Top