over 3 years ago

本站之文章皆禁止以任何方式轉載,如有需求,可使用幾種方式來保存:
1. 加到我的最愛
2. 使用連結及標題作為引用
3. 存檔至自己電腦僅供自己離線閱讀

任何未經本人同意對本站文章之轉載、重製、散布等行為皆已違反著作權法,請務必留意。

Read on →
 
2 months ago

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

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

另一個不須status的NPC

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

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(); 
        } 
    } 
}  

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

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

// 這是一種註解

// 這種註解只能單行使用,這也是為什麼它叫做單行註解。


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

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

第四課: 學習專門術語

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

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

#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、性別

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)); // 取得角色擁有某道具的數量

職業代碼

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 - 對話框 1 選擇後顯示幾個選項

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

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

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

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 腳本的一個小片段:

        if (cm.haveItem(4001129, 10)) { // 檢查道具

            cm.sendOk("如果 (if) 你選擇選項 0,簡單來說就是第一個選項,我就會說這句話。"); // 如果 (if) 你有道具,會顯示這個對話窗

            cm.dispose();
        }

看看第一行...

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

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

}

右括號結束條件,所以任何介於左括號與右括號之間的事情都將會在指定的條件成立後發生。
右括號又經常伴隨著 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.
\ \ 條件或

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

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

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

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.

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

if (cm.getMeso() >= 1000) { // 如果角色的楓幣「大於或等於」1000 

if (cm.getPlayer().getLevel() == 120) { // 如果角色的等級「等於」120 

if (cm.getJobById() <= 112) { // 如果角色的職業「小於或等於」112 (Hero, Crusader, Fighter, Warrior, Beginner)

第三課: 學習使用變數

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

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.

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 個字。未來如果要編輯腳本,有使用變數的話就更加簡單了。

第三級: 高手過招

第一課: 學習陣列

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

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.

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.

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.

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.

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.

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...

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

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

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.

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:

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

How to use

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

 
3 months ago

下載完遊戲啟動後出現這樣的錯誤訊息:


Script compilation error

There were errors compiling scripts. Unable to run game.
Maybe some selected user packages are incompatible?

game\quests\quest_functions.ws[2282]: parse error, near '{'
game\scenes\scene_functions.ws[1794]: parse error, near '{'


看得出來是腳本中出現了一點問題,讓我們來拆看看。

先用 Gibbed RED Tools 將 SteamLibrary\steamapps\common\the witcher 2\CookedPC\base_scripts.dzip解開。

接著用 Notepad++ 開啟 game\quests\quest_functions.ws

按 Ctrl + G 到錯誤中說明的第 2282 行。

看到在第 2284 行有亂碼的註解,大概就是因為語系不同解碼出錯造成的。我們可以先到編碼\字元集將編碼設定成中歐\Windows-1250,就能看到原始的樣貌。以下是該行波蘭文:

[2284] //Funkcja w której gracz może nosić postać

為了保留檔案的原汁原味,我們把他改成多行註解/**/,後面的波蘭文就能保留下來了。

[2284] /* Funkcja w której gracz może nosić postać */

存檔完我們接著處理下一個錯誤。打開到 game\scenes\scene_functions.ws 的第 1796 行,改好編碼,這邊造成錯誤的是這行:

[1796] //Funkcja w której gracz może nosić postać

一樣改成多行註解:

[1796] /* Funkcja w której gracz może nosić postać */

存檔完畢,接著是要把剛才解開的檔案包回遊戲檔案。

base_scripts.dzip 覆蓋回 SteamLibrary\steamapps\common\the witcher 2\CookedPC\,再執行遊戲。

大功告成!

 
5 months ago

chrome://net-internals/#hsts

 
8 months ago

確認 Nvidia 驅動版本以及 macOS 版本是支援的:
https://github.com/mologie/NVWebDriverLibValFix/releases

接著下載 NVWebDriverLibValFix.kext.1.0.0.tar.gz

接著在 Terminal 輸入
sudo tar -xv -C /Library/Extensions -f ~/Downloads/NVWebDriverLibValFix.kext.1.0.0.tar.gz
sudo kextutil /Library/Extensions/NVWebDriverLibValFix.kext

完成

 
8 months ago

我的 Windows 10 + 黑蘋果雙系統最近在 Windows 自動更新後 Clover 就從 UEFI 開機選單消失了
因為我的 Windows BCD 和 Clover 放在同一個分區
又 Windows 10 這次更新復原了 bootmgfw.efi 導致業障了 Clover 的引導
因此只要將引導分區內 \efi\Microsoft\boot\bootmgfw.efi 刪除或更名即可

 
12 months ago

已知可造成楓之谷私服斷線之弱點。
研究人員已發現大規模攻擊行為,造成不少私服出現斷線情形。

已知受影響版本

  • 113

處理狀態

未公開
Last Update : 2017/05/30

  • 新提交
  • 已審核
Read on →
 
about 1 year ago

首先先至官網 https://dev.mysql.com/downloads/mysql/ 下載 MySQL Community Server 安裝檔 mysql-5.7.18-macos10.12-x86_64.dmg,點兩下開啟,再點兩下 mysql-5.7.18-macos10.12-x86_64.pkg 安裝。

安裝完畢後會跳出一個訊息,告訴你 root 的密碼,請務必將它記下來,後續操作會用到,否則就要再重設一次了。

現在可以到 System Preferences 啟動 MySQL Server。

如果現在馬上就用這組帳號連線,會出現這樣的錯誤訊息:

Your password has expired. To log in you must change it using a client that supports expired passwords.

代表著我們還需要設定新的密碼給 root。在那之前,先開啟 Terminal,建立 alias:

alias mysql=/usr/local/mysql/bin/mysql
alias mysqladmin=/usr/local/mysql/bin/mysqladmin

接著輸入:

mysqladmin -u root password -p

此時會出現Enter password:請你輸入 root 的密碼,請輸入安裝程式幫我們設定的密碼,輸入時不會顯示在畫面上。
接著出現New password再輸入新的密碼,Confirm new password:亦同。

到這邊算是完成了,你可以設定 PATH 或是將 alias 加到~/.zshrc方便之後使用。

Read on →
 
about 1 year ago

最近 Eyny 論壇爆發勒索軟體中毒潮,官方稱系統遭到攻擊置入惡意代碼,我也拿到了傳說中的 Flash Player 安裝程式,於是來一探究竟。

先講結論,這次真的不是你更不更新系統就不會中毒,要更新的可能是你的腦袋。

這次感染勒索軟體的途徑不是神奇的開了網頁就中毒,而是百分之百使用者自己下載自己安裝的。因為黑客在論壇植入了假的 Flash Player 更新通知,利用大家對 Flash Player 更新的信任,於是自己把惡意程式帶回家中,還讓他在家裡跑。加上整個安裝程序根本就是原版的,只是被重新包裝塞入惡意代碼,所以根本不知道自己已經中標。

在執行這個安裝程式後,會自動建立一個排程,並且於每次登入時執行。

另外也會新增一個登錄值,是一個 base64 編碼的字串,前後填充空白以讓編輯器顯示空白。

很快的就能發現他利用 PowerShell 來執 payload,有一大串 base64 編碼的參數被傳給 PowerShell。經過解碼與重新命名,可以發現 ThunderCrypt 的代碼都是在記憶體中執行的,整個過程除了 PowerShell 外,不會另外產生 ThunderCrypt 的檔案。登錄檔中的資料是編碼過的 payload,內含 32 位元以及 64 位元的 native payload,經由 PowerShell 代碼解碼後寫入記憶體中執行。

下圖顯示判別位元數並解碼對應的 payload,其中 $MyRegPath = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Shell"; $MyUUID = "{A48209E7-FBD5-56B9-B26561F44C9DF968}";

其他的 PowerShell 代碼在做的就是將 payload 寫入記憶體、建立一個 Delegate 以執行非託管代碼,並執行它。把 payload dump 下來後就能再繼續做靜態分析,我想後續還是交給專業的安全人員來做就好了。

啊我的分析只到這邊,加上平常我也沒在關注病毒,只是剛好從朋友那拿到傳說中的安裝程式而已,所以本文並不是一個完整正確的分析,歡迎各路大神入鞭!

以下白話文

如何預防?

  1. 檢查有效的數位簽章。在下載任何程式後,如果你要安裝的東西是大廠發佈的,那請務必檢查是否有有效的簽章,這次的 Flash Player 就沒有 Adobe 的簽章,顯然是被動過手腳。而平時下載的小程式,很可能作者跟我一樣窮,沒買簽章,這時候就只能靠你自己判別了。
  2. 防毒軟體不是萬能。這次的中毒途徑就很難被防毒軟體偵測,你可能會需要具有啟發式偵測能力的安全軟體,但是安全跟便利是無法兼備的,你勢必會需要做許多的允許、封鎖程式行為的操作。
  3. 要有危機意識。當你知道你已經禁用了 Flash Player,這時候網頁又叫你更新,就是一件很可疑的事情。危機意識是靠平時的培養,可以多多爬文看各路大神的建議。
Read on →
 
about 1 year ago

室友的朋友上星期中了ThunderCrypt勒索軟體,開導他重灌後在選擇安裝磁碟區遇到這個問題「Windows 無法安裝至此磁碟。選取的磁碟不屬於 GPT 磁碟分割樣式」,這時就要重新格式化硬碟到 GPT 樣式,以使用 UEFI 模式開機。

按下Shift+F10開啟 CMD,進入DiskPart工具:

diskpart

列出所有磁碟:

list disk

選擇磁碟:

select disk <disk number>

清除所有資料:

clean

轉換到 GPT 分割樣式:

convert gpt

離開:

exit

到這邊就能關掉 CMD,重新整理後繼續安裝Windows。

Read on →