從零開始學習 NPC

Hello, and welcome to my attempt to teach everyone NPC scripts better. Regardless of the countless tutorials, and countless Help threads that have been posted and solved, people still seem to have issues with NPCs. Hopefully, with this tutorial, people will learn something. Before we start, let me tell you a bit about myself. I joined RageZone in October of 2008. I started just like most of you guys, not knowing a god damn thing. I’ve had my share of nub questions asked, but I’ve also learned from the help others have given me. I read tutorial after tutorial to learn more, so I didn’t have to rely on others to answer my questions. Sure, there are still quite a few things I don’t know, no one knows everything. Anyone who says otherwise is a damn fool. The main point I’m trying to make is, if you truly want to be good at something, it takes work. You can’t sit back, have others do the work, and expect to become a pro at whatever it is you wish to do. You have to show initiative, and want to learn. I hope my tutorial teaches you something, even if it’s just one thing.

第一級: 初心者

第一課: 實用的腳本編輯器

諸多程式都能編寫程式及腳本,有些還支援語法與高亮,如此一來就能看到花括號是從哪裡開始與到哪裡結束,整理腳本時給予不少幫助。以下是幾款推薦的軟體:

NotePad++
PsPad

第二課: Types of NPCs

NPC 主要有兩種:需要status的,以及不需要status的。最簡單的區分方法就是…顯然地…status。編寫 NPC 時,時時刻刻捫心自問:我的 NPC 會需要兩個以上的對話窗嗎?如果答案為是,那你絕大多數時候會需要用到status;若答案為非,那status可能用不太到。請見以下範例:

不須status的NPC

1
2
3
4
function start() { 
cm.sendOk("我是一個沒有 status 的 NPC。");
cm.dispose();
}

另一個不須status的NPC

1
2
3
4
5
6
7
8
9
10
function start() { 
cm.sendOk("這是另一個沒有 status 的 NPC。");
}

function action(mode, type, selection) {
cm.warp(100000000, 0);
cm.gainItem(4001126, 1);
cm.sendOk("看到了嗎?我不用 status 就能把你傳送走並給你道具。");
cm.dispose();
}

須要status的NPC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var status;

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

function action(mode, type, selection) {
if (mode == 1) {
status++;
} else {
status--;
}
if (status == 0) {
cm.sendNext("請按下一步,我才能繼續下一個 status。");
} else if (status == 1) {
cm.sendSimple("你看到我如何操作 status 了嗎?\r\n #L0# 是 #l \r\n #L1# 否 #l");
} else if (status == 2) {
if (selection == 0) {
cm.sendOk("我在這!這是另一個 status。如你在腳本中看到的,這個對話窗是在 status 2。");
cm.dispose();
} else if (selection == 1) {
cm.sendOk("好吧,這個對話窗也是在 status 2 :)");
cm.dispose();
}
}
}

第三課: 與你和其他腳本作者溝通

編寫腳本的重點之一就是能夠了解你到底在做什麼或是能夠了解其他作者在幹嘛。不作紀錄還能有什麼好方法幫你了解腳本?你可曾看過//出現在腳本某一行的後面?他們叫做「註解」。以下是兩種註解:

1
2
3
4
5
6
7
// 這是一種註解
// 這種註解只能單行使用,這也是為什麼它叫做單行註解。

/**
這裡是多行註解,
任何被 * 包起來的東西都不會被腳本解釋器解讀。
**/

註解會被腳本解釋器忽略,意味著任何「被註解」的東西都是給人讀的。註解在你編寫很長的腳本時能幫你紀錄你正在做什麼而十分有用,還能讓你的程式碼讀者在不懂程式時能瞭解你想做什麼。

第四課: 學習專門術語

該來的還是會來,你不可能不知道正確的專門術語就能編寫 NPC。以下是幾個小抄:

NPC 顏色代碼、道具圖片、其他

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#b = 藍色文本
#d = 紫色文本
#g = 綠色文本
#k = 黑色文本
#r = 紅色文本
#e = 粗體文本
#n = 正常文本 (移除粗體)
#c[道具ID]# 顯示玩家背包中有多少 [道具ID]
#h # - 顯示玩家名稱
#m[地圖ID]# - 顯示地圖名稱
#o[怪物ID]# - 顯示怪物名稱
#p[NPCID]# - 顯示 NPC 名稱
#q[技能ID]# - 顯示技能名稱
#s[技能ID]# - 顯示技能圖片
#t[道具ID]# - 顯示道具名稱
#i[道具ID]# - 顯示道具圖片
#z[道具ID]# - 顯示道具名稱
#v[道具ID]# - 顯示道具圖片
#x - Returns "0%" (need more information on this).
#B[%]# - 顯示進度條
#f[圖片位址]# - 顯示 WZ 檔案中的圖片
#F[圖片位址]# - 顯示 WZ 檔案中的圖片
#L[數子]# 選項開始
#l - 選項結束
\r\n - 換行
\r = 確認(回車)
\n = 新行
\t = Tab (4 個空格)
\b = Backwards

cm.[指令]

dispose
結束與 NPC 的對話,讓你可以與其他 NPC 對話。
用法: cm.dispose();

sendNext
顯示一個帶有「下一個」按鈕的對話窗。
用法: cm.sendNext(“[文本]”);

sendPrev
顯示一個帶有「上一個」按鈕的對話窗。
How to use: cm.sendPrev(“[文本]”);

sendNextPrev
顯示一個帶有「上一個」、「下一個」按鈕的對話窗。
用法: cm.sendNextPrev(“[文本]”);

sendOk
顯示一個帶有「確認」按鈕的對話窗。
用法: cm.sendOk(“[文本]”);

sendYesNo
顯示一個帶有「是」、「否」按鈕的對話窗,「否」將結束對話,除非額外改寫。
用法: cm.sendYesNo(“[文本]”);

sendAcceptDecline
顯示一個帶有「接受」、「拒絕」按鈕的對話窗,「拒絕」將結束對話,除非額外改寫。
用法: cm.sendAcceptDecline(“[文本]”);

sendSimple
顯示一個不帶有任何按鈕的對話框。
用法: cm.sendSimple(“[文本]”);

sendStyle
顯示一個選擇造型的對話框。
用法: cm.sendStyle(“[文本]”, [變數]); // 你需要宣告該變數

warp
傳送腳本到地圖。
用法: cm.warp([地圖ID], [傳送點]); // 預設則設定 [傳送點] 為 0

openShop
開啟商店視窗。
用法: cm.openShop([商店ID]);

haveItem
檢查角色是否有道具 (背包或身上)。
用法: cm.haveItem([道具ID]);

gainItem
給予或收回角色道具
用法: cm.gainItem([道具ID], [數量]); // 設定 [數量] 為負數來收回道具

changeJob
修改角色的職業。
用法: cm.changeJob([職業ID]);

getJob
取得角色的職業。
用法: cm.getJob();

startQuest
開始任務。
用法: cm.startQuest([任務ID]);

completeQuest
完成任務。
用法: cm.completeQuest([任務ID]);

forfeitQuest
放棄任務。
用法: cm.forfeitQuest([任務ID]);

getMeso
取得角色楓幣。
用法: cm.getMeso();

gainMeso
給予或收回角色楓幣。
用法: cm.gainMeso([數量]); // 設定 [數量] 為負數來收回楓幣

gainExp
給予或收回角色經驗值。
用法: cm.gainExp([數量]); // 設定 [數量] 為負數來收回經驗值

getLevel
取得角色的等級。
用法: cm.getLevel();

teachSkill
教角色技能。
用法: cm.teachSkill([技能ID], [技能等級], [技能最大等級]);

get[狀態]
取得角色的 [狀態]。[狀態] 可以是: HP, MP, STR, DEX, INT, LUK.
用法: cm.get狀態;

modifyNX
Gives/Takes the player nx
How to use: cm.gainNX([amount]);
Make it negative to make it take away.

// 外流端專屬
modifyCSPoint
給予或收回點數。
用法:cm.modifyCSPoint([點數], [種類]); // [種類]:1 = GASH 點數,2 = 楓葉點數

檢查楓幣、道具、GM、性別

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (cm.getPlayer().isGM()) { // 檢查是否為 GM

if (cm.getChar().isDonator() == true) { // checks for donator

if (cm.getJob().equals(net.sf.odinms.client.MapleJob.BOWMAN)) { // checks for Bowman job (list of jobs in below spoiler)

if (cm.getLevel() >= 30) { // 檢查等級是否足滿 30

if (cm.getChar().getGender() == 0) { // 0 = 公, 1 = 母
if (cm.getPlayer().getGender() == 0) { // 0 = 公, 1 = 母

if (cm.getMeso() >= 數量) { // 檢查楓幣數量

if (cm.haveItem(itemid, amount)) { // 檢查角色是否擁有某道具某數量

if (cm.getPlayer().getitemQuantity(itemid)); // 取得角色擁有某道具的數量

職業代碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
BEGINNER - 0
WARRIOR - 100
FIGHTER - 110
CRUSADER - 111
HERO - 112
PAGE - 120
WHITEKNIGHT - 121
PALADIN - 122
SPEARMAN - 130
DRAGONKNIGHT - 131
DARKKNIGHT - 132
MAGICIAN - 200
FP_WIZARD - 210
FP_MAGE - 211
FP_ARCHMAGE - 212
IL_WIZARD - 220
IL_MAGE - 221
IL_ARCHMAGE - 222
CLERIC - 230
PRIEST - 231
BISHOP - 232
BOWMAN - 300
HUNTER - 310
RANGER - 311
BOWMASTER - 312
CROSSBOWMAN - 320
SNIPER - 321
CROSSBOWMASTER - 322
THIEF - 400
ASSASSIN - 410
HERMIT - 411
NIGHTLORD - 412
BANDIT - 420
CHIEFBANDIT - 421
SHADOWER - 422
PIRATE - 500
BRAWLER - 510
MARAUDER - 511
BUCCANEER - 512
GUNSLINGER - 520
OUTLAW - 521
CORSAIR - 522
MAPLELEAF_BRIGADIER - 800
GM - 500(v55) / 900(v62+)
SUPERGM 510(v55) / 910(v62+)
DAWNWARRIOR1 - 1000
DAWNWARRIOR2 - 1010
DAWNWARRIOR3 - 1011
DAWNWARRIOR4 - 1012
BLAZEWIZARD1 - 1100
BLAZEWIZARD2 - 1110
BLAZEWIZARD3 - 1111
BLAZEWIZARD4 - 1112
WINDARCHER1 - 1200
WINDARCHER2 - 1210
WINDARCHER3 - 1211
WINDARCHER4 - 1212
NIGHTWALKER1 - 1300
NIGHTWALKER2 - 1310
NIGHTWALKER3 - 1311
NIGHTWALKER4 - 1312
THUNDERBREAKER1 - 1400
THUNDERBREAKER2 - 1410
THUNDERBREAKER3 - 1411
THUNDERBREAKER4 - 1412
ARAN1 - 2100
ARAN2 - 2110
ARAN3 - 2111
ARAN4 - 2112

Keep in mind that some repacks use a different Terminology than this, so it’s best to browse your repack/source folder to get familiar with yours.

第二級: 中堅份子

第一課: Learning to code an NPC

編寫 NPC 時最好對自己在幹嘛有個概念,簡單的方法是打開記事本,把你想要 NPC 做的事情寫下來。例如:

1
2
3
4
5
6
7
道具交換
對話框 1 - 顯示幾個選項
對話框 2 - 對話框 1 選擇後顯示幾個選項

對話框 1 選項 - 礦石換黃金楓葉標誌、礦石換楓葉
對話框 2 選項 - 如果選擇黃金楓葉標誌,10 個礦石交換 1 個黃金楓葉標誌,20 個礦石交換 3 個黃金楓葉標誌
如果選擇楓葉,5 個礦石交換 1 個楓葉,10 個礦石交換 3 個楓葉

以上只是個範例,但這樣子做紀錄,你就能事先想好最佳的佈局,甚至能提醒你當初的想法,假如你沒法一次寫好腳本的話。

現在,在你開始之前,你要先思考你需要什麼類型的 NPC。如果你需要參考,請回到第一級第二課。在這個範例中,我會使用需要 status 的 NPC,請閱讀註解(第一級第三課)來瞭解我在做什麼。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var status; 

function start() { // NPC 開始
status = -1; // 設定 status 為 -1
action(1, 0, 0); // 設定 mode 為 1, type 為 0, selection 為 0
} // 關閉 start 函數

function action(mode, type, selection) { // calls what you set above in function start, almost all actions are done here
if (mode == 1) { // mode 被設為 1,因為函數 start,如上
status++; // 讓 NPC 進到下一個 status,此時 status 變為 0
} else { // 如果 mode 不是 1
status--; // 不讓 NPC 進到下一個 status
}

if (status == 0) { // 如果 mode 是 1,status 會從 -1 變為 0。如果 status 為 0,以下會發生
cm.sendSimple("哈囉,我來示範如何使用 #belse#k 和 #bif#k,準備好了嗎?\r\n #L0# 是的,船長。 #l \r\n #L1# 不,我還沒好。 #l"); // 顯示一個有兩個選項的對話窗
} else if (status == 1) { // 如果作出了選擇,NPC 會進到下一個 status
if (selection == 0) { // 選項 0 是 #L0#, "是的,船長。"
if (cm.haveItem(4001129, 10)) { // 檢查道具
cm.sendOk("如果 (if) 你選擇選項 0,簡單來說就是第一個選項,我就會說這句話。"); // 如果 (if) 你有道具,會顯示這個對話窗
cm.dispose();
} else {
cm.sendOk("抱歉,你沒有該道具。"); // 否則 (else),你沒有該道具
cm.dispose();
}
} else if (selection == 1) { // "不,我還沒好。"
cm.sendOk("否則如果(else if)你選擇選項 1,我會說這句話。");
cm.dispose();
}
}
}

這裡是一些能幫你弄懂 NPC 的東西:


sendNext(); & sendOk();

Type = 0
如果點了停止 - mode = -1
如果點了下一個/確認 - mode = 1


sendNextPrev();

Type = 0
如果點了停止 - mode = -1
如果點了下一個 - mode = 1
如果點了上一個 - mode = 0


sendYesNo();

Type = 1
如果點了停止 - mode = -1
如果點了是 - mode = 1
如果點了否 - mode = 0


sendAcceptDecline();

Type = 12
如果點了停止 - mode = -1
如果點了接受 - mode = 1
如果點了拒絕 - mode = 0


sendGetText();

沒事兒


sendGetNumber();

Type = 3
如果點了停止 - mode = 0
如果點了確認 - mode = 1


sendSimple();

Type = 4
如果點了停止 - mode = 0
如果點了選擇 - mode = 1
Credits: BENG

括號會是決定 NPC 能不能運作的關鍵。讓我們看看上面 NPC 腳本的一個小片段:

1
2
3
4
if (cm.haveItem(4001129, 10)) { // 檢查道具
cm.sendOk("如果 (if) 你選擇選項 0,簡單來說就是第一個選項,我就會說這句話。"); // 如果 (if) 你有道具,會顯示這個對話窗
cm.dispose();
}

看看第一行…

1
if (cm.haveItem(4001129, 10)) {

在第一行的尾端有一個左括號,這代表當條件為真時,其後所接的事情將會發生。以該行來說,如果你有 10 個 4001129,下一行就會執行。
看看此片段的最後一行,注意到右括號了嗎?

1
}

右括號結束條件,所以任何介於左括號與右括號之間的事情都將會在指定的條件成立後發生。
右括號又經常伴隨著 else,也就是當你沒有足夠的道具時,else 之後的片段便會執行。

第二課: 使用運算子

在這一課,你會學到幾種運算子以及如何用他們。運算子可以幫你決定某A是大於、小於還是等於某B。以下是你最有可能會用到的運算子清單。

運算子 名稱 類型 描述
! 一元 Returns true if 右運算元 evaluates to false. Returns false If the 右運算元 is true.
&& 條件且 二元 If the operand on the left returns false, returns false without evaluating the operand on the right.
|| 條件或 二元 If the operand on the left returns true, returns true without evaluating the operand on the right.

以上運算子常用在條件句之間。什麼是條件句?那是 NPC 腳本常看到的東西。

1
2
3
if (cm.haveItem(itemid, amount)) { 
if (cm.getMeso() >= amount) {
if (cm.getPlayer().getLevel() >= amount) {

以上都是條件句。因此,在其中運用運算子,可以讓你指定某種條件。例如:

1
2
3
if (cm.haveItem(4001129, 50) && cm.getMeso() >= 1000) { // 此處需要角色擁有 50 黃金楓葉標誌 且 擁有 1000 楓幣
if (cm.haveItem(4001129, 50) || cm.getMeso() >= 1000) { // 此處需要角色擁有 50 黃金楓葉標誌 或 擁有 1000 楓幣
if (!cm.haveItem(4001129, 50)) { // 此處會檢查角色是否 非 擁有 50 黃金楓葉標誌

Using this is a good way to limit players from saving up loads of an item to cash in at once. 接著,我們還有關係運算子,他們很常被用在條件當中。列舉其中一部分:

運算子 名稱 描述
== 等於  Returns true if the expression on the left evaluates to the same value as the expression on the right.
< 小於 Returns true if the expression on the left evaluates to a value that is less than the value of the expression on the right.
<= 小於或等於 Returns true if the expression on the left evaluates to a value that is less than or equal to the expression on the right.
> 大於 Returns true if the expression on the left evaluates to a value that is greater than the value of the expression on the right.
>= 大於或等於 Returns true if the expression on the left evaluates to a value that is greater than or equal to the expression on the right.

我已經給過範例了,如果你沒注意到的話,我再列幾個給你:

1
2
3
if (cm.getMeso() >= 1000) { // 如果角色的楓幣「大於或等於」1000 
if (cm.getPlayer().getLevel() == 120) { // 如果角色的等級「等於」120
if (cm.getJobById() <= 112) { // 如果角色的職業「小於或等於」112 (Hero, Crusader, Fighter, Warrior, Beginner)

第三課: 學習使用變數

在你學習編寫時,你一定有發現一些以var起頭的東西出現在腳本開頭,那就是變數。簡單來說,變數是用來「取代」東西的。你可以用你命名的變數來「取代」數字或文字等。一般來說,你會想要用變數來縮短腳本,即使只是幾個字而已。範例:

1
2
3
4
5
6
var gl = 4000313; 

function start() {
cm.sendOk("哈囉,你帶來 #v" + gl + "#了嗎?"); // Calls the variable gl, and the information it is replacing
cm.dispose();
}

As you can see from the example, the item id 4000313 is being replaced with the variable “gl”. Even though it’s only a minor difference, I shortend the script by 1 character. Now imagine having hundreds of places on a script where 4000313 was replaced by “gl”. It adds up on how much space you save by using variables. That was an example on how to use a variable for a number. Here is one on how to use a variable for a string, or text sentence.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var yes = "太好了,你帶齊了黃金楓葉!"; 
var no = "抱歉,你身上的黃金楓葉不夠。";
var status;

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

function action(mode, type, selection) {
if (mode == 1) {
status++;
}else{
status--;
}
if (status == 0) {
cm.sendSimple("#L0# 我有 10 個黃金楓葉 #l \r\n #L1# 我有 20 個黃金楓葉 #l");
}else if (status == 1) {
if (selection == 0) {
if (cm.haveItem(4000313, 10)) {
cm.sendOk(yes); // 使用變數「yes」,並顯示你設定給他的內容
cm.dispose();
} else {
cm.sendOk(no); // 使用變數「no」,並顯示你設定給他的內容
cm.dispose();
}
} else if (selection == 1) {
if (cm.haveItem(4000313, 20)) {
cm.sendOk(yes); // 使用變數「yes」,並顯示你設定給他的內容
cm.dispose();
} else {
cm.sendOk(no); // 使用變數「no」,並顯示你設定給他的內容
cm.dispose();
}
}
}
}

如你清楚地看見,使用這些變數省下了很大的空間。我只用了 111 個字就打完了腳本,還包含開頭的變數,而不用打長長的 202 個字。未來如果要編輯腳本,有使用變數的話就更加簡單了。

第三級: 高手過招

第一課: 學習陣列

好玩的來了!「高手」都會強調陣列,如果能用就用。使用陣列不僅讓腳本更簡潔,還能提高腳本整體的表現。陣列十分容易修改,一旦你學會如何使用,你就能瞭解腳本作者在做什麼。以下是幾個陣列的範例:

1
2
3
4
5
6
7
8
9
10
11
12
var item = [4000313, 4001129, 4001126];

function start() {
cm.sendSimple("你想要什麼? \r\n #L0# 黃金楓葉 #l \r\n #L1# 黃金楓葉標誌 #l \r\n #L2# 楓葉 #l");
}

function action(mode, type, selection) {
if (mode == 1) {
cm.gainItem(item[selection], 1);
}
cm.dispose();
}

As you can see, the item ids are placed in the order they are shown in the sendSimple. The [selection] in the cm.gainItem method, calls the Array to determine which item to give the player. If they chose the second selection, it would call the second number in the Array. By placing the items in an Array, it shortens the script by a lot. Here is what the script would look like without an Array.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function start() { 
cm.sendSimple("你想要什麼? \r\n #L0# 黃金楓葉 #l \r\n #L1# 黃金楓葉標誌 #l \r\n #L2# 楓葉 #l");
}

function action(mode, type, selection) {
if (mode == 1) {
if (selection == 0)
cm.gainItem(4000313, 1);
else if (selection == 1)
cm.gainItem(4001129, 1);
else if (selection == 2)
cm.gainItem(4001126, 1);
}
cm.dispose();
}

There is a clear difference between the two in which is longer. One of the most common ways to use Arrays, is with the infamous for loop, which I will explain in a later lesson.

第二課: 學習多維陣列與亂數

本節我會教你多維陣列和亂數。Now, I’m not an expert at this, so I’ll only be able to show you what I know. 什麼是多維陣列?多維陣列就是由陣列組成的陣列。Basically, you can have more than one array, and simplify them further by making them into a multi-dimentional array. Unfortunately, I only know how to use them with randoms, so that is what I’ll be teaching you. First, lets’ set up a simple multi-dimentional array NPC.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
var status; 
var item = [[4001129, 4001129], [1082025, 1102023], [4000313, 1002085]];
var rand = Math.floor(Math.random() * 100);

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

function action(mode, type, selection) {
if (mode == -1) {
cm.dispose();
} else {
if (status >= 2 && mode == 0) {
cm.sendOk("再見!");
cm.dispose();
return;
}

if (mode == 1) {
status++;
} else {
status--;
}

if (status == 0) {
cm.sendYesNo("你想要試試手氣嗎?");
} else if (status == 1) {
var rand2;
if ((rand >= 1) && (rand <= 50)) {
rand2 = Math.floor(Math.random() * item[0].length);
} else if ((rand >= 51) && (rand <= 90)) {
rand2 = Math.floor(Math.random() * item[1].length);
} else {
rand2 = Math.floor(Math.random() * item[2].length);
}
cm.gainItem([rand >= 1 && rand <= 50 ? item[0][rand2] : rand >= 51 && rand <= 90 ? item[1][rand2] : item[2][rand2]]);
cm.sendOk("Congrats on your item.");
cm.dispose();
}
}

Alright, time to explain. This is the multi-dimentional array.

1
var item = [[4001129, 4001129], [1082025, 1102023], [4000313, 1002085]];

Each separete array is colored, so you can see the 3 different arrays within the multi-dimentional array. Next is the random part, or this little snippet.

1
2
3
4
5
6
7
8
var rand2; 
if ((rand >= 1) && (rand <= 50)) {
rand2 = Math.floor(Math.random() * item[0].length);
} else if ((rand >= 51) && (rand <= 90)) {
rand2 = Math.floor(Math.random() * item[1].length);
}else{
rand2 = Math.floor(Math.random() * item[2].length);
}

Ok, at the top of the script, you see this line.

1
var rand = Math.floor(Math.random()*100);

Think of this as if it were a dice. The number symbolizes the sides of a dice. So on this dice, there are 100 sides. Continue reading the random part as you read this. If the dice lands on side 1 - 50, give an item in the first array. If the dice lands on side 51 - 90, give an item in the second array. If it lands on any other side, give an item in the third array. This line here…

1
cm.gainItem([rand >= 1 && rand <= 50 ? item[0][rand2] : rand >= 51 && rand <= 90 ? item[1][rand2] : item[2][rand2]]);

Basically follows through with the action. That is all I know about these types of arrays.

第三課: Learning the Infamous For Loop

你可曾在腳本看見這樣的東西?

1
2
for (var i = 0; i < options.length; i++) 
text += "\r\n#L"+i+"#"+options[i]+"#l";

This is the infamous for loop. It simplifies almost anything it is used with. It was created to specifically deal with Arrays. Here is the entire part of the code, so I can explain what it means.

1
2
3
4
5
var text = "#e#k What region have you trained in?#b"; 
var options = new Array("Aqua Road Region = 1 #v4001010#", "Ariant Region = 1 #v4001011#", "El Nath Region = 1 #v4001013#", "Ludas Lake Region = 1 #v4001012#", "Minar Forest Region = 1 #v4001009#", "Victoria Island Region = 5 #v4001126#", "World Tour = 5 #v4001129#", "Ores/Crystals = 1 #v4001014#");
for (var i = 0; i < options.length; i++)
text += "\r\n#L"+i+"#"+options[i]+"#l";
cm.sendSimple(text);

Ok so, i = 0. While 0 is less than the options array, do the code. After executing the code, do i++. i++ basically tells the program running the script to increase i until it reaches the length of the array. So since the options array has a length of 8, it will keep increasing until it displays all 8 options.

Contributions from other members
Spoiler:

Quote Originally Posted by .:LastBreath:. View Post

Insert In NpcConversatonManager:

1
2
3
4
public void changeKeyBinding (int key, int type, int action) {
getPlayer().changeKeybinding(key, new MapleKeyBinding(type, action));
getPlayer().sendKeymap();
}

How to use

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var status = 0;

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

function action(mode, type, selection) {
if (mode == -1) {
cm.dispose();
} else {
if (mode == 0 && status == 1) {
cm.dispose();
return;
}
if (mode == 1)
status++;
else
status--;
if (status == 0) {
cm.sendYesNo("Hello there! Do you want to learn 二段跳?");
} else if (status == 1) {
cm.sendSimple("Which key do you want #fSkill/411.img/skill/4111006/icon# on? #b\r\n#L59#F1#L60#F2#L61#F3#L62#F4#L63#F5#L64#F6#L65#F7#L66#F8#L67#F9 \r\n #L68#F10#L87#F11#L88#F12 \r\n#L2#1#L3#2#L4#3#L5#4#L6#5#L7#6#L8#7#L9#8#L10#9#L11#0#L12#-#L13#= \r\n#L16#Q#L17#W#L18#E#L19#R#L20#T#L21#Y#L22#U#L23#I#L24#O#L25#P#L26#[#L27#] \r\n#L30#A#L31#S#L32#D#L33#F#L34#G#L35#H#L36#J#L37#K#L38#L#L39#;#L40#' \r\n#L42#Shift#L44#Z#L45#X#L46#C#L47#V#L48#B#L49#N#L50#M#L51#,#L52#.#L42#Shift \r\n#L29#Ctrl#L56#Alt#L57#SPACE#L56#Alt#L29#Ctrl \r\n#L82#Ins#L71#Hm#L73#Pup#L83#Del#L79#End#L81#Pdn");
} else if (status == 2) {2
cm.sendOk("There you go!");
cm.changeKeyBinding(selection, 1, 4111006);
cm.dispose();
} else {
cm.sendOk("See you next time then.");
cm.dispose();
}
}
}

CM command:
Code:
changeKeyBinding
allows you to place a skill anywhere in your key config.
How to use: cm.changeKeyBinding(selection, 1, SKILLID);
(THINKS THATS HOW TO USE?)
ALL CREDITS GOES TO “made4forum” FROM HIS RELEASE
LINK:Skill teaching NPC
….
Further Explanation of the For Loop
Quote Originally Posted by Rice

Final Words

This concludes the tutorial. There is still quite a bit I don’t know, even with NPCs. If anyone has something they wish to add/contribute, feel free to post it and I’ll include it with credits. I hope you enjoyed my wall of text and learned something valuable. Please leave credits if this is posted anywhere else. Lastly, if you need help with an NPC script or have trouble understanding something, feel free to ask HERE. Do not quote the entire tutorial. If you do, I will ask a Mod to remove your post, because it’s an inconvenience for others.

Credits:
Shawn aka bboy242 aka DevonsDaddy

Special Thanks:
Moogra and Osiris
.:LastBreath:.
made4forum
Alcohol
Rice