自動採集插件的功能就是自動採集身邊的礦和草藥,當人物停下來的時候,如果身邊有可以採集到的礦和草,插件就是自動開始採集。
為了實現自動採集,我們的插件需要做這些工作:
1獲得身邊(視野內)的採集物信息。
2判斷這些採集物是否可以採集(是否是礦/草?距離是否夠近?)。
3如果滿足條件,則開始採集。
由於要實現自動,所以以上步驟必須不斷的進行。
在開始之前,我們先來建立插件的文件結構。先在插件目錄Interface下面建立自動採集插件的目錄AutoGather,然後在AutoGather下面建立3個文本文件:info.ini AutoGather.lua AutoGather.ini 。
看到AutoGather.ini了吧,這就是劍3的窗體文件了,我們這次就要用到它啦。別被窗體這兩個字嚇到了,在本例中,窗體其實是來打醬油的,你基本可以無視它。
Info.ini文件的結構我在上一篇教程中已經介紹過了,這裡就不再贅述了,直接放上該文件內容:
代碼:
[AutoGather]
name=自動採集
desc=自動採集 by myself
default=1
version=0.5
lua_0=Interface\AutoGather\AutoGather.lua
在文章的開頭我說過,要實現自動,就要不斷的循環一些動作。學過編程的同學應該知道如何實現不斷的重複某些動作的方法吧?對了,就是死循環。在嵌入式編程中,一般都會看到主函數的最後有一個while(1){},各種需要重複執行的代碼就放在裡面,Windows編程的消息循環應該也是死循環吧(我沒學過win編程,不太懂)。
但是,如果你在劍三中執行一個死循環會發上什麼事情呢?你可以自己試試看,在cube中執行一個死循環 while true do end,點執行之後看到效果了沒?是的,遊戲死掉了。
為何會死掉呢?我介紹一下劍3的Lua執行機制你就能明白了。劍3中Lua腳本的執行並不是並行的,也就是說,腳本的執行並不是多任務的。劍3的Lua引擎是基於「幀」的方式執行腳本的,簡單的說,要執行的代碼是放在「幀」裡面的,這一幀的代碼執行完畢後才會執行下一幀,劍三的客戶端一般每秒鐘會執行10幀左右(這取決與你的插件數量和CPU速度)。
這回明白了吧?你如果把死循環代碼放到一個幀裡執行,那麼這一幀就永遠不會執行完畢,所以遊戲就卡死在這一幀了。所以,大家就要注意了,劍三的Lua腳本編寫有一個原則:代碼的執行流程必須是有限的並且是可以預測的,而且流程要盡量的少。這樣,你的插件才不會拖慢遊戲的速度。
插件裝多了遊戲會變慢也是這個原因。這裡我吐槽一下金山的服務器,實際上,劍三的服務端程序也是基於這種幀的執行機制的(其實從劍1開始就是這樣),但是服務端和客戶端不一樣,它的幀速是嚴格的16幀/秒,客戶端有個函數能讀取到服務端的邏輯幀( GetLogicFrameCount() )。正常情況下,服務器的腳本執行是沒有問題的,但是到了陣營攻防的時候……尤其是雙方幾百人打到一起的時候,服務器就需要運算大量的數據,這時候,1/16秒執行完一幀就有點費勁了……為了保持16幀/秒,這時候服務器就會開始丟東西,於是我們就會發現[郭煒煒]釋放了技能[郭煒煒之怒],我們被全地圖鎖足……以上只是我猜的,猜錯了也別噴我哦。
明白了這個幀的機制,我想很多同學就懂了:只要使我們的腳本在每一幀都執行一遍,不就可以實現無限循環了嗎。於是,下面我就開始介紹它的實現方法啦。
先介紹一下OnFrameBreathe(),其實是一個窗體事件函數,如果你在一個腳本中打開了一個窗體,並且這個窗體是可呼吸的,那麼劍3的引擎每一幀都會調用這個腳本的OnFrameBreathe()函數。簡單點說,如果在AutoGather的腳本中定義了AutoGather.OnFrameBreathe()函數,並且用Wnd.OpenWindow打開了AutoGather窗體,那麼每一幀AutoGather.OnFrameBreathe()函數都會執行一次。請注意:OnFrameBreathe()的調用頻率並不是固定的,它取決於你的cpu速度以及其他因素,一般來說是每秒10次左右,但絕不是想當然的16次/秒。
為了使用OnFrameBreathe(),我們必須構建一個窗體。別被窗體這個詞嚇到,其實你不需要有任何劍3的窗體控制的知識。只需要編輯AutoGather.ini加入如下內容就可以了:
代碼:
[AutoGather]
._WndType=WndFrame
._Parent=Lowest
Left=0
Top=0
Width=0
Height=0
DragAreaLeft=0
DragAreaTop=0
DragAreaRight=0
DragAreaBottom=0
AnimateStartPosX=0
AnimateStartPosY=0
AnimateEndPosX=0
AnimateEndPosY=0
AnimateTimeSpace=0
AnimateMoveSpeed=0
ScriptFile=Interface\AutoGather\AutoGather.lua
IsCustomDragable=0
DragAreaWidth=0
DragAreaHeight=0
DummyWnd=1
DisableBringToTop=1
DisableBreath=0
BreatheWhenHide=1
下面的._WndType=WndFrame表示這是一個窗體
._Parent=Lowest這一行表明這個窗體是在最底層的
下面那一堆xxx=0表示窗口大小為0。也就是說,這是一個在最底層的、不可見的隱形窗口(因為我們只需要用它呼吸不需要讓他露臉)。
ScriptFile=Interface\AutoGather\AutoGather.lua這個要指向插件lua腳本的路徑(實際上這行不寫也沒事)
IsCustomDragable=0 ;禁止自定義界面拖動(shift+u那個)
DragAreaWidth=0 ;可拖動寬度範圍0
DragAreaHeight=0 ;可拖動高度範圍0
DummyWnd=1
DisableBringToTop=1 ;禁止移動到上層
DisableBreath=0 ;允許呼吸
BreatheWhenHide=1 ;在窗體隱藏後繼續呼吸
把那堆東西寫進AutoGather.ini保存之後,這個窗體就創建好了,之後我們要在AutoGather.lua裡面打開它。
打開AutoGather.lua寫入這一行(注意:這一條語句最好放在lua文件的末尾,也就是你定義的函數的後面)
複製內容到剪貼板
Wnd.OpenWindow裡面的兩個參數應該一看就明白了吧,第一個參數是窗體文件路徑,第二個參數是窗體名,也就是AutoGather.ini的第一行那個名字。代碼:
Wnd.OpenWindow ("Interface/AutoGather/AutoGather.ini","AutoGather")
之後就可以定義OnFrameBreathe函數,我們在Wnd.OpenWindow的前面定義OnFrameBreathe函數:
複製內容到剪貼板
為了測試是否能正常呼吸,我們在這個函數里加入測試語句:
代碼:
function AutoGather.OnFrameBreathe()
end
複製內容到剪貼板
於是現在AutoGather.lua的內容是這樣的:
代碼:
if GetLogicFrameCount()%16==0 then
OutputMessage("MSG_SYS","我在呼吸哦\n")
end
代碼:
AutoGather={}
function AutoGather.OnFrameBreathe()
if GetLogicFrameCount()%16==0 then
OutputMessage("MSG_SYS","我在呼吸哦\n")
end
end
Wnd.OpenWindow ("Interface/AutoGather/AutoGather.ini","AutoGather")
之後就刪掉那3行測試語句,然後繼續下一步吧。
小知識:我們在遊戲中看到的東西,除了固定的地圖之外,只有三類,分別是Player、Npc、Doodad。顧名思義,Player是玩家,Npc是Npc。但是Doodad呢?doodad這個詞的字面意思是小擺設。基本上遊戲中那些不會動的小物件都是doodad,包括了各種採集物,某些桌椅板凳,甚至主城裡房子上那塊牌子。我們今天要採集的草和礦,就都是doodad
為了能採集草/礦也就是doodad,我們首先需要有一個視野內doodad的列表,但是很不幸,早期的劍三並沒有提供一個能直接獲得doodad列表的函數,所以那個時候收集doodad列表就要用到事件。當然,現在我們有了更便捷的方法,但是為了介紹事件,我先來講解一下這個以前的笨方法。
事件這個概念肯定大家都懂,劍三在發生某些事的時候會產生一個事件,如果RegisterEvent註冊過這個事件,程序就會去調用你定義過的事件處理函數,如果這個事件帶有參數的話,遊戲會用arg0~arg9這幾個全局變量傳遞參數。
這裡我們要用到2個事件:DOODAD_ENTER_SCENE和DOODAD_LEAVE_SCENE,顧名思義,他們分別是doodad進入視野和doodad離開視野,他們使用arg0傳遞doodad的ID。利用這2個事件,我們定義一個列表,在doodad進入視野以後把它加進去,在doodad離開視野以後再刪掉,就能取得視野內的doodad列表了。
為了實現它,先定義一個表來存放doodad列表,我們在AutoGather.lua開頭加入:
複製內容到剪貼板
之後用RegisterEvent註冊兩個事件:
代碼:
AutoGather.DooList={}
代碼:
RegisterEvent("DOODAD_ENTER_SCENE",function() table.insert(AutoGather.DooList,arg0) end)
RegisterEvent("DOODAD_LEAVE_SCENE",function()table.remove(AutoGather.DooList,arg0) end)
於是現在AutoGather.lua的內容是這樣的:
代碼:
AutoGather={}
AutoGather.DooList={}
function AutoGather.OnFrameBreathe()
if GetLogicFrameCount()%32==0 then
OutputMessage("MSG_SYS","我在呼吸哦\n")
end
end
RegisterEvent("DOODAD_ENTER_SCENE",function() table.insert(AutoGather.DooList,arg0) end)
RegisterEvent("DOODAD_LEAVE_SCENE",function()table.remove(AutoGather.DooList,arg0) end)
Wnd.OpenWindow ("Interface/AutoGather/AutoGather.ini","AutoGather")
列表有了,就可以開始往OnFrameBreathe()裡面寫採集代碼啦,不過在這之前,先先給它加個開關:
代碼:
AutoGather.bOn = false
function AutoGather.OnFrameBreathe()
if not AutoGather.bOn then
return
end
end
Hotkey.AddBinding("AutoGather", "切換開啟狀態", "自動採集",
function()
if AutoGather.bOn then
AutoGather.bOn = fales
OutputMessage("MSG_SYS","自動採集關閉\n")
else
AutoGather.bOn = true
OutputMessage("MSG_SYS","自動採集開啟\n")
end
end,
nil)
之後我們繼續在OnFrameBreathe裡面加料,在採集之前,顯然要保證人物在站立狀態並且不在讀條,所以我們要加入判斷,如果人物不是站立狀態或者人物在讀條就返回:
代碼:
local player = GetClientPlayer()
if not player then
return
end
if player.nMoveState ~= MOVE_STATE.ON_STAND or player.GetOTActionState() ~= 0 then
return
end
代碼:
for _,dwID in pairs(AutoGather.DooList) do
local doodad = GetDoodad(dwID)
if doodad and doodad.CanDialog(player) then
if doodad and doodad.nKind == DOODAD_KIND.CRAFT_TARGET then
InteractDoodad(dwID)
end
end
end
複製內容到剪貼板
代碼:
AutoGather={}
AutoGather.bOn = false
AutoGather.DooList={}
function AutoGather.OnFrameBreathe()
if not AutoGather.bOn then
return
end
local player = GetClientPlayer()
if not player then
return
end
if player.nMoveState ~= MOVE_STATE.ON_STAND or player.GetOTActionState() ~= 0 then
return
end
for _,dwID in pairs(AutoGather.DooList) do
local doodad = GetDoodad(dwID)
if doodad and doodad.CanDialog(player) then
if doodad and doodad.nKind == DOODAD_KIND.CRAFT_TARGET then
InteractDoodad(dwID)
end
end
end
end
有料
淚奔
無聊
XD
掀桌
KUSO