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