[閒聊] tmi-2 efun 與 simul_efun 簡單說明

看板mud (網路地下城/文字遊戲)作者 (小太保)時間11年前 (2014/06/26 12:14), 11年前編輯推噓1(100)
留言1則, 1人參與, 最新討論串1/5 (看更多)
這篇說明也會同步丟到 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 則可以傳回一個變數它到底是什麼型態。 其它下一篇介紹。(吃飯時間到了) -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 210.61.157.53 ※ 文章網址: http://www.ptt.cc/bbs/mud/M.1403756049.A.761.html

06/26 12:36, , 1F
推L大的每日MUD課堂
06/26 12:36, 1F
※ 編輯: laechan (210.61.157.53), 06/26/2014 13:31:38
文章代碼(AID): #1JgvuHTX (mud)
文章代碼(AID): #1JgvuHTX (mud)