• Hello World

    Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

    Quick Start

    Create a new post

    1
    $ hexo new "My New Post"

    More info: Writing

    Run server

    1
    $ hexo server

    More info: Server

    Generate static files

    1
    $ hexo generate

    More info: Generating

    Deploy to remote sites

    1
    $ hexo deploy

    More info: Deployment

  • CentOS 7 LEMP Nginx MariaDB PHP Setup

    | /

    User

    1
    2
    3
    4
    adduser username
    passwd username
    Grant sudo permission
    gpasswd -a username wheel

    Install SSH public key:
    @local$ ssh-copy-id username@SERVER_IP_ADDRESS

    1
    vi /etc/ssh/sshd_config

    Hint: To search for this line, type /PermitRoot then hit ENTER. This should bring the cursor to the “P” character on that line.

    Uncomment the line by deleting the “#” symbol (press Shift-x).

    Now move the cursor to the “yes” by pressing c.

    Now replace “yes” by pressing cw, then typing in “no”. Hit Escape when you are done editing. It should look like this:

    PermitRootLogin no

    DigitalOcean
    1
    2
    systemctl reload sshd
    exit

    Login with new user
    @local$ ssh username@SERVER_IP_ADDRESS

    Firewall

    1
    2
    3
    4
    5
    6
    sudo yum install firewalld
    sudo systemctl start firewalld
    sudo firewall-cmd --get-services
    sudo firewall-cmd --permanent --add-service=ssh
    sudo firewall-cmd --permanent --add-service=http
    sudo firewall-cmd --permanent --add-service=https

    If it’s a port:

    sudo firewall-cmd –permanent –add-port=8484/tcp

    1
    2
    3
    sudo firewall-cmd --permanent --list-all
    sudo firewall-cmd --reload
    sudo systemctl enable firewalld

    Date & Time

    1
    2
    3
    4
    5
    6
    sudo timedatectl list-timezones
    sudo timedatectl set-timezone Asia/Taipei
    sudo systemctl start ntpd
    sudo systemctl enable ntpd
    sudo yum install ntp
    sudo timedatectl

    Prerequisites

    1
    2
    3
    sudo yum install epel-release
    sudo yum install yum-utils
    sudo yum install https://rpms.remirepo.net/enterprise/remi-release-7.rpm

    Nginx

    1
    sudo vi /etc/yum.repos.d/nginx.repo
    /etc/yum.repos.d/nginx.repo
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    [nginx-stable]
    name=nginx stable repo
    baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
    gpgcheck=1
    enabled=1
    gpgkey=https://nginx.org/keys/nginx_signing.key
    module_hotfixes=true

    [nginx-mainline]
    name=nginx mainline repo
    baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
    gpgcheck=1
    enabled=0
    gpgkey=https://nginx.org/keys/nginx_signing.key
    module_hotfixes=true
    1
    2
    3
    sudo yum-config-manager --enable nginx-mainline
    sudo yum install nginx
    sudo systemctl start nginx

    Test http://server_domain_name_or_IP/

    1
    sudo systemctl enable nginx

    MariaDB

    1
    sudo vi /etc/yum.repos.d/MariaDB.repo
    /etc/yum.repos.d/MariaDB.repo
    1
    2
    3
    4
    5
    6
    7
    # MariaDB 10.4 CentOS repository list - created 2020-03-17 15:09 UTC
    # http://downloads.mariadb.org/mariadb/repositories/
    [mariadb]
    name = MariaDB
    baseurl = http://yum.mariadb.org/10.4/centos7-amd64
    gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
    gpgcheck=1
    1
    2
    3
    4
    sudo yum install MariaDB-server MariaDB-client
    sudo systemctl start mariadb
    sudo mysql_secure_installation
    sudo systemctl enable mariadb

    PHP

    1
    2
    sudo yum-config-manager --enable remi-php72
    sudo yum install php php-fpm php-mysql php-cli php-mbstring php-mcrypt php-gd php-curl php-zip php-xml
    1
    sudo vi /etc/php.ini
    /etc/php.ini
    1
    2
    3
    ...
    cgi.fix_pathinfo=0
    ...
    1
    sudo vi /etc/php-fpm.d/www.conf
    /etc/php-fpm.d/www.conf
    1
    2
    3
    4
    5
    user = nginx
    group = nginx
    listen.owner = nobody
    listen.group = nobody
    listen = /var/run/php-fpm/php-fpm.sock
    1
    2
    sudo systemctl start php-fpm
    sudo systemctl enable php-fpm

    Composer

    1
    sudo yum install composer

    Node.js

    1
    2
    curl -sL https://rpm.nodesource.com/setup_13.x | sudo bash -
    sudo yum install -y nodejs

    To install the Yarn package manager, run:
    curl -sL https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo
    sudo yum install yarn

    Git

    1
    2
    3
    sudo yum remove git
    sudo rpm -U https://centos7.iuscommunity.org/ius-release.rpm
    sudo yum install git2u

    Nginx Config

    https://www.digitalocean.com/community/tools/nginx#
    sudo nginx -t && systemctl restart nginx

    Note that the path of php-fpm.sock might be different from the template.

    GODDAMN SELinux

    Got Permission denied in /var/log/nginx/error.log???

    Check the SELinux audit log:

    1
    sudo cat /var/log/audit/audit.log | grep nginx | grep denied
    1
    2
    ls -Z /var/www
    sudo chcon -Rv -t httpd_sys_content_t /var/www/

    To enable write permission for httpd:

    1
    2
    3
    4
    5
    sudo chcon -Rv -t httpd_sys_rw_content_t /var/www/html/storage
    sudo chcon -Rv -t httpd_sys_rw_content_t /var/www/html/bootstrap/cache
    sudo chown $USER:nginx -R /var/www/html
    sudo chmod -R 775 /var/www/html/storage
    sudo chmod -R 775 /var/www/html/bootstrap/cache

    some says sudo chmod u=+srwX,g=+srX,o=rX -R /var/www/html/

    To allow httpd to create connection (usually to a load balancer or WebSocket):

    1
    sudo setsebool -P httpd_can_network_connect 1

    Fix PHP session permission:

    1
    sudo chown -R nginx: /var/lib/php/session

    If there’s another permission denied problem:
    sudo chcon -R -t httpd_var_run_t /var/lib/php/session
    Reference (maybe httpd_var_run_t is enough?):
    sudo restorecon -v /var/lib/php/session
    sudo semanage fcontext -a -t httpd_sys_rw_content_t /var/lib/php/session

    MDFK

    Flying is learning how to throw yourself at the ground and miss.

    Douglas Adams
  • Recover Mac OS From Time Machine Image on Windows through SAMBA

    /

    Backup Stage:
    hdiutil create -size 999g -type SPARSEBUNDLE -fs “HFS+J” TimeMachine.sparsebundle
    sudo tmutil setdestination /Volumes/TimeMachine
    copy TimeMachine.dmg to remote computer
    double click on TimeMachine.dmg from remote computer to mount
    sudo tmutil setdestination /Volumes/TimeMachine

    Recovery Stage:
    Connect to WiFi
    Open Utilities > Terminal
    ls /Volumes/
    mkdir /Volumes/foldername
    mount_smbfs //username@hostname/foldername /Volumes/foldername
    ls /Volumes/foldername
    hdiutil attach /Volumes/foldername/TimeMachine.dmg
    ls /Volumes/TimeMachine
    tmutil setdestination /Volumes/TimeMachine

    Select Restore From Time Machine Backup

  • How to Enable/Disable Windows Defender Credential Guard

    Got “VMware Workstation and Device/Credential Guard are not compatible” error in VMware Workstation on Windows 10?
    Solution: You need to disable Device Guard and turn off Hyper-V.

    Step 1:
    gpedit.msc
    Computer Configuration -> Administrative Template -> System -> Device Guard
    Set “Turn On Virtualization Based Security” to Disabled

    Or regedit
    Navigate HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\DeviceGuard
    Add a new DWORD value named EnableVirtualizationBasedSecurity and set its value to 0

    Step 2:
    Turn off Hyper-V
    bcdedit /set hypervisorlaunchtype off

    Step 3:
    Reboot.

    Recovery steps:
    Step 1:
    gpedit.msc
    Computer Configuration -> Administrative Template -> System -> Device Guard
    Set “Turn On Virtualization Based Security” to Not configured

    Or regedit
    Navigate HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\DeviceGuard
    Delete the registry “EnableVirtualizationBasedSecurity”

    Step 2:
    Turn on Hyper-V
    bcdedit /set hypervisorlaunchtype auto

    Step 3:
    Reboot.

  • How to Setup L2TP/IPSec VPN Server on Ubuntu 16.04

    sudo apt-get install strongswan xl2tpd ppp lsof
    echo “net.ipv4.ip_forward = 1” | sudo tee -a /etc/sysctl.conf
    sudo sysctl -p
    sudo nano /etc/ipsec.conf

    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
    # ipsec.conf - strongSwan IPsec configuration file

    # basic configuration

    config setup
    # strictcrlpolicy=yes
    # uniqueids = no

    conn L2TP-PSK-noNAT
    authby=secret
    #shared secret. Use rsasig for certificates.

    auto=add
    #the ipsec tunnel should be started and routes created when the ipsec daemon itself starts.

    keyingtries=3
    #Only negotiate a conn. 3 times.

    ikelifetime=8h
    keylife=1h

    ike=aes256-sha1,aes128-sha1,3des-sha1

    type=transport
    #because we use l2tp as tunnel protocol

    left=1.2.3.4 # Your server's public IP
    #fill in server IP above

    leftprotoport=17/1701

    right=%any
    rightprotoport=17/%any

    dpddelay=10
    # Dead Peer Dectection (RFC 3706) keepalives delay

    dpdtimeout=20
    # length of time (in seconds) we will idle without hearing either an R_U_THERE poll from our peer, or an R_U_THERE_ACK reply.

    dpdaction=clear
    # When a DPD enabled peer is declared dead, what action should be taken. clear means the eroute and SA with both be cleared.

    # Add connections here.

    # Sample VPN connections

    #conn sample-self-signed
    # leftsubnet=10.1.0.0/16
    # leftcert=selfCert.der
    # leftsendcert=never
    # right=192.168.0.2
    # rightsubnet=10.2.0.0/16
    # rightcert=peerCert.der
    # auto=start

    #conn sample-with-ca-cert
    # leftsubnet=10.1.0.0/16
    # leftcert=myCert.pem
    # right=192.168.0.2
    # rightsubnet=10.2.0.0/16
    # rightid="C=CH, O=Linux strongSwan CN=peer name"
    # auto=start

    sudo nano /etc/ipsec.secrets

    1
    %any : PSK "PASSWORD"

    sudo nano /etc/xl2tpd/xl2tpd.conf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    [global]
    ipsec saref = yes
    saref refinfo = 30

    ;debug avp = yes
    ;debug network = yes
    ;debug state = yes
    ;debug tunnel = yes

    [lns default]
    ip range = 192.168.1.2-192.168.1.100 ; IP address range for clients
    local ip = 192.168.1.1 ; Local IP address for VPN server
    require chap = yes
    refuse pap = yes
    require authentication = yes
    ;ppp debug = yes
    pppoptfile = /etc/ppp/options.xl2tpd
    length bit = yes

    sudo nano /etc/ppp/options.xl2tpd

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    require-mschap-v2
    ms-dns 8.8.8.8
    ms-dns 8.8.4.4
    auth
    mtu 1200
    mru 1000
    crtscts
    hide-password
    modem
    name l2tpd
    proxyarp
    lcp-echo-interval 30
    lcp-echo-failure 4

    sudo nano /etc/ppp/chap-secrets

    1
    username        l2tpd   PASSWORD                *

    sudo ipsec update
    sudo ipsec reload
    sudo ipsec restart
    sudo service xl2tpd restart

  • Michelle Featherstone - Careful

    從 How I Met Your Mother S4E23 聽到的
    經歷了數場戀愛卻都宣告失敗的 Ted 正向 Stella 吐苦水
    Ted 多麼希望能擁有像 Marshall 和 Lilly 一樣的真愛
    Stella 說了這句話安慰感到人生很難的 Ted:
    “She’s on her way, Ted. And she’s getting here as fast as she can.”
    此時這首歌正好和兩人的心境相呼應:兩人都被分手了
    Ted 上一次被分手真的受了不小的打擊,原本以為可以和她走到以後
    Stella 則是好幾年前分手後,就專心當個全職媽媽,不再對男人抱持希望

    這首歌是在講
    有個人曾經被狠狠傷過,而築起內心的高牆
    不願再陷入愛情的泥淖
    可是事與願違,愛情正悄悄萌芽
    他/她知道會發生什麼事,所以不願正視心裡的感覺
    然而最後還是免不了的愛上了
    所以他/她希望對方不要傷人心
    只求這一件事:「愛護我」

    試著翻譯一下歌詞
    歌詞裡面的同樣一句話用了 end 跟 start

    This is gonna hurt if it ever ends
    This is gonna hurt if it ever starts

    兩句的意義都是

    假如我們真有愛情而它在將來結束的話,我到時候一定會很心痛。

    第一句的 ends 是指「愛情的結束」,當然會心痛。
    第二句的 starts 是「我們真有愛情」,因為愛情來了這次躲不掉啦,但一想到曾經被傷過的畫面,就感到心痛,因此向對方傳達一個訊息:我想跟你在一起,答應我你會愛護我好不好?

    都在看英文而很少接觸中文的下場就是
    心裡有感觸可是卻翻不出那個味道
    所以歡迎指正和建議啦

    It's the little things
    They pulled me in
    And I'm defenseless
    I try to ignore
    Like I've done before
    But it's just useless
    
    

    I’ve made up my mind that I’m gonna let you in
    And I’m not afraid
    But I have to say

    This is gonna hurt if it ever ends
    But somehow you out shattered my defense
    This is gonna hurt if it ever starts
    So promise you’ll be careful with my heart

    It’s the things you do
    They made me fall hard for you and I can’t help it
    And it’s every day that I feel this way
    So just don’t stop it

    I’ve made up my mind that I’m gonna let you in
    And I’m not afraid
    But I have to say

    This is gonna hurt if it ever ends
    But somehow you out shattered my defense
    This is gonna hurt if it ever starts
    So promise you’ll be careful with my heart

    I won’t make excuses
    They just all seem useless
    You don’t have the time
    I guess I’ll take my chances now that I know love is on the line

    This is gonna hurt if it ever ends
    But somehow you out shattered my defense
    This is gonna hurt if it ever starts
    So promise you’ll be careful with my heart

    Careful with my heart
    Careful with my heart

    悸動一絲一絲
    竄出我的心
    而我毫無防備
    我想忽視它
    像曾經那樣
    但卻沒什麼用
    
    

    終於決心讓你闖入我的心
    我不害怕
    可是我要說

    如果真的不愛了 我會很心痛
    但你的出現擊潰了我的防備
    如果真的發生了 我會很心痛
    所以答應我你會愛護我

    是你的貼心舉動
    讓我深深愛上你 無法自拔
    日日夜夜不停歇
    所以請你不要丟下我

    終於決心讓你闖入我的心
    我不害怕
    可是我要說

    如果真的不愛了 我會很心痛
    但你的出現擊潰了我的防備
    如果真的發生了 我會很心痛
    所以答應我你會愛護我

    再多的藉口
    都於事無補
    你的心意有期限
    我想是時候放手一搏去愛

    如果真的不愛了 我會很心痛
    但你的出現擊潰了我的防備
    如果真的發生了 我會很心痛
    所以答應我你會愛護我

    所以答應我你會愛護我
    所以答應我你會愛護我

  • 從零開始學習 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

  • 手把手解決《巫師2:王國刺客》無法遊玩的錯誤

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


    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,就能看到原始的樣貌。以下是該行波蘭文:

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

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

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

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

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

    一樣改成多行註解:

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

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

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

    大功告成!