[楓之谷私服] NPC腳本詳細寫法

最後更新於 2022 年 6 月 20 日

前言

本篇面向 完全不懂 JS 、從沒接觸過程式語言的小白,因此廢話極多,有水平的請繞道。

楓之谷的 NPC 腳本是使用 javascript 去寫的,如果你想要經營一個私服,你可以不懂 js 是怎麼寫的沒關係,畢竟網路上資源一抓一大把,但如果你想要自己添加新的內容或者修改成你想要的效果,不懂一點 js 是很難去寫腳本的。

在這邊我提供幾個學習 js 的網站:

本篇內容主要是放在「寫」NPC 腳本的部分,至於要怎麼去改,需要用什麼工具請參考我之前發的文:

腳本架構

首先請你將 伺服器端\Libs\scripts\npc\10200.js 用文字編輯器打開,你會看到有兩個 function 打頭的程式碼:

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

function action(mode, type, selection) {
 // code
}

這個以 function 打頭的東西是什麼?

這是 函式

函式是構成JS的基本要素之一,一個函式本身就是一段JS程序,我們可以用它來執行於某一個任務或計算。

一個函式的定義由一系列的函式關鍵詞組成,依次為:

  • 函式的名稱。
  • 包圍在括號()中,並由逗號區隔的一個函式參數列表。
  • 包圍在大括號{}中,用於定義函式功能的一些JavaScript語句。
// action 為函式名稱
// 參數為 mode, type, selection
function action(mode, type, selection) {
 // code
}

大概理解了函式的意思,就可以來解讀這段程式碼的涵義了:

function start() {
  action(1, 0, 0); // 調用 action方法,並且依次傳入 1, 0, 0 對應到action函式的參數 mode, type, selection
}

function action(mode, type, selection) {
  // mode = 1
  // type = 0
  // selection = 0
}

通常來說,會需要「做事」的NPC都是需要使用一個變數 status 來控制。

var status = -1; //1. 將 status 預設為 -1

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

function action(mode, type, selection) {
  if (mode == 1) {
    // 2. 當 start()一調用action(1, 0, 0)時, mode為1
    // 因此 status 會增加 , 此時 status 為 0
    status++;
  } else {
    if (status == 1) {
      cm.sendNext("如果你想體驗弓箭手的感覺,再來跟我對話。");
      cm.dispose();
      return;
    }
    status--;
  }

  if (status == 0) {
    // 3. status = 0 , 因此會執行這個判斷式中的敘述
    cm.sendNext(
      "弓箭手有靈敏與力量的支援,主要負責長途攻擊,為前線的戰鬥者提供支援。非常擅長使用弓,作為攻擊的一部分。"
    );
  } else if (status == 1) {
    cm.sendYesNo("你想體驗一下弓箭手的感覺嗎?");
  } else if (status == 2) {
    cm.MovieClipIntroUI(true);
    cm.warp(1020300, 0);
    cm.dispose();
  }
}

action() 其中這段程式碼可以這麼理解:

  • 在與NPC對話時,按 OK、下一頁、接受…等時 mode = 1status 增加 1;
  • 點了否、拒絕 mode = 0status 減少 1;
  • 若是停止對話mode = -1,就會讓 status 減少 1。
if (mode == 1) {
  status++;
} else {
  if (status == 1) {
    cm.sendNext("如果你想體驗弓箭手的感覺,再來跟我對話。");
    cm.dispose();
    return;
  }
  status--;
}

if else 是什麼?請參考:https://developer.mozilla.org/zh-TW/docs/Learn/JavaScript/Building_blocks/conditionals,就不多提了。

當然,並不是所有腳本的架構都長成上面那樣,全看你的NPC需要做到什麼功能。

比如什麼事情都不需要做只需要說說話(不是)的NPC腳本如下:

function start() {
    cm.sendOk("Hello.");
	cm.dispose(); 
}

有的還只有 action 函式( 1002005.js )

function action(mode, type, selection) {
    cm.sendStorage();
    cm.dispose();
}

基本NPC方法

對話框

cm.sendSimple("內容"); // 顯示對話框 內容
cm.sendNext("內容"); // 顯示對話框 下一頁
cm.sendOk("內容"); // 顯示對話框 OK
cm.sendNextPrev("內容"); // 顯示對話框 上一頁和下一頁
cm.sendYesNo("內容"); // 顯示對話框 是 和 否
cm.sendAcceptDecline("內容"); // 顯示對話框 接受 和 取消
cm.sendGetNumber("內容",1010001,1010001,1012672); // 顯示對話框 接收玩家輸入的數值 (初始值 1010001, 最小值 1010001, 最大值 1012672)
cm.dispose(); // 關閉 NPC 對話

其他常用功能

cm.warp(1020300, 0); // 將玩家傳送至地圖 1020300
cm.haveItem(itemid); // 玩家是否擁有 itemid 道具
cm.gainItem(itemid, 1); // 給予玩家 itemid 1 個 (如果要從玩家身上拿取 則將 1 改為 -1)
cm.getPlayer().getName(); // 獲取玩家id
cm.getPlayer().getMeso(); // 獲取玩家楓幣
cm.getPlayer().itemQuantity(4001126); // 獲取玩家擁有 4001126 物品的數量
cm.getPlayer().getCSPoints(1); // 1:獲取玩家擁有的GASH點數 2:楓葉點數
cm.getPlayer().getLevel(); // 獲取玩家等級
cm.canHold(itemid); // 判斷該玩家背包是否有空閒能獲取該道具
cm.getMapId(); // 獲取當前地圖id
cm.isLeader(); // 玩家是否為隊伍隊長
cm.openShop(shopid); // 開啟shopid 商店

什麼是cm?

如果有看過 NPCHandler.java 就會知道為什麼在撰寫NPC腳本的時候,使用方法前面都要加上一個 cm

因為在 handling\channel\handler\NPCHandler.javaNPCMoreTalk() 方法中,有這麼一句程式碼:

final NPCConversationManager cm = NPCScriptManager.getInstance().getCM(c);

如果沒有JAVA基礎的話這看起來和天書沒什麼兩樣。

這裡的 NPCConversationManager 代表 NPCConversationManager.java 中的 NPCConversationManager 類別,與 NPC 對話相關的方法都會寫在裡面,比如 sendYesNo()、sendOk()、dispose()…等。

你只需要將這句程式碼理解成:我可以透過 cm 去調用 NPCConversationManager.java 中的方法 就好。

比如我想要使用一個 OK 的對話視窗,就可以寫成:

cm.sendOk("內容");

這只是一個補充,不懂也沒事。

對話內容

對話內容字體和顏色

  • #b – 藍色
  • #d – 紫色
  • #g – 綠色
  • #k – 黑色
  • #r – 紅色
  • #e – 粗體
  • #n – 正常 (移除粗體)

用法:

cm.sendOk("#b 這邊是藍字 , #k 這邊變黑字");

其他

以下 [] 用於區隔,並不是真的要打上 []

  • #c[道具ID]#-顯示玩家背包中有多少
  • #h # – 顯示玩家名稱
  • #m[地圖ID]# – 顯示地圖名稱
  • #o[怪物ID]# – 顯示怪物名稱
  • #p[NPC ID]# – 顯示 NPC 名稱
  • #q[技能ID]# – 顯示技能名稱
  • #s[技能ID]# – 顯示技能圖片
  • #t[道具ID]# – 顯示道具名稱
  • #i[道具ID]# – 顯示道具圖片
  • #i[道具ID]:# – 顯示道具圖片+滑鼠移動圖片顯示
  • #z[道具ID]# – 顯示道具名稱+詳細資料
  • #v[道具ID]# – 顯示道具圖片
  • #B[%]# – 顯示進度條
  • #f[圖片位址]# – 顯示 WZ 檔案中的圖片
  • #F[圖片位址]# – 顯示 WZ 檔案中的圖片

用法:

cm.sendOk("道具圖示:#i4001126#,道具資訊:#z4001126#");

格式

  • \r\n – 換行
  • \r – 回車
  • \n – 新行
  • \t – TAB(4個空格)

帶有選項的NPC

se1 1 [楓之谷私服] NPC腳本詳細寫法

在對話中列出選項:

  • #L0# 選項文字 #l
  • 0 是可以隨機定義的數,不一定要從 0 開始,並且注意數字不能重複,會出錯。
  • 點擊選項後 status 會增加 1 => 點選項一後 status = 0 變成 status = 1
cm.sendSimple("你好,請選擇選項。\r\n #L0# 我要選擇選項一 #l \r\n #L1# 我要選擇選項二 #l \r\n #L2# 我要選擇選項三 #l");
se2 1 [楓之谷私服] NPC腳本詳細寫法

接著會需要使用到 action 函式的 selection 參數,我們需要宣告一個變數,將 selection 賦予給該變數,然後使用這個變數來做條件判斷(switch case)。

var sel = selection; // 接收玩家點選的選項值
switch (sel) { // 做條件判斷
  case 0: // 選了 L0 之後做的事
    cm.sendNext("你選擇了選項一!");
    break;
  case 1: // 選了 L1 之後做的事
    cm.sendNext("你選擇了選項二!!");
    break;
  case 2: // 選了 L2 之後做的事
    cm.sendNext("你選擇了選項三!!!");
    break;
  default:
  	break;
}

:每個case 之間互不影響,case最後都需要加上一個 break; 離開 switch 區塊。

完整寫法

var status = -1;

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

function action(mode, type, selection) {
  if (mode == 1) {
    status++;
  } else {
    status--;
    cm.dispose();
  }
  if (status == 0) {
    // 點擊選項後 status 會增加 1 => 點選項一後 status = 0 變成 status = 1
    cm.sendSimple(
      "你好,請選擇選項。\r\n #L0# 我要選擇選項一 #l \r\n #L1# 我要選擇選項二 #l \r\n #L2# 我要選擇選項三 #l"
    );
  } else if (status == 1) {
    var sel = selection; // 接收玩家點選的選項值
    switch (
      sel // 做條件判斷
    ) {
      case 0: // 選了 L0 之後做的事
        cm.sendNext("你選擇了選項一!");
        break;
      case 1: // 選了 L1 之後做的事
        cm.sendNext("你選擇了選項二!!");
        break;
      case 2: // 選了 L2 之後做的事
        cm.sendNext("你選擇了選項三!!!");
        break;
      default:
        break;
    }
  } else if (status == 2) {
    cm.dispose();
  }
}

學到這裡,大部分的NPC腳本應該都能看懂了,如果還想要更深入的話可以去學一下javascript基礎,其實不是很難的。

我自己有寫了幾個常見的NPC腳本,有興趣想參考的可以看一下:

延伸閱讀:
NPC Scripting, What You Can Do To Shorten Your NPC Scripts!
Ultimate NPC Scripting

0 0 評分數
Article Rating
訂閱
通知
guest
10 Comments
在線反饋
查看所有評論
CAT
CAT
1 年 前

非常詳細的教學 謝謝

Zisha
Zisha
3 月 前

大大你好,我想使用您最後一種模式寫萬能NPC但遇到一點困難

我將9010000的楓之谷GM設定為萬能NPC
新增了一個選項想要用cm.openNpc的方式連結到另一個NPC
但在連結的過程會顯示角色狀態異常,要用@ea解卡

實在找不到該怎麼處理,希望大大知道的話可以提點一下QQ

Zisha
Zisha
回复  Zisha
3 月 前

沒事了XD 發現少一個dispose 感恩大大

Zisha
Zisha
1 月 前

大大你好,我這幾天遇到龍王沒辦法換地圖的情況
傳點按上一直顯示這句話
Horntail’s Seal is Blocking this Door.
我用HaCreator找地圖240060000檢查有問題的傳送點
有看到他對應的portal是next00但沒有找到
只有找到下面那個hontale_BR
我用notepad打開hontale_BR.js 還是看不出個所以然…
如果大大知道怎麼解的話,麻煩您指點一下

asz910858 最後編輯於 1 月 前
Zisha
Zisha
回复  Zisha
1 月 前

補圖

龍王.jpg
Zisha
Zisha
回复  pluto
1 月 前

謝謝大大的回覆,我剛剛看到大大說回傳的是no所以沒辦法傳送
不知道哪根筋不對把if (avail != “yes”) 我把yes改成no就變成可以傳送
只是龍頭在也能硬闖

另外大大的程式碼沒辦法下載qq

Zisha
Zisha
回复  pluto
1 月 前

感恩大大…原來跟portal/hontale_BR.js無關

剛剛用GM帳號測試了一下
如果是用指令 !killall 殺掉龍頭的話
系統一樣會沒偵測到龍頭死了
必須先攻擊他一下才能用指令殺掉
這樣才會偵測到龍頭死了然後放行

asz910858 最後編輯於 1 月 前