這篇說明也會同步丟到 tmi2_v3_改 的 document 資料夾。
efun 可以想成是「先天的全域函數」,simul_efun 可以想成是
「後天的全域函數」,前者就可以隨時取用的如 time(),後者則
定義在 /adm/simul_efun/ 目錄下的各個函數物件內,然後再用
/adm/obj/simul_efun.c 將它們一一 include 進來,如 atoi.c
的 atoi 函數就是透過這樣的方式才能成為全域函數。
我這次會在 tmi2_v3_改 資料夾裡面放一個 func_spec.c 檔,這
裡面所列的函數就是 efun 函數,簡單舉幾個常用的:
unknown call_other
比方 ppl->set("level",10) 相當於
目標 函數 這個函數所接的參數們
call_other(ppl, "set", "level", 10);
object this_object
這東西也是 efun,相同的還有 this_player() 等等,因為很
直覺就不做說明。
object clone_object _new(string, ...);
從這裡可看出 clone_object 的動作實際上就跟做 new() 的動
作是差不多的。
int sizeof(mixed);
計算陣列的 size,如 tmps=({"a","b","c"}), sizeof(tmps) = 3
int strlen sizeof(string);
從上面可以發現 string 就跟「陣列」的概念是類似的,也就是
說如果一 string = "abcde", 它就類似({"a","b","c","d",e"})
這樣的陣列排在一起的結果。
strlen 就是計算字串的長度。strwidth 也相當於 strlen 只是
一般都用 strlen。
void destruct(object default: F__THIS_OBJECT);
簡單的說 ob->remove() 一般就相當於 destruct(ob)。
destruct 因為是 efun,所以它就是很單純的把 ob 給 destruct
掉而已,ob->remove() 我們還可以對 remove() 函數動一些手腳
,這就是兩者的差異。(remove 一般最終也是把 ob destruct)
string file_name(object default: F__THIS_OBJECT);
file_name 跟 base_name 讀出來的東西差異如下
base_name(me) = "/std/user"
file_name(me) = "/std/user#10"
其中 #10 就是「已載入的物件編號」,它的編號方式是採流水號
的做法,確保每一個已被載入的物件都有獨特的編號。
(因為在編輯檔案時檔名不能帶 #,所以它用#來當編號開頭)
string capitalize(string);
首字大寫。
比方 name="laechan", 則 capitalize(name) = "Laechan"
string *explode(string, string);
比方 tmp="1,2,3,4,5" 那
mixed tmps;
tmps=explode(tmp,",");
則 tmps = ({"1","2","3","4","5"})
也就是說 explode 就是對一個字串依特定的子字串去做拆解,將
拆解後的結果一一存進陣列,相當於其它程式語言常見的 split
常見的 explode 如下
string tmp=read_file("/x/x/xxx"); // 讀入一個檔案的內容
mixed tmps=explode(tmp,"\n"); // 依分行符號做拆解
則 tmps 裡面每一個元素就相當於該檔案的「每一行」。
mixed implode(mixed *, string | function, void | mixed);
這東西就是 explode 的相反,把陣列變成字串,並在陣列的元素
之間塞進指定的分隔字串。
例如 tmps=({"1","2","3","4","5"})
string tmp=implode(tmps,",");
↑指定的分隔字串
則 tmp = "1,2,3,4,5"
↑implode 出來的字串就會依指定的分隔字串分隔
所以 explode 與 implode 是相對的。
int call_out(string | function, int,...);
call_out("哪個函數",幾秒,要帶過去的參數們);
比方一程式執行到最底下
write("你躺在床上開始休息,Z z z...\n");
call_out("rest_over",2,ppl);
return 1;
}
// 兩秒後這個函數才被呼叫
int rest_over(object ppl)
{
if(!ppl) return 1;
ppl->set("hp",ppl->query("hp_src"));
write("你睡飽了, 感覺體力全部回來了!\n");
return 1;
}
但是建議 call_out 能免則免,因為它的執行類似底下
// t=目標時間
while(time()<t)
{
}
有學過程式的都知道上面的 loading 是很重的。
int member_array
int strsrch
這兩個就跟 sizeof 及 strlen 一樣是可以放在一起講的,就是
一個是針對陣列做處理,一個是針對字串做處理。
對陣列來說它是 第0個 第1個
↓ ↓
例如 mixed tmps=({"a","b"});
member_array("a",tmps); 傳回的結果是 0
member_array("b",tmps); 傳回的結果是 1
member_array("c",tmps); 傳回的結果是 -1 代表沒找到
又例如 string tmp="ab";
strsrch(tmp,"a"); 傳回的結果是 0
strsrch(tmp,"b"); 傳回的結果是 1
strsrch(tmp,"c"); 傳回的結果是 -1 代表沒找到
所以要注意的是,要判斷一個 sub element 是不是存在於一個集
合裡,用的做法不是
if(!strsrch(tmp,"a")) 或 if(strsrch(tmp,"a"))
而是
if(strsrch(tmp,"a")==-1) 或 if(strsrch(tmp,"a")!=-1)
int input_to
這東西要講可以講好幾頁,所以只簡單講。
write("請輸入 ");
輸入完按enter後呼叫的函數 輸入模式 參數群 你輸入了什麼被存在這
input_to("input_over", 0, ...., str);
一般來說輸入模式用 0 即可(也就是一般輸入),像如果是輸入密
碼的情況會用 3 居多。
參數群就是指要帶過去給 input_over 函數用的,str 就是儲存你
所輸入的東西,input_over 大概長這樣
你輸入的東西 帶過來的參數群
int input_over(string str, ..............)
所以要注意的就是「你輸入的東西」要放在 input_over 函數的最
前面,而在它之後所接的,才是你原先要讓 input_to 帶過來的參
數群,底下是例子
write("請輸入: ");
input_to("input_over",0,ppl,n,str);
return 1;
}
int input_over(string str,object ppl,int n)
{
// 使用者沒輸入東西就按 enter
if(!str || str=="")
{
write("請輸入: ");
input_to("input_over",0,ppl,n,str);
return 1;
}
write("你輸入的東西是: "+str+".\n");
return 1;
}
然後以上面的用法為例,它也可以簡略如下
input_to("input_over",0,ppl,n); // 不需要有 str
則這時候
↓它同樣會是你輸入的東西
int input_over(string str,object ppl,int n)
int random
簡單的說 random(10) 跑出來的數字有可能 0~9。
object environment
簡單的說 environment(ppl) 能傳回 ppl 所在的空間,它不一定
是房間,比方某個東西 ob 放在你的身上,那 environmnt(ob)傳
回的就是你,因為 ob 在你身上。
object *all_inventory
簡單的說如果你所在的空間是 env
mixed obs=all_inventory(env); // 傳回在這個空間的所有物件
mixed obs=all_inventory(me); // 傳回在 你身上 的所有物件
object *deep_inventory
object first_inventory
object next_inventory
這三個非常非常少用,有興趣可自行用 running code 測試。
void say
void tell_room
簡單的說 say("test.\n"),呼叫主體本身看不到,呼叫主體以外
的同房間物件收得到,所以一般常看到的寫法就是
write("你說道: test.\n"); // 給自己看的
say(me->query("cap_name")+"說道: test.\n"); // 給其它人看
而 tell_room(environment(me),"......") 就是給房間裡所有的
人看的訊息。
然後它可以用來模擬 say,例如
write("你說道: test.\n");
tell_room(environment(me),me->query("cap_name")+
"說道: test.\n",({me}) );
↑哪些物件排除於 tell_room 之外
object present
這個函數非常非常的常用。
ob=present("laechan",environment(me));
它的意思就是去找 environment(me) 這個空間裡面有沒有 id 有
"laechan" 的物件存在,有的話 ob 就是這個叫 laechan 的物件
請注意是 "id",所以如果有兩個以上的物件擁有相同的 id,例
如
大螞蟻(big ant) id = ({"big ant","ant"})
小螞蟻(small ant) id = ({"small ant","ant"})
當同一房間 env 的兩個物件其 id 都有 "ant" 時
ob=present("ant",env)
這時候 ob = 大螞蟻(big ant),因為它是同 id 裡面排第一個的
ob=present("small ant",env)
這時候 ob = 小螞蟻(small ant),因為只有一隻叫 "small ant"
ob=present("ant 2",env)
這時候 ob 也是 小螞蟻(small ant),因為它是第二隻 ant。
ob=present("xxx",env)
這時候 ob = 空(UNDEFINED),因為 env 沒有 id 叫 xxx 的物件
void move_object(object | string);
這個很少用,所以我也不曉得它是幹嘛的,有興趣的可以自己試。
void add_action
這個最常見於 void init() 函數內,它可以定義一個動作指令,
當觸發 init 函數的使用者執行了這個動作指令時,就可以指定
此時要呼叫哪一個函數來做處理:
add_action("要做處理的函數","動作指令");
或者也有這種用法
↓執行哪些指令會呼叫同一函數
add_action("要做處理的函數",({"指令1","指令2",..}));
例如
add_action("openup_box",({"openup","打開"}));
則玩家下 openup 指令或是 打開 指令,都會呼叫 openup_box。
常見的做法如下
void init()
{
add_action("drink_water","drink");
}
↓使用者在 drink 後面接了什麼
int drink_water(string str)
{
if(!str || str=="")
return notify_fail("你要喝什麼?\n");
當觸發者與觸發主體已經不在同一 env 時,add_action 的指令就
自動失效,最常見的就是「當玩家離開了有 add_action 的房間後
」,比方 a 房間可 drink water,當你離開 a 房間時自然就不能
在 drink water。
string query_verb();
以上面為例
int drink_water(string str)
{
string verb;
verb=query_verb(this_player());
這時 verb 就是 "drink " + str,也就是說它可以截取剛剛使用
者下了什麼指令,比方使用者下 drink water <= 這就是 verb。
int command(string);
它可以令呼叫主體執行一道命令,例如
command("say hi");
command("quit");
command("suicide");
請注意是「呼叫主體」,非呼叫主體是不能令它人 command 的。
int remove_action(string, string);
既然有 add_action 當然就有 remove_action
add_action("drink_water","drink"); // 增加觸發者可下的指令
remove_action("drink_water","drink"); // 將該指令移除掉
int living
這東西是用來判斷一個 ob 是不是「生物」,比方
if(living(ob))
大概就是這樣用,是生物的話(包含玩家與怪物)就傳回 1。
mixed *commands();
這東西很少用。
void disable_commands();
void enable_commands();
一般在 mob 檔案內會看到 enable_commands(),它的用意就是要
讓該 mob 處於可執行指令的狀態。
反過來說 disable_commands() 就是要 disable 掉該 mob 能下
指令的狀態。
void set_living_name(string);
這東西跟 set("id") 的差異,可以想成是是否有登錄為「全域id
」,比方:
set("id",({"small ant","ant"}));
set_living_name("ant");
當一隻怪物有 set_living_name 並被載入時,你 chat *hi ant
就有可能透過 find_living("ant") 找到這隻 ant。
若沒有 set_living_name 的話 find_living 就找不到。
一般 mob 都是會 set_living_name 的,但是反過來說,如果這隻
怪物只是做一般用途,不 set_living_name 反而是比較好的。
(因為 living 判斷不會因有無 set_living_name 而改變,只是沒
有 set_living_name 的話 find_living 會找不到而已)
object *livings();
object *users();
object *objects();
這三個可以一起講
livings 傳回的就是所有被 set_living_name 且被載入的生物
users 傳回的就是所有線上的玩家
objects 傳回的就是所有已被載入的物件
所以其包含範圍是 objects > livings > users
要注意的就是 livings 包含 users 這一點,因為玩家也是生物,
也同樣有被 set_living_name。
object find_living(string);
object find_player(string);
find_living 就上面提過的。find_player 則將目標鎖定在玩家。
object ppl=find_player("laechan");
當 "laechan" 這個玩家有在線上時,ppl 就是 laechan 這個玩家
void notify_fail
這個現在也很常用,可以把它跟 write 放在一起。
write("test.\n");
return 1;
與
notify_fail("test.\n");
return 0;
或寫成
return notify_fail("test.\n");
為什麼要有 return 1 跟非 return 1 的區別呢,比方說我們下了一
個指令 10 n,結果你往北 5 格就會撞牆,在 _go.c 裡面會這樣寫
if(!room->query("exits/"+dir))
return notify_fail("往 "+dir+" 這個方向沒路喔.\n");
me->move(room->query("exits/"+dir));
return 1;
而 10 n 的迴圈判斷就是這樣寫
for(i=0;i<10;i++)
if(command("go north")<1)
break;
也就是說,如果我們在執行 10 n 的過程中遇到失敗(return 0),
那剩下的就不需要再做(break),因為再做也是失敗,所以我們才
需要有一個用來判斷成功或者是失敗的回傳值,以上面的例子為例
它定義的方式就是
return 1: 成功
return 0: 失敗
string lower_case(string)
它可以把一個字串裡面的全部字母通通小寫
string tmp1="LaeChAn";
string tmp2=lower_case(tmp1);
則此時 tmp2 = "laechan" 全部都會小寫。
string replace_string
簡單的說例如 string tmp1="我 是 一 隻 小小 小 小 鳥"
tmp2=replace_string(tmp," ","");
上面的意思就是說我要把 tmp1 這個字串裡面的 " " 空格,全部替
換成 "",則這時候
tmp2="我是一隻小小小小鳥"; // 空格全部消失
也就是說要找尋的目標字串放前面,要用來替換的字串放後面。
int restore_object(string, void | int);
mixed save_object(string | int | void, void | int);
這東西在有儲存資料的系統很常見。
if(file_exists("/data/xxx.o"))
restore_object("/data/xxx");
上面的意思就是說,如果資料檔 xxx.o 存在的話,就 restore 它
save_object("/data/xxx");
然後如果要儲存資料到 /data/xxx.o 的話其呼叫就如上。
這個可自行觀看有使用這兩個呼叫的系統物件,會比較清楚。
string save_variable(mixed);
mixed restore_variable(string);
這兩個很少用。
mixed *get_dir(string, int default: 0);
這東西主要用來讀取一個目錄下有哪些「檔名以及目錄名」,例如
說
> ls /d/area/test
Path: [/d/area/test]
1 boat.c* 1 port1.c* 1 port2.c* tool/
可以看到有三個 .c 檔以及一個 tool 目錄,則
mixed tmps=get_dir("/d/area/test/");
則 tmps=({ "boat.c", "port1.c", "port2.c", "tool" })
請注意傳回的是「檔名」而不是「完整檔案路徑名稱」;傳回的是
「目錄名」而不是「完整目錄路徑名稱」。
則通常要用來判斷傳回的是目錄還是檔案的做法就如下
string tmp,paths="/d/area/test/";
mixed tmps=get_dir(paths);
foreach(tmp in tmps)
{
// 要用完整的路徑檔名去做判斷
if(file_size(paths+tmp)==-2)
write(paths+" 目錄下的 "+tmp+" 是一個目錄.\n");
else
write(paths+" 目錄下的 "+tmp+" 是一個檔案.\n");
}
void message(mixed, mixed, string | string * | object | object *,
void | object | object *);
這東西在 simul_efun 還算蠻常見的,但是實際用的情況很少,因
為多半都有自訂一些跑訊息用的函數了,這個「源頭函數」就很少
用。
理論上要做簡繁轉換可以靠修改它來做。
mixed *values(mapping);
mixed *keys(mapping);
void map_delete(mapping, mixed);
(allocate_mapping 很少用)
這幾個就是用在 mapping 變數的操作上的,例如今天宣告一個
:號前面叫 key :號後面叫 value
↓ ↓
mapping data=([ "laechan" : "小寶",
"spock" : "小強", ]);
mapping 資料簡單的說就是 ([key:value, key:value, ...])
mixed tmps=keys(data);
則 tmps = ({"laechan","spock"}) 就是所有 key 的集合
mixed tmps=values(data);
則 tmps = ({"小寶","小強"}) 就是所有 value 的集合
map_delete(data,"laechan");
則 data 就剩下 (["spock":"小強"]), 也就是把 "laechan"
這個 key 及它所接的 value 給 delete 掉的意思。
int clonep(mixed default: F__THIS_OBJECT);
int intp(mixed);
int undefinedp(mixed);
int nullp undefinedp(mixed);
int floatp(mixed);
int stringp(mixed);
int virtualp(object default: F__THIS_OBJECT);
int functionp(mixed);
int pointerp(mixed);
int arrayp pointerp(mixed);
int objectp(mixed);
int classp(mixed);
string typeof(mixed);
這些都很直覺,例如說
mixed data=(["laechan":"小寶"]);
if(mapp(data)) 就是用來做 data 是不是一個 mapping 的判斷
if(arrayp(data)) 就是用來做 data 是不是一個陣列的判斷
if(undefinedp(data["spock"])) 就是用來做 data 是不是有內含
一筆 key 為 "spock" 的資料,當沒有這筆資料時,undefinedp就
會傳回 1(因為是 undefinedp, "un"), 代表沒有.
其它 intp、stringp、floatp、functionp、objectp 這些都很直
覺,pointerp、virtualp 很少用,nullp 也不常用。
typeof 則可以傳回一個變數它到底是什麼型態。
其它下一篇介紹。(吃飯時間到了)