ID:53110
 
Keywords: article
This post is honestly a bit of a response to the "Make your NPC's Lively" post, but I believe that it merits more than a simple comment on the original post, as I intend to go well beyond simply replying.

I'm not trying to insult the author of that post, or rebut them, I simply think that they only scratched the surface.

First, since this is my first post of this nature, I'm going to put a little information about my credentials and a little disclaimer. If you don't care, skip over it, I'm just writing it for the sake of full disclosure. It's at the bottom of the post, but some people probably like to read that stuff first.

Anyways, my biggest problem with the article linked too earlier (and I suppose with many traditional RPGs for that matter) is the basis on which NPCs and players interact.
Most RPGs fall under RPG cliché #93 "Puddin' Tame Rule": The average passer-by will always say the same thing no matter how many times you talk to them, and they certainly won't clarify any of the vaguely worded warnings or cryptic half-sentences they threw at you the previous time. (The full list that I'm refering to is here)
The "Make NPCs More Lively" post takes it a step up from there, basing its NPC interactions based on things such as the players gender or how much gold they have. In reality though, we aren't quite so shallow. (I'd hope) Instead, we base our interactions with others based upon previous interactions. What I say to my best friend will be a little different than what I say to my mortal enemy, even if they are the same gender, race, economic class, etc. (Thats right, I have mortal enemies) Yet, most games neglect this.
The main reason that most games don't have a system like this is that 1) it's cumbersome, requiring information about each player's rapport with a specific NPC and 2) It requires quite a bit of creativity and work. 3) The information takes up file space. For many games, its simply not worth the effort and I don't blame them.

Now, my solution to this is datum based. Instead of storing information about NPC/Player interactions in lists or an obscene number of variables, I use datums to store information. Among other reasons, its cleaner, and allows for methods to be called.
Unfortunately, I was having a hard time thinking of a good name, for these datums, so the best I could come up with was NPCInteraction, which is usually abbreviated to NPCI for variable names. Despite numerous typos, the name stuck.
Each NPCI corresponds with all of the interactions between one player and one NPC.
(I should also mention that I use Player datums and NPC datums, both of which inherit from /mob. They both need their own type, but I don't like using /mob/NPC or /mob/Player. It's not 100% necessary yet it really helps and will probably prevent bugs later on)
Each NPCI itself needs very little information.
Player/var/list/NPCIs = list()
NPC/var/ID
NPCInteraction
var/ID
var/relation = 0 //Positive means they like you, negative means they dislike you.
var/status = 0
New(Player/P,_ID)
..()
ID = _ID
if(P) P.NPCIs[ID] = src

Player/proc/getNPCI(ID)
if(isnull(NPCIs) || NPCIs.len == 0) return null
var/NPCInteraction/NPCI = NPCIs[ID]
if(!NPCI || !istype(NPCI))
return null
return NPCI

The NPCI is implemented by an ID system and a list. Every NPC has an ID, which is a string. Each NPCI is given an ID identical (that's almost a pun: ID IDentical?) to the one of the NPC that it corresponds to. And, when the NPCI is created, it is added to the Player's list of all of their NPCIs and, for convienience, the NPCI is associated with the ID. (This is why IDs must be strings, and not numbers)

Now, this may not look like nearly enough information to run an NPC system, but the basic NPCI is generic. So far, I haven't thought of any other variables that cover all NPC Interactions. Status is a very flexible variable. A value of zero (or any false value) usually indicates that they've never met you at all. For some NPCs, this is all that status will be used for. For others, it may indicate how far along you are in a quest for example.
Relation is just how they feel about you. Do they like you or not? I actually use this for shop keeper greetings. If you repeatedly talk to him without buying anything, he will like you less and less and his greeting towards you will be come less and less friendly.
If you buy from him quite a bit, the relation goes up, and he becomes friendlier and may even offer you a discount.
These are just the variables I use.

Obviously, though, these variables can't handle all of the NPC interactions. But, child types of /NPCInteraction can be defined containing other variables. For example, one might decide to make a /NPCInteraction/Shopkeeper which could have a variable to keep track of when you last visited the shop.

Now, an example of an NPC using this system.
NPCI/ExampleNPCI
var/knowsYourName = 0
NPC/ExampleNPC
name = "Joe the NPC" //Kudos to those who get the joke
ID = "joe"
Talk() //Talk is already defined, range is defaulted to view(1)
//Check for stuff like istype here, deal with freezing etc...
var/Player/P = usr
var/NCPInteraction/ExampleNPCI/NPCI = P.getNPCI(ID)
if(!NPCI || !istype(NPCI)) NPCI = new NPCI(P,ID)
if(!NPCI.status)
P << "Hello, I don't believe we've met before."
if(alert(P,"Tell him your name?","Joe","Yes","No")=="Yes")
NPCI.knowsYourName = 1
P << "Good to meet you [P.name]"
else
NPCI.relation -= 5
P << "Fine then."
NPCI.status = 1
else
if(NPCI.relation < 20) //You must have really pissed the guy off
P << "He ignores your attempt to begin a convesation.
return
if(NPCI.knowsYourName) P << "Good to see you again [P.name]"
else P << "Good to see you... whoever you are."
switch(input(P,null,name) in list("End the conversation","Raise his taxes","Make smalltalk")
if("End the conversation")
NPCI.relation-- //It's a little impolite.
if("Raise his taxes")
P << "How am I supposed to expand my small business and stimulate the economy and create more jobs with this tax burden?"
NPCI.relation-=10 //Yes. I'm a conservative. Sue me.
if("Make smalltalk")
//I'm not going to bore you with the details here, suffice it to say, they have a fairly long intimate conversation in which each person comes to know each other and themselves and connect on a spiritual level.
//(Or you could just ask how the weather is)
NPCI.relation++

Now, a few notes:
1) I don't really use the << to handle output like that, I pass it through procs which handle the formatting of the text and then optionally the time delay between each message. But for the sake of simplicity I used it here.
2) Now, obviously this doesn't show much, (except maybe my attempts at humor) but it shows the general idea.

Anyways I'll list a few pros and cons with my idea that I've seen so far.

Pros:
-I've yet to think of a scenario that can't be handled fairly easily with this scenario.
-Datums are small and require very little savefile space.
-Most NPCs will only use the basic NPCI, using only the three variables, and some may not use the NPCI system at all.
-Many quests (or pseudo-quests) can be handled using these NPC Interactions alone, without even needing a separate system for them.
-You get what you put into it: You can put a very simple system in that does very little (perhaps only records whether you've talked to them before or not), or you can put in a highly detailed NPC that borders on AI. (Or you can mix and match, some simple NPCs, some complex ones)



Cons:
-Burdens Talk() proc with several lines of code, including istype checks. (I've actually created a #define macro to take a few lines out of each)
-May be hard to adapt to an already existing NPC system.
-In some cases, a system like this may be overkill.
-Adds to savefile size.
-Requires code for NPCs to be divided into two parts, making layout a little cumbersome.
-I'm always mistyping NPCI as NCPI. (Look carefully, I may have done it in this post)




////////////////////////
Credentials/Disclaimer
////////////////////////
Whenever I read posts like this, I tend to assume that the person writing them is an expert coder, even though they aren't necessarily. I'm really not one.

I've been coding in DM for about 2 years now, and have studied other languages, including C++ and alot of Java, recently. I've spent much of that two years looking at libraries and reading posts on the Dream Makers guild.
My only major project (excluding the few small projects I worked on while still learning coding) is my involvement in Hogwarts: A History. I am currently attempting to work on a solo project, which is where this idea comes from.
I've been heavily influenced by my studies of Java, which has, to some degree, caused me to use datums extensively in recent work.

My disclaimer is this: my method is essentially untested. I've put it in my newest project, and I like what I've seen from it so far, but I can't really guarantee that it's a good way of handling this. So, if the idea is flawed, please comment, and if you're considering using it, check the comments.
//////////////////////////
End disclaimer/credentials
//////////////////////////

Comments are appreciated, positive or negative

This is a more extensible and in-depth system for handling NPC interactions, but I'm quite sure I don't want to be ultimately penalized for canceling a conversation before a proper "goodbye" option surfaces. When certain events take place in the game world, players expect NPCs to offer information on a previously unattainable quests, point them toward level-specific areas, be referred to as a hero, or just to hear something different at all. Because of this, many players (myself included) make rounds on NPCs they've previously encountered for any of the above or otherwise, and upon getting the exact same spiel, start looking for a cancel button.

This is some good solid advice, but I think more needs to be done at the intuitive level as opposed to the functional level -- things like marking NPCs that have something novel to offer can go much further than some simple pizazz, because you're avoiding the issue of stale dialogue at the basic level.
Storage of the various talk options is something that could use more work Hard-coding all the text and such is going to bump you up against BYOND's compile-time string limits. A better solution might be to keep NPC text in files to load at runtime, possibly with a little pseudo-language that can be used for most NPC interactions.

Creating a mini-language for NPC interactions shouldn't be too hard because even with this expanded system the number of things they can say is still limited. The main benefit, aside from not taking up the allotment of compile-time strings, is that you're also not hard-coding anything they do or say so it's trivial to expand the world or improve their dialogue later.