本站之文章皆禁止以任何方式轉載,如有需求,可使用幾種方式來保存:
1. 加到我的最愛
2. 使用連結及標題作為引用
3. 存檔至自己電腦僅供自己離線閱讀
任何未經本人同意對本站文章之轉載、重製、散布等行為皆已違反著作權法,請務必留意。
Read on →眼見 Ubuntu 16.04 LTS 即將在 2021 年 4 月結束支援,是時候升級到 18.04 LTS 了。當初從 Microsoft Store 上安裝 Ubuntu 沒標示版本的不需要是重新安裝,可直接透過 do-release-upgrade 升級到最近的 PointRelease。
WSL 1 或 WSL 2?
本來想順便把 WSL 1 更新到 WSL 2,因為據官方所述,WSL 2 能有更好的效能,不過後來實測發現,實際上的效能更差。比如執行git status
,不管是在 WSL 的 ~ 還是 /mnt/c/,都會有非常非常嚴重的 latency,而且因為我用了 oh-my-zsh 預設開啟的 git plugin,造成不論 pwd 在任何位置都有明顯的 latency,故後來決定再把 WSL 版本改回 1。
目前這個 issue 似乎還沒解決。
故想要測試 WSL 2 的可以試試看,不然可以直接略過這部分。
開啟 PowerShell
檢查已安裝的 distro
wsl --list --verbose
若要設定 WSL 版本為 2
wsl --set-version Ubuntu 2
若要設定 WSL 版本為 1
wsl --set-version Ubuntu 1
升級 Ubuntu 16.04 LTS 到 18.04 LTS
開啟 WSL
看一下現在的 release 版本
lsb_release -a
先更新 repositories
sudo apt update -y
更新 packages 到最新(必須)
sudo apt upgrade -y
安裝更新用的 package (之前 remove python3 導致這個 depends 跟著 remove 了)
sudo apt install -y update-manager-core
執行 LTS 升級
sudo do-release-upgrade
按照畫面提示可能會有一些地方要按 Enter 或 y 繼續
完成後直接關閉重開 console 即可
檢查安裝的版本
lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 18.04.5 LTS
Release: 18.04
Codename: bionic
解決後續問題
Problem 1
後續在安裝一些 packages 時遇到 unmet dependencies,比如安裝libxml2-dev
和libssl-dev
時碰到這樣的錯誤:
The following packages have unmet dependencies:
libssl-dev : Depends: libssl1.1 (= 1.1.0g-2ubuntu4) but 1.1.1-1ubuntu2.1~18.04.4 is to be installed
Recommends: libssl-doc but it is not going to be installed
libxml2-dev : Depends: libicu-dev but it is not going to be installed
Depends: libxml2 (= 2.9.4+dfsg1-6.1ubuntu1) but 2.9.4+dfsg1-6.1ubuntu1.2 is to be installed
E: Unable to correct problems, you have held broken packages.
(實際的 message 沒有保留,以上為在網路上找到幾乎一樣的)
這是因為一些 dependencies 仍然是 16.04 的,此時不論怎麼sudo apt upgrade
、sudo apt install -f
都無效者,可以用aptitude
來解決。
先安裝 aptitude
sudo apt install aptitude
用 aptitude 來安裝需要的 packages
sudo aptitude install libssl-dev libxml2-dev
通常第一個跳出來的 solution 是維持現狀,但沒辦法修復問題,回答n
。
第二個跳出來的 solution 是試著 downgrade 再安裝,回答y
。
Problem 2
後來用 apt 安裝任何套件都出現E: Could not read response to hello message from hook
的錯誤:
➜ sudo apt install htop
Reading package lists... Done
Building dependency tree
Reading state information... Done
E: Could not read response to hello message from hook [ ! -f /usr/bin/snap ] || /usr/bin/snap advise-snap --from-apt 2>/dev/null || true: Success
E: Could not read response to hello message from hook [ ! -f /usr/bin/snap ] || /usr/bin/snap advise-snap --from-apt 2>/dev/null || true: Success
看起來是因為舊版的 snap 在升級的時候被移除了,但是 apt hook 沒有跟著移除。
找該 hook 位於哪個設定檔
grep snap -r /etc/apt/apt.conf.d/
停用 snapd hook
sudo mv /etc/apt/apt.conf.d/20snapd.conf{,.disabled}
問題
最近需要在玉山證券業務相關網站上操作,卻遇到憑證沒辦法匯入到 Google Chrome 的狀況
不像玉山證券富果帳戶(Fugle)可以直接從網站上申請並安裝憑證,
玉山證券本身網站上的憑證只能透過玉山證券憑證百寶箱申請並安裝,
然而匯入憑證到 Google Chrome 時卻遇到這樣的錯誤:
憑證佈署失敗!!
無法佈署憑證到以下網域:
m.esunsec.com.tw(逾時未回應)
stocksavings.esunsec.com.tw(逾時未回應)
onlineaccount.esunsec.com.tw(逾時未回應)
cts.esunsec.com.tw(逾時未回應)
www.esunsec.com.tw(逾時未回應)
ec2.esunsec.com.tw(逾時未回應)
preview.esunsec.com.tw(逾時未回應)
www.esunconsulting.com.tw(逾時未回應)
web.esunsec.com.tw(逾時未回應)
分析
看了一下 console 紀錄了這樣的錯誤訊息:
Uncaught DOMException: Failed to read the 'localStorage' property from 'Window': Access is denied for this document.
根據 MDN 上的說明:
SecurityError
The request violates a policy decision, or the origin is not a valid scheme/host/port tuple (this can happen if the origin uses the file: or data: scheme, for example). For example, the user may have their browser configured to deny permission to persist data for the specified origin.
發現問題在於玉山從 file:// protocol 網頁上存取 localStorage 而被瀏覽器限制存取。
看來只能手動安裝了,以下是手動安裝玉山證券憑證教學:
解決方法
玉山證券憑證百寶箱會在玉山證券的九個網域上安裝憑證,因此我們手動安裝憑證也需要一個一個進行安裝:
- 複製安裝憑證跳出錯誤的那個空白網頁的網址,長得會像是
file:///C:/Users/toby/twca/TWCADepCert.20210121012904090.htm
。 - 使用記事本或是慣用的編輯器開啟這個檔案,打開記事本後,選擇檔案→開啟舊檔,把剛才複製的網址貼到
檔案名稱
,再按開啟
。 - 往下拉找到
var certInfo={
,從這行的開頭開始用滑鼠選取,一直到var redirectUrl = "#backUrl#";
這行之前(不含),選取之前可以先找一下位置以免超過。 - 檢查一下選取的範圍應該會像是這樣:
var certInfo={ "opType":"deploy", ... ... ... };
- 複製起來,貼在新的記事本上備用。
- 依序點進玉山證券憑證安裝網址,先點第一個:
- https://m.esunsec.com.tw/importCert.htm
- https://stocksavings.esunsec.com.tw/importCert.htm
- https://onlineaccount.esunsec.com.tw/importCert.htm
- https://cts.esunsec.com.tw/ESUNWEB/importCert.htm
- https://www.esunsec.com.tw/importCert.htm
- https://ec2.esunsec.com.tw/securities/NewReceptionOne/importCert.htm
- https://preview.esunsec.com.tw/importCert.htm
- https://www.esunconsulting.com.tw/importCert.htm
- https://web.esunsec.com.tw/epassbook/importCert.htm
- 從瀏覽器選單開啟開發人員工具(或是按熱鍵
Ctrl
+Shift
+I
) - 切換到
Console
分頁,把剛才複製下來的貼到底下,然後按 Enter。 - 貼上或是輸入
saveCert(certInfo);
,然後按 Enter。 - 切換到
Application
分頁,左邊欄找到Storage
,展開Local Storage
,點一下https://
開頭的網址,右邊有看到TWCACertIdxRef
就表示憑證安裝完成了,回到第6步繼續安裝到下一個網址。
九個網址都操作完後,就完成了玉山證券憑證手動安裝,可以回到玉山證券網站上繼續操作了!
玉山證券富果帳戶
你是否覺得解決這些電腦問題十分麻煩?前面提到過玉山證券富果帳戶完全不需要額外的軟體來設定,還提供比起其他證券下單軟體更現代化、好看的介面,還有不錯的手續費折扣,非常適合年輕人小資族開始投資理財,歡迎使用我的推薦連結開戶:https://openaccount.fugle.tw/?referral=f-59aa781&utm_source=referral&utm_medium=link,完成開戶你和我就可以分別賺取 108 富果幣!
玉山證券富果帳戶提供電腦網頁版和 App 版,讓你不論是使用桌機、筆電、MacBook、iPad、iPhone 或是 Android 手機或平板,都能隨時隨地看盤下單,讓我們一起投資理財規劃未來!
wslbridge error: failed to start backend process
note: backend error output: -v: -c: line 0: unexpected EOF while looking for matching `''
-v: -c: line 1: syntax error: unexpected end of file
ConEmuC: Root process was alive less than 10 sec, ExitCode=0.
Press Enter or Esc to close console...
Solution
Settings->Startup->Tasks->{WSL::bash}->Commands
set PATH="%ConEmuBaseDirShort%\wsl";%PATH% & wsl -d ubuntu
User
adduser username
passwd username
Grant sudo permission
gpasswd -a username wheel
Install SSH public key:
@local$ ssh-copy-id username@SERVER_IP_ADDRESS
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
systemctl reload sshd
exit
Login with new user
@local$ ssh username@SERVER_IP_ADDRESS
Firewall
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
sudo firewall-cmd --permanent --list-all
sudo firewall-cmd --reload
sudo systemctl enable firewalld
Date & Time
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
sudo yum install epel-release
sudo yum install yum-utils
sudo yum install https://rpms.remirepo.net/enterprise/remi-release-7.rpm
Nginx
sudo vi /etc/yum.repos.d/nginx.repo
[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
sudo yum-config-manager --enable nginx-mainline
sudo yum install nginx
sudo systemctl start nginx
Test http://server_domain_name_or_IP/
sudo systemctl enable nginx
MariaDB
sudo vi /etc/yum.repos.d/MariaDB.repo
# 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
sudo yum install MariaDB-server MariaDB-client
sudo systemctl start mariadb
sudo mysql_secure_installation
sudo systemctl enable mariadb
PHP
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
sudo vi /etc/php.ini
...
cgi.fix_pathinfo=0
...
sudo vi /etc/php-fpm.d/www.conf
user = nginx
group = nginx
listen.owner = nobody
listen.group = nobody
listen = /var/run/php-fpm/php-fpm.sock
sudo systemctl start php-fpm
sudo systemctl enable php-fpm
Composer
sudo yum install composer
Node.js
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
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:
sudo cat /var/log/audit/audit.log | grep nginx | grep denied
ls -Z /var/www
sudo chcon -Rv -t httpd_sys_content_t /var/www/
To enable write permission for httpd:
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):
sudo setsebool -P httpd_can_network_connect 1
Fix PHP session permission:
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.
很簡單的殼之後來寫繁體中文教學
OD 載入 Sample
HWBP OEP 00401000
F9
LordPE 載入、Correct Image Size、Save Full
ImpREC 載入、OEP:1000、IAT AutoSearch、Get Imports、Show Invalid、Trace Level1、Fix Dump
Done
課外讀物:
https://bbs.pediy.com/thread-65261.htm
http://www.reversing.be/article.php?story=20050725002631386
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 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
悸動一絲一絲 竄出我的心 而我毫無防備 我想忽視它 像曾經那樣 但卻沒什麼用 終於決心讓你闖入我的心 我不害怕 可是我要說 如果真的不愛了 我會很心痛 但你的出現擊潰了我的防備 如果真的發生了 我會很心痛 所以答應我你會愛護我 是你的貼心舉動 讓我深深愛上你 無法自拔 日日夜夜不停歇 所以請你不要丟下我 終於決心讓你闖入我的心 我不害怕 可是我要說 如果真的不愛了 我會很心痛 但你的出現擊潰了我的防備 如果真的發生了 我會很心痛 所以答應我你會愛護我 再多的藉口 都於事無補 你的心意有期限 我想是時候放手一搏去愛 如果真的不愛了 我會很心痛 但你的出現擊潰了我的防備 如果真的發生了 我會很心痛 所以答應我你會愛護我 所以答應我你會愛護我 所以答應我你會愛護我
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
下載完遊戲啟動後出現這樣的錯誤訊息:
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\
,再執行遊戲。
大功告成!