//趁今天一次把進度衝完,大綱:driver運作機制、進階資料類型、預編譯指令、字串處理、深入繼承、除錯教學
//之後研究方向: 區域撰寫、戰鬥、心跳、互動
ch1 簡介
主要障礙:mudlib不合,建議往上找大神改善
注意:不教shadow、不教系統安全和OOP
ch2 LPMud driver
2.1 簡短回顧
2.2 週期cycle
driver週期內執行工作
2.3 使用者命令
命令表: 活物件名稱、給予命令者物件、命令對應函數
輸入命令的人=>給予命令者(通常是玩家this_player())
改變環境時,物件會清空環境中的其他物件列表和環境中的可用命令,然後呼叫新環境的init()
//沒有示意圖,光看文字說明不太懂,需要搭配實際程式碼
//控制權轉移部分,直接看程式碼比較快,作者…
"north" 在 room 的 set_exits()中定義
"tell" 在 player 的 cmd_hook()中處理對應關係
命令執行過程:
north
"north" 轉換為 room_obj->do_move(0)
this_player()->move_player()
move_object()
tell descartes
"descartes"作為參數傳給tell()
2.4 set_heart_beat(), call_out()
set_heart_beat(1) 加入心跳物件列表中
set_heart_beat(0) 從心跳物件列表移除
心跳發生場景:治療、怪物、戰鬥
玩家的heart_beat()
注意:大量的心跳物件是MUD效能低落的原因
call_out()算是在遊戲中實作的簡單排程函數
只是名字取不好,取 call_after 或 call_later 會感覺好一點
remove_call_out()能夠移除已經加入排程的函數,但如果出現同名的排程函數,程式就會出問題
如果要弄成循環呼叫的話,在被呼叫的函數內加call_out()形成遞迴
//幹嘛不直接實作一個週期呼叫的列表就好= =
心跳和延遲指令都能完成目標,但對系統的消耗不太一樣
經驗法則:十秒以上用延遲,十秒以下用心跳
ch3 複雜資料類型
3.1 簡單資料類型 int, string, object, void
object是奇怪的特例 //工三小?
3.2 NULL在認知上造成的困擾
NULL和0常常是等價的關係,但實際上它們是不該混用的
//其他程式語言也存在一樣的問題,不要混用就好了
3.3 陣列 Array
//當時的C應該沒有string這個型別
//陣列採用({})分隔,跟原本陣列的[]區隔
3.4 使用陣列
member_array(x,ary) 回傳 x 在 ary 中出現的位置
arr = ({ str1 }) + ({ str2 }); //糟糕例子
arr = ({ str1, str2 }) //好例子
注意:單一元素加入陣列會出現類別錯誤,需要將元素轉換成「單一元素的陣列」才行
3.5 映射 Mapping
說明一些array不好發揮的場景,這時候透過 mapping 就比較好處理
//這種資料類別現在已經很廣泛了,在當時的C還是很先進的語言實作
//作者又舉了很糟糕的例子,強烈建議不要使用Array的加減法運算子,非常容易造成誤解
([ "gold":10, "silver":20 ])
概念上跟今天json的object是相反的,採用([])作區隔
map_delete(money, type); //MudOS
money = m_delete(money, type); //LPMud 3.* 衍生版本
m_delete(money, type); //LPMud 3.* 衍生版本
由此可見driver並沒有統一標準
map = ([]);
map = allocate_mapping(10) //map也有大小限制
map = ([ "gold": 20, "silver": 15 ]);
關於array和mapping常見錯誤:
原因:
未正確初始化 => 1.3.
索引不存在 => 2.
單一元素對陣列操錯 => 3.
//以現今的語言標準實作的話,痛苦指數應該會少很多,不用在像以前那樣痛苦了
ch4 預編譯LPC
作者再次強調LPC沒有編譯過程,預編譯是一種假象,它只是在LPC解析前會做的動作而已
實際語法跟C的編譯器巨集差不多
#define #undefine #include #ifdef #ifndef #if #elseif #else #endif #pragma
提醒: 預編譯指令只有單行,換行記得在結尾加\。跟C一樣
ch5 字串處理
字串是經過driver抽象化處理的簡單資料型態
//一切都是輕薄的假象
strlen(),回傳字串長度
'a' 是字元,"a" 是字串,兩者不同
範圍運算子 (range operator),算是弱化版的python切片
語法範例:str[0..1]
LPC沒有char類別,最基本的類別就是string
sscanf(str, format, arg...) 從 str 中按照 format 解析資料,將結果存到 arg 中
%*s,*代表丟棄運算子,解析但不採用
//題外話:範例中的參數沒有用&,代表這些arg都是採用指標的方式運作的
sscanf() 回傳值代表成功解析的變數數量
其他推薦: explode() implode() replace_string() sprintf()
ch6 再談繼承機制
6.2 複製和繼承
所有遊戲物件都會有一個主本(master copy),一般是拿來複製用,玩家不會跟它發生互動
概念有點像本金利息,主本放著,讓它去生利息(遊戲物件)
慣例是寫區域用繼承,寫怪物用複製。
ob = new("/std/monster");
檢查主本是否存在?
存在,複製它
不存在,建立主本,再複製它
如果是繼承過的怪物,就需要同時檢查"/std/monster"和"/wizards/descartes/my_monster"
作者建議使用標準怪物物件的原因應該是為了節省記憶體
因為系統運作安全的關係,開發者不可能直接修改原始內容,所有的東西都是複製品
只有在大量使用複製的情況下,繼承才會發揮功效。個人經驗:數量超過3就可以使用繼承了
OOP這時候應該還在萌芽階段,而後發展成的class-based和prototype-based兩條路線
class要求所有的物件都要先經過定義;prototype的運作很像目前LPMUD的樣子,建立物件之後再衍生出去
6.3 繼承
C基本物件
A是繼承C的基本活物件
B是繼承C的基本武器
感覺C有很大可能變成「超級物件」,影響系統效能
6.4 標籤
用來修飾函數、變數在繼承時候的可見度
public 公有
private 私有,能夠防止其他物件取用,但防不了call_other()
static 靜態
nomask 不可覆蓋,防止繼承物件重寫原始函式
注意:標籤支援度在某些driver存在差異
靜態標籤只防止外部的外界呼叫. 另外, this_object()->foo() 就算有靜態標籤, 也視為內部呼叫.
一個靜態變數無法經由save_object() 外部函式儲存, 也無法由 restore_object() 還原
//有點難懂,算了…
這部分的內容跟mudlib開發有比較大的關係,撰寫區域通常不用注意這些細節
一個不可遮蓋函式無法經由繼承或投影 (shadow) 僭越之. 投影是一種反向繼承,將在高級 LPC 課本中詳細介紹
//高級 LPC 斷更,殘念
//僭越(override),目前都是翻重寫,年代感很重的文章…
ch7 除錯
常見的錯誤:
不確定目前有沒有改善開發/測試環境?
畢竟LPC上面全部都是動態的東西,遇到錯誤只能從log判斷,但不知道能不能把當時記憶體狀態dump下來?
執行錯誤通常跟NULL指標有關
最後一種錯誤只能從頭下去找,一步一步地追蹤
以現在來說,除錯技術可以把整個程式運行變成逐步執行,也能插入中斷點
也有區分層級的logger把資訊詳細記錄下來,但這些要從driver改善才行
異常處理: