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

作者: tenyfish (何時才有發言權?)   2014-06-27 02:18:46
※ 引述《laechan (小太保)》之銘言:
: 這篇說明也會同步丟到 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 則可以傳回一個變數它到底是什麼型態。
: 其它下一篇介紹。(吃飯時間到了)
下面只是長一點的推文,沒什麼參考價值這樣
首先一樣要感謝L大,這幾份efun對
有程式基礎的新手admin要上手很有幫助,
上次寫完一個簡單的指令,簡單的觸發事件
其實能熟練用函數,跟一些現在的程式比起來
其實LPC複雜度並不高。
另外是我個人的心得,因為我不是很聰明啦
我了解你使用縮寫代號的目的是提升運算速度,
而改也希望能輕量化,實作一些在之前mud的想法
舉例以ds來說,資料夾很多,loading的檔案也很多
但它不錯的一個部分是分類十分清楚,
很多素材讀起來比較直覺一點,輕鬆一點
先不論效率和長期的問題,感覺較容易上手
請不要介意我的觀點
畢竟改是你這十幾年心血優化的作品
而我是以一個新人來了解它
以上 辛苦了

Links booklink

Contact Us: admin [ a t ] ucptt.com