魔兽世界插件开发入门教程——3

一周就这么过去了
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之类的那些库编写的,都是在此基础上的衍生,具体资料可以自己查找

最近写这个教程的时候,终于知道什么叫心有余而力不足,事说多不多,但是时间总是感觉流失得太快,没有太多的耐心把这最后一节好好写仔细,只是草草做个了概括,而且这份代码是我最早写插件的时候从原作上修改的,并不是太规范,所以本教程如果说得不好的地方还请见谅。

有问题请留言,日后若有空,再重新翻新续写也不一定。

Tagged , , ,

3 thoughts on “魔兽世界插件开发入门教程——3

  1. biaozy说道:

    收藏了,不错的文章。

  2. Y3说道:

    你的代码不能下载啊. 才128字节

  3. Tyreal说道:

    不好意思,不知道为什么服务器上的那个文件就损坏了。我重新上传了一份,再试看看

发表评论

电子邮件地址不会被公开。