• Unfortunately, we have experienced significant hard drive damage that requires urgent maintenance and rebuilding. The forum will be a state of read only until we install our new drives and rebuild all the configurations needed. Please follow our Facebook page for updates, we will be back up shortly! (The forum could go offline at any given time due to the nature of the failed drives whilst awaiting the upgrades.) When you see an Incapsula error, you know we are in the process of migration.

Moving on with your NPC Understanding

Junior Spellweaver
Joined
Oct 12, 2008
Messages
130
Reaction score
38
Hello! I'm going to write a few paragraphs about continuing from this general good knowledge of how to write a good NPC Script into actual understanding of what your NPC Tries to do in a certain situation and so on.
Remember! This guide is intended to be read part by part, then experiment between each part. Write a few of the new scripts we've discussed and then continue after you've got a good idea how how it works. Don't try to rush through it!

Who is this for?
This is for the people out there who has a generally good knowledge of how to write a npc that works steadily and for those of you who are stuck trying to figure out how to do something because "You don't know if there's a way to do it in a npc".

When can we start?
Now!

What is a NPC REALLY?
No this isn't one of those descriptions where you tell them it's some sort of code to explain to the "system" what the npc should do and not. No, I see NPC Scripts very differently. I see them as mentioned as scripts. Wikipedia has a very good article about them and you can fine them here:
Typically what I want to emphasize is this part:
Typically, a scripting language is characterized by the following properties:

  • Ease of use. Scripting languages are intended to be very fast to pick up and author programs in. This generally implies relatively simple syntax and semantics.
  • OS facilities - especially filesystem and related, built in with easy interfaces. Scripting is usually aimed at desktops, limiting the portability needs of the pre-built libraries.
  • Interpreted from source code - to give the fastest turnaround from script to execution. On a desktop, the performance of even a slow interpreter is often non-problematic. In comparison, non-scripting languages intended for large programs are often precompiled in at least some sense for superior performance.
  • Relatively loose structure. It would be difficult to use Java as a scripting language due to the rules about which classes exist in which files - contrast to Python, where it's possible to simply define some functions in a file.

Scripts can be written and executed "on-the-fly", without explicit compile and link steps; they are typically created or modified by the person executing them.[1] A scripting language is usually interpreted from source code or bytecode.[2] By contrast, the software environment the scripts are written for is typically written in a compiled language and distributed in machine code form; the user may not have access to its source code, let alone be able to modify it.

Interpreted from source code - Remember this, something is actually reading your text and defining what to do. People usually define HTML and say it's a styling language where you say what will go where. I think, similarly a NPC In Javascript is a way to say where you're going to write your text, and what will happen when the user decides something in your NPC. It's basically instructions for the real code telling it what to do, just like HTML Is instructions for your browser telling it what to do.
This is what we will discuss shortly.

As sunnyboy184 required me to mention, they are named "NPC" as an abbreviation from Non-player Character.

Getting started with the Java side of Maplestory
To understand how the scripts work, we don't need to become Java experts. Although for many this may be the next step, but for some writing npcs is enough. I'm not going to go through any java coding in this tutorial, perhaps a small piece of it but there are some fairly good books and/or tutorials already you can read.

To get started you need some sort of Java source, these can be obtained in the forum. Go look for them if you need one and get one before you continue. You can also read the files on a SVN such as Assembla, you won't need to compile anything in this tutorial.
For NPCS we'll only need the Scripting folder, located in the src folder probably in your source.
When I mention a file I will mention it relative to this folder "Scripting".

NPCConversationManager, AbstractPlayerInteraction and NPCScriptManager
I will to clear up one question go through NPCScriptManger.

How is my NPC Run?
This is exactly what the NPCScriptManager is intended to do. All scripts are located under the scripts folder and we should know this by now. When your NPC is about to be run, this function is called inside NPCScriptManger: start(final MapleClient c, final int npc)

You might recognize it, the only thing we need to remember is it's being run and given the number parameter npc to determine which npc you're trying to click on.
Next it loads the script from the map: getInvocable("npc/" + npc + ".js", c, true);
Not long after, it does something interesting however. What we should remember by now is that whenever we do something in an npc, the command is preceded by the "cm" then a dot and the command. You might guess what's going to happen next?
NPCConversationManager cm = new NPCConversationManager(c, npc, -1, (byte) -1, iv);

Yes, we're creating this mystical cm command and this is done with the NPCConversationManager file, which is what your cm is trying to get the command from. As you can see here: scriptengine.put("cm", cm);
You can define more of these by just writing scriptengine.put("custom", 5);
Whenever I type custom in my npc now it means 5. Just like cm means NPCConversationManager.

NPCConversationManager
This mysterious file.... What does it do?
I said cm is defined to mean NPCConversationManager, and I literally meant that. Whenever you type cm it's like replacing it. However don't try doing that since it won't work. If you want to know why google "Object Oriented Programming". To move on, what we're doing when typing cm.sendOk("Hello"); is trying to execute the function (method in java) sendOk from NPCConversationManager, and we give it the parameter "Hello". The " quotation marks are to indicate it's a string, which is what the function takes in. If you search your file, you'll probably find something like:
PHP:
public void sendOk(String text) {
        sendOk(text, id);
}
Which is referring to the other method with the id. This is a very good way to learn which commands are named what, and it's very useful if you forget a command. Don't use google, check this file for it. I'll soon tell you what to do when you don't find it in here.

However, say you wanted to do something in an npc that you thought to be impossible. It's really not, for example you would just write a function in here using Java. If you don't know how to you look for other things which do almost the same in java and copy paste it until you've learned what does what and so on. Using an example which isn't standard in OdinMS:
PHP:
public final void resetStats(int str, int dex, int z, int luk) {
        c.getPlayer().resetStats(str, dex, z, luk);
}
Resets your stats to the values you give it. To use the command you do cm.resetStats(1, 2, 3, 4); and you'll get stats equal to 1 str, 2 dex, 3 int and 4 luk. Cool huh? So this file defines almost everything you can do in your script with the cm.functionName stuff. It's true!

AbstractPlayerInteraction
What the heck is this file? It doesn't even have anything with NPC Scripts to do? Yes it does in fact. The files we discussed "inherit" other classes, meaning that they use them as base and then add functionality on top of that. See it as you taking someone's drawing (lines only) and then painting colour onto it. :wink:
NPCConversationManager extends AbstractPlayerInteraction, meaning that NPCConversationManager, the one where all your commands were extends this file.

So all commands in AbstractPlayerInteraction will be available here too. Again, still we use the cm.functionName syntax because it's still the same file.
Say, Still the same picture, it's still the same lines, only that we now have extra colours too!
You may have used cm.getPlayer(), this is in this file as:
PHP:
public final MapleCharacter getPlayer() {
        return c.getPlayer();
}
This file is used for commands that can be used in ALL Scripts. Like cm.warp, since a portal (also a script) can also warp you to places or open npcs, they define commands that can be used in all sorts of scripts while NPCConversationManager only gives you the commands which are for NPCS Only. So when you want to write commands that can be used in all sorts of scripts, write them here.

Writing Commands
This is a tricky part to write.
Mostly because we shouldn't be discussing how to write proper java code only the basics of really how to make a new simple command to use. I'm going to mark them with colours and dicuss them a bit:
public final MapleCharacter getPlayer() {
return c.getPlayer();
}


public:
This determines whether you're going to use the command in the NPC or for another command in the file. Public means you can use it in the NPC, private or not typing public at all means you cannot.

final:
This can be ignored for now. Just always use it and you'll be fine unless you need to write some functions which use others as bases.

MapleCharacter :
This tells you what the function will "return" to the thing that ran the function. Typically returning something exits the function. There are a few different types you might want to know:
int = a number between -2147483648 and 2147483647 or something like that. 2^32 atleast
byte = Smaller
float = typically used for decimals and precise numbers (such as x, y coordinates)
There's more number types but I stop there... google them if you need any specific.

bool = A boolean type, named after George Boole. It's either true or false.
char = One single letter, denoted with ' in java. Such as 'c'.
string = A string of letters, what you're used to think of as a "text". It is denoted with quotation marks "like this" when used.
All of these are available at:

As you might notice, MapleCharacter wasn't there. That's because you can also use classes for this. Typically MapleCharacter, MapleClient, MapleJob and so on. Look in the client folder, that's usually all you'll need. Perhaps the maps folder too?

There's also one more type, it's called void. As the suggestion names "empty" means it doesn't return anything. So you just write your code to execute like it should. :eek:tt1:

getPlayer:
This is the name of your function. You'll typically use this such as: cm.getPlayer in your NPC.
If you change it to something else like testPlayer you'll use that instead like cm.testPlayer.

():
This is the parameters. This is kind of a bad example. I'll use another one here:
warp(final int map)
Just like before with the MapleCharacter we use the type (int here) and then a name (like the getPlayer one) to use in the function. We will then be using it in the next part.

{
return c.getPlayer();
}
:

This is what our function does. In this example c is by before said to be client. And in client we use getPlayer() to get player. So we return this, which is MapleCharacter. This might seem confusing at first but if you just keep trying you'll understand eventually. In the warp example as above they use the variable it required:
PHP:
        final MapleMap mapz = getWarpMap(map); <- Our variable map
        try {
            c.getPlayer().changeMap(mapz, mapz.getPortal(Randomizer.nextInt(mapz.getPortals().size()))); <- Randomizes portal, but theres a warp function which lets you select which portal
        } catch (Exception e) {
            c.getPlayer().changeMap(mapz, mapz.getPortal(0)); <- If it fails, use portal 0
        }

Learning what to do can be hard. But if you want to do something common that people do in commands but doesn't exist for npcs, you can easily replace the command into your npc code like this.

We will continue with Other sorts of Scripts!

Portal Scripts
So, now you may say what does this have to do with my NPC Scripting to do? There's more to life than just NPC Scripts, and you'll love me for telling you this I hope. :eek:tt1: Either way, you can do some pretty neat stuff to maps if you'd like to. To use an example, which is what I'll be coding quick here some servers have a "Donator Map" Where only donators can enter and hang out / grind or whatever your map does.

People have discussed how to disable the maps for non donators and so far the only thing I've seen on this forum is removing the portal instance from your xml files which is pretty uncool according to me. I'd prefer if you entered the portal, up comes a small message saying "You are not allowed to enter this map, please donate on to continue further." Sounds neat eh? So how do we do this?

First, we'll just like before quickly discuss the files involved with portal scripts so we know what we can do with them! If we don't know which commands we can use, how are we supposed to make any scripts?

PortalScript.java
This barely even deserves one sentence. It's the shortest file you'll probable ever use. :wink: This is all it contains:
PHP:
public void enter(PortalPlayerInteraction ppi);

This is what we'll use to start the portal script. Just like NPC Scripts have:
PHP:
start(final MapleClient c, final int npc)
We have the enter function here instead.

PortalScriptManager
This is like the NPCScriptManager, what actually runs your script. We're interested in these few lines:
PHP:
final File scriptFile = new File("scripts/portal/" + scriptName + ".js");
Which determines the directory of the scripts. You can of course change this but when you need to change the real map name too. Next we have a few rows that precompile your script:
PHP:
        FileReader fr = null;
        final ScriptEngine portal = sef.getScriptEngine();
        try {
            fr = new FileReader(scriptFile);
            CompiledScript compiled = ((Compilable) portal).compile(fr);
            compiled.eval();
        }

And then the script gets added to a list:
scripts.put(scriptName, script);

This doesn't really run your script as some of you may ask. Why not? Portal scripts aren't done on any user click or similar. In fact, they have relation to the xml files where you determine what portal runs what script and so on. It is then the manager that reads the xml files that executes your script. In the MaplePortal file you can find the line:
PHP:
PortalScriptManager.getInstance().executePortalScript(this, c);
Which executes the code we just looked at. I hope you grasped that not to hard. On to the next file...

PortalPlayerInteraction
Yep one of those cool files again, in the top of the file I can right away see:
PHP:
PortalPlayerInteraction extends AbstractPlayerInteraction
Which means the same thing as with the NPC Scripts, all commands in AbstractPlayerInteraction can be used in our Portal Scripts too! Good news, but of course this file has to have some Event - Only commands, we're going to look at one...
PHP:
public final void inFreeMarket() {
        if (getMapId() != 910000000) {
            if (getPlayer().getLevel() >= 15) {
                saveLocation("FREE_MARKET");
                playPortalSE();
                warp(910000000, "st00");
            } else {
                playerMessage(5, "You must be level 15 in order to be able to enter the FreeMarket.");
            }
        }
    }
This is what they use for the portals that lead to the FM. To save location when going back out! The save location has your old map id so you're all safe when returning. We can also here change the required level for entering the FM As you might see on this line:
PHP:
if (getPlayer().getLevel() >= 15) {

Simple and straight forward, those are all the files. But now then, how do I Make a Portal Script?

As we said, we need to create a new file. It should be in the scripts/portal map but in differ to npc scripts, these you can name anything you want. There is a way to let yourself name your npc scripts anything too but I'll perhaps include that as a sideline in the end.

We create the file, give it a good descriptive name. We were going to let it block a map for people who are not donators. I'll be naming my file "donatorPortal01.js" but anything without spaces is fine. Now that we have the file, we said we needed the enter function instead of start & action. We also saw that we get a PortalPlayerInteraction in the enter parenthesis like so:
PHP:
public void enter(PortalPlayerInteraction ppi);
Which implies our commands with be run with what we put there...
I'm going to name it pi for PlayerInteraction, since it includes Abstract also! (Because the file extends it, remember with the coloured picture? :wink:)
We then type:
PHP:
function enter(pi) {
}
To start it off. We now have a script we can run but it does nothing yet. We want to check if the player was a donator first. Some people have different methods for this. If you have a command it's easy to understand how you do it. It might be:
PHP:
if(pi.getPlayer().isDonator())
For you, but I'm using GM Level 1 for my donators so that's what I'll check for. Since the getPlayer() returned the MapleCharacter class, this is where we look for what command to use after getPlayer().
I found:
PHP:
public int getGMLevel() {
        return gmLevel;
    }
This gives us a number, then what I want to check for is if this is higher than 0. So this code will do that:
PHP:
function enter(pi) {
if(pi.getPlayer().getGMLevel() > 0) {
}
}
Now we just need to implement the messages, but before we do that I want to stop and rethink REALLY about what it's suppose to do. We don't want the donators getting any message, just the people who aren't donators. So we add a ! in the start to indicate it will be true if this GM Level is 0... We're not at:
PHP:
function enter(pi) {
if(!pi.getPlayer().getGMLevel() > 0) {
}
}

Lastly, we saw warp was in AbstractPlayerInteraction, we'll use this to warp him out. Also we can just use this function from AbstractPlayerInteraction:
PHP:
public final void playerMessage(final String message) {
        playerMessage(5, message);
    }
To give the message to the player. As suggested we use this like: pi.playerMessage("Hello");
We now have all we need to finish up:
PHP:
function enter(pi) {
if(!pi.getPlayer().getGMLevel() > 0) {
pi.playerMessage("Only donators are allowed in this map. Visit http://servername.com/donate to donate and access this map!");
pi.warp(910000000); // FM :)
}
}

We've now successfully written our script, tons of cool stuff can be done with Portal scripts... You can play music, show smegas or events (like chinese firework?) Just like with NPC Scripts, these are only limited by how good you know Java and if you can write your own method for it.

Next we need to get our Portal Script to run in the right Portal. For this example I'm going to use FM Room 1 as example. As I said these are being run by the map xml so we need to go to the wz directory then into the right map. The following directory is for the FM Maps:
PHP:
wz\Map.wz\Map\Map9
You look at the map ID, FM Map 1 has 910000001, So the 9 in the start means we go to the Map9 Folder inside Map.wz\Map then Map9
Now the only problem we have is, the portal isn't in FM 1.. It's in the FM Map right? So we need to open the map that has the portal. No, because the script is being run when you enter the map. We don't want anyone who enters the map in any way to access it if they're donors. So open up 910000001.img.xml and let's start. Try searching for "onUserEnter" and you'll probably only find: <string name="onUserEnter" value="" />

If not, search for <imgdir name="info"> and add <string name="onUserEnter" value="" /> anywhere under there. The value determines which script to run. I named my script donatorPortal01.js so similarly I type donatorPortal01 under the value. It should look like so:
PHP:
 <string name="onUserEnter" value="donatorPortal01" />

Now, since these were precompiled as I said before and also because we edited the XML Which gets loaded on server startup, you should restart the server. You don't need to compile the script with the source, that is done by the source when it's run. Congratulations for finishing your (perhaps) first portal script! On to more advanced stuff! Don't forget to experiment some before you continue. The next part will be tricky (I think).

Quest Scripts
You can do some pretty hefty things with quest scripts in fact. We're going through the same procedure as before with the files involved then we'll write a nice sample. Unfortunately for you, there's no separate quest file in most sources. This is kinda strategically done too. Remember all the commands we used before? All commands possible to use in a NPC Is for quests too. Since we discussed all the files, but I left out the quest part I'm going to fast go through what in the NPC files handles quests.

NPCScriptManager
startQuest(final MapleClient c, final byte mode, final byte type, final int selection)
As it may sound, this is what starts each quest. It uses the same "cm" as before:
final NPCConversationManager cm = cms.get(c);

Then our cm won't be used with "cm" but as here defined "qm":
scriptengine.put("qm", cm);

And you start the quest with the "start" function:
cm.getIv().invokeFunction("start", mode, type, selection);

We have the same NPCConversationManager just that we address it by "qm" instead. So something like: qm.dispose(); is completely correct usage.

After that, we use all the commands that are in the NPCConversationManager just like before but with qm.
Writing a simple sample :D:
Let's write a fast quest script. I'm just going to have the idea of someone talking to an old man and he appreciates tells you to go get 100 mesos for him to buy some bread.
We're going to use qm.forceStartQuest(); to start the quest. Similarly qm.forceCompleteQuest(); Completes it. We have the function start(mode, type, selection) and end(mode, type, selection) for starting and completing the quests too. So here we go:
PHP:
function start(mode, type, selection) { // Runs if not started
        qm.sendOk("Great! Please get me some money, I'm so hungry. I need some bread! Experience is at stake for you!"); // To make him talk
        qm.forceStartQuest(); // Start Quest as I said
	qm.dispose(); // Disposing, just like cm.dispose();
}

function end(mode, type, selection) { // Runs if started
	if (qm.getMeso() > 99) { // Check if we aquired the items.
		qm.gainExp(1000); // Give exp
                qm.gainMeso(-100); // Remove meso
                qm.forceCompleteQuest(); // Complete Quest as I said
	}
	qm.dispose(); // Disposing again
}

This shouldn't be too hard considering it's just the same as a npc script except we have no action and instead it can enter on two different places. Now we need to run our quest too somewhere right? We do this on the NPCs if you remember? A sample of how to write someone with Yes/No accept quest:
PHP:
function start() {
    status = -1;
    action(1, 0, 0);
}

function action(mode, type, selection) {
    if (status >= 0 && mode == 0) { // Means we pressed No.
	cm.sendOk("That's too bad! I really needed it.");
	cm.dispose();
    }
    if (mode == 1)
		status++;
    else
		status--;
	if(!cm.isQuestStarted(questId)) { // Checks if quest has NOT been started.
		if (status == 0) {
			cm.sendYesNo("Can you help me out with this quest o' mine good fella?"); // The yes/no
		} else {
			cm.startQuest(questId); // Pressed yes, runs our start code in the quest script
                        cm.dispose();
		}
	} else { // Since the quest script runs forceStartQuest() it should come here when the last if is started
		cm.completeQuest(questId); // Runs end code DOESN'T HAVE TO COMPLETE IT!(The forceCompleteQuest is in an if-statement in the quest script)
		if (cm.isQuestCompleted(questId)) { // Checks if it REALLY is completed :)
			cm.sendOk("Thank you and goodbye!"); // Completed, he gets coins from quest script
		} else {
			cm.sendOk("Go out there and gather coins for me son. You will be rewarded justly!"); // Not Completed, tells him to go back out there.
		}
		cm.dispose(); // Dispose for entire
	}
}

Together, these two can make quite a powerful experience. I advice you to try make at least 4-5 of these and feel free to ask below if anything goes wrong. A lot lies in keeping the logic separated.
More being written!
 
Last edited:
Have Fun!
Joined
Nov 2, 2008
Messages
481
Reaction score
70
Wonderful guide! Very informative and detailed, and explains it just right.
Great job! Thanks for sharing.

-Sent from my mobile-
 
Junior Spellweaver
Joined
Oct 12, 2008
Messages
130
Reaction score
38
You ask "What is a NPC REALLY?"n then you go into talking about what an NPC Script is, but you haven't answered what an NPC is. It stands for None Player Controlled!! YW!

Yaya sure, Non Player Character
"A non-player character (NPC), sometimes known as a non-person character or non-playable character"

You try :p Either way, it's a guide for those who moved on from those questions anyways.
 
Junior Spellweaver
Joined
Feb 11, 2013
Messages
137
Reaction score
0
nice guide :D
what about event script that i think the most important for this guide
 
Junior Spellweaver
Joined
Oct 12, 2008
Messages
130
Reaction score
38
This is what I'm kinda thinking on what approach to take, it could be a guide on it's own but we'll see.
 
Skilled Illusionist
Joined
Jul 19, 2012
Messages
313
Reaction score
11
I wonder if anyone could make an event script guide perhaps? that plays a great part in game as well, and there's too many npc scripting guide out there but not event o.o
 
Junior Spellweaver
Joined
Oct 12, 2008
Messages
130
Reaction score
38
I wonder if anyone could make an event script guide perhaps? that plays a great part in game as well, and there's too many npc scripting guide out there but not event o.o

Read my post, I'm adding it to here or writing an entire guide of it.
 
Back
Top