一周就这么过去了
DTP居然没有面试
不知道出了什么问题
毕设也还没开始弄
此刻这个教程估计要真挖出大坑了
我尽量整体介绍完
细节部分如果你想了解
留言或者给我Email
我看到会尽早回复
本节目标
前两节我们已经实现了这个插件的最基本界面
这一节我们开始将插件功能的核心实现——Lua文件的编写
请建立一个QuestRepeat.lua文件,并加入到第一节的Toc文件最后的文件列表中
本节的代码你可以在此获得
[----请猛击我----]
代码讲解
QuestRepeat.lua整体结构
function initialize()
--插件一些变量的初始化
end
function QuestRepeat_OnLoad()
--插件框体加载成功事件的响应处理函数
end
function QuestRepeat_OnEvent()
--插件注册事件的处理函数
end
function Item_Check(name)
--计算角色包里以及货币栏里物品的数量
end
function QuestRepeatAllButton_OnClick()
--全部完成按钮的响应函数
end
function QuestRepeatCompleteButton_OnClick()
--完成一次按钮的响应函数
end
function QuestRepeatDecrement_OnClick()
--文本框数值减少按钮的响应函数
end
function QuestRepeatIncrement_OnClick()
--文本框数值减少按钮的处理函数
end
function QuestRepeatInputBox_OnTextChanged()
--文本框数值越界处理
end
上面我们可以了解到整个Lua的大致结构
各个函数的具体作用注释都说的很清楚
回忆下这个软件的功能
是要可以快速完成重复上缴物品的任务
那Lua文件至少要干三件事情:
1.检测NPC给的任务是不是可重复完成任务
2.如果是重复任务,检查我们的包裹里是否有足够的物品可以重复上缴,假如只能交一次,我们就没使用插件的必要
3.满足重复上缴的条件,我们怎样才能快速完成任务
实际上,即使上述3个事情我们都考虑好了,细节上还是有些麻烦的地方,比如:
4.任务上缴的过程中,如何中断
5.任务最后要完成或者用户选择中断的时候,如何初始化插件,恢复最初的状态
6.要是任务要求的不是我们包裹里的东西,而是货币面板上的虚拟代币我们要如何判断数量呢
在此基础上,考虑与上两节的界面的操作处理:
7.我们如何处理各种按钮响应,文本框的数值超过我们最大能上缴任务的次数时候要如何处理
也就是说,解决了这7个问题,最初级版本的QuestRepeat就完成了。
不过,这是考虑的时候才会分解成这样的几个问题,实际编写的时候,有时候一个代码块就可以一起解决掉几个问题
我们对相关联的问题放一起进行解答
问题1/2/6
function QuestRepeat_OnLoad()
initialize();
this:RegisterEvent("GOSSIP_CLOSED");
this:RegisterEvent("GOSSIP_SHOW");
this:RegisterEvent("QUEST_COMPLETE");
this:RegisterEvent("QUEST_PROGRESS");
--将下面这句注释取消可以测试进入游戏时插件是否加载成功
--DEFAULT_CHAT_FRAME:AddMessage("QuestRepeat Enabled(Debugged by Tyreal)");
end
function QuestRepeat_OnEvent()
--[[..这里省略与问题无关部分,请看源文件..--]]
elseif (event == "QUEST_PROGRESS") then
if (IsQuestCompletable() and iterator == nil) then
n = GetNumQuestItems();
i = 0;
if (n ~= nil) then
if (n ~= 0) then
MAX_VALUE = 999;
while i < n do
i = i + 1;
local name, texture, numItems, quality, isUsable = GetQuestItemInfo("required",i);
value = floor(Item_Check(name)/numItems)
if (value < MAX_VALUE) then MAX_VALUE = value; end end end end this:RegisterEvent("PLAYER_TARGET_CHANGED")--为上缴中断做准备,注册角色目标切换事件,后面解释 --以下代码是问题3的一部分 elseif(iterator) then if (iterator > 0) then
QuestProgressCompleteButton_OnClick();
end
end
--[[...--]]
end
--计算包里物品的数量
function Item_Check(name)
Count = 0;
for bag = 4, 0, -1 do
local size = GetContainerNumSlots(bag);
if (size > 0) then
--遍历包裹的东西
for slot = 1, size do
local texture, itemCount = GetContainerItemInfo(bag, slot);
if (itemCount) then
local itemLink = GetContainerItemLink(bag,slot)
local _, _, itemCode = strfind(itemLink, "(%d+):")
local itemName, _, _, _, _, _ = GetItemInfo(itemCode)
--判断是否包含该物品
if (itemName ~= "" and itemName ~= nil) then
if (itemName == name) then
Count = Count + itemCount;
end
end
end
end
end
end
--TBC之后有些任务上缴需要货币,此处查询角色的货币数量
local scrollFrame = TokenFrameContainer;
local offset = HybridScrollFrame_GetOffset(scrollFrame);
local buttons = scrollFrame.buttons;
local numButtons = #buttons;
local itemName, itemCount;
local index;
for i=1, numButtons do
index = offset+i;
itemName, _, _, _, _, itemCount = GetCurrencyListInfo(index);
if (itemName == name) then
Count = Count + itemCount;
end
end
return Count;
end
- 当我们选择了一个任务,进入任务说明页面,可以看见任务要求物品列表的时候,WoW会抛出一个QUEST_PROGRESS事件
所以我们需要注册这个事件,可以在QuestRepeat_OnLoad()看到
this:RegisterEvent("QUEST_PROGRESS");--注册捕获QUEST_PROGRESS事件
- 然后看QuestRepeat_OnEvent()中间的部分,我们会判断捕获的事件是不是QUEST_PROGRESS,是就进行处理
最开始是判断我们是否有足够的物品,这是靠Item_Check(name)来完成的,后面的一点代码是给后面用
- Item_Check(name)这是自己写的函数
前半部分是检查包裹里物品数量
后半部分是检查货币面板,这是对问题6的解答
最后返回符合查询物品的数量,如果用户含有的数量大于任务要求数量,就可以重复上缴,这样问题2就完成了
- 那问题1呢?其实WoW中的任务,只能交一次的任务,你几乎是不会拥有要求物品的数量达到可以交2次的时候,所以,当我们问题2帮我们算好了数量,知道可以交超过2次以上,我们就知道这是个可重复任务,问题1也就搞定了
实际上WoW也有类似的API实现这样的功能,你可以去搜索WowProgramming和WowWiki,不同的方法,一样的结果
WoW更新版本的时候,有可能会开放新的API或者封掉一些不合理的API,所以写插件的时候请留意自己用的方法会不会太bug,免得到时候要重新修改,虽然有时候循规蹈矩的,WoW大改的时候几乎大家都得改,比如2.0时代。
请学会查看WowProgramming和WowWiki这样的网站上的API说明,返回值,参数列表,作用,这些都是日后的基本功
DEFAULT_CHAT_FRAME:AddMessage(“”)
可以在WoW的聊天窗口输出字符
我们后面会用到这个来输出一些变量的值,用来调试插件
不要怀疑,就是这么原始
若想要高端开发环境,去看In-Game Addon Studio,不过这个跟我此刻讲解的开发流程就是两回事了,你可以了解看看
问题3/5
要快速完成任务
我最早的设想,设定完成的次数,点击一次“全部完成”,插件就不停的自动点击完成
可是这样的设计是违反BLZ的插件开发原则的
因为这样等于是类似外挂自动完成的功能
这么搞是不太安全的
何况BLZ根本就没开放自动选择NPC这个API(我已经翻烂了相关的手册,真没有,有的是BLZ自己官方插件才可以使用的API)
所以这是无法实现的
那退一步,NPC让玩家手工选择
但是任务的各步骤中的点击按钮插件来自动完成
查查API后,发现这是可行的
我也是基于这个思想来完成的
一起来看下代码
function QuestRepeat_OnEvent()
--[[..老规矩,省略问题无关部分..--]]
elseif(iterator) then
if (iterator > 0) then
QuestProgressCompleteButton_OnClick();
end
end
elseif (event == "QUEST_DETAIL") then
if (iterator) then
if (iterator > 0 ) then
QuestDetailAcceptButton_OnClick();
end
end
elseif (event == "QUEST_COMPLETE") then
if (MAX_VALUE > 1) then
QuestFrameCompleteQuestButton:Hide();
QuestRepeat:Show();
if(GetTitleText() == questName) then
if(iterator) then
if(iterator > 0)then
QuestRepeatInputBox:SetNumber(iterator);
QuestRepeatCompleteButton_OnClick();
else
initialize();
end
end
else
QuestRepeatInputBox:SetNumber(1);
questName = GetTitleText();
end
else
initialize()
QuestFrameCompleteQuestButton:Show()
QuestRepeat:Hide()
end
elseif (event == "GOSSIP_SHOW") then
if (iterator) then
if (iterator > 0) then
if GetGossipAvailableQuests() then
i = 1;
for index, value in pairs({GetGossipAvailableQuests()}) do
--测试输出数据
--[[
DEFAULT_CHAT_FRAME:AddMessage("i="..i);
DEFAULT_CHAT_FRAME:AddMessage("index="..index);
DEFAULT_CHAT_FRAME:AddMessage("value="..value);
DEFAULT_CHAT_FRAME:AddMessage("questName="..questName);
DEFAULT_CHAT_FRAME:AddMessage("-------------------");--]]
if( mod(index-1, 3) == 0 ) then
if (value == questName) then
SelectGossipAvailableQuest(i);
break;
end
i = i + 1;
end
end
end
end
end
end
end
这段代码中,有个很重要的变量iterator,它控制着是否进行不间断的任务自动上缴,大于1时重复上缴,小于1停止
- 刚最开始跟NPC对话的时候,会有个GOSSIP_SHOW的事件,我们判断是否进行QuestRepeatCompleteButton_OnClick()重复点击按钮进入下一步,如果是的话我们在这事件之后获取NPC存在多少个任务GetGossipAvailableQuests()并根据以前存储的questName自动选择对应的任务,不是就什么都不干,玩家手工选择任务
- 接着QUEST_DETAIL事件,依旧判断iterator的值来进行自动按按钮
- 然后是QUEST_PROGRESS事件,这主要是问题1/2/6的部分,但是最后面一直延续对iterator的判断
- 最后是QUEST_COMPLETE事件,判断可完成的最大次数是否为1,为1就回复WoW默认完成界面并initialize()初始化插件,这里就要解决问题5;如果不是就判断是不是还未启动自动上缴过程,还未启动就记录任务名称,已经启动就继续显示本插件,并自动点击按钮。至此,问题3也解决了。
initialize()的代码很简单,就初始化3个变量
function initialize()
MAX_VALUE = 1;--玩家可以完成任务的最高次数
iterator = nil;--是否自动上缴任务控制变量
questName = "";--自动进行的任务的名称
end
问题4
如果你认真研究完以上代码,差不多对整个流程有个印象了
接下来要解决的就是中断自动完成的问题
因为有时候我们交到一半就不想交了
有个中断的方法是必要
我后面的QuestRepeat有新的面板可以方便控制任务中断过程
关掉那个面板就等于退出本插件
但是现在这个最初级的版本没有那个面板
所以设置玩家目标改变的时候就取消自动上缴过程
function QuestRepeat_OnEvent()
--[[...--]]
elseif (event == "PLAYER_TARGET_CHANGED") then
initialize();
this:UnregisterEvent("PLAYER_TARGET_CHANGED")
--[[...--]]
QuestRepeat_OnEvent()里面完成了很多事情,中断处理也是在这里完成的
由于我们一开始有注册了PLAYER_TARGET_CHANGED事件
所以这里响应之后我们可以调用initialize()进行插件状态初始化
然后注销事件PLAYER_TARGET_CHANGED,因为此时我们已经不需要它了
当然留着也不是不可以
只是这样游戏过程中你每切换一次目标,这里都会来初始化一次插件
没什么影响
丑陋代码,不严谨就是了
最后的问题7,相信你忍着看到这里,也可以自己看源代码解决了
最后唠叨
读完这三节,基本上你就可以使用Blizzard给的官方API加上Windows自带的记事本来编写插件了,至于有些插件用ACE之类的那些库编写的,都是在此基础上的衍生,具体资料可以自己查找
最近写这个教程的时候,终于知道什么叫心有余而力不足,事说多不多,但是时间总是感觉流失得太快,没有太多的耐心把这最后一节好好写仔细,只是草草做个了概括,而且这份代码是我最早写插件的时候从原作上修改的,并不是太规范,所以本教程如果说得不好的地方还请见谅。
有问题请留言,日后若有空,再重新翻新续写也不一定。