[闲聊] tmi-2 efun 与 simul_efun 简单说明

楼主: laechan (挥泪斩马云)   2014-06-26 12:14:05
这篇说明也会同步丢到 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 则可以传回一个变量它到底是什么型态。
其它下一篇介绍。(吃饭时间到了)

Links booklink

Contact Us: admin [ a t ] ucptt.com