美文网首页艾泽拉斯日常
wow插件补充说明篇

wow插件补充说明篇

作者: 听风轻咛 | 来源:发表于2019-10-24 18:18 被阅读0次

关于TOC文件

  • Interface - 标记该插件用于wow的哪个游戏版本,如果标记值低于当前游戏版本将不能被加载,除非用户选择加载过期插件,所以,大版本更新时,最好修改这个标记值来符合新的游戏版本,可以在进入游戏后使用宏来获得当前游戏的版本号: /run print((select(4, GetBuildInfo())))
  • Title - 插件的名称,不需要和插件名一致,会被显示在选择人物界面的插件列表中。如果需要实现不同语种的客户端显示不同的文字的话(本地化机能),可以修改标签名,在标签名后加上一个"-",再跟上客户端的语种名就可以了,比如国服的实际标签名: Title-zhCN。如果客户端的语种对应的标签不存在,那么就使用默认的Title标签的值。
  • Notes - 在插件列表中,鼠标移到插件名称上时显示的提示信息。同样,可以用类似-zhCN这样的后缀来做本地化申明。
  • RequiredDeps, Dependencies, 或者任意以 "Dep" 开始的字符串 - 一些以逗号分割的其他插件的名字,表示本插件必须在这些插件都存在,且都已经被加载的情况下,才能被加载,也就是插件依赖。
  • OptionalDeps - 一些以逗号分割的其他插件的名字,表示如果这些插件存在,那么这些插件需要在本插件加载前,被加载,可选依赖。
  • LoadOnDemand - 如果这个值被设置为1,那么这个插件不会在游戏开始时就加载,而是由其他插件在需要时,另行加载,wow自带的一些插件都是如此设置的。
  • LoadWith - 一些以逗号分割的其他插件的名字, 如果本插件的LoadOnDemand为1,那么,当这些插件在游戏开始被加载时,也加载本插件。
  • LoadManagers - 一些以逗号分割的其他插件的名字, 如果这些插件都不存在,那么客户端会自动加载本插件,如果这些插件中的一个存在,那么,本插件将按照LoadOnDemand设置为1的情况处理。
  • SavedVariables - 一些以逗号分割的变量名称,这些变量一般是保存在游戏中的Lua全局环境中的表,这些变量的值在退出游戏时将被保存到硬盘上,在下一次游戏开始再被加载进入游戏,可以用来保存用户的配置信息等。这些变量的值对同一账户在同一服务器的所有角色都是通用的。
  • SavedVariablesPerCharacter - 类似SavedVariables,不过,每个角色都有各自不同的变量值,也就是说,SavedVariables适合保存通用配置,SavedVariablesPerCharacter适合保存个人配置。
  • DefaultState - 本插件安装后默认是否开启,如果设置成 "disabled",那么只有在插件列表中勾选本插件后,才会被加载。默认值 "enabled"
  • Author - 作者名字
  • Version - 插件自身的版本号

关于lua文件加载

假设插件名为TestAddon,有两个Lua文件test.lua,another.lua`。

下面是加载插件的代码:

-- Load TestAddon
local TestAddon = {}

f = loadfile("test.lua")
f("TestAddon", TestAddon)

f = loadfile("another.lua")
f("TestAddon", TestAddon)
-- 上面两个参数,第一个表示插件名,第二个是一个table(这个table可以作为各个文件间交换数据的存储位置)

test.lua:

local addonName, addon = ...

print(addonName .. " is loaded.")

addon.TestAddon = 123

another.lua:

local addonName, addon = ...
print("TestAddon is " .. addon.TestAddon)

每次书写全局变量都需要addon.,开头也是很麻烦的事情,下面推荐一种普通插件的写法:

test.lua中:

-- 插件第一个lua文件中,以下一行代码确保代码以addon为环境,而不是_G
-- 在addon环境中可以访问_G的任意思变量;
-- 所定义的全部全局变量会保存在addon表中,而不是_G;
setfenv(1, setmetatable(select(2, ...), {
    __index = function(self, key) 
        local v = _G[key];
        rawset(self, key, v);
        return v
    end
}))

function testA()
    print("test case A")
end

another.lua中:

setfenv(1, select(2, ...))

-- 在test.lua中定义的函数可以直接调用
testA()

插件的系统事件

local frame = CreateFrame("Frame")

frame:RegsterEvent("ADDON_LOADED")

frame:SetScript("OnEvent", function(self, event, ...)
    if event == "ADDON_LOADED" then
        local addonName = ...
        print(addonName .. " is loaded.")
    end
end)

解释下上面的代码:

frame = CreateFrame(type[, name[, parent[, inheritFrom]])
  • type - 控件类型,本贴中,只需要用到最基本的Frame控件类型。
  • name - 控件名称,如果设定,生成的控件将以这个名字被保存到_G中,多数情况下可以省略,确保和其他插件制作的控件不会产生冲突。
  • parent - 每个界面控件都会附着到一个父界面控件,当父界面控件隐藏时,子界面元素也相应的会被隐藏,一般情况下,WOW提供了UIParent 和 WorldFrame 作为其他界面控件的父界面控件。
  • inheritFrom - 模版名称,模版具体介绍放在其他贴中。

因为我们创建的Frame控件仅用来处理系统事件,所以并不在意它如何被显示(实际上因为没有任何的显示设置,它不会被描绘,也无法被用户实际看到),我们可以省略所有参数来创建一个匿名的Frame界面控件。

frame:RegisterEvent("ADDON_LOADED")

RegisterEvent方法被用来将frame注册到监听 ADDON_LOADED 系统事件的列表中,当 ADDON_LOADED 系统事件发生时,frame会被通知,并进行处理。这个方法只有一个参数,就是需要注册的系统事件名称。

另外,如果需要取消系统事件监听的话,可以使用 UnregisterEvent 方法来取消一个系统事件的监听:

frame:UnregisterEvent("ADDON_LOADED")

wow中,使用CreateFrame创建的Frame对象本身含有一组预定义的方法,这些方法可以用来设置/获取Frame如何被显示,大小,位置等等。如果感兴趣的,可以利用Cube之类的代码编辑器运行下面的代码看看,Frame控件拥有的方法:
for k in pairs(getmetatable(UIParent).__index) do print(k) end

frame:SetScript("OnEvent", function(self, event, ...)
    if event == "ADDON_LOADED" then
        local addonName = ...
        print( addonName .. " is loaded.")
    end
end)
-- https://bbs.nga.cn/read.php?&tid=6622128&pid=133288279&to=1

当系统事件发生时,系统会根据监听列表调用各个监听对象去处理系统事件,这里涉及到监听对象如何预先设置处理函数的问题。这点是通过注册控件事件来完成。

RegisterEvent 方法注册系统事件是将 frame 控件对象自己注册到系统事件的监听列表。

  • self - 控件对象本身
  • event - 系统事件名称,对于上面的例子,event只可能时 ADDON_LOADED
  • ... - 系统事件参数,对于ADDON_LOADED事件来说,即被加载的插件名称

重要

使用 if 根据 event 判定的处理方式当我们需要处理大量系统事件时,会比较麻烦,所以,推荐的OnEvent处理代码如下:

-- 当某个系统事件发生时,查找控件对象本身,是否存在和系统事件同名的函数,如果有,就认为是系统事件处理函数,并调用;
-- 注意调用该函数时,控件对象作为第一个参数被传入,这种做法比较适合面向对象的处理思路

frame:SetScript("OnEvent", function(self, event, ...)
    if type(self[event]) == "function" then
        return self[event](self, ...)
    end
end)

function frame:ADDON_LOADED(name)
    print(name .. " is loaded. ")
end

自定义宏命令(代码文件执行阶段)

以下是一个简单的宏命令:

_G.SLASH_TestCmd1 = "/test"
_G.SLASH_TestCmd2 = "/tt"

_G.SlashCmdList.TestCmd = function(msg, input)
    print(msg)
end

_G中创建形如SLASH_ + 名称 + 数字来设置命令,在_G.SlashCmdList为 名称 设置处理函数,如下,上述命令调用;

/tt 这是一个测试

则会打印以下内容,“这是一个测试”;

插件初始化和SavedVariables(游戏进行间数据保存)

TestAddon.toc

## Interface: 50400
## Title: TestAddon
## Notes: A test addon
## DefaultState: Enabled
## LoadOnDemand: 0
## SavedVariables: TestAddonSave

TestAddon.lua

TestAddon.lua

local addonName, addon = ...

-- 构建系统事件监听器
local frm = CreateFrame("Frame")
frame:SetScript("OnEvent", function (self, event, ...)   if type( self[event] ) == "function" then return self[ event ] ( self, ... ) end  end)

-- 注册和处理系统事件
frm:RegisterEvent("ADDON_LOADED")

function frm: ADDON_LOADED(name)
    -- 仅处理自身加载的情况
    if name == addonName then
        -- 获取或者创建SavedVariables
        TestAddonSave = TestAddonSave or {}
    end
end

当退出游戏后,你可以在 WTF\Account[账户名]\SavedVariables\ 下面找到TestAddon.lua,它里面的内容是形如:

TestAddonSave = {

}

PLAYER_LOGIN 进入游戏世界

AutoSell.lua:

local addonName, addon = ...

-- 构建系统事件监听器
local frm = CreateFrame("Frame")
frame:SetScript("OnEvent", function (self, event, ...)   if type( self[event] ) == "function" then return self[ event ] ( self, ... ) end  end)

-- 注册和处理系统事件
frm:RegisterEvent("PLAYER_LOGIN")

-- 进入游戏后,监听商店界面打开事件
function frm:PLAYER_LOGIN( )
    self:RegisterEvent("MERCHANT_SHOW")
end

function frm:MERCHANT_SHOW()
    -- 当商店界面打开时,遍历包裹,售出所有品质为0(灰色)的物品
    for bag = 0, NUM_BAG_FRAMES do
    for slot = 1, GetContainerNumSlots(bag) do
      local itemId = GetContainerItemID(bag,slot)
      if itemId then
        local _, _, itemRarity = GetItemInfo(itemId)

        if itemRarity == 0 then
          UseContainerItem(bag,slot)
        end
      end
    end
  end
end

延时处理和定时器

/in 延时命令

/in 40 /raid 嗜血结束,该自定义命令实现了一个这样的功能,施放嗜血40秒后,通知团队时间结束;

-- 常用函数最好局部变量化,可以加快访问速度
local GetTime = GetTime
local tremove = tremove
local tinsert = tinsert
local floor = floor
local tonumber = tonumber
local strtrim = strtrim
local GetUnitName = GetUnitName

local TARGET_TOKEN_NOT_FOUND = TARGET_TOKEN_NOT_FOUND

-- 事件监听用对象
local frm = CreateFrame("Frame")

-- 隐藏frm后,OnUpdate 事件不会被触发,避免额外的CPU消耗
frm:Hide()

-- 任务列表
local taskList = {}

-- 任务有无标识
local hasTask = false

-- 可重复使用的任务缓存表
-- 实际上同时运行的/in命令很少,每次创建一个新的table会造成额外的浪费
-- 所以复用每个table可以提升性能
local cache = {}

-- 新建任务
local function newTask(delay, command)
  -- 优先使用缓存表,没有备用再创建新表
  local task = tremove(cache) or {}

  -- 替换 %t -> 目标名字
  if command:find("%%t") then
    command = command:gsub("%%t", GetUnitName("target") or TARGET_TOKEN_NOT_FOUND)
  end

  -- 替换 %f -> 焦点名字
  if command:find("%%f") then
    command = command:gsub("%%f", GetUnitName("focus") or TARGET_TOKEN_NOT_FOUND)
  end

    -- 补充 /
  if command:sub(1, 1) ~= "/" then command = "/" .. command end

  -- 因为以0.1秒为最小单位,所以乘10倍整数化利于后续处理
  task.Time = floor( ( GetTime() + delay ) * 10 )
  task.Command = command

  -- 添加任务进入任务列表
  taskList[task] = true

  if not hasTask then
    -- 调整标记,并显示frm,开始监听OnUpdate事件
    hasTask = true
    frm:Show()
  end
end

-- 销毁任务
local function disposeTask(task)
  -- 移除任务
  taskList[task] = nil

  -- 放入缓存表备用
  tinsert(cache, task)

  -- 如果没有额外任务,调整标记,并隐藏frm,停止监听OnUpdate事件
  if not next(taskList) then
    hasTask = false
    frm:Hide()
  end
end

-- 运行后续命令,调用了WOW自带插件的函数,现在不用太在意如何实现
local function runTask(task)
  local command = task.Command

  -- 销毁任务
  disposeTask(task)

  -- 运行命令
  if not command then return end

  ChatFrame10.editBox:SetText(command)
  ChatEdit_SendText(ChatFrame10.editBox)
  ChatFrame10.editBox:SetText("")
end

-- 每0.1秒扫描任务表,所以用lastScan记录上次扫描时间*10 的整数值
local lastScan = 0

frm:SetScript("OnUpdate", function(self, elapsed)
  local nowScan = floor( GetTime() * 10 )

  -- 因为一般情况下,两次OnUpdate间隔仅0.01-0.06左右,不满0.1秒
  -- 减少扫描次数来提高性能
  if lastScan == nowScan then return end

  -- 记录新时间
  lastScan = nowScan

  -- 扫描任务列表并执行
  for task in pairs(taskList) do
    if nowScan >= task.Time then
      -- 使用pcall确保不会因为错误中断扫描
      local ok, ret = pcall(runTask, task)

      -- 如果有错,交给其他错误程序处理
      if not ok then geterrorhandler()(ret) end
    end
  end
end)

-- 注册命令行
SLASH_CMDIN1 = "/in"

-- 处理命令
SlashCmdList.CMDIN = function(msg, input)
    if type(msg) == "string" then
      -- 分拆形如 '40.0 /raid 嗜血结束' -> '40.0', '/raid 嗜血结束'
      local delay, command = msg:match("([%d.]+)%s*(.+)")

      delay = tonumber(delay)
      command = strtrim(command or "")

      -- 检查输入,生成新的任务
      if delay and delay >= 0.1 and command ~= "" then
        return newTask(delay, command)
      end
    end
end

定时器

写一个自动循环叫卖的小插件,命令名字是 /sell,/sell off 用来停止叫卖,/sell 30 /s 无脑循环叫卖 表示每 30 秒叫卖一次,那么,可以修改上面的代码:

-- 常用函数最好局部变量化,可以加快访问速度
local GetTime = GetTime
local tremove = tremove
local tinsert = tinsert
local floor = floor
local tonumber = tonumber
local strtrim = strtrim

-- 事件监听用对象
local frm = CreateFrame("Frame")

-- 隐藏frm后,OnUpdate 事件不会被触发,避免额外的CPU消耗
frm:Hide()

-- 任务列表
local taskList = {}

-- 任务有无标识
local hasTask = false

-- 可重复使用的任务缓存表
-- 实际上同时运行的/in命令很少,每次创建一个新的table会造成额外的浪费
-- 所以复用每个table可以提升性能
local cache = {}

-- 新建任务
local function newTask(delay, command)
  -- 优先使用缓存表,没有备用再创建新表
  local task = tremove(cache) or {}

    -- 补充 /
  if command:sub(1, 1) ~= "/" then command = "/" .. command end

  -- 因为以0.1秒为最小单位,所以乘10倍整数化利于后续处理
  task.Delay = delay
  task.Time = floor( ( GetTime() + delay ) * 10 )
  task.Command = command

  -- 添加任务进入任务列表
  taskList[task] = true

  if not hasTask then
    -- 调整标记,并显示frm,开始监听OnUpdate事件
    hasTask = true
    frm:Show()
  end
end

-- 销毁任务
local function disposeTask()
  if hasTask then
    -- 放入缓存表备用
    for task in pairs(taskList) do
      tinsert(cache, task)
    end

    -- 清空任务列表
    wipe(taskList)

    -- 调整标记,并隐藏frm,停止监听OnUpdate事件
    hasTask = false
    frm:Hide()
  end
end

-- 运行后续命令,调用了WOW自带插件的函数,现在不用太在意如何实现
local function runTask(task)
  local command = task.Command

  -- 重新计算任务下次时间
  task.Time = floor( ( GetTime() + task.Delay ) * 10 )

  -- 运行命令
  if not command then return end

  ChatFrame10.editBox:SetText(command)
  ChatEdit_SendText(ChatFrame10.editBox)
  ChatFrame10.editBox:SetText("")
end

-- 每0.1秒扫描任务表,所以用lastScan记录上次扫描时间*10 的整数值
local lastScan = 0

frm:SetScript("OnUpdate", function(self, elapsed)
  local nowScan = floor( GetTime() * 10 )

  -- 因为一般情况下,两次OnUpdate间隔仅0.01-0.06左右,不满0.1秒
  -- 减少扫描次数来提高性能
  if lastScan == nowScan then return end

  -- 记录新时间
  lastScan = nowScan

  -- 扫描任务列表并执行
  for task in pairs(taskList) do
    if nowScan >= task.Time then
      -- 使用pcall确保不会因为错误中断扫描
      local ok, ret = pcall(runTask, task)

      -- 如果有错,交给其他错误程序处理
      if not ok then geterrorhandler()(ret) end
    end
  end
end)

-- 注册命令行
SLASH_CMDSELL1 = "/sell"

-- 处理命令
SlashCmdList.CMDSELL = function(msg, input)
  if msg == "off" then
    return disposeTask()
    elseif type(msg) == "string" then
      -- 分拆形如 '40.0 /say come on' -> '40.0', '/say come on'
      local delay, command = msg:match("([%d.]+)%s*(.+)")

      delay = tonumber(delay)
      command = strtrim(command or "")

      -- 检查输入,生成新的任务
      if delay and delay >= 0.1 and command ~= "" then
        return newTask(delay, command)
      end
    end
end

相关文章

  • wow插件补充说明篇

    关于TOC文件 Interface - 标记该插件用于wow的哪个游戏版本,如果标记值低于当前游戏版本将不能被加载...

  • Wow.js滚动动画

    动画,各种动画。 Vue使用Wow插件步骤 安装wow插件 wow包内有animate的css文件,无需再引入an...

  • 插件的运用

    常用的插件:wow、Swiper、lightbox、fullpage wow使用步骤:第一步拷贝 wow.js a...

  • 前端自动化构建工具gulp

    优秀资源1:《gulp入门介绍》(重点推荐)补充资源1:《gulp插件详细说明》补充资源2:sourcemap的使...

  • 食物篇:补充说明

    1.养成饮食清淡的习惯,戒油炸,戒辣,戒糖。 心情不好,昨天暴饮暴食了一天。又是炸鸡又是蛋糕。体重多少会有浮动。但...

  • 动画插件wow.js的使用

    wow.js使用步骤: 1.wow.js插件可以使页面形成各种各样的动画效果。 2.使用步骤: wow.js依赖于...

  • wow插件入门

    入门级测试 入门级资源 插件的放置位置在/Interface/Addons目录下; WowLua DevTools...

  • wow插件的构成

    今天来说一说wow插件是个什么鬼? 插件是魔兽世界开放的第三个接口,有朋友会问,什么是第三方?这个是软件业的一个术...

  • 补充说明

    首先得声明,我不是真如老师的粉丝。真如老师创作了不少赞颂,来赞颂师长,三宝。可以看出,她心中是常常感恩师长,忆念三...

  • 补充说明

    1.Ω物质寄生分为两种:潜伏状态与发病状态。理论上Ω物质没有潜伏期,只有“感染者”才会出现抑制住“他”的情况。(不...

网友评论

    本文标题:wow插件补充说明篇

    本文链接:https://www.haomeiwen.com/subject/qfvdvctx.html