[Tut]Guide to NPC scripting

Experienced Elementalist
Joined
Jul 29, 2008
Messages
274
Reaction score
20
I got tired of everyone releasing simple-ass NPCs that do the same thing, so I decided to write a guide because really, it isn't hard.
Ok, starting off, all methods in your NPC scripts are defined by a source file called NPCConversationManager in your net.sf.odinms.scripting.npc package, or it might be in AbstractPlayerInteraction (the class is not an abstract class) which has methods that are universal or all scripting, meaning if you put a method in there, you can use it in ANY script.
For exmaple, if I put a method like this
Code:
public void setHp(int newhp) {
    c.getPlayer().setHp(newHp);
}
In abstractPlayerInteraction, you can use it in ANY script, for example you could do cm.setHp(5), pi.setHp(5), or anything, (pi is for portals)
The thing in the paranthesis after the declaration of the method is called a parameter, something you have to provide when using the method. You may put a method with no parameter if you dont need any parameters.
The basic NPC Script
You will need to start the NPC script with variable, status, which is used in any NPC that is properly scripted to keep it from flooding everything at once.
So at the top of your script, add
Code:
 var status = 0;
The var status is set to 0 because that's just a better way to do it, you can set it to 5 if you want, or even 666, but that just makes your code weird and harder to read.
All NPCs that are properly scripted have two functions start and action.
Usually, in the start function, all that is set is the status and action.
You should always set the status to -1 unless you're making a weird-ass NPC and action doesnt matter right now, most of the time it should be (1, 0, 0)
So your start function should look like this
Code:
function start() {
	status = -1;
	action(1, 0, 0);
}
And your action method, which should be the body of your NPC, aka what it does (say stuff, open a shop, warp, give an item, whatever). This part will mostly be coded with the methods in your NPCConversationManager and AbstractPlayerInteraction, with a cm. in front of it. For example, your NPCConversationManager has a openshop method inside of it, which is a void. (If you dont then you have a weird source) (A void means it has no return value, we'll go into more detail about that later)
So to use this method, you would look at the parameter, which is an integer value, so you would have to fill in an integer value for the parameter and use the method like this: cm.openShop(INTVALUEHERE).
Usually the name of the parameter should give you an idea what it is, like int shopid should give you a clue that the parameter is the shopId.
If there is nothing in the paranthesis, that means that you dont need to add anything in the paranthesis, such as getJob, you do not need a parameter for that so you can just use it as cm.getJob();
NOTE: Captilization matters, openSHOP isn't the same as openShop.

When you use a method, you need to add a ; after all of them Ex: cm.sendOk("Hi");
You miss the semicolon and it wont work.
Some of the most commonly used methods are listed
Code:
cm.sendOk(String text) : Sends a box with the text in the paranthesis (Note: All strings must have "Quotes" around them
cm.sendNext(String text) : Like sendok except with a next button
cm.sendNextPrev (String text): Like sendNext except with a next and a previous button
cm.openShop(int shopid): Opens the shop with the specified ID
cm.getPlayer() : This is a huge one, it lets you access most of the methods in the 2nd biggest file in your OdinMS folder (unless yours is really fucked up), MapleCharacter, but it simply gets the object of the player, and you can apply maplecharacter methods here such as saveToDb and other important ones, you will use this A LOT.
cm.dispose(): disposes the NPC, ends the instance, whatever, use this when you want the NPC box to close, you should always add this method to the end of your NPC scripts, or the player will have to relog after using the NPC to talk to another NPC.
Too lazy to add the rest, just read your NPCConversationManager

Now time for the hard part (not really, still really easy :P) the NPC body.
First you have to start your action function which should be like this
Code:
function action(mode, type, selection) { //close this bracket when the NPC ends


Now to code this usually you start off with something like
Code:
	if (mode == -1) {
		cm.dispose();
	} else {
		if (mode == 0 && status == 0) {
			cm.dispose();
			return;
		}
		if (mode == 1)
			status++;
		else
			status--;
To read this requires, *gasp*, COMMON SENSE
if (mode = -1) happens if the mode is negative 1, which in that case the NPC does nothing.
if (mode == 1) status++;, this increments status by 1 if the mode is 1, which allows you to use status correctly and code your NPC.
But before we get started on coding your body, I have to explain if, else, and else if statements which are very basic.
If statements are used like this
Code:
if (BOOLEAN GOES HERE) {
   BLAHBLAHBLAH;
} else {
BLAHBLAH
}
[CODE]
Basically, a boolean returns true or false, it can be something like a cm method that returns a boolean (public boolean <methodname>) or something that returns true or false, such as (mode == 1) (NOTE: = is not the same as ==, = assigns a value to a variable, == is comparing to see if the two values are the same)

If the boolean in paranthesis returns true, the BLAHBLAHBLAH gets execute (put whatever you want there, such as cm.sendOk("Whatever");) if not, it executes the else, simple right?
Now how you work this with status is that everytime your NPC does something, the status is incremented, so simply code your NPC like this.
[CODE]
 if (status == 1) {
whateveryouwanttodohere;
} else if (status ==2) {
blahblah;
}else if (status == 3) {
hhhhhh;
} 
and so on
You should try to limit it to 1 line per status if possible, so if you want an NPC to say hi to a player, you could do this (example)
Note: anything with // in front of it is a comment, which is completely ignored when the NPC is executed
Code:
//This only includes the action part of the action that makes the NPC do stuff
if (status == 1) {
cm.sendOk("Hi, would you like a brownie?");
} else if (status == 2) {
cm.sendOk("Well too bad, you're a fag and you're not getting one.");
}
So now put the whole script together
Code:
var status = 0; //status

function start() {
	status = -1;
	action(1, 0, 0);
}

function action(mode, type, selection) {
	if (mode == -1) {
		cm.dispose();
	} else {
		if (mode == 0 && status == 0) {
			cm.dispose();
			return;
		}
		if (mode == 1)
			status++;
		else
			status--;
//This only includes the action part of the action that makes the NPC do stuff
if (status == 1) {
cm.sendOk("Hi, would you like a brownie?");
} else if (status == 2) {
cm.sendOk("Well too bad, you're a fag and you're not getting one.");
cm.dispose(); //ends the NPC and exits window
}
} //closes the action function
And you got yourself a NPC!
What's different about coding NPCs is the body, there are many methods in your NPCConversationManager, and you can even add your own, I suggest you first start out reading NPCConversationManager and make sure you understand what all the methods in there do, this will help you when you want to code NPCs that do more than sit and talk, because that isn't very useful, this guide was made in hope of stopping the flood of useless NPC releases, I dont know if this was helpful, I hope it was atleast feel free to ask me any questions, but keep in mind that I dont even have a basic knownledge of javascript and all the statements I know in JS are usually just simple statements in java that are universal (like if elses are practically the same in every language). I'm not great at writing guides, so in case I didnt explain something clearly, let me know (post on thread dont PM) and I will try to help you if I can and Im not too lazy.
 
Re: [Guide]Guide to NPC scripting

Nice guide.

A little bit of spacing on some of your blocks of text would be nice :):
 
Re: [Guide]Guide to NPC scripting

Hi,
Just wondering

how do you script NPC that is already multi-funtion

I want to add a price on these, but i don't know what to do when there are 3 things in it.
PHP:
var bossmaps = Array(100000005, 105070002, 105090900, 230040420, 280030000, 220080001, 240020402, 240020101, 801040100, 240060200, 610010005, 610010012, 610010013, 610010100, 610010101, 610010102, 610010103, 610010104); // Someone else's House, The Grave of Mushmom, The cursed Sanctuary, The Cave of Pianus, Zakums Altar, Origin of Clocktower, Manons Forest, Griffey Forest, The Nightmarish Last Days, Horntails Cave, Bigfoot- Phantom Forest-Forgotten Path, Phantom Forest-Evil Rising, Phantom Forest-The Evil Dead, Phantom Forest-Twisted Path 1, Phantom Forest-Twisted Path 2, Phantom Forest-Twisted Path 3, Phantom Forest-Twisted Path 4, Phantom Forest-Twisted Path 5
var monstermaps = Array(100040001, 101010100, 104040000, 103000101, 103000105, 101030110, 106000002, 101030103, 101040001, 101040003, 101030001, 104010001, 105070001, 105090300, 105040306, 230020000, 230010400, 211041400, 222010000, 220080000, 220070301, 220070201, 220050300, 220010500, 250020000, 251010000, 200040000, 200010301, 240020100, 240040500, 240040000, 600020300, 801040004, 800020130); // Dungeon Southern Forest I, Tree that Grew 1, Henesys Hunting Ground 1, Line 1 Area 1, Line 1 Area 4, Camp 1, Dangerous Valley II, Excavation Site III, Land of Wild Boar, Iron Boar Land, The Land of Wild Boar II, The Pig Beach, Ant Tunnel Park, Drakes Meal Table, The Forest of Golem, Forked Road: East Sea, Forked Road: West Sea, Forest of Dead Trees 4, Entrance to Black Mountain, Deep Inside the Clock Tower, Forbidden Time, Lost Time, Path of Time, Terrace Hall, Practice Field, Beginner, 10-Year-Old Herb Garden, Cloud Park 3, Garden of Darkness 1, Battlefield of Fire & Darkness, Entrance to Dragon Nest, The Dragon Canyon, Wolf Spider Cavern, Armory, Encounter with the Budda, 
var townmaps = Array(1010000, 680000000, 230000000, 101000000, 211000000, 0, 100000000, 251000000, 103000000, 222000000, 104000000, 240000000, 220000000, 250000000, 800000000, 600000000, 221000000, 200000000, 102000000, 801000000, 105040300, 60000, 610010004, 260000000, 540010000, 120000000); // Amherst, Amoria, Aquarium, Ellinia, El Nath, Entrance - Mushroom Town Training Camp, Henesys, Herb Town, Kerning City, Korean Folk Town, Leafre, Lith Harbor, Ludibrium, Mu Lung, Mushroom Shrine, New Leaf City, Omega Sector, Orbis, Perion, Showa Town, Sleepywood, Southperry, Crimsonwood, Ariant, Singapore, Nautilus Port
var chosenMap = -1;
var monsters = 0;
var towns = 0;
var bosses = 0;

importPackage(net.sf.odinms.client);

function start() {
	status = -1;
	action(1, 0, 0);
}

function action(mode, type, selection) {
            if (mode == -1) {
                cm.dispose();
            }
            else {
                if (status >= 3 && mode == 0) {
			cm.sendOk("See you next time!.");
			cm.dispose();
			return;                    
                }
                if (mode == 1) {
			status++;
		}
		else {
			status--;
		}
               if (status == 0) {
                        cm.sendNext("Hey I'm the All-In-One Teleport Manager!");                  
                }
               if (status == 1) {
                   cm.sendSimple("#fUI/UIWindow.img/QuestIcon/3/0#\r\n#L0#World Tour#l\r\n#L1#Leave#l");
               }
               else if (status == 2) {
                   if (selection == 0) {
                       cm.sendSimple("#fUI/UIWindow.img/QuestIcon/3/0#\r\n#L0#Towns#l\r\n#L1#Monstermaps#l\r\n#L2#Bossmaps#l");
                   }
                   else if (selection == 1) {
                       cm.dispose();
                   }
               }
               else if (status == 3) {
                   if (selection == 0) {
                        var selStr = "Select your destination.#b";
			for (var i = 0; i < townmaps.length; i++) {
				selStr += "\r\n#L" + i + "##m" + townmaps[i] + "#";
			}
                        cm.sendSimple(selStr);
                        towns = 1;
                   }
                   if (selection == 1) {
                       var selStr = "Select your destination.#b";
                       for (var i = 0; i < monstermaps.length; i++) {
				selStr += "\r\n#L" + i + "##m" + monstermaps[i] + "#";
                       }
                       cm.sendSimple(selStr);
                       monsters = 1;
                   }
                   if (selection == 2) {
                       var selStr = "Select your destination.#b";
                       for (var i = 0; i < bossmaps.length; i++) {
				selStr += "\r\n#L" + i + "##m" + bossmaps[i] + "#";
                       }
                       cm.sendSimple(selStr);
                       bosses = 1;
                   }
               }
            else if (status == 4) {
                if (towns == 1) {
                cm.sendYesNo("Do you want to go to #m" + townmaps[selection] + "#?");
		chosenMap = selection;
                towns = 2;
                }
                else if (monsters == 1) {
                cm.sendYesNo("Do you want to go to #m" + monstermaps[selection] + "#?");
                chosenMap = selection;
                monsters = 2;
                }
                else if (bosses == 1) {
                cm.sendYesNo("Do you want to go to #m" + bossmaps[selection] + "#?");
                chosenMap = selection;
                bosses = 2;
                }
            }
            else if (status == 5) {
                if (towns == 2) {
                    cm.warp(townmaps[chosenMap], 0);
                    cm.dispose();
                }
                else if (monsters == 2) {
                    cm.warp(monstermaps[chosenMap], 0);
                    cm.dispose();
                }
                else if (bosses == 2) {
                    cm.warp(bossmaps[chosenMap], 0);
                    cm.dispose();
                }
            }
              
            }
}
 
Re: [Guide]Guide to NPC scripting

Hi,
Just wondering

how do you script NPC that is already multi-funtion

I want to add a price on these, but i don't know what to do when there are 3 things in it.
PHP:
var bossmaps = Array(100000005, 105070002, 105090900, 230040420, 280030000, 220080001, 240020402, 240020101, 801040100, 240060200, 610010005, 610010012, 610010013, 610010100, 610010101, 610010102, 610010103, 610010104); // Someone else's House, The Grave of Mushmom, The cursed Sanctuary, The Cave of Pianus, Zakums Altar, Origin of Clocktower, Manons Forest, Griffey Forest, The Nightmarish Last Days, Horntails Cave, Bigfoot- Phantom Forest-Forgotten Path, Phantom Forest-Evil Rising, Phantom Forest-The Evil Dead, Phantom Forest-Twisted Path 1, Phantom Forest-Twisted Path 2, Phantom Forest-Twisted Path 3, Phantom Forest-Twisted Path 4, Phantom Forest-Twisted Path 5
var monstermaps = Array(100040001, 101010100, 104040000, 103000101, 103000105, 101030110, 106000002, 101030103, 101040001, 101040003, 101030001, 104010001, 105070001, 105090300, 105040306, 230020000, 230010400, 211041400, 222010000, 220080000, 220070301, 220070201, 220050300, 220010500, 250020000, 251010000, 200040000, 200010301, 240020100, 240040500, 240040000, 600020300, 801040004, 800020130); // Dungeon Southern Forest I, Tree that Grew 1, Henesys Hunting Ground 1, Line 1 Area 1, Line 1 Area 4, Camp 1, Dangerous Valley II, Excavation Site III, Land of Wild Boar, Iron Boar Land, The Land of Wild Boar II, The Pig Beach, Ant Tunnel Park, Drakes Meal Table, The Forest of Golem, Forked Road: East Sea, Forked Road: West Sea, Forest of Dead Trees 4, Entrance to Black Mountain, Deep Inside the Clock Tower, Forbidden Time, Lost Time, Path of Time, Terrace Hall, Practice Field, Beginner, 10-Year-Old Herb Garden, Cloud Park 3, Garden of Darkness 1, Battlefield of Fire & Darkness, Entrance to Dragon Nest, The Dragon Canyon, Wolf Spider Cavern, Armory, Encounter with the Budda, 
var townmaps = Array(1010000, 680000000, 230000000, 101000000, 211000000, 0, 100000000, 251000000, 103000000, 222000000, 104000000, 240000000, 220000000, 250000000, 800000000, 600000000, 221000000, 200000000, 102000000, 801000000, 105040300, 60000, 610010004, 260000000, 540010000, 120000000); // Amherst, Amoria, Aquarium, Ellinia, El Nath, Entrance - Mushroom Town Training Camp, Henesys, Herb Town, Kerning City, Korean Folk Town, Leafre, Lith Harbor, Ludibrium, Mu Lung, Mushroom Shrine, New Leaf City, Omega Sector, Orbis, Perion, Showa Town, Sleepywood, Southperry, Crimsonwood, Ariant, Singapore, Nautilus Port
var chosenMap = -1;
var monsters = 0;
var towns = 0;
var bosses = 0;

importPackage(net.sf.odinms.client);

function start() {
	status = -1;
	action(1, 0, 0);
}

function action(mode, type, selection) {
            if (mode == -1) {
                cm.dispose();
            }
            else {
                if (status >= 3 && mode == 0) {
			cm.sendOk("See you next time!.");
			cm.dispose();
			return;                    
                }
                if (mode == 1) {
			status++;
		}
		else {
			status--;
		}
               if (status == 0) {
                        cm.sendNext("Hey I'm the All-In-One Teleport Manager!");                  
                }
               if (status == 1) {
                   cm.sendSimple("#fUI/UIWindow.img/QuestIcon/3/0#\r\n#L0#World Tour#l\r\n#L1#Leave#l");
               }
               else if (status == 2) {
                   if (selection == 0) {
                       cm.sendSimple("#fUI/UIWindow.img/QuestIcon/3/0#\r\n#L0#Towns#l\r\n#L1#Monstermaps#l\r\n#L2#Bossmaps#l");
                   }
                   else if (selection == 1) {
                       cm.dispose();
                   }
               }
               else if (status == 3) {
                   if (selection == 0) {
                        var selStr = "Select your destination.#b";
			for (var i = 0; i < townmaps.length; i++) {
				selStr += "\r\n#L" + i + "##m" + townmaps[i] + "#";
			}
                        cm.sendSimple(selStr);
                        towns = 1;
                   }
                   if (selection == 1) {
                       var selStr = "Select your destination.#b";
                       for (var i = 0; i < monstermaps.length; i++) {
				selStr += "\r\n#L" + i + "##m" + monstermaps[i] + "#";
                       }
                       cm.sendSimple(selStr);
                       monsters = 1;
                   }
                   if (selection == 2) {
                       var selStr = "Select your destination.#b";
                       for (var i = 0; i < bossmaps.length; i++) {
				selStr += "\r\n#L" + i + "##m" + bossmaps[i] + "#";
                       }
                       cm.sendSimple(selStr);
                       bosses = 1;
                   }
               }
            else if (status == 4) {
                if (towns == 1) {
                cm.sendYesNo("Do you want to go to #m" + townmaps[selection] + "#?");
		chosenMap = selection;
                towns = 2;
                }
                else if (monsters == 1) {
                cm.sendYesNo("Do you want to go to #m" + monstermaps[selection] + "#?");
                chosenMap = selection;
                monsters = 2;
                }
                else if (bosses == 1) {
                cm.sendYesNo("Do you want to go to #m" + bossmaps[selection] + "#?");
                chosenMap = selection;
                bosses = 2;
                }
            }
            else if (status == 5) {
                if (towns == 2) {
                    cm.warp(townmaps[chosenMap], 0);
                    cm.dispose();
                }
                else if (monsters == 2) {
                    cm.warp(monstermaps[chosenMap], 0);
                    cm.dispose();
                }
                else if (bosses == 2) {
                    cm.warp(bossmaps[chosenMap], 0);
                    cm.dispose();
                }
            }
              
            }
}

Make another array of costs and make it take away the amount in the index specified.
Ex: var prices = Array(NUMBERS GO HERE);
Then get the price from the index as an int like this
cm.gainMeso(-(prices[chosenMap]) or something...
If you want to know more about arrays simply google it, the internet has tons of useful stuff :)
Generally arrays are basically like
var NAMEHERE = Array(number1, numer2, number3, etc.)
And array can be ints, strings, or anything.
And to get info from the array, use NAMEHERE[INDEXHERE]
Indexhere is the index, which is like the number, like number1 would be index 0, number2 would be 1 etc.
The datatype that comes back after you try to get the info from the array is whatever you put into the array.
 
Re: [Guide]Guide to NPC scripting

Make another array of costs and make it take away the amount in the index specified.
Ex: var prices = Array(NUMBERS GO HERE);
Then get the price from the index as an int like this
cm.gainMeso(-(prices[chosenMap]) or something...
If you want to know more about arrays simply google it, the internet has tons of useful stuff :)
Generally arrays are basically like
var NAMEHERE = Array(number1, numer2, number3, etc.)
And array can be ints, strings, or anything.
And to get info from the array, use NAMEHERE[INDEXHERE]
Indexhere is the index, which is like the number, like number1 would be index 0, number2 would be 1 etc.
The datatype that comes back after you try to get the info from the array is whatever you put into the array.

Add-on , Array values are seperated by commas ',' (if im nt wrong =/) .
Hey Steve, maybe space out your words a little? o.o
 
Re: [Guide]Guide to NPC scripting

Still in need of an answer to my question.

Is there a way I can have an NPC change something in a mysql database. Like, is someone had some random item like a green snail shell, it would change the mysql entry from false to true? If you catch my drift.
 
Re: [Guide]Guide to NPC scripting

Eh, you should add something about comparing string values (wiith .equals() instead of ==). Not a huge deal but I hate when I see it.

Lawl, .equals is java only because String isn't a primitive value, in JS you use == because strings are primitive value.
 
Well really nice guide but... where we choose npc name and picture? im sorry for noob question,i may have missed somewhere in guide,its so wordy but really good! can you help me with my question? :D
 
can someone help..? please? also i tried to change in "String.wz" ,didnt work,do i need to compile in wz also? o.o and if yes,where is the "dist" folder for that ?
 
Lol,the random script you did doesn't even work. Why is it so? And any idea about this:

PHP:
var status = 0;

function start() {
	status = -1;
	action(1, 0, 0);
}

function action(mode, type, selection) {  // close after all

if (cm.getMeso() >= 50000000) {
  cm.sendYesNo("Do you want to buy an Eye of Fire? (50,000,000 Mesos)");
  }
  else {
  cm.sendOk("You need 50,000,000 Mesos to buy anything here.");
  cm.dispose();
  }
  if (status = 1) {
  cm.gainMeso(-50000000);
  cm.gainItem(4001017,1);
  cm.dispose();
  }
  }
 
Code:
var status = 0;

function start() {
    status = -1;
    action(1, 0, 0);
}
        
	if (mode == -1) {
		cm.dispose();
	} else {
		if (mode == 0 && status == 0) {
			cm.dispose();
			return;
		}
		if (mode == 1)
			status++;
		else
			status--;

if (cm.getMeso() >= 50000000) {
  cm.sendYesNo("Do you want to buy an Eye of Fire? (50,000,000 Mesos)");
  }
  else {
  cm.sendOk("You need 50,000,000 Mesos to buy anything here.");
  cm.dispose();
  }     Where it says mode = 0. I'm not sure if it's mode or status. Same thing with the next one.                                                               
  if (mode = 0) {
  cm. dispose();
  }
  if (mode = 1) {
  cm.gainMeso(-50000000);
  cm.gainItem(4001017,1);
  cm.dispose();
  }



Try that, Lejgolacsz. Hope it helps.

Note: I've been looking over coding for a couple of days. Not too experienced. If that doesn't work, please don't flame, I'm just trying to help
 
Back