--- title: 'MUD 遊戲的魔法系統設計' author: hsins tags: MUD, LPC --- [toc] ## LPC 程式設計 ### 程式特點 使用 LPC 語言所編寫的程式,其內容統稱為物件(objects);一般來說,在運行一個程式的時候,是有開始和結束的,所有的程式在開始執行的時候,會進入主函數 `main()` 進行處理,當程式執行完畢就終止了。而 LPC 程式有所不同,整個 `mudlib` 的 `driver` 系統運行的是用 LPC 語言所編寫的程式,這些程式會在不同的時間和情況下被呼叫與調用,也就是說這些程式在 `mudlib` 中並不存在絕對的觸發點和結束點。 ### 資料型別 #### `Object` 是定义一个对象,具体是定义一个 NPC、一个物品、一个场景、一个运行于内存里的 文件。实际上是一段由后面很多变量按一定运算方式组合在一起的程式。一个程序里制造 成一件新的物品,则必须先定义一个变量,如:object obj,然后再 obj = new(xx) 将这个 obj 实际上就 clone 了出来,括弧里的 xx 代表它的文件绝对路径名。 #### `Int` 表明定义的是一个整型,可以为正负或 0,定义出来的数字可以进行各种数字运算, 但结果只保留小数点前的数字。 #### `Float` 表示定义的是一个浮点型,对于使用的 MUDOS 来说,机器支持浮点值到小数点得后 7 位(精确度)。 #### `String` 表明定义的是一个字符串型,字符串的长度在理论上是没有限制的。在 LPMUD 里, 限于网络响应,一般是在编译 MUDOS 时,在 config.h 文件里进行设置与限制。对于字符 串型变量的识别,有简单的区别标准:有用“”括起来的是 string 型,没有则看其是否整 数而分辨为整数数值与浮点数值。因此在一些不严谨的语句中,如没有强制定义,也可将 8 int、float 与 string 区分出来。 A、set("number",783);------->int 型 B、set("number",78.3);------>float 型 C、set("number","783");----->string 型 D、set("number","78.3");---->string 型 同时 string 型可以相加,但决非数字意义上的运算,而是一种合并,例如上面的 C+ D 就是"78378.3"; #### `Array` 表明定义的是一个数组型, LPC 中的数组是在类型后面加 * 来表示数组,且不用预 先定大小,但若有需要可以用 allocate(size)来固定大小。 如:a = allocate(10); 在固定了 SIZE 之后好处是可以任意用下标定位来对数组元素操 作。 #### `Mapping` LPC 中利用率最高的散列型(映射型),可以用任意的数据类型来建立索引(如 string、 object、int、array 等等),而不是仅仅用整数。 例:mapping fam = (["a":2,"b":13,"c":2.333,"d":"一条小河","e":"158"]); fam 里的 a、b 子变量是 int 型的,c 是 float 型的,d、e 是 string 型的。LPC 的说明文 件里,a、b、c、d 被叫做“关键字”,而后面的 2、13、2.333、一条小河、158 被叫做“内容 值”。 散列型的变量可以用 “变量名["关键字"]” 的形式进行调用,并可以用 “变量名["关键 字"]=新内容值” 的方式进行赋值。例:fam["e"]的值是"158" ,如果 fam["e"]="400",那么 再次调用时:fam["e"]的值就是"400"。 ### 運算符號 - 算數運算符號 - `+` (加) - `-` (減) - `*` (乘) - `/` (除) - `%` (取餘) - `++` (自加) - `--` (自減) - 關係運算符號 - `>` (大於) - `<` (小於) - `==` (等於) - `>=` (大於等於) - `<=` (小於等於) - `!=` (不等於) - 邏輯運算符號 - `&&` (與) - `||` (或) - `!` (非) - 條件運算符號 - `(condition) ? (statement1) : (statement2)` - 成員運算符號 - `->` ### 流程控制 if(运算式) 指令; if(运算式) 指令; else 指令; if(运算式) 指令; else if(运算式) 指令; else 指令 while(运算式) 指令; do { 指令; } while(运算式); switch(运算式) { case (运算式): 指令; break; default: 指令; } 介于这类语句的规则与 C 类似,所以论文中不予深入讨论其用法。(详:查阅 C 或 C++相 关书籍) ### 錯誤訊息 以某一文件为例(简述): 1) 编译时段错误:/u/llm/npc/test.c line 13: parse error parse error 一般表示错误出在基本的拼写上,多是像逗号、分号写错,或者是各种括 号前后多写或漏写的情况,可以在提示的第 13 行或之前的几句里找错误; 2) 编译时段错误:/u/llm/npc/test.c line 13: Undefined variable 'HIY' Undefined variable 表示有一些未曾定义、不知其意义的东西存在。后面跟着的就是这 个不明意义的字串。这个错误有三种可能,一是将一些变量的词拼错。二是因为这个变量 未曾定义或者根本就没有声明,三是这个变量是定义在一些继承文件里,但在这个文件里 却没有继承; 3) 重新编译 /u/llm/npc/test.c:错误讯息被拦截: 执行时段错误:*Bad argument 1 to call_other() 一般是指这个文件里在调用其它文件里的函数或者是对象时发生错误了; 4) 重新编译 /u/llm/npc/test.c:错误讯息被拦截: 执行时段错误:F_SKILL: No such skill (froce) 属于拼写错误,系统无法找到 froce,应该是 force(游戏中的人物的气力); 5) 重新编译 /u/llm/npc/test.c:编译时段错误:/u/llm/npc/test.c line 75: Type of returned value doesn't match function return type ( int vs string ) 表示在某一个函数里,返回值的类型与定义的不同,并指出是因为 string 与 int 的错 误; 6) 重新编译 /u/llm/npc/test.c:编译时段错误:/u/llm/npc/test.c line 72: Warning: Return type doesn't match prototype ( void vs int ) 表示错在函数类型上了,是因为函数与前面的声明相冲突,一个是 int,一个是 void。 以上是在 LPC 程序编译时经常出现的出错信息,并对其原因做了比较简易的解释。程序编 译中会涉及更多的出错信息,可能关系到游戏构架底层的某些规则,或者是整个游戏中的 文件相互间权限的问题。这些都需要对游戏构架有进一步的了解才能解决。 ## LPMUD 遊戲 ### 人物 #### NPC 人物 #### PPL 人物 ### 房間(Room) ## 法術系統 ### 概念拆分 首先必須要區分 **法術系統** 與 **法術效果** 兩個概念,在多數的 MUD 遊戲中會允許玩家使用以下的指令來施展任一法術: ``` > cast 'magic missile' ``` 對於玩家而言只需要記住 `cast` 這一指令即可,而對於開發者而言,不需要針對不同職業的法術單獨開發一個指令,亦即不需要在每個 `main()` 程式中針對職業類別進行過濾。在這樣的機制下可以大大提高開發效率並減少後續修改所需要做的事情。 ### 程式邏輯 ![](https://i.imgur.com/WaAsGSR.png) 如圖所示,當玩家輸入 `cast 'fire'` 指令時,會在 `cast.c` 的主程式 `main()` 中呼叫 `call_spell()` 函數,此一函數被定義在 `Skill` 中,作用是啟動繼承他的每個職業的法術文件 `daemon/skills/xxx.c`: 图 4-1,在 cast.c 文件的主程序 main()中调用 cast_spell 函数,该函数定义在 Skill 文件 中,其作用是启动继承它的每个门派魔法文件(daemon/skills/xxx.c),比如方寸上使用的魔 法类别是道术(dao)。这些类别文件的关键作用是寻找该门派魔法的路径(cast 中需要保存 玩家的魔法,原因是其与类别文件同名,省去了复杂的语句),并且作为字符串返回给 cast_spell()处理,如果没有找到该文件或者说文件的大小为 0,那么此次 cast 失败。否则按 照路径呼叫 fire 中的 cast()实现 fire 的魔法效果。值得注意的是,在 cast()中的返回值是有 其作用的,按照流程图反向传回,该值在 cast.c 文件中被赋给了 nocast 变量。 nocast=(int)SKILL_D(spells)->cast_spell(me, spl, target); 用于对玩家计算两次魔法的间隔时间,同时防止了魔法被无限施放的问题。 在前章节中提到的 NPC 随机行为是可以施放随机魔法的,其中大致原理和流程图 4-1 相似,只是进入魔法系统的入口有所改变,NPC 是不可能通过实行 cast 命令来施放魔法的。 所以 mud 在 NPC 的人物继承中编写了一个 cast_spell()函数,它的作用是提取 NPC 的魔法 种类,并且调用 SKILL。这样 NPC 就进入了如上述施放魔法的流程。 游戏中魔法所对应的基本技能是 spells,除了这些人物也具有各种武功,而武功对应 的是内力 force。由于各个门派的内力武功不同,所以内力使出的 perform 技能也利用了和 魔法系统同样的构架原理。此文中不再做详述。 通过前面的分析,确定魔法系统的构架需要利用 windows 下已经建好的文件夹。每个 编写好的魔法只需按门派种类放在对应的地方,有魔法系统读取即可。并且在运行中出错 时,可以快速的查出问题处于那个流程中。