Anonymous 6 months ago
commit b92831e815
  1. 44
      README.md
  2. 82
      dist/_index.lua
  3. 11
      dist/actions/mage/arcane.lua
  4. 11
      dist/actions/mage/fire.lua
  5. 11
      dist/actions/mage/frost.lua
  6. 191
      dist/managers/action-manager.lua
  7. 84
      dist/managers/aura-manager.lua
  8. 325
      dist/managers/bindings-manager.lua
  9. 34
      dist/managers/command-manager.lua
  10. 59
      dist/managers/event-manager.lua
  11. 233
      dist/managers/mob-manager.lua
  12. 116
      dist/managers/player-state-manager.lua
  13. 32
      dist/utils/aura-utils.lua
  14. 57
      dist/utils/cast-util.lua
  15. 517
      dist/utils/mob-utils.lua
  16. 152
      dist/utils/spell-utils.lua
  17. 163
      dist/utils/vector-utils.lua
  18. 1
      lib/BlizzardInterfaceCode
  19. 14
      lib/dkjson/CMakeLists.txt
  20. 118
      lib/dkjson/cmake/FindLua.cmake
  21. 321
      lib/dkjson/cmake/dist.cmake
  22. 390
      lib/dkjson/cmake/lua.cmake
  23. 14
      lib/dkjson/dist.info
  24. 30
      lib/dkjson/dkjson-2.5-2.rockspec
  25. 714
      lib/dkjson/dkjson.lua
  26. 653
      lib/dkjson/jsontest.lua
  27. 211
      lib/dkjson/readme.txt
  28. 131
      lib/dkjson/speedtest.lua
  29. 107
      lib/dkjson/versions.txt
  30. 34
      package.json
  31. 1794
      pnpm-lock.yaml
  32. 3
      sample.env
  33. 17
      scripts/clear-dir/index.ts
  34. 3
      scripts/copy-dist/index.ts
  35. 68
      scripts/gen-types/gen-types.ts
  36. 71
      scripts/gen-types/helpers/generate-script-object.ts
  37. 42
      scripts/gen-types/helpers/get-parsed-callbacks.ts
  38. 42
      scripts/gen-types/helpers/get-parsed-constants.ts
  39. 28
      scripts/gen-types/helpers/get-parsed-enums.ts
  40. 37
      scripts/gen-types/helpers/get-parsed-event-data.ts
  41. 48
      scripts/gen-types/helpers/get-parsed-structs.ts
  42. 45
      scripts/gen-types/helpers/get-parsed-systems.ts
  43. 3
      scripts/gen-types/shared/constants.ts
  44. 3
      scripts/gen-types/shared/helpers/only-unique.ts
  45. 43
      scripts/gen-types/shared/helpers/parse-field.ts
  46. 41
      scripts/gen-types/shared/helpers/parse-function.ts
  47. 87
      scripts/gen-types/shared/types/doc-types.ts
  48. 129
      scripts/gen-types/shared/types/wow-type-aliases.ts
  49. 70262
      scripts/lua-docs/docs/docs.json
  50. 58
      scripts/lua-docs/get-docs.lua
  51. 9
      scripts/tsconfig.json
  52. 95
      src/_index.ts
  53. 16
      src/actions/mage/arcane.ts
  54. 16
      src/actions/mage/fire.ts
  55. 16
      src/actions/mage/frost.ts
  56. 230
      src/managers/action-manager.ts
  57. 99
      src/managers/aura-manager.ts
  58. 170
      src/managers/bindings-manager.ts
  59. 51
      src/managers/command-manager.ts
  60. 87
      src/managers/event-manager.ts
  61. 239
      src/managers/mob-manager.ts
  62. 104
      src/managers/player-state-manager.ts
  63. 53
      src/types/Mekanome.d.ts
  64. 319
      src/types/Tinkr.d.ts
  65. 55
      src/types/Wow/callbacks.d.ts
  66. 168
      src/types/Wow/constants.d.ts
  67. 5033
      src/types/Wow/enums.d.ts
  68. 5121
      src/types/Wow/events.d.ts
  69. 2413
      src/types/Wow/scripts.d.ts
  70. 5548
      src/types/Wow/structs.d.ts
  71. 10532
      src/types/Wow/systems.d.ts
  72. 982
      src/types/Wow/undocumented.d.ts
  73. 52
      src/utils/aura-utils.ts
  74. 75
      src/utils/cast-util.ts
  75. 682
      src/utils/mob-utils.ts
  76. 249
      src/utils/spell-utils.ts
  77. 210
      src/utils/vector-utils.ts
  78. 26
      tsconfig.json

@ -0,0 +1,44 @@
# Mekanome
Mekanome is a TypeScript based SDK for building combat rotations for the tinkr unlocker on macOS. Heavily inspired by the Bastion project, it aims to be a more type-safe, developer friendly, and potentially more secure (maybe, who really knows) way of building rotations for tinkr.
## Developing
1. Clone or fork this repo and run `pnpm i` to install its dependencies. I prefer `pnpm` so that's what this project uses as a package manager, but you prefer npm or yarn you should be able to switch to them without issue.
2. The types in the repo should be up to date, but if not you'll need to re-generate them. See the below section for details on how to do that.
3. Create a .env file and copy ./sample.env into it. Fill in the config so it matches your set up.
4. Running `pnpm run build:full` will clear out your Mekanome dist directory, your Tinkr scripts directory, compile your typescript code to lua, and copy it to both the Mekanome dist and Tinkr scripts directories. *WARNING: This will completely delete and replace your Tinkr scripts directory. If you have anything in there you dont want to delete, I suggest running `build:dist` instead and copying the generated code manually.*
6. You should now have a working version of Mekanome ready to be loaded when you next run the game.
## Generating types
This repo should come with up to date types, but just in case it doesn't and you need to generate your own the process is as follows:
1. Navigate into the blizzard interface code library with `cd ./lib/BlizzardInterfaceCode` and run `git pull` to pull in the latest documentation, then navigate back to the root with `cd ../../`.
2. Running `pnpm run lua-docs:full` will grab the documentation from the interface code and format them into JSON.
3. Next run `pnpm run types:full` to convert the JSON we just generated into usable types. If you run into an error that says something like "Error: Encountered unknown string type: "X"" its because "X" is typed in the documentation in a way that cant easily be translated into typescript. See `wow-type-aliases.ts` to fix that.
4. You should now have workable types for most of the World of Warcraft lua APIs. Some functions / systems are not included in the BlizzardInterfaceCode repo and need to be stubbed in manually, to add those see `undocumented.d.ts`.
## Documentation
This project is still in its infancy. Once TWW launches I hope to have a solid v1 done and ready to use, including some documentation. Until then things are too in flux document.
## FAQ
#### More Secure?
Ideally 😅. Mekanome interacts with WoW a little bit differently than Bastion does, in a way that I suspect (and may never be able to confirm) is "safer" from a detection standpoint. Not too long ago Blizzard checked in some code that seemed to be incrementing a count of binding usage whenever an action button was pressed or clicked. This could mean nothing, or it could mean that they intend to use this as some sort of detection method when comparing real players vs those using an unlocker like Tinkr since most rotations / bots will use the "CastSpellByName" function to cast their spells skipping the incrementation of this new binding usage count. If Blizzard sees a player with a bunch of successful spell casts, and a binding usage count that is wildly off that number it could look suspect.
Again though, we may never know if this is anything worth being worried about. Ive just decided to err on the side of caution.
#### So I have to bind all my spells?
All of the spells used by the rotations built with Mekanome require that the spell is on a default Blizzard UI action bar. They don't necessarily have to have a corresponding keybind -- since clicking a button will also increment its usage count -- but I'd suggest at least binding the rotations most used spells since a real human being cant really click buttons with the speed that Mekanome will run their corresponding bindings.
#### Wait, default UI?
Currently Mekanome will not work with action bars from ELVUI, Bartender, or similar addons. I use default action bars, and supporting these addons would require installing them and implementing handling for the additional action buttons they create. If someone wants to put in the effort to add that support I'd gladly approve their PR, I just don't really want to. Sorry!
#### Who is this for?
Developers who like typescript and developing rotations for Tinkr. I'll be including some rotations that I use as part of this package, but they'll be tailored to my needs and if you message me asking about them I'll just block you 💅.

82
dist/_index.lua vendored

@ -0,0 +1,82 @@
--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
-- Lua Library inline imports
local function __TS__ObjectKeys(obj)
local result = {}
local len = 0
for key in pairs(obj) do
len = len + 1
result[len] = key
end
return result
end
local function __TS__ArrayForEach(self, callbackFn, thisArg)
for i = 1, #self do
callbackFn(thisArg, self[i], i - 1, self)
end
end
-- End of Lua Library inline imports
local ____exports = {}
local _Tinkr = ...
local Tinkr = _Tinkr
local function Print(____bindingPattern0)
local color
local level
local prefix
local text
text = ____bindingPattern0.text
prefix = ____bindingPattern0.prefix
level = ____bindingPattern0.level
color = ____bindingPattern0.color
local textColor = color
if not textColor then
textColor = level == "error" and "FF76CE" or (level == "info" and "94FFD8" or (level == "warn" and "FDFFC2" or "A3D8FF"))
end
print((((("|cFF" .. textColor) .. "[") .. (prefix or "Mekanome")) .. "]|r |cFFFFFFFF ") .. text)
end
local function GetObject(token)
return Object(token)
end
local function Error(text)
Print({text = text, prefix = "ERROR", level = "error"})
error(text)
end
local Mekanome = {enabled = false, Print = Print, Error = Error, GetObject = GetObject}
local function Load(path)
return Tinkr.Util.Evaluator:LoadString(
Tinkr.Util.File:Read(path),
{Tinkr, Mekanome},
path
).default
end
local function LoadDirectoryFiles(path)
local directories = ListDirectories(path)
for ____, directory in ipairs(directories) do
LoadDirectoryFiles((path .. "/") .. directory)
end
local files = ListFiles(path)
for ____, file in ipairs(files) do
Load((path .. "/") .. file)
Print({text = (path .. "/") .. file, prefix = "Loaded"})
end
end
local function Init()
Print({text = "(V) (°,,,,°) (V)"})
LoadDirectoryFiles("scripts/utils")
LoadDirectoryFiles("scripts/managers")
LoadDirectoryFiles("scripts/actions")
local keys = __TS__ObjectKeys(Mekanome)
__TS__ArrayForEach(
keys,
function(____, key)
local module = Mekanome[key]
if module ~= nil and type(module) == "table" and module.Init ~= nil then
module:Init()
Print({text = key, prefix = "Initialized", level = "info"})
end
end
)
end
Init()
____exports.default = {}
return ____exports

@ -0,0 +1,11 @@
--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
local ____exports = {}
local _, _Mekanome = ...
local Mekanome = _Mekanome
local ArcaneInt = Mekanome.SpellUtils.Create(1459)
local function ArcaneMage()
return Mekanome.SpellUtils.SelfCast(ArcaneInt)
end
Mekanome.ActionManager.Add("arcane_mage", {run = ArcaneMage, priority = 1, loadConditions = {spec = 1, class = 8}})
____exports.default = nil
return ____exports

@ -0,0 +1,11 @@
--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
local ____exports = {}
local _, _Mekanome = ...
local Mekanome = _Mekanome
local function FireMage()
print("Fire!")
return
end
Mekanome.ActionManager.Add("fire_mage", {run = FireMage, priority = 1, loadConditions = {spec = 2, class = 8}})
____exports.default = nil
return ____exports

@ -0,0 +1,11 @@
--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
local ____exports = {}
local _, _Mekanome = ...
local Mekanome = _Mekanome
local function FrostMage()
print("Frost!")
return nil
end
Mekanome.ActionManager.Add("frost_mage", {run = FrostMage, priority = 1, loadConditions = {spec = 3, class = 8}})
____exports.default = nil
return ____exports

@ -0,0 +1,191 @@
--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
-- Lua Library inline imports
local function __TS__ArraySort(self, compareFn)
if compareFn ~= nil then
table.sort(
self,
function(a, b) return compareFn(nil, a, b) < 0 end
)
else
table.sort(self)
end
return self
end
local function __TS__ObjectAssign(target, ...)
local sources = {...}
for i = 1, #sources do
local source = sources[i]
for key in pairs(source) do
target[key] = source[key]
end
end
return target
end
-- End of Lua Library inline imports
local ____exports = {}
local _, _Mekanome = ...
local Mekanome = _Mekanome
local actions = {}
local isInitialized = false
local isMouseoverSettingEnabled = false
--- Evaluates the actions load conditions to tell the ActionManager
-- wether or not this action should be loaded.
--
-- @param loadConditions ActionLoadConditions
-- @returns boolean
local function GetIsActionLoaded(loadConditions)
local matchesClass = true
local matchesSpec = true
local matchesCustom = true
if loadConditions.class ~= nil then
local ____, ____, classId = UnitClass("player")
matchesClass = loadConditions.class == classId
end
if (loadConditions and loadConditions.spec) ~= nil then
matchesSpec = loadConditions.spec == GetSpecialization()
end
if loadConditions.custom ~= nil then
matchesCustom = loadConditions:custom()
end
return matchesClass == true and matchesSpec == true and matchesCustom == true
end
--- Returns the actions map in array format, sorted by priority.
local function GetSortedActions()
local actionsArray = {}
for ____, action in pairs(actions) do
actionsArray[#actionsArray + 1] = action
end
return __TS__ArraySort(
actionsArray,
function(____, a, b)
return a.priority - b.priority
end
)
end
--- Adds an action to the manager to call inside its ticker if loaded.
--
-- @param name A unique name to identify the action.
-- @param action The function to call for the action.
-- @param options Options for the action to add.
local function Add(name, options)
if actions[name] ~= nil then
error(
Mekanome.Error(("Tried to add action " .. name) .. " but it already exists."),
0
)
end
local ____temp_2
if options.loadConditions ~= nil then
____temp_2 = GetIsActionLoaded(options.loadConditions)
else
____temp_2 = false
end
local loaded = ____temp_2
local priority = options.priority
if priority == nil then
local sortedActions = GetSortedActions()
if #sortedActions == 0 then
priority = 1
else
local lastAction = sortedActions[#sortedActions]
priority = lastAction.priority + 1
end
end
actions[name] = {
priority = priority,
run = options.run,
loadConditions = options.loadConditions,
loaded = loaded,
name = name
}
if loaded then
Mekanome.Print({text = name, prefix = "Module Loaded", level = "info"})
end
end
--- Removes the action from the list.
--
-- @param name The name of the action to remove.
local function Remove(name)
if actions[name] ~= nil then
actions[name] = nil
end
end
--- Calls the list of loaded actions, in order.
-- If an action returns a binding, that binding is run and the loop is terminted
-- for a tick.
local function CallActions()
local actionList = GetSortedActions()
for ____, action in ipairs(actionList) do
if action and action.loaded then
local castable = action.run()
if castable ~= nil then
Mekanome.Cast(castable)
break
end
end
end
end
--- In order to cast on targets that arent the active target, we need
-- to leverage mouse over casting. If the setting isnt enabled, Mekanome
-- will not be able to cast on mobs that aren't the current target.
local function CheckSettings()
local mouseoverSetting = C_CVar.GetCVar("enableMouseoverCast")
if mouseoverSetting ~= nil and mouseoverSetting ~= "0" then
isMouseoverSettingEnabled = true
else
isMouseoverSettingEnabled = false
error(
Mekanome.Error("Please enable mouseover casting in the settings menu."),
0
)
end
end
--- Initializes the action manager.
local function Init()
if isInitialized == false then
CheckSettings()
C_Timer.NewTicker(
0.15,
function()
if Mekanome.enabled and isMouseoverSettingEnabled then
Mekanome.MobManager.Refresh()
Mekanome.PlayerStateManager.Refresh()
CallActions()
end
end,
nil
)
Mekanome.EventManager.RegisterEventHandler(
"PLAYER_SPECIALIZATION_CHANGED",
function()
for name, action in pairs(actions) do
if action.loadConditions then
local loaded = GetIsActionLoaded(action.loadConditions)
if loaded ~= action.loaded then
actions[name] = __TS__ObjectAssign({}, action, {loaded = loaded})
Mekanome.Print({text = name, level = "info", prefix = loaded and "Loaded Module" or "Unloaded Module"})
end
end
end
end,
"am_spec_change"
)
Mekanome.EventManager.RegisterEventHandler(
"CVAR_UPDATE",
function(____, ____bindingPattern0)
local eventName
eventName = ____bindingPattern0[1]
if eventName == "enableMouseoverCast" then
CheckSettings()
end
end,
"am_cvar_update"
)
isInitialized = true
end
end
local _ActionManager = {Add = Add, Remove = Remove, Init = Init}
Mekanome.ActionManager = _ActionManager
____exports.default = {}
return ____exports

@ -0,0 +1,84 @@
--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
-- Lua Library inline imports
local function __TS__ArrayForEach(self, callbackFn, thisArg)
for i = 1, #self do
callbackFn(thisArg, self[i], i - 1, self)
end
end
-- End of Lua Library inline imports
local ____exports = {}
local _, _Mekanome = ...
local Mekanome = _Mekanome
local function New(token)
local auras = {}
local helpfulSlots = {C_UnitAuras.GetAuraSlots(token, "HELPFUL", 200, nil)}
__TS__ArrayForEach(
helpfulSlots,
function(____, s)
if s ~= nil then
local aura = C_UnitAuras.GetAuraDataBySlot(token, s)
if aura then
auras[aura.auraInstanceID] = aura
end
end
end
)
local harmfulSlots = {C_UnitAuras.GetAuraSlots(token, "HARMFUL", 200, nil)}
__TS__ArrayForEach(
harmfulSlots,
function(____, s)
if s ~= nil then
local aura = C_UnitAuras.GetAuraDataBySlot(token, s)
if aura then
auras[aura.auraInstanceID] = aura
end
end
end
)
return auras
end
local function GetUpdatedAuras(updateInfo, mob)
local auras = {}
if updateInfo.isFullUpdate then
return New(mob.token)
end
if mob.auras ~= nil then
for instanceId, aura in pairs(mob.auras) do
auras[instanceId] = aura
end
end
if updateInfo.removedAuraInstanceIDs ~= nil then
__TS__ArrayForEach(
updateInfo.removedAuraInstanceIDs,
function(____, id)
if auras[id] ~= nil then
auras[id] = nil
end
end
)
end
if updateInfo.addedAuras ~= nil then
__TS__ArrayForEach(
updateInfo.addedAuras,
function(____, data)
auras[data.auraInstanceID] = data
end
)
end
if updateInfo.updatedAuraInstanceIDs then
__TS__ArrayForEach(
updateInfo.updatedAuraInstanceIDs,
function(____, id)
local data = C_UnitAuras.GetAuraDataByAuraInstanceID(mob.token, id)
if data then
auras[data.auraInstanceID] = data
end
end
)
end
return auras
end
local _AuraManager = {New = New, GetUpdatedAuras = GetUpdatedAuras}
Mekanome.AuraManager = _AuraManager
____exports.default = {}
return ____exports

@ -0,0 +1,325 @@
--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
-- Lua Library inline imports
local function __TS__Class(self)
local c = {prototype = {}}
c.prototype.__index = c.prototype
c.prototype.constructor = c
return c
end
local __TS__Symbol, Symbol
do
local symbolMetatable = {__tostring = function(self)
return ("Symbol(" .. (self.description or "")) .. ")"
end}
function __TS__Symbol(description)
return setmetatable({description = description}, symbolMetatable)
end
Symbol = {
asyncDispose = __TS__Symbol("Symbol.asyncDispose"),
dispose = __TS__Symbol("Symbol.dispose"),
iterator = __TS__Symbol("Symbol.iterator"),
hasInstance = __TS__Symbol("Symbol.hasInstance"),
species = __TS__Symbol("Symbol.species"),
toStringTag = __TS__Symbol("Symbol.toStringTag")
}
end
local __TS__Iterator
do
local function iteratorGeneratorStep(self)
local co = self.____coroutine
local status, value = coroutine.resume(co)
if not status then
error(value, 0)
end
if coroutine.status(co) == "dead" then
return
end
return true, value
end
local function iteratorIteratorStep(self)
local result = self:next()
if result.done then
return
end
return true, result.value
end
local function iteratorStringStep(self, index)
index = index + 1
if index > #self then
return
end
return index, string.sub(self, index, index)
end
function __TS__Iterator(iterable)
if type(iterable) == "string" then
return iteratorStringStep, iterable, 0
elseif iterable.____coroutine ~= nil then
return iteratorGeneratorStep, iterable
elseif iterable[Symbol.iterator] then
local iterator = iterable[Symbol.iterator](iterable)
return iteratorIteratorStep, iterator
else
return ipairs(iterable)
end
end
end
local Map
do
Map = __TS__Class()
Map.name = "Map"
function Map.prototype.____constructor(self, entries)
self[Symbol.toStringTag] = "Map"
self.items = {}
self.size = 0
self.nextKey = {}
self.previousKey = {}
if entries == nil then
return
end
local iterable = entries
if iterable[Symbol.iterator] then
local iterator = iterable[Symbol.iterator](iterable)
while true do
local result = iterator:next()
if result.done then
break
end
local value = result.value
self:set(value[1], value[2])
end
else
local array = entries
for ____, kvp in ipairs(array) do
self:set(kvp[1], kvp[2])
end
end
end
function Map.prototype.clear(self)
self.items = {}
self.nextKey = {}
self.previousKey = {}
self.firstKey = nil
self.lastKey = nil
self.size = 0
end
function Map.prototype.delete(self, key)
local contains = self:has(key)
if contains then
self.size = self.size - 1
local next = self.nextKey[key]
local previous = self.previousKey[key]
if next ~= nil and previous ~= nil then
self.nextKey[previous] = next
self.previousKey[next] = previous
elseif next ~= nil then
self.firstKey = next
self.previousKey[next] = nil
elseif previous ~= nil then
self.lastKey = previous
self.nextKey[previous] = nil
else
self.firstKey = nil
self.lastKey = nil
end
self.nextKey[key] = nil
self.previousKey[key] = nil
end
self.items[key] = nil
return contains
end
function Map.prototype.forEach(self, callback)
for ____, key in __TS__Iterator(self:keys()) do
callback(nil, self.items[key], key, self)
end
end
function Map.prototype.get(self, key)
return self.items[key]
end
function Map.prototype.has(self, key)
return self.nextKey[key] ~= nil or self.lastKey == key
end
function Map.prototype.set(self, key, value)
local isNewValue = not self:has(key)
if isNewValue then
self.size = self.size + 1
end
self.items[key] = value
if self.firstKey == nil then
self.firstKey = key
self.lastKey = key
elseif isNewValue then
self.nextKey[self.lastKey] = key
self.previousKey[key] = self.lastKey
self.lastKey = key
end
return self
end
Map.prototype[Symbol.iterator] = function(self)
return self:entries()
end
function Map.prototype.entries(self)
local items = self.items
local nextKey = self.nextKey
local key = self.firstKey
return {
[Symbol.iterator] = function(self)
return self
end,
next = function(self)
local result = {done = not key, value = {key, items[key]}}
key = nextKey[key]
return result
end
}
end
function Map.prototype.keys(self)
local nextKey = self.nextKey
local key = self.firstKey
return {
[Symbol.iterator] = function(self)
return self
end,
next = function(self)
local result = {done = not key, value = key}
key = nextKey[key]
return result
end
}
end
function Map.prototype.values(self)
local items = self.items
local nextKey = self.nextKey
local key = self.firstKey
return {
[Symbol.iterator] = function(self)
return self
end,
next = function(self)
local result = {done = not key, value = items[key]}
key = nextKey[key]
return result
end
}
end
Map[Symbol.species] = Map
end
local function __TS__New(target, ...)
local instance = setmetatable({}, target.prototype)
instance:____constructor(...)
return instance
end
-- End of Lua Library inline imports
local ____exports = {}
local _, _Mekanome = ...
local Mekanome = _Mekanome
local ActionBarButtonNames = {
"ActionButton",
"MultiBarBottomLeftButton",
"MultiBarBottomRightButton",
"MultiBarLeftButton",
"MultiBarRightButton",
"MultiBar5Button",
"MultiBar6Button",
"MultiBar7Button"
}
local BarMap = {
ActionButton = "ACTIONBUTTON",
MultiBarBottomLeftButton = "MULTIACTIONBAR1BUTTON",
MultiBarBottomRightButton = "MULTIACTIONBAR2BUTTON",
MultiBarRightButton = "MULTIACTIONBAR3BUTTON",
MultiBarLeftButton = "MULTIACTIONBAR4BUTTON",
MultiBar5Button = "MULTIACTIONBAR5BUTTON",
MultiBar6Button = "MULTIACTIONBAR6BUTTON"
}
local bindings = __TS__New(Map)
local isInitialized = false
--- Loops through all of the WoW action bar buttons, checks to see if there is a spell
-- or item on them, then stores that relationship for later use.
local function CheckBindings(self)
bindings:clear()
for ____, bar in ipairs(ActionBarButtonNames) do
for i = 1, 12 do
local actionButton = bar .. tostring(i)
local button = _G[actionButton]
if button ~= nil and type(button) == "table" and button.action ~= nil then
local ____type, id = GetActionInfo(button.action)
if ____type == "spell" or ____type == "item" then
local barName = BarMap[bar]
local binding = tostring(barName) .. tostring(i)
bindings:set(button.action, {binding = binding, id = id, type = ____type})
end
end
end
end
Mekanome.Print({
text = tostring(bindings.size),
prefix = "Bindings Update",
level = "warn"
})
end
--- Returns the ActionButtons binding for a given spell.
--
-- @param spell
-- @returns ActionButtons
local function GetBindingForSpell(spell)
local binding = nil
for ____, ____value in __TS__Iterator(bindings) do
local bindingInfo = ____value[2]
if bindingInfo.id == spell.id then
binding = bindingInfo.binding
break
end
end
return binding
end
local function CallBinding(binding)
RunBinding(binding)
RunBinding(binding, "up")
end
--- Initializes the bindings manager.
local function Init()
if isInitialized == false then
CheckBindings(nil)
Mekanome.EventManager.RegisterEventHandler(
"ACTIONBAR_SLOT_CHANGED",
function(____, args)
local slot = args[1]
local binding = bindings:get(slot)
local ____type, id = GetActionInfo(slot)
if binding ~= nil then
if ____type ~= binding.type or id ~= binding.id then
CheckBindings(nil)
end
else
if ____type == "spell" or ____type == "item" then
CheckBindings(nil)
end
end
end,
"bm_action_bar_slot_changed"
)
Mekanome.EventManager.RegisterEventHandler(
"UPDATE_BONUS_ACTIONBAR",
function()
CheckBindings(nil)
end,
"bm_player_mount_display"
)
Mekanome.EventManager.RegisterEventHandler(
"PLAYER_SPECIALIZATION_CHANGED",
function()
CheckBindings(nil)
end,
"bm_spec_change"
)
isInitialized = true
end
end
local _BindingsManager = {Init = Init, GetBindingForSpell = GetBindingForSpell, CallBinding = CallBinding}
Mekanome.BindingsManager = _BindingsManager
____exports.default = {}
return ____exports

@ -0,0 +1,34 @@
--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
local ____exports = {}
local _Tinkr, _Mekanome = ...
local Tinkr = _Tinkr
local Mekanome = _Mekanome
local meka = Tinkr.Util.Commands:New("meka")
--- Registers a commant to be used under the "meka" namespace
--
-- @param prefix /meka {prefix}
-- @param callback The function to be called when the command is entered.
-- @param helpText Optional text to help describe the command.
local function Register(prefix, callback, helpText)
meka:Register(prefix, callback, helpText)
end
--- Initializes core commands.
local function Init()
Register(
"toggle",
function()
if Mekanome.enabled == false then
Mekanome.Print({text = "Started"})
Mekanome.enabled = true
else
Mekanome.Print({text = "Stopped"})
Mekanome.enabled = false
end
end,
"Toggles Mekanome."
)
end
local _CommandManager = {Register = Register, Init = Init}
Mekanome.CommandManager = _CommandManager
____exports.default = {}
return ____exports

@ -0,0 +1,59 @@
--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
local ____exports = {}
local _, _Mekanome = ...
local Mekanome = _Mekanome
local events = {}
local frame = CreateFrame("Frame")
frame:SetScript(
"OnEvent",
function(f, event, ...)
local args = {...}
local callbacks = events[event]
if callbacks then
for ____, cb in pairs(callbacks) do
cb(nil, args)
end
end
end
)
--- Registers an event to listen to.
--
-- @param event The name of the wow event.
-- @param callback The function to call when the event triggers.
-- @param id The ID specific to this callback, so that multiple handlers can be added per event.
local function RegisterEventHandler(event, callback, id)
local existingHandlers = events[event]
if existingHandlers then
if existingHandlers[id] ~= nil then
return
end
existingHandlers[id] = callback
events[event] = existingHandlers
else
frame:RegisterEvent(event)
local handlers = {}
handlers[id] = callback
events[event] = handlers
Mekanome.Print({text = event, prefix = "Registered", level = "info"})
end
end
--- Unregisters a Wow event handler.
--
-- @param event The event to unregister from.
-- @param id The ID of the handler to unregister.
local function UnregisterEventHandler(event, id)
local handlers = events[event]
if handlers and handlers[id] ~= nil then
handlers[id] = nil
Mekanome.Print({text = (("Event: " .. event) .. " | ID: ") .. id, prefix = "Handler", level = "error"})
end
if handlers and next(handlers) == nil == true then
events[event] = nil
frame:UnregisterEvent(event)
Mekanome.Print({text = event, prefix = "Event", level = "error"})
end
end
local _EventManager = {RegisterEventHandler = RegisterEventHandler, UnregisterEventHandler = UnregisterEventHandler}
Mekanome.EventManager = _EventManager
____exports.default = {}
return ____exports

@ -0,0 +1,233 @@
--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
-- Lua Library inline imports
local function __TS__ArrayIncludes(self, searchElement, fromIndex)
if fromIndex == nil then
fromIndex = 0
end
local len = #self
local k = fromIndex
if fromIndex < 0 then
k = len + fromIndex
end
if k < 0 then
k = 0
end
for i = k + 1, len do
if self[i] == searchElement then
return true
end
end
return false
end
local function __TS__ArrayIsArray(value)
return type(value) == "table" and (value[1] ~= nil or next(value) == nil)
end
local function __TS__ArrayConcat(self, ...)
local items = {...}
local result = {}
local len = 0
for i = 1, #self do
len = len + 1
result[len] = self[i]
end
for i = 1, #items do
local item = items[i]
if __TS__ArrayIsArray(item) then
for j = 1, #item do
len = len + 1
result[len] = item[j]
end
else
len = len + 1
result[len] = item
end
end
return result
end
local function __TS__ObjectAssign(target, ...)
local sources = {...}
for i = 1, #sources do
local source = sources[i]
for key in pairs(source) do
target[key] = source[key]
end
end
return target
end
-- End of Lua Library inline imports
local ____exports = {}
local CreateMobFromObject, GetMob, Mekanome, DUMMYS, mobs
function CreateMobFromObject(mobObject)
local ____type = "unknown"
local id = mobObject:id()
local unit = mobObject:unit()
if UnitIsFriend("player", unit) or UnitInParty(unit, nil) or UnitInRaid(unit, nil) then
____type = "friend"
end
if UnitCanAttack("player", unit) or type(id) == "number" and __TS__ArrayIncludes(DUMMYS, id) then
____type = "enemy"
end
return {
type = ____type,
token = mobObject:unit(),
guid = mobObject:guid(),
id = mobObject:id(),
auras = Mekanome.AuraManager.New(mobObject:unit())
}
end
function GetMob(____bindingPattern0)
local guid
local token
token = ____bindingPattern0.token
guid = ____bindingPattern0.guid
local id = nil
if guid then
id = guid
elseif token then
id = token == "none" and token or ObjectGUID(token)
end
if id == nil then
return nil
end
if mobs[id] ~= nil == false then
local gameObject = Object(id)
if gameObject ~= false then
mobs[id] = CreateMobFromObject(gameObject)
end
end
return mobs[id]
end
local _, _Mekanome = ...
Mekanome = _Mekanome
local isInitialized = false
local CREATURE_TYPES_TO_IGNORE = {8, 12, 13, 14}
DUMMYS = {
31146,
31144,
32666,
32667,
46647,
114832,
153285,
153292,
198594,
194648,
189632,
194643,
194644,
197833,
189617,
194649,
193563,
65310,
66374,
196394,
196406,
199057
}
mobs = {}
local function GetMobFromObject(mobObject)
return mobs[mobObject:guid()]
end
--- Updates the time in which a mob was last in combat.
--
-- @param guid The GUID of the mob to update.
local function UpdateCombatTime(guid)
local mob = GetMob({guid = guid})
if mob and mob.type == "enemy" then
mob.lastCombatTime = GetTime()
mobs[guid] = mob
end
end
--- Refreshes the list of mobs.
local function Refresh()
local touchedMobs = {}
local unitObjects = Objects(5)
local playerObjects = Objects(6)
local activePlayerObject = Objects(7)
local objects = __TS__ArrayConcat(
__TS__ArrayConcat(unitObjects, playerObjects),
activePlayerObject
)
for ____, gameObject in ipairs(objects) do
local objectType = ObjectCreatureType(gameObject)
if __TS__ArrayIncludes(CREATURE_TYPES_TO_IGNORE, objectType) == false then
local existingMob = GetMobFromObject(gameObject)
local mob = CreateMobFromObject(gameObject)
touchedMobs[#touchedMobs + 1] = mob.guid
if existingMob == nil then
mobs[mob.guid] = mob
else
local isStale = existingMob.id ~= mob.id or existingMob.token ~= mob.token
if isStale then
mobs[existingMob.guid] = __TS__ObjectAssign({}, existingMob, {id = mob.id, token = mob.token})
end
end
end
end
for guid, _ in pairs(mobs) do
if __TS__ArrayIncludes(touchedMobs, guid) == false then
mobs[guid] = nil
end
end
end
--- Iterates over the mob list, and calls a function for each mob.
--
-- @param callback Function called for each mob in the list.
-- @param type The type of mobs to iterate over. "enemies_active" iterates over enemies that are in combat.
local function Iterate(callback, ____type)
for _, mob in pairs(mobs) do
if ____type ~= nil then
if ____type == "enemies_active" then
local combatOdds = Mekanome.MobUtils.InCombatOdds(mob)
if combatOdds ~= nil and combatOdds < 0.4 then
callback(nil, mob)
end
elseif mob.type == ____type then
callback(nil, mob)
end
else
callback(nil, mob)
end
end
end
--- Initializes the mob manager.
local function Init()
if isInitialized == false then
Mekanome.EventManager.RegisterEventHandler(
"COMBAT_LOG_EVENT_UNFILTERED",
function()
if Mekanome.enabled then
local ____, ____, ____, source, ____, ____, ____, dest = CombatLogGetCurrentEventInfo()
UpdateCombatTime(source)
UpdateCombatTime(dest)
end
end,
"mm_combat_log_event_unfiltered"
)
Mekanome.EventManager.RegisterEventHandler(
"UNIT_AURA",
function(____, ____bindingPattern0)
local auraInfo
local token
token = ____bindingPattern0[1]
auraInfo = ____bindingPattern0[2]
local mob = GetMob({token = token})
if mob then
local updatedAuras = Mekanome.AuraManager.GetUpdatedAuras(auraInfo, mob)
mob.auras = updatedAuras
mobs[mob.guid] = mob
end
end,
"mm_unit_aura"
)
isInitialized = true
end
end
local _MobManager = {Iterate = Iterate, Refresh = Refresh, GetMob = GetMob, Init = Init}
Mekanome.MobManager = _MobManager
____exports.default = {}
return ____exports

@ -0,0 +1,116 @@
--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
-- Lua Library inline imports
local function __TS__Number(value)
local valueType = type(value)
if valueType == "number" then
return value
elseif valueType == "string" then
local numberValue = tonumber(value)
if numberValue then
return numberValue
end
if value == "Infinity" then
return math.huge
end
if value == "-Infinity" then
return -math.huge
end
local stringWithoutSpaces = string.gsub(value, "%s", "")
if stringWithoutSpaces == "" then
return 0
end
return 0 / 0
elseif valueType == "boolean" then
return value and 1 or 0
else
return 0 / 0
end
end
-- End of Lua Library inline imports
local ____exports = {}
local _, _Mekanome = ...
local Mekanome = _Mekanome
local isMoving = false
local isCasting = false
local isChanneling = false
local isAlive = true
local isMounted = false
local Player = nil
local Target = nil
local Mouseover = nil
local sqw = C_CVar.GetCVar("SpellQueueWindow")
local spellQueueWindow = sqw ~= nil and __TS__Number(sqw) or 0
--- Refresh the players state
local function Refresh()
local MobUtils = Mekanome.MobUtils
local MobManager = Mekanome.MobManager
local player = MobManager.GetMob({token = "player"})
local target = MobManager.GetMob({token = "target"})
local mouseover = MobManager.GetMob({token = "mouseover"})
if player == nil then
error(
Mekanome.Error("Unable to refresh player state as player object doesnt exist."),
0
)
end
Player = player
Target = target
Mouseover = mouseover
isMoving = MobUtils.GetIsMoving(player)
isMounted = MobUtils.IsMounted(player)
isAlive = MobUtils.IsAlive(player)
local castEnd = MobUtils.GetCastEndTime(player)
local channelEnd = MobUtils.GetChannelEndTime(player)
if castEnd then
local timeUntilEnd = castEnd - GetTime()
isCasting = timeUntilEnd > spellQueueWindow / 1000
else
isCasting = false
end
if channelEnd then
local timeUntilEnd = channelEnd - GetTime()
isChanneling = timeUntilEnd > spellQueueWindow / 1000
else
isChanneling = false
end
end
--- Get the players state
local function GetPlayerState()
if Player == nil then
error(
Mekanome.Error("Unable to get player state as player object doesnt exist."),
0
)
end
return {
Player = Player,
Target = Target,
Mouseover = Mouseover,
spellQueueWindow = spellQueueWindow,
isAlive = isAlive,
isMounted = isMounted,
isMoving = isMoving,
isCasting = isCasting,
isChanneling = isChanneling
}
end
local function Init()
Mekanome.EventManager.RegisterEventHandler(
"CVAR_UPDATE",
function(____, ____bindingPattern0)
local eventName
eventName = ____bindingPattern0[1]
if eventName == "SpellQueueWindow" then
local newSQW = C_CVar.GetCVar("SpellQueueWindow")
if newSQW ~= nil then
spellQueueWindow = __TS__Number(newSQW)
end
end
end,
"tm_cvar_update"
)
end
local _PlayerStateManager = {Refresh = Refresh, GetPlayerState = GetPlayerState, Init = Init}
Mekanome.PlayerStateManager = _PlayerStateManager
____exports.default = {}
return ____exports

@ -0,0 +1,32 @@
--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
local ____exports = {}
local _, _Mekanome = ...
local Mekanome = _Mekanome
--- Creates a useable aura
local function Create(id, isPlayerAura)
return {id = id, isPlayerAura = isPlayerAura}
end
--- Returns whether or not an aura is active in a list of auras
local function IsUp(aura, mob, secondsToIgnore)
local isUp = false
for ____, auraData in pairs(mob.auras) do
local matchesPlayerFilter = aura.isPlayerAura == nil or auraData.sourceUnit == "player"
if auraData.spellId == aura.id and matchesPlayerFilter then
if auraData.expirationTime == 0 then
isUp = true
else
local expirationTime = auraData.expirationTime - GetTime()
if secondsToIgnore ~= nil then
isUp = expirationTime > secondsToIgnore
else
isUp = expirationTime > 0
end
end
break
end
end
return isUp
end
local _AuraUtils = {IsUp = IsUp, Create = Create}
Mekanome.AuraUtils = _AuraUtils
return ____exports

@ -0,0 +1,57 @@
--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
local ____exports = {}
local _, _Mekanome = ...
local Mekanome = _Mekanome
--- Casts the provided castable. Can be used for spells, items, macros etc as long as there
-- is a corresponding binding and target / position info.
local function Cast(castable)
local mouseover = Mekanome.GetObject("mouseover")
local function ExecuteBinding()
print(castable.binding)
RunBinding(castable.binding)
RunBinding(castable.binding, "up")
end
local function ClearMouseover()
SetMouseover(nil)
end
local function RestoreMouseover()
if mouseover ~= false then
SetMouseover(mouseover)
end
end
--- Will execute the binding on the player, or active 'target'.
local function TargetOrSelfCast()
ClearMouseover()
ExecuteBinding()
RestoreMouseover()
end
if castable.variant == "self" then
TargetOrSelfCast()
end
if castable.variant == "ground" then
local position = castable.position
ExecuteBinding()
local pending = IsSpellPending()
if pending == 64 then
Click(position.x, position.y, position.z)
end
end
if castable.variant == "target" then
local target = Mekanome.GetObject("target")
local targetGuid = target ~= false and ObjectGUID(target) or nil
local castTarget = castable.target
if targetGuid == nil or castTarget.guid ~= targetGuid then
local targetObj = Mekanome.GetObject(castTarget.token)
if targetObj ~= false then
SetMouseover(targetObj)
ExecuteBinding()
RestoreMouseover()
end
else
TargetOrSelfCast()
end
end
end
Mekanome.Cast = Cast
____exports.default = {}
return ____exports

@ -0,0 +1,517 @@
--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
-- Lua Library inline imports
local function __TS__ArrayIncludes(self, searchElement, fromIndex)
if fromIndex == nil then
fromIndex = 0
end
local len = #self
local k = fromIndex
if fromIndex < 0 then
k = len + fromIndex
end
if k < 0 then
k = 0
end
for i = k + 1, len do
if self[i] == searchElement then
return true
end
end
return false
end
-- End of Lua Library inline imports
local ____exports = {}
local ____, _Mekanome = ...
local Mekanome = _Mekanome
--- Check whether or not the mob is a valid wow mob.
local function IsValid(mob)
return mob.token ~= "none" and mob.token ~= nil and Mekanome.GetObject(mob.token) ~= false
end
--- Get whether or not the mob is alive.
local function IsAlive(mob)
return UnitIsDeadOrGhost(mob.token) == false
end
--- Get whether or not the mob is a pet.
local function IsPet(mob)
return UnitIsUnit(mob.token, "pet")
end
--- Get whether or not the player can attack this mob.
local function CanAttack(mob)
local token = mob.token
local isFriend = UnitIsFriend("player", token)
if isFriend then
return false
end
local isAttackable = UnitCanAttack("player", token)
if isAttackable == false then
return false
end
local reaction = UnitReaction("player", token)
if reaction == nil then
return false
else
local isPositiveReaction = reaction >= 5
if isPositiveReaction then
return false
end
end
return true
end
--- Get the name of the mob.
local function GetName(mob)
local unitName = UnitName(mob.token)
return unitName
end
--- Get the health of the mob.
local function GetHealth(mob)
local token = mob.token
local absorbAmount = UnitGetTotalHealAbsorbs(token)
local health = UnitHealth(token, false) - absorbAmount
return health
end
--- Get the mobs current health expressed as a percentage of their max health.
local function GetHealthPercent(mob)
local health = GetHealth(mob)
local maxHealth = UnitHealthMax(mob.token)
return health / maxHealth * 100
end
--- Get the mobs default powertype, eg 1 for Rage.
local function GetPowerType(mob)
local powerType = UnitPowerType(mob.token, 0)
return powerType
end
--- Get the mobs maximum power of their default power type, or a specified one.
local function GetMaxPower(mob, powerType)
local maxPower = UnitPowerMax(
mob.token,
powerType or GetPowerType(mob),
false
)
return maxPower
end
--- Get the current value for the mobs default powertype, or a specified one.
local function GetCurrentPower(mob, powerType)
local power = UnitPower(
mob.token,
powerType or GetPowerType(mob),
false
)
return power
end
--- Get the current value for the mobs default powertype, or a specified one, expressed as a percentage of the max for that powertype.
local function GetPowerPercent(mob, powerType)
local power = GetCurrentPower(mob, powerType)
local maxPower = GetMaxPower(mob, powerType)
return power / maxPower * 100
end
--- Gets the group role of the mob if it exists.
local function GetRole(mob)
return UnitGroupRolesAssigned(mob.token)
end
--- Check whether or not the mob is in combat according to the wow api.
local function IsAffectingCombat(mob)
return UnitAffectingCombat(mob.token)
end
--- Returns the Vector3 coords of the mob.
local function GetPosition(mob)
local x, y, z = ObjectPosition(mob.token)
return Mekanome.VectorUtils.Create({x = x, y = y, z = z})
end
--- Returns distance between two mobs.
local function GetDistanceBetween(referenceMob, compareMob)
return Mekanome.VectorUtils.Distance(
GetPosition(referenceMob),
GetPosition(compareMob)
)
end
--- Get whether or not a mob can see another mob un-obstructed.
local function CanSee(reference, compare)
local ax, ay, az = ObjectPosition(reference.token)
local ah = ObjectHeight(reference.token)
local attx, atty, attz = GetUnitAttachmentPosition(compare.token, 34)
if attx == nil or ax == nil or ah == nil then
return false
end
if ax == 0 and ay == 0 and az == 0 or attx == 0 and atty == 0 and attz == 0 then
return true
end
local x, y, z = TraceLine(
ax,
ay,
az + ah,
attx,
atty,
attz,
0
)
if x ~= 0 or y ~= 0 or z ~= 0 then
return false
else
return true
end
end
--- Returns whether or not a mob is casting a spell
local function IsCasting(mob)
return ({UnitCastingInfo(mob.token)}) ~= nil
end
--- Returns whether or not a mob is channeling a spell
local function IsChanneling(mob)
return ({UnitChannelInfo(mob.token)}) ~= nil
end
--- Returns whether or not a mob is channeling or casting a spell
local function IsCastingOrChanneling(mob)
return IsChanneling(mob) or IsCasting(mob)
end
local function GetEndTime(startMs, endMs, percent)
local castLength = endMs - startMs
local startTime = startMs / 1000
local timeUntil = castLength / 1000 * ((percent or 100) / 100)
return startTime + timeUntil
end
--- If a mob is casting a spell, return the time when it will end.
--
-- @param percent : By default the cast end time is when the cast is complete. If you want to consider
-- a spell as being done once it reaches a certain percentage threshold, pass in this value.
local function GetCastEndTime(mob, percent)
local castingInfo = {UnitCastingInfo(mob.token)}
if castingInfo ~= nil then
local name = castingInfo[1]
local startTimeMS = castingInfo[4]
local endTimeMs = castingInfo[5]
if name ~= nil and startTimeMS ~= nil and endTimeMs ~= nil then
return GetEndTime(startTimeMS, endTimeMs, percent)
end
end
return nil
end
--- If a mob is channeling a spell, return the time when it will end.
--
-- @param percent : By default the cast end time is when the channel ends. If you want to consider
-- a spell as being done once it reaches a certain percentage threshold, pass in this value.
local function GetChannelEndTime(mob, percent)
local channelInfo = {UnitChannelInfo(mob.token)}
if channelInfo ~= nil then
local name = channelInfo[1]
local startTimeMS = channelInfo[4]
local endTimeMs = channelInfo[5]
if name ~= nil and startTimeMS ~= nil and endTimeMs ~= nil then
return GetEndTime(startTimeMS, endTimeMs, percent)
end
end
return nil
end
local function GetCastOrChannelEndTime(mob, percent)
return GetCastEndTime(mob, percent) or GetChannelEndTime(mob, percent)
end
--- If the mob is casting or channeling a spell, this returns the target mob of said spell.
local function GetCastTarget(mob)
local isMobCasting = IsCastingOrChanneling(mob)
if isMobCasting then
local target = ObjectCastingTarget(mob.token)
if target ~= false then
local targetMob = Mekanome.MobManager.GetMob({guid = target:guid()})
return targetMob or Mekanome.MobManager.GetMob({token = "none"})
else
return Mekanome.MobManager.GetMob({token = "none"})
end
end
return nil
end
--- If the mob is casting or channeling a spell, this returns how far into the cast the mob is, as a percentage.
local function GetCastOrChannelPercentComplete(mob)
local name = nil
local startTimeMS = nil
local endTimeMs = nil
local castingInfo = {UnitCastingInfo(mob.token)}
if castingInfo == nil then
local channelInfo = {UnitChannelInfo(mob.token)}
if channelInfo ~= nil then
name = channelInfo[1]
startTimeMS = channelInfo[4]
endTimeMs = channelInfo[5]
end
else
name = castingInfo[1]
startTimeMS = castingInfo[4]
endTimeMs = castingInfo[5]
end
if name ~= nil and startTimeMS ~= nil and endTimeMs ~= nil then
local start = startTimeMS / 1000
local finish = endTimeMs / 1000
local current = GetTime()
return (current - start) / (finish - start) * 100
end
return nil
end
--- If the mob is casting or channeling a spell, returns whether or not that spell is interruptible.
local function GetIsInterruptible(mob)
local name = nil
local notInterruptible = nil
local castingInfo = {UnitCastingInfo(mob.token)}
if castingInfo == nil then
local channelInfo = {UnitChannelInfo(mob.token)}
if channelInfo ~= nil then
name = channelInfo[1]
notInterruptible = channelInfo[7]
end
else
name = castingInfo[1]
notInterruptible = castingInfo[8]
end
if name ~= nil and notInterruptible ~= nil then
return notInterruptible == false
end
return false
end
--- Returns whether or not a mob is interruptible at a specific percentage of their cast.
--
-- @param interruptPercent The percent to check against.
-- @param ignoreInterruptible By default this will return false if the active spell is not interruptible. This bypasses that check.
local function GetIsInterruptibleAt(mob, interruptPercent, ignoreInterruptible)
if not ignoreInterruptible and GetIsInterruptible(mob) == false then
return false
end
local castPercent = GetCastOrChannelPercentComplete(mob)
if castPercent and castPercent >= interruptPercent then
return true
end
return false
end
--- Gets whether or not the mob is moving.
local function GetIsMoving(mob)
local currentSpeed = GetUnitSpeed(mob.token)
return currentSpeed > 0
end
--- Checks if a mob is facing another using their current positions.
local function IsFacingMob(reference, compare)
local rotation = ObjectRotation(reference.token)
local x, y = ObjectPosition(reference.token)
local x2, y2 = ObjectPosition(compare.token)
if not x or not x2 or not rotation then
return false
end
local angle = math.atan2(y2 - y, x2 - x) - rotation
angle = math.deg(angle)
angle = angle % 360
if angle > 180 then
angle = angle - 360
end
return math.abs(angle) < 90
end
--- Checks if a mob is behind another using their current positions.
local function IsBehindMob(reference, compare)
local rotation = ObjectRotation(reference.token)
local x, y = ObjectPosition(reference.token)
local x2, y2 = ObjectPosition(compare.token)
if not x or not x2 or not rotation then
return false
end
local angle = math.atan2(y2 - y, x2 - x) - rotation
angle = math.deg(angle)
angle = angle % 360
if angle > 180 then
angle = angle - 360
end
return math.abs(angle) > 90
end
--- Gets the model ID for the mob.
local function GetModelId(mob)
return ObjectModelId(mob.token)
end
--- Returns whether or not the mob is in a party with the player.
local function IsInParty(mob)
return UnitInParty(mob.token, nil)
end
--- Returns whether or not the mob is in a raid with the player.
local function IsInRaid(mob)
return UnitInRaid(mob.token, nil) ~= nil
end
--- Returns whether or not the mob is in a party or raid with the player.
local function IsInPartyOrRaid(mob)
return IsInParty(mob) or IsInRaid(mob)
end
--- Returns whether or not the player is mounted, or shapeshifted into a mount form.
local function IsMounted(mob)
local isMounted = UnitIsMounted(mob.token)
if isMounted then
return true
end
local mountFormIds = {3, 27, 29}
local shapeShiftId = GetShapeshiftFormID()
return shapeShiftId ~= nil and __TS__ArrayIncludes(mountFormIds, shapeShiftId)
end
--- Returns the "Combat reach" of a mob. The combat reach of a mob is a bounding radius from which distance calcs begin for the purposes of calculating combat distance.
local function GetCombatReach(mob)
return ObjectCombatReach(mob.token) or 0
end
--- Returns the distance between two mobs, with combat reach accounted for.
local function GetCombatDistanceBetween(reference, compare)
local ____ = GetDistanceBetween(reference, compare) - GetCombatReach(compare)
end
--- Gets whether or not the mob is currently online.
local function GetIsOnline(mob)
return UnitIsConnected(mob.token)
end
--- Get whether or not the mob is being resurrected.
local function HasIncomingRessurection(mob)
return IsAlive(mob) == false and UnitHasIncomingResurrection(mob.token)
end
--- Get whether or not the mob is currently targetting something.
local function HasTarget(mob)
return ObjectTarget(mob.token) ~= nil
end
--- Get the mobs current target.
local function GetTarget(mob)
local objTarget = ObjectTarget(mob.token)
if not objTarget then
return nil
end
local mobManagerMob = Mekanome.MobManager.GetMob({token = objTarget:unit()})
return mobManagerMob
end
--- Returns whether or not a mob is within a certain distance from another mob, with combat reach accounted for.
local function IsWithinCombatDistance(reference, compare, distance)
if not IsValid(compare) then
return false
end
return GetDistanceBetween(reference, compare) <= distance + GetCombatReach(compare)
end
--- Returns whether or not a mob is within a certain distance from another mob.
local function IsWithinDistance(reference, compare, distance)
return GetDistanceBetween(reference, compare) <= distance
end
--- Returns the number of loss of control effects on the mob. Interrupt lockouts included.
local function GetLossOfControlCount(mob)
return C_LossOfControl.GetActiveLossOfControlDataCountByUnit(mob.token)
end
--- Returns the mobs outgoing missiles.
local function GetOutgoingMissles(self, ____bindingPattern0)
local spellVisualId
local spellId
local target
local reference
reference = ____bindingPattern0.reference
target = ____bindingPattern0.target
spellId = ____bindingPattern0.spellId
spellVisualId = ____bindingPattern0.spellVisualId
local missiles = Missiles()
local results = {}
if type(missiles) == "table" and IsValid(reference) then
for ____, missile in pairs(missiles) do
local ____opt_0 = missile.source
local missileSource = ____opt_0 and ____opt_0:unit()
if missileSource then
local ____UnitIsUnit_result_6 = UnitIsUnit(reference.token, missileSource)
if ____UnitIsUnit_result_6 then
local ____temp_5 = not target
if not ____temp_5 then
local ____UnitIsUnit_4 = UnitIsUnit
local ____opt_2 = missile.target
____temp_5 = ____UnitIsUnit_4(
____opt_2 and ____opt_2:unit() or "none",
target.token
)
end
____UnitIsUnit_result_6 = ____temp_5
end
if ____UnitIsUnit_result_6 and (not spellId or not not spellId and spellId == missile.spellId) and (not spellVisualId or not not spellVisualId and spellVisualId == missile.spellVisualId) then
results[#results + 1] = missile
end
end
end
end
return results
end
--- Returns the mobs incoming missiles.
local function GetIncomingMissiles(____bindingPattern0)
local spellVisualId
local spellId
local source
local reference
reference = ____bindingPattern0.reference
source = ____bindingPattern0.source
spellId = ____bindingPattern0.spellId
spellVisualId = ____bindingPattern0.spellVisualId
local missiles = Missiles()
local results = {}
if type(missiles) == "table" and IsValid(reference) then
for ____, missile in pairs(missiles) do
local ____opt_7 = missile.target
local missileTarget = ____opt_7 and ____opt_7:unit()
if missileTarget and UnitIsUnit(reference.token, missileTarget) and (not source or UnitIsUnit(source.token or "none", reference.token)) and (not spellId or spellId == missile.spellId) and (not spellVisualId or spellVisualId == missile.spellVisualId) then
table.insert(results, missile)
end
end
end
return results
end
--- Gets the amount of time the mob has been in combat.
local function GetCombatTime(mob)
if not mob.lastCombatTime then
return nil
end
return GetTime() - mob.lastCombatTime
end
--- Get combat odds (if the last combat time is less than 1 minute ago return 1 / time, else return 0)
-- the closer to 0 the more likely the unit is to be in combat (0 = 100%) 60 = 0%
--
-- @param mob The mob to check
-- @returns number | undefined
local function InCombatOdds(mob)
local combatTime = GetCombatTime(mob)
if combatTime == nil then
return nil
end
local percent = 1 - combatTime / 60
return percent * 100
end
local _MobUtils = {
GetLossOfControlCount = GetLossOfControlCount,
GetTarget = GetTarget,
IsWithinCombatDistance = IsWithinCombatDistance,
IsWithinDistance = IsWithinDistance,
GetOutgoingMissles = GetOutgoingMissles,
GetIncomingMissiles = GetIncomingMissiles,
HasTarget = HasTarget,
HasIncomingRessurection = HasIncomingRessurection,
GetCombatDistanceBetween = GetCombatDistanceBetween,
GetCombatReach = GetCombatReach,
IsInPartyOrRaid = IsInPartyOrRaid,
IsMounted = IsMounted,
GetModelId = GetModelId,
IsInRaid = IsInRaid,
IsInParty = IsInParty,
IsBehindMob = IsBehindMob,
GetIsOnline = GetIsOnline,
IsFacingMob = IsFacingMob,
GetIsMoving = GetIsMoving,
GetIsInterruptible = GetIsInterruptible,
GetCastOrChannelPercentComplete = GetCastOrChannelPercentComplete,
IsCasting = IsCasting,
GetCastOrChannelEndTime = GetCastOrChannelEndTime,
IsChanneling = IsChanneling,
GetIsInterruptibleAt = GetIsInterruptibleAt,
IsCastingOrChanneling = IsCastingOrChanneling,
IsAffectingCombat = IsAffectingCombat,
GetPowerType = GetPowerType,
CanSee = CanSee,
GetMaxPower = GetMaxPower,
GetCurrentPower = GetCurrentPower,
GetPowerPercent = GetPowerPercent,
GetCastTarget = GetCastTarget,
InCombatOdds = InCombatOdds,
GetName = GetName,
CanAttack = CanAttack,
GetHealthPercent = GetHealthPercent,
IsAlive = IsAlive,
GetPosition = GetPosition,
GetDistanceBetween = GetDistanceBetween,
IsPet = IsPet,
GetRole = GetRole,
GetCombatTime = GetCombatTime,
GetChannelEndTime = GetChannelEndTime,
GetCastEndTime = GetCastEndTime
}
Mekanome.MobUtils = _MobUtils
____exports.default = {}
return ____exports

@ -0,0 +1,152 @@
--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
local ____exports = {}
local _, _Mekanome = ...
local Mekanome = _Mekanome
--- Creates a useable spell
local function Create(id, traits)
local spellInfo = C_Spell.GetSpellInfo(id)
if spellInfo == nil or spellInfo.name == nil then
error(
Mekanome.Error("Unable to find info for spell " .. tostring(id)),
0
)
end
return {
id = id,
minRange = spellInfo.minRange,
maxRange = spellInfo.maxRange,
name = spellInfo.name,
traits = traits
}
end
--- Get cooldown information for the spell.
local function GetCooldownInfo(spell)
local ____, cdDuration, enabled = GetSpellCooldown(spell.id)
local ____, ccDuration = GetSpellLossOfControlCooldown(spell.id)
local ____Mekanome_PlayerStateManager_GetPlayerState_result_0 = Mekanome.PlayerStateManager.GetPlayerState()
local spellQueueWindow = ____Mekanome_PlayerStateManager_GetPlayerState_result_0.spellQueueWindow
local secondsUntilReady = cdDuration == 0 and ccDuration or cdDuration
local secondsUntilCastable = secondsUntilReady - spellQueueWindow / 1000
return {secondsUntilReady = secondsUntilReady, secondsUntilCastable = secondsUntilCastable < 0 and 0 or secondsUntilCastable, isActive = enabled == 0}
end
--- Handles undefined / funciton traits to get a more usuable set of trait values
local function GetSpellTraits(spell)
local isCastableWhileMoving = false
local isCastableWhileCasting = false
local isOverride = false
if spell.traits ~= nil then
if spell.traits.isOverride ~= nil then
isOverride = spell.traits.isOverride
end
if spell.traits.isCastableWhileCasting ~= nil then
if type(spell.traits.isCastableWhileCasting) == "function" then
isCastableWhileCasting = spell.traits:isCastableWhileCasting()
else
isCastableWhileCasting = spell.traits.isCastableWhileCasting
end
end
if spell.traits.isCastableWhileMoving ~= nil then
if type(spell.traits.isCastableWhileMoving) == "function" then
isCastableWhileMoving = spell.traits:isCastableWhileMoving()
else
isCastableWhileMoving = spell.traits.isCastableWhileMoving
end
end
end
return {isCastableWhileMoving = isCastableWhileMoving, isCastableWhileCasting = isCastableWhileCasting, isOverride = isOverride}
end
--- Evaluates the spells traits & conditions to see if it is currently castable.
local function IsCastable(____bindingPattern0)
local spell
spell = ____bindingPattern0.spell
local target = ____bindingPattern0.target
local position = ____bindingPattern0.position
local player = Mekanome.MobManager.GetMob({token = "player"})
local ____GetSpellTraits_result_1 = GetSpellTraits(spell)
local isCastableWhileCasting = ____GetSpellTraits_result_1.isCastableWhileCasting
local isCastableWhileMoving = ____GetSpellTraits_result_1.isCastableWhileMoving
local isOverride = ____GetSpellTraits_result_1.isOverride
if player == nil then
return false
end
local traits = Mekanome.PlayerStateManager.GetPlayerState()
if isOverride == true then
if IsSpellKnownOrOverridesKnown(spell.id) ~= true then
return false
end
else
if IsSpellKnown(spell.id) ~= true then
return false
end
end
local ____GetCooldownInfo_result_2 = GetCooldownInfo(spell)
local isActive = ____GetCooldownInfo_result_2.isActive
local secondsUntilCastable = ____GetCooldownInfo_result_2.secondsUntilCastable
print("COOLDOWN: " .. tostring(secondsUntilCastable))
if isActive then
return false
end
if secondsUntilCastable ~= 0 then
return false
end
if traits.isMoving and isCastableWhileMoving == false then
return false
end
if traits.isCasting == true and isCastableWhileCasting == false then
return false
end
return true
end
--- Get the localized name of the spell.
local function GetName(spell)
local name = C_Spell.GetSpellInfo(spell.id).name
return name
end
--- Gets the action button binding for the provided spell.
local function GetBindingForSpell(spell)
local binding = Mekanome.BindingsManager.GetBindingForSpell(spell)
if binding == nil then
error(
Mekanome.Error(("Unable to cast " .. GetName(spell)) .. " without binding."),
0
)
end
return binding
end
--- Generates a castable to cast the spell on the player
local function SelfCast(spell)
local binding = GetBindingForSpell(spell)
if IsCastable({spell = spell}) == false then
return nil
end
return {id = spell.id, variant = "self", binding = binding}
end
--- Generates a castable to cast the spell at the specified target.
local function TargetCast(spell, _target)
local binding = GetBindingForSpell(spell)
local target = type(_target) == "function" and _target(nil) or _target
if IsCastable({spell = spell, target = target}) == false then
return nil
end
return {id = spell.id, variant = "target", binding = binding, target = target}
end
--- Generates a castable to cast the spell at the specified location
local function GroundCast(spell, _position)
local binding = GetBindingForSpell(spell)
local position = type(_position) == "function" and _position(nil) or _position
if IsCastable({spell = spell, position = position}) == false then
return nil
end
return {id = spell.id, variant = "ground", binding = binding, position = position}
end
local _SpellUtils = {
Create = Create,
GetName = GetName,
GetCooldownInfo = GetCooldownInfo,
SelfCast = SelfCast,
GroundCast = GroundCast,
TargetCast = TargetCast
}
Mekanome.SpellUtils = _SpellUtils
____exports.default = {}
return ____exports

@ -0,0 +1,163 @@
--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
local ____exports = {}
local _, _Mekanome = ...
local Mekanome = _Mekanome
local function Create(____bindingPattern0)
local z
local y
local x
x = ____bindingPattern0.x
y = ____bindingPattern0.y
z = ____bindingPattern0.z
return {x = x, y = y, z = z}
end
local function Add(a, b)
if type(b) == "number" then
return Create({x = a.x + b, y = a.y + b, z = a.z + b})
end
return Create({x = a.x + b.x, y = a.y + b.y, z = a.z + b.z})
end
local function Subtract(a, b)
if type(b) == "number" then
return Create({x = a.x - b, y = a.y - b, z = a.z - b})
end
return Create({x = a.x - b.x, y = a.y - b.y, z = a.z - b.z})
end
local function Multiply(a, b)
if type(b) == "number" then
return Create({x = a.x * b, y = a.y * b, z = a.z * b})
end
return Create({x = a.x * b.x, y = a.y * b.y, z = a.z * b.z})
end
local function Divide(a, b)
if type(b) == "number" then
return Create({x = a.x / b, y = a.y / b, z = a.z / b})
end
return Create({x = a.x / b.x, y = a.y / b.y, z = a.z / b.z})
end
local function Equals(a, b)
return a.x == b.x and a.y == b.y and a.z == b.z
end
local function LessThan(a, b)
return a.x < b.x and a.y < b.y and a.z < b.z
end
local function LessThanOrEqualTo(a, b)
return a.x <= b.x and a.y <= b.y and a.z <= b.z
end
local function Dot(a, b)
return a.x * b.x + a.y * b.y + a.z + b.z
end
local function Cross(a, b)
return Create({x = a.y * b.z - a.z * b.y, y = a.z * b.x - a.x * b.z, z = a.x * b.y - a.y * b.x})
end
local function Distance(a, b)
return FastDistance(
a.x,
a.y,
a.z,
b.x,
b.y,
b.z
)
end
local function NormalizeOrientation(orientation)
if orientation < 0 then
local mod = orientation * -1
mod = mod % (2 * math.pi)
mod = -mod + 2 * math.pi
return mod
else
return orientation % (2 * math.pi)
end
end
local function GetAbsoluteAngle(a, b)
return NormalizeOrientation(math.atan2(b.y - a.y, b.x - a.x))
end
local function Angle(a, b)
return math.acos(Dot(a, b) / (math.sqrt(a.x * a.x + a.y * a.y + a.z * a.z) * math.sqrt(b.x * b.x + b.y * b.y + b.z * b.z)))
end
local function _GetNormalized(vector)
local length = math.sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z)
return Create({x = vector.x / length, y = vector.y / length, z = vector.z / length})
end
local function ClampMagnitude(vector, maxLength)
if Dot(vector, vector) > maxLength * maxLength then
return Multiply(
_GetNormalized(vector),
maxLength
)
else
return vector
end
end
local function _GetMagnitude(vector)
return math.sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z)
end
local function DirectionOrZero(vector)
local magnitude = _GetMagnitude(vector)
if magnitude < 1e-7 then
return Create({x = 0, y = 0, z = 0})
elseif magnitude < 1.00001 and magnitude > 0.99999 then
return vector
else
return Multiply(vector, 1 / magnitude)
end
end
local function Clamp(x, min, max)
return x < min and min or (x > max and max or x)
end
local function Lerp(a, b, _t)
local t = Clamp(_t, 0, 1)
return Create({x = a.x + (b.x - a.x) * t, y = a.y + (b.y - a.y) * t, z = a.z + (b.z - a.z) * t})
end
local function MoveTowards(reference, target, maxDistanceDelta)
local toVector = Subtract(target, reference)
local distance = _GetMagnitude(toVector)
if distance <= maxDistanceDelta or distance == 0 then
return target
end
return Divide(
Add(reference, toVector),
distance * maxDistanceDelta
)
end
local function Project(reference, normal)
local num = Dot(normal, normal)
if num < 1.401298e-45 then
return Create({x = 0, y = 0, z = 0})
end
return Multiply(
normal,
Dot(reference, normal) / num
)
end
local function ProjectOnPlane(reference, plane)
return Subtract(
reference,
Project(reference, plane)
)
end
local _VectorUtils = {
Create = Create,
Add = Add,
Subtract = Subtract,
Multiply = Multiply,
Divide = Divide,
Equals = Equals,
LessThan = LessThan,
LessThanOrEqualTo = LessThanOrEqualTo,
Dot = Dot,
Cross = Cross,
Distance = Distance,
Angle = Angle,
GetAbsoluteAngle = GetAbsoluteAngle,
ClampMagnitude = ClampMagnitude,
DirectionOrZero = DirectionOrZero,
Clamp = Clamp,
Lerp = Lerp,
MoveTowards = MoveTowards,
ProjectOnPlane = ProjectOnPlane
}
Mekanome.VectorUtils = _VectorUtils
____exports.default = {}
return ____exports

@ -0,0 +1 @@
Subproject commit 31f933887a8f4fadd7740e8beae2d6c248023238

@ -0,0 +1,14 @@
# Copyright (C) 2007-2013 LuaDist.
# Created by Peter Drahoš
# Redistribution and use of this file is allowed according to the terms of the MIT license.
# For details see the COPYRIGHT file distributed with LuaDist.
# Please note that the package source code is licensed under its own license.
project ( dkjson NONE )
cmake_minimum_required ( VERSION 2.8 )
include ( cmake/dist.cmake )
include ( lua )
install_lua_module ( dkjson dkjson.lua )
install_example ( jsontest.lua speedtest.lua )
install_data ( versions.txt )

@ -0,0 +1,118 @@
# Locate Lua library
# This module defines
# LUA_EXECUTABLE, if found
# LUA_FOUND, if false, do not try to link to Lua
# LUA_LIBRARIES
# LUA_INCLUDE_DIR, where to find lua.h
# LUA_VERSION_STRING, the version of Lua found (since CMake 2.8.8)
#
# Note that the expected include convention is
# #include "lua.h"
# and not
# #include <lua/lua.h>
# This is because, the lua location is not standardized and may exist
# in locations other than lua/
#=============================================================================
# Copyright 2007-2009 Kitware, Inc.
# Modified to support Lua 5.2 by LuaDist 2012
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
# (To distribute this file outside of CMake, substitute the full
# License text for the above reference.)
#
# The required version of Lua can be specified using the
# standard syntax, e.g. FIND_PACKAGE(Lua 5.1)
# Otherwise the module will search for any available Lua implementation
# Always search for non-versioned lua first (recommended)
SET(_POSSIBLE_LUA_INCLUDE include include/lua)
SET(_POSSIBLE_LUA_EXECUTABLE lua)
SET(_POSSIBLE_LUA_LIBRARY lua)
# Determine possible naming suffixes (there is no standard for this)
IF(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR)
SET(_POSSIBLE_SUFFIXES "${Lua_FIND_VERSION_MAJOR}${Lua_FIND_VERSION_MINOR}" "${Lua_FIND_VERSION_MAJOR}.${Lua_FIND_VERSION_MINOR}" "-${Lua_FIND_VERSION_MAJOR}.${Lua_FIND_VERSION_MINOR}")
ELSE(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR)
SET(_POSSIBLE_SUFFIXES "52" "5.2" "-5.2" "51" "5.1" "-5.1")
ENDIF(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR)
# Set up possible search names and locations
FOREACH(_SUFFIX ${_POSSIBLE_SUFFIXES})
LIST(APPEND _POSSIBLE_LUA_INCLUDE "include/lua${_SUFFIX}")
LIST(APPEND _POSSIBLE_LUA_EXECUTABLE "lua${_SUFFIX}")
LIST(APPEND _POSSIBLE_LUA_LIBRARY "lua${_SUFFIX}")
ENDFOREACH(_SUFFIX)
# Find the lua executable
FIND_PROGRAM(LUA_EXECUTABLE
NAMES ${_POSSIBLE_LUA_EXECUTABLE}
)
# Find the lua header
FIND_PATH(LUA_INCLUDE_DIR lua.h
HINTS
$ENV{LUA_DIR}
PATH_SUFFIXES ${_POSSIBLE_LUA_INCLUDE}
PATHS
~/Library/Frameworks
/Library/Frameworks
/usr/local
/usr
/sw # Fink
/opt/local # DarwinPorts
/opt/csw # Blastwave
/opt
)
# Find the lua library
FIND_LIBRARY(LUA_LIBRARY
NAMES ${_POSSIBLE_LUA_LIBRARY}
HINTS
$ENV{LUA_DIR}
PATH_SUFFIXES lib64 lib
PATHS
~/Library/Frameworks
/Library/Frameworks
/usr/local
/usr
/sw
/opt/local
/opt/csw
/opt
)
IF(LUA_LIBRARY)
# include the math library for Unix
IF(UNIX AND NOT APPLE)
FIND_LIBRARY(LUA_MATH_LIBRARY m)
SET( LUA_LIBRARIES "${LUA_LIBRARY};${LUA_MATH_LIBRARY}" CACHE STRING "Lua Libraries")
# For Windows and Mac, don't need to explicitly include the math library
ELSE(UNIX AND NOT APPLE)
SET( LUA_LIBRARIES "${LUA_LIBRARY}" CACHE STRING "Lua Libraries")
ENDIF(UNIX AND NOT APPLE)
ENDIF(LUA_LIBRARY)
# Determine Lua version
IF(LUA_INCLUDE_DIR AND EXISTS "${LUA_INCLUDE_DIR}/lua.h")
FILE(STRINGS "${LUA_INCLUDE_DIR}/lua.h" lua_version_str REGEX "^#define[ \t]+LUA_RELEASE[ \t]+\"Lua .+\"")
STRING(REGEX REPLACE "^#define[ \t]+LUA_RELEASE[ \t]+\"Lua ([^\"]+)\".*" "\\1" LUA_VERSION_STRING "${lua_version_str}")
UNSET(lua_version_str)
ENDIF()
INCLUDE(FindPackageHandleStandardArgs)
# handle the QUIETLY and REQUIRED arguments and set LUA_FOUND to TRUE if
# all listed variables are TRUE
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Lua
REQUIRED_VARS LUA_LIBRARIES LUA_INCLUDE_DIR
VERSION_VAR LUA_VERSION_STRING)
MARK_AS_ADVANCED(LUA_INCLUDE_DIR LUA_LIBRARIES LUA_LIBRARY LUA_MATH_LIBRARY LUA_EXECUTABLE)

@ -0,0 +1,321 @@
# LuaDist CMake utility library.
# Provides sane project defaults and macros common to LuaDist CMake builds.
#
# Copyright (C) 2007-2012 LuaDist.
# by David Manura, Peter Drahoš
# Redistribution and use of this file is allowed according to the terms of the MIT license.
# For details see the COPYRIGHT file distributed with LuaDist.
# Please note that the package source code is licensed under its own license.
## Extract information from dist.info
if ( NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/dist.info )
message ( FATAL_ERROR
"Missing dist.info file (${CMAKE_CURRENT_SOURCE_DIR}/dist.info)." )
endif ()
file ( READ ${CMAKE_CURRENT_SOURCE_DIR}/dist.info DIST_INFO )
if ( "${DIST_INFO}" STREQUAL "" )
message ( FATAL_ERROR "Failed to load dist.info." )
endif ()
# Reads field `name` from dist.info string `DIST_INFO` into variable `var`.
macro ( _parse_dist_field name var )
string ( REGEX REPLACE ".*${name}[ \t]?=[ \t]?[\"']([^\"']+)[\"'].*" "\\1"
${var} "${DIST_INFO}" )
if ( ${var} STREQUAL DIST_INFO )
message ( FATAL_ERROR "Failed to extract \"${var}\" from dist.info" )
endif ()
endmacro ()
#
_parse_dist_field ( name DIST_NAME )
_parse_dist_field ( version DIST_VERSION )
_parse_dist_field ( license DIST_LICENSE )
_parse_dist_field ( author DIST_AUTHOR )
_parse_dist_field ( maintainer DIST_MAINTAINER )
_parse_dist_field ( url DIST_URL )
_parse_dist_field ( desc DIST_DESC )
message ( "DIST_NAME: ${DIST_NAME}")
message ( "DIST_VERSION: ${DIST_VERSION}")
message ( "DIST_LICENSE: ${DIST_LICENSE}")
message ( "DIST_AUTHOR: ${DIST_AUTHOR}")
message ( "DIST_MAINTAINER: ${DIST_MAINTAINER}")
message ( "DIST_URL: ${DIST_URL}")
message ( "DIST_DESC: ${DIST_DESC}")
string ( REGEX REPLACE ".*depends[ \t]?=[ \t]?[\"']([^\"']+)[\"'].*" "\\1"
DIST_DEPENDS ${DIST_INFO} )
if ( DIST_DEPENDS STREQUAL DIST_INFO )
set ( DIST_DEPENDS "" )
endif ()
message ( "DIST_DEPENDS: ${DIST_DEPENDS}")
## 2DO: Parse DIST_DEPENDS and try to install Dependencies with automatically using externalproject_add
## INSTALL DEFAULTS (Relative to CMAKE_INSTALL_PREFIX)
# Primary paths
set ( INSTALL_BIN bin CACHE PATH "Where to install binaries to." )
set ( INSTALL_LIB lib CACHE PATH "Where to install libraries to." )
set ( INSTALL_INC include CACHE PATH "Where to install headers to." )
set ( INSTALL_ETC etc CACHE PATH "Where to store configuration files" )
set ( INSTALL_SHARE share CACHE PATH "Directory for shared data." )
# Secondary paths
option ( INSTALL_VERSION
"Install runtime libraries and executables with version information." OFF)
set ( INSTALL_DATA ${INSTALL_SHARE}/${DIST_NAME} CACHE PATH
"Directory the package can store documentation, tests or other data in.")
set ( INSTALL_DOC ${INSTALL_DATA}/doc CACHE PATH
"Recommended directory to install documentation into.")
set ( INSTALL_EXAMPLE ${INSTALL_DATA}/example CACHE PATH
"Recommended directory to install examples into.")
set ( INSTALL_TEST ${INSTALL_DATA}/test CACHE PATH
"Recommended directory to install tests into.")
set ( INSTALL_FOO ${INSTALL_DATA}/etc CACHE PATH
"Where to install additional files")
# Tweaks and other defaults
# Setting CMAKE to use loose block and search for find modules in source directory
set ( CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS true )
set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH} )
option ( BUILD_SHARED_LIBS "Build shared libraries" ON )
# In MSVC, prevent warnings that can occur when using standard libraries.
if ( MSVC )
add_definitions ( -D_CRT_SECURE_NO_WARNINGS )
endif ()
# RPath and relative linking
option ( USE_RPATH "Use relative linking." ON)
if ( USE_RPATH )
string ( REGEX REPLACE "[^!/]+" ".." UP_DIR ${INSTALL_BIN} )
set ( CMAKE_SKIP_BUILD_RPATH FALSE CACHE STRING "" FORCE )
set ( CMAKE_BUILD_WITH_INSTALL_RPATH FALSE CACHE STRING "" FORCE )
set ( CMAKE_INSTALL_RPATH $ORIGIN/${UP_DIR}/${INSTALL_LIB}
CACHE STRING "" FORCE )
set ( CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE CACHE STRING "" FORCE )
set ( CMAKE_INSTALL_NAME_DIR @executable_path/${UP_DIR}/${INSTALL_LIB}
CACHE STRING "" FORCE )
endif ()
## MACROS
# Parser macro
macro ( parse_arguments prefix arg_names option_names)
set ( DEFAULT_ARGS )
foreach ( arg_name ${arg_names} )
set ( ${prefix}_${arg_name} )
endforeach ()
foreach ( option ${option_names} )
set ( ${prefix}_${option} FALSE )
endforeach ()
set ( current_arg_name DEFAULT_ARGS )
set ( current_arg_list )
foreach ( arg ${ARGN} )
set ( larg_names ${arg_names} )
list ( FIND larg_names "${arg}" is_arg_name )
if ( is_arg_name GREATER -1 )
set ( ${prefix}_${current_arg_name} ${current_arg_list} )
set ( current_arg_name ${arg} )
set ( current_arg_list )
else ()
set ( loption_names ${option_names} )
list ( FIND loption_names "${arg}" is_option )
if ( is_option GREATER -1 )
set ( ${prefix}_${arg} TRUE )
else ()
set ( current_arg_list ${current_arg_list} ${arg} )
endif ()
endif ()
endforeach ()
set ( ${prefix}_${current_arg_name} ${current_arg_list} )
endmacro ()
# install_executable ( executable_targets )
# Installs any executables generated using "add_executable".
# USE: install_executable ( lua )
# NOTE: subdirectories are NOT supported
set ( CPACK_COMPONENT_RUNTIME_DISPLAY_NAME "${DIST_NAME} Runtime" )
set ( CPACK_COMPONENT_RUNTIME_DESCRIPTION
"Executables and runtime libraries. Installed into ${INSTALL_BIN}." )
macro ( install_executable )
foreach ( _file ${ARGN} )
if ( INSTALL_VERSION )
set_target_properties ( ${_file} PROPERTIES VERSION ${DIST_VERSION}
SOVERSION ${DIST_VERSION} )
endif ()
install ( TARGETS ${_file} RUNTIME DESTINATION ${INSTALL_BIN}
COMPONENT Runtime )
endforeach()
endmacro ()
# install_library ( library_targets )
# Installs any libraries generated using "add_library" into apropriate places.
# USE: install_library ( libexpat )
# NOTE: subdirectories are NOT supported
set ( CPACK_COMPONENT_LIBRARY_DISPLAY_NAME "${DIST_NAME} Development Libraries" )
set ( CPACK_COMPONENT_LIBRARY_DESCRIPTION
"Static and import libraries needed for development. Installed into ${INSTALL_LIB} or ${INSTALL_BIN}." )
macro ( install_library )
foreach ( _file ${ARGN} )
if ( INSTALL_VERSION )
set_target_properties ( ${_file} PROPERTIES VERSION ${DIST_VERSION}
SOVERSION ${DIST_VERSION} )
endif ()
install ( TARGETS ${_file}
RUNTIME DESTINATION ${INSTALL_BIN} COMPONENT Runtime
LIBRARY DESTINATION ${INSTALL_LIB} COMPONENT Runtime
ARCHIVE DESTINATION ${INSTALL_LIB} COMPONENT Library )
endforeach()
endmacro ()
# helper function for various install_* functions, for PATTERN/REGEX args.
macro ( _complete_install_args )
if ( NOT("${_ARG_PATTERN}" STREQUAL "") )
set ( _ARG_PATTERN PATTERN ${_ARG_PATTERN} )
endif ()
if ( NOT("${_ARG_REGEX}" STREQUAL "") )
set ( _ARG_REGEX REGEX ${_ARG_REGEX} )
endif ()
endmacro ()
# install_header ( files/directories [INTO destination] )
# Install a directories or files into header destination.
# USE: install_header ( lua.h luaconf.h ) or install_header ( GL )
# USE: install_header ( mylib.h INTO mylib )
# For directories, supports optional PATTERN/REGEX arguments like install().
set ( CPACK_COMPONENT_HEADER_DISPLAY_NAME "${DIST_NAME} Development Headers" )
set ( CPACK_COMPONENT_HEADER_DESCRIPTION
"Headers needed for development. Installed into ${INSTALL_INC}." )
macro ( install_header )
parse_arguments ( _ARG "INTO;PATTERN;REGEX" "" ${ARGN} )
_complete_install_args()
foreach ( _file ${_ARG_DEFAULT_ARGS} )
if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" )
install ( DIRECTORY ${_file} DESTINATION ${INSTALL_INC}/${_ARG_INTO}
COMPONENT Header ${_ARG_PATTERN} ${_ARG_REGEX} )
else ()
install ( FILES ${_file} DESTINATION ${INSTALL_INC}/${_ARG_INTO}
COMPONENT Header )
endif ()
endforeach()
endmacro ()
# install_data ( files/directories [INTO destination] )
# This installs additional data files or directories.
# USE: install_data ( extra data.dat )
# USE: install_data ( image1.png image2.png INTO images )
# For directories, supports optional PATTERN/REGEX arguments like install().
set ( CPACK_COMPONENT_DATA_DISPLAY_NAME "${DIST_NAME} Data" )
set ( CPACK_COMPONENT_DATA_DESCRIPTION
"Application data. Installed into ${INSTALL_DATA}." )
macro ( install_data )
parse_arguments ( _ARG "INTO;PATTERN;REGEX" "" ${ARGN} )
_complete_install_args()
foreach ( _file ${_ARG_DEFAULT_ARGS} )
if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" )
install ( DIRECTORY ${_file}
DESTINATION ${INSTALL_DATA}/${_ARG_INTO}
COMPONENT Data ${_ARG_PATTERN} ${_ARG_REGEX} )
else ()
install ( FILES ${_file} DESTINATION ${INSTALL_DATA}/${_ARG_INTO}
COMPONENT Data )
endif ()
endforeach()
endmacro ()
# INSTALL_DOC ( files/directories [INTO destination] )
# This installs documentation content
# USE: install_doc ( doc/ doc.pdf )
# USE: install_doc ( index.html INTO html )
# For directories, supports optional PATTERN/REGEX arguments like install().
set ( CPACK_COMPONENT_DOCUMENTATION_DISPLAY_NAME "${DIST_NAME} Documentation" )
set ( CPACK_COMPONENT_DOCUMENTATION_DESCRIPTION
"Application documentation. Installed into ${INSTALL_DOC}." )
macro ( install_doc )
parse_arguments ( _ARG "INTO;PATTERN;REGEX" "" ${ARGN} )
_complete_install_args()
foreach ( _file ${_ARG_DEFAULT_ARGS} )
if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" )
install ( DIRECTORY ${_file} DESTINATION ${INSTALL_DOC}/${_ARG_INTO}
COMPONENT Documentation ${_ARG_PATTERN} ${_ARG_REGEX} )
else ()
install ( FILES ${_file} DESTINATION ${INSTALL_DOC}/${_ARG_INTO}
COMPONENT Documentation )
endif ()
endforeach()
endmacro ()
# install_example ( files/directories [INTO destination] )
# This installs additional examples
# USE: install_example ( examples/ exampleA )
# USE: install_example ( super_example super_data INTO super)
# For directories, supports optional PATTERN/REGEX argument like install().
set ( CPACK_COMPONENT_EXAMPLE_DISPLAY_NAME "${DIST_NAME} Examples" )
set ( CPACK_COMPONENT_EXAMPLE_DESCRIPTION
"Examples and their associated data. Installed into ${INSTALL_EXAMPLE}." )
macro ( install_example )
parse_arguments ( _ARG "INTO;PATTERN;REGEX" "" ${ARGN} )
_complete_install_args()
foreach ( _file ${_ARG_DEFAULT_ARGS} )
if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" )
install ( DIRECTORY ${_file} DESTINATION ${INSTALL_EXAMPLE}/${_ARG_INTO}
COMPONENT Example ${_ARG_PATTERN} ${_ARG_REGEX} )
else ()
install ( FILES ${_file} DESTINATION ${INSTALL_EXAMPLE}/${_ARG_INTO}
COMPONENT Example )
endif ()
endforeach()
endmacro ()
# install_test ( files/directories [INTO destination] )
# This installs tests and test files, DOES NOT EXECUTE TESTS
# USE: install_test ( my_test data.sql )
# USE: install_test ( feature_x_test INTO x )
# For directories, supports optional PATTERN/REGEX argument like install().
set ( CPACK_COMPONENT_TEST_DISPLAY_NAME "${DIST_NAME} Tests" )
set ( CPACK_COMPONENT_TEST_DESCRIPTION
"Tests and associated data. Installed into ${INSTALL_TEST}." )
macro ( install_test )
parse_arguments ( _ARG "INTO;PATTERN;REGEX" "" ${ARGN} )
_complete_install_args()
foreach ( _file ${_ARG_DEFAULT_ARGS} )
if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" )
install ( DIRECTORY ${_file} DESTINATION ${INSTALL_TEST}/${_ARG_INTO}
COMPONENT Test ${_ARG_PATTERN} ${_ARG_REGEX} )
else ()
install ( FILES ${_file} DESTINATION ${INSTALL_TEST}/${_ARG_INTO}
COMPONENT Test )
endif ()
endforeach()
endmacro ()
# install_foo ( files/directories [INTO destination] )
# This installs optional or otherwise unneeded content
# USE: install_foo ( etc/ example.doc )
# USE: install_foo ( icon.png logo.png INTO icons)
# For directories, supports optional PATTERN/REGEX argument like install().
set ( CPACK_COMPONENT_OTHER_DISPLAY_NAME "${DIST_NAME} Unspecified Content" )
set ( CPACK_COMPONENT_OTHER_DESCRIPTION
"Other unspecified content. Installed into ${INSTALL_FOO}." )
macro ( install_foo )
parse_arguments ( _ARG "INTO;PATTERN;REGEX" "" ${ARGN} )
_complete_install_args()
foreach ( _file ${_ARG_DEFAULT_ARGS} )
if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" )
install ( DIRECTORY ${_file} DESTINATION ${INSTALL_FOO}/${_ARG_INTO}
COMPONENT Other ${_ARG_PATTERN} ${_ARG_REGEX} )
else ()
install ( FILES ${_file} DESTINATION ${INSTALL_FOO}/${_ARG_INTO}
COMPONENT Other )
endif ()
endforeach()
endmacro ()
## CTest defaults
## CPack defaults
set ( CPACK_GENERATOR "ZIP" )
set ( CPACK_STRIP_FILES TRUE )
set ( CPACK_PACKAGE_NAME "${DIST_NAME}" )
set ( CPACK_PACKAGE_VERSION "${DIST_VERSION}")
set ( CPACK_PACKAGE_VENDOR "LuaDist" )
set ( CPACK_COMPONENTS_ALL Runtime Library Header Data Documentation Example Other )
include ( CPack )

@ -0,0 +1,390 @@
# LuaDist CMake utility library for Lua.
#
# Copyright (C) 2007-2012 LuaDist.
# by David Manura, Peter Drahos
# Redistribution and use of this file is allowed according to the terms of the MIT license.
# For details see the COPYRIGHT file distributed with LuaDist.
# Please note that the package source code is licensed under its own license.
set ( INSTALL_LMOD ${INSTALL_LIB}/lua
CACHE PATH "Directory to install Lua modules." )
set ( INSTALL_CMOD ${INSTALL_LIB}/lua
CACHE PATH "Directory to install Lua binary modules." )
option ( SKIP_LUA_WRAPPER
"Do not build and install Lua executable wrappers." OFF)
# List of (Lua module name, file path) pairs.
# Used internally by add_lua_test. Built by add_lua_module.
set ( _lua_modules )
# utility function: appends path `path` to path `basepath`, properly
# handling cases when `path` may be relative or absolute.
macro ( _append_path basepath path result )
if ( IS_ABSOLUTE "${path}" )
set ( ${result} "${path}" )
else ()
set ( ${result} "${basepath}/${path}" )
endif ()
endmacro ()
# install_lua_executable ( target source )
# Automatically generate a binary wrapper for lua application and install it
# The wrapper and the source of the application will be placed into /bin
# If the application source did not have .lua suffix then it will be added
# USE: lua_executable ( sputnik src/sputnik.lua )
macro ( install_lua_executable _name _source )
get_filename_component ( _source_name ${_source} NAME_WE )
if ( NOT SKIP_LUA_WRAPPER )
enable_language ( C )
find_package ( Lua REQUIRED )
include_directories ( ${LUA_INCLUDE_DIR} )
set ( _wrapper ${CMAKE_CURRENT_BINARY_DIR}/${_name}.c )
set ( _code
"// Not so simple executable wrapper for Lua apps
#include <stdio.h>
#include <signal.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
lua_State *L\;
static int getargs (lua_State *L, char **argv, int n) {
int narg\;
int i\;
int argc = 0\;
while (argv[argc]) argc++\;
narg = argc - (n + 1)\;
luaL_checkstack(L, narg + 3, \"too many arguments to script\")\;
for (i=n+1\; i < argc\; i++)
lua_pushstring(L, argv[i])\;
lua_createtable(L, narg, n + 1)\;
for (i=0\; i < argc\; i++) {
lua_pushstring(L, argv[i])\;
lua_rawseti(L, -2, i - n)\;
}
return narg\;
}
static void lstop (lua_State *L, lua_Debug *ar) {
(void)ar\;
lua_sethook(L, NULL, 0, 0)\;
luaL_error(L, \"interrupted!\")\;
}
static void laction (int i) {
signal(i, SIG_DFL)\;
lua_sethook(L, lstop, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1)\;
}
static void l_message (const char *pname, const char *msg) {
if (pname) fprintf(stderr, \"%s: \", pname)\;
fprintf(stderr, \"%s\\n\", msg)\;
fflush(stderr)\;
}
static int report (lua_State *L, int status) {
if (status && !lua_isnil(L, -1)) {
const char *msg = lua_tostring(L, -1)\;
if (msg == NULL) msg = \"(error object is not a string)\"\;
l_message(\"${_source_name}\", msg)\;
lua_pop(L, 1)\;
}
return status\;
}
static int traceback (lua_State *L) {
if (!lua_isstring(L, 1))
return 1\;
lua_getfield(L, LUA_GLOBALSINDEX, \"debug\")\;
if (!lua_istable(L, -1)) {
lua_pop(L, 1)\;
return 1\;
}
lua_getfield(L, -1, \"traceback\")\;
if (!lua_isfunction(L, -1)) {
lua_pop(L, 2)\;
return 1\;
}
lua_pushvalue(L, 1)\;
lua_pushinteger(L, 2)\;
lua_call(L, 2, 1)\;
return 1\;
}
static int docall (lua_State *L, int narg, int clear) {
int status\;
int base = lua_gettop(L) - narg\;
lua_pushcfunction(L, traceback)\;
lua_insert(L, base)\;
signal(SIGINT, laction)\;
status = lua_pcall(L, narg, (clear ? 0 : LUA_MULTRET), base)\;
signal(SIGINT, SIG_DFL)\;
lua_remove(L, base)\;
if (status != 0) lua_gc(L, LUA_GCCOLLECT, 0)\;
return status\;
}
int main (int argc, char **argv) {
L=lua_open()\;
lua_gc(L, LUA_GCSTOP, 0)\;
luaL_openlibs(L)\;
lua_gc(L, LUA_GCRESTART, 0)\;
int narg = getargs(L, argv, 0)\;
lua_setglobal(L, \"arg\")\;
// Script
char script[500] = \"./${_source_name}.lua\"\;
lua_getglobal(L, \"_PROGDIR\")\;
if (lua_isstring(L, -1)) {
sprintf( script, \"%s/${_source_name}.lua\", lua_tostring(L, -1))\;
}
lua_pop(L, 1)\;
// Run
int status = luaL_loadfile(L, script)\;
lua_insert(L, -(narg+1))\;
if (status == 0)
status = docall(L, narg, 0)\;
else
lua_pop(L, narg)\;
report(L, status)\;
lua_close(L)\;
return status\;
};
")
file ( WRITE ${_wrapper} ${_code} )
add_executable ( ${_name} ${_wrapper} )
target_link_libraries ( ${_name} ${LUA_LIBRARY} )
install ( TARGETS ${_name} DESTINATION ${INSTALL_BIN} )
endif()
install ( PROGRAMS ${_source} DESTINATION ${INSTALL_BIN}
RENAME ${_source_name}.lua )
endmacro ()
macro ( _lua_module_helper is_install _name )
parse_arguments ( _MODULE "LINK;ALL_IN_ONE" "" ${ARGN} )
# _target is CMake-compatible target name for module (e.g. socket_core).
# _module is relative path of target (e.g. socket/core),
# without extension (e.g. .lua/.so/.dll).
# _MODULE_SRC is list of module source files (e.g. .lua and .c files).
# _MODULE_NAMES is list of module names (e.g. socket.core).
if ( _MODULE_ALL_IN_ONE )
string ( REGEX REPLACE "\\..*" "" _target "${_name}" )
string ( REGEX REPLACE "\\..*" "" _module "${_name}" )
set ( _target "${_target}_all_in_one")
set ( _MODULE_SRC ${_MODULE_ALL_IN_ONE} )
set ( _MODULE_NAMES ${_name} ${_MODULE_DEFAULT_ARGS} )
else ()
string ( REPLACE "." "_" _target "${_name}" )
string ( REPLACE "." "/" _module "${_name}" )
set ( _MODULE_SRC ${_MODULE_DEFAULT_ARGS} )
set ( _MODULE_NAMES ${_name} )
endif ()
if ( NOT _MODULE_SRC )
message ( FATAL_ERROR "no module sources specified" )
endif ()
list ( GET _MODULE_SRC 0 _first_source )
get_filename_component ( _ext ${_first_source} EXT )
if ( _ext STREQUAL ".lua" ) # Lua source module
list ( LENGTH _MODULE_SRC _len )
if ( _len GREATER 1 )
message ( FATAL_ERROR "more than one source file specified" )
endif ()
set ( _module "${_module}.lua" )
get_filename_component ( _module_dir ${_module} PATH )
get_filename_component ( _module_filename ${_module} NAME )
_append_path ( "${CMAKE_CURRENT_SOURCE_DIR}" "${_first_source}" _module_path )
list ( APPEND _lua_modules "${_name}" "${_module_path}" )
if ( ${is_install} )
install ( FILES ${_first_source} DESTINATION ${INSTALL_LMOD}/${_module_dir}
RENAME ${_module_filename} )
endif ()
else () # Lua C binary module
enable_language ( C )
find_package ( Lua REQUIRED )
include_directories ( ${LUA_INCLUDE_DIR} )
set ( _module "${_module}${CMAKE_SHARED_MODULE_SUFFIX}" )
get_filename_component ( _module_dir ${_module} PATH )
get_filename_component ( _module_filenamebase ${_module} NAME_WE )
foreach ( _thisname ${_MODULE_NAMES} )
list ( APPEND _lua_modules "${_thisname}"
"${CMAKE_CURRENT_BINARY_DIR}/\${CMAKE_CFG_INTDIR}/${_module}" )
endforeach ()
add_library( ${_target} MODULE ${_MODULE_SRC})
target_link_libraries ( ${_target} ${LUA_LIBRARY} ${_MODULE_LINK} )
set_target_properties ( ${_target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY
"${_module_dir}" PREFIX "" OUTPUT_NAME "${_module_filenamebase}" )
if ( ${is_install} )
install ( TARGETS ${_target} DESTINATION ${INSTALL_CMOD}/${_module_dir})
endif ()
endif ()
endmacro ()
# add_lua_module
# Builds a Lua source module into a destination locatable by Lua
# require syntax.
# Binary modules are also supported where this function takes sources and
# libraries to compile separated by LINK keyword.
# USE: add_lua_module ( socket.http src/http.lua )
# USE2: add_lua_module ( mime.core src/mime.c )
# USE3: add_lua_module ( socket.core ${SRC_SOCKET} LINK ${LIB_SOCKET} )
# USE4: add_lua_module ( ssl.context ssl.core ALL_IN_ONE src/context.c src/ssl.c )
# This form builds an "all-in-one" module (e.g. ssl.so or ssl.dll containing
# both modules ssl.context and ssl.core). The CMake target name will be
# ssl_all_in_one.
# Also sets variable _module_path (relative path where module typically
# would be installed).
macro ( add_lua_module )
_lua_module_helper ( 0 ${ARGN} )
endmacro ()
# install_lua_module
# This is the same as `add_lua_module` but also installs the module.
# USE: install_lua_module ( socket.http src/http.lua )
# USE2: install_lua_module ( mime.core src/mime.c )
# USE3: install_lua_module ( socket.core ${SRC_SOCKET} LINK ${LIB_SOCKET} )
macro ( install_lua_module )
_lua_module_helper ( 1 ${ARGN} )
endmacro ()
# Builds string representing Lua table mapping Lua modules names to file
# paths. Used internally.
macro ( _make_module_table _outvar )
set ( ${_outvar} )
list ( LENGTH _lua_modules _n )
if ( ${_n} GREATER 0 ) # avoids cmake complaint
foreach ( _i RANGE 1 ${_n} 2 )
list ( GET _lua_modules ${_i} _path )
math ( EXPR _ii ${_i}-1 )
list ( GET _lua_modules ${_ii} _name )
set ( ${_outvar} "${_table} ['${_name}'] = '${_path}'\;\n")
endforeach ()
endif ()
set ( ${_outvar}
"local modules = {
${_table}}" )
endmacro ()
# add_lua_test ( _testfile [ WORKING_DIRECTORY _working_dir ] )
# Runs Lua script `_testfile` under CTest tester.
# Optional named argument `WORKING_DIRECTORY` is current working directory to
# run test under (defaults to ${CMAKE_CURRENT_BINARY_DIR}).
# Both paths, if relative, are relative to ${CMAKE_CURRENT_SOURCE_DIR}.
# Any modules previously defined with install_lua_module are automatically
# preloaded (via package.preload) prior to running the test script.
# Under LuaDist, set test=true in config.lua to enable testing.
# USE: add_lua_test ( test/test1.lua [args...] [WORKING_DIRECTORY dir])
macro ( add_lua_test _testfile )
if ( NOT SKIP_TESTING )
parse_arguments ( _ARG "WORKING_DIRECTORY" "" ${ARGN} )
include ( CTest )
find_program ( LUA NAMES lua lua.bat )
get_filename_component ( TESTFILEABS ${_testfile} ABSOLUTE )
get_filename_component ( TESTFILENAME ${_testfile} NAME )
get_filename_component ( TESTFILEBASE ${_testfile} NAME_WE )
# Write wrapper script.
# Note: One simple way to allow the script to find modules is
# to just put them in package.preload.
set ( TESTWRAPPER ${CMAKE_CURRENT_BINARY_DIR}/${TESTFILENAME} )
_make_module_table ( _table )
set ( TESTWRAPPERSOURCE
"local CMAKE_CFG_INTDIR = ... or '.'
${_table}
local function preload_modules(modules)
for name, path in pairs(modules) do
if path:match'%.lua' then
package.preload[name] = assert(loadfile(path))
else
local name = name:gsub('.*%-', '') -- remove any hyphen prefix
local symbol = 'luaopen_' .. name:gsub('%.', '_')
--improve: generalize to support all-in-one loader?
local path = path:gsub('%$%{CMAKE_CFG_INTDIR%}', CMAKE_CFG_INTDIR)
package.preload[name] = assert(package.loadlib(path, symbol))
end
end
end
preload_modules(modules)
arg[0] = '${TESTFILEABS}'
table.remove(arg, 1)
return assert(loadfile '${TESTFILEABS}')(unpack(arg))
" )
if ( _ARG_WORKING_DIRECTORY )
get_filename_component (
TESTCURRENTDIRABS ${_ARG_WORKING_DIRECTORY} ABSOLUTE )
# note: CMake 2.6 (unlike 2.8) lacks WORKING_DIRECTORY parameter.
set ( _pre ${CMAKE_COMMAND} -E chdir "${TESTCURRENTDIRABS}" )
endif ()
file ( WRITE ${TESTWRAPPER} ${TESTWRAPPERSOURCE})
add_test ( NAME ${TESTFILEBASE} COMMAND ${_pre} ${LUA}
${TESTWRAPPER} "${CMAKE_CFG_INTDIR}"
${_ARG_DEFAULT_ARGS} )
endif ()
# see also http://gdcm.svn.sourceforge.net/viewvc/gdcm/Sandbox/CMakeModules/UsePythonTest.cmake
# Note: ${CMAKE_CFG_INTDIR} is a command-line argument to allow proper
# expansion by the native build tool.
endmacro ()
# Converts Lua source file `_source` to binary string embedded in C source
# file `_target`. Optionally compiles Lua source to byte code (not available
# under LuaJIT2, which doesn't have a bytecode loader). Additionally, Lua
# versions of bin2c [1] and luac [2] may be passed respectively as additional
# arguments.
#
# [1] http://lua-users.org/wiki/BinToCee
# [2] http://lua-users.org/wiki/LuaCompilerInLua
function ( add_lua_bin2c _target _source )
find_program ( LUA NAMES lua lua.bat )
execute_process ( COMMAND ${LUA} -e "string.dump(function()end)"
RESULT_VARIABLE _LUA_DUMP_RESULT ERROR_QUIET )
if ( NOT ${_LUA_DUMP_RESULT} )
SET ( HAVE_LUA_DUMP true )
endif ()
message ( "-- string.dump=${HAVE_LUA_DUMP}" )
if ( ARGV2 )
get_filename_component ( BIN2C ${ARGV2} ABSOLUTE )
set ( BIN2C ${LUA} ${BIN2C} )
else ()
find_program ( BIN2C NAMES bin2c bin2c.bat )
endif ()
if ( HAVE_LUA_DUMP )
if ( ARGV3 )
get_filename_component ( LUAC ${ARGV3} ABSOLUTE )
set ( LUAC ${LUA} ${LUAC} )
else ()
find_program ( LUAC NAMES luac luac.bat )
endif ()
endif ( HAVE_LUA_DUMP )
message ( "-- bin2c=${BIN2C}" )
message ( "-- luac=${LUAC}" )
get_filename_component ( SOURCEABS ${_source} ABSOLUTE )
if ( HAVE_LUA_DUMP )
get_filename_component ( SOURCEBASE ${_source} NAME_WE )
add_custom_command (
OUTPUT ${_target} DEPENDS ${_source}
COMMAND ${LUAC} -o ${CMAKE_CURRENT_BINARY_DIR}/${SOURCEBASE}.lo
${SOURCEABS}
COMMAND ${BIN2C} ${CMAKE_CURRENT_BINARY_DIR}/${SOURCEBASE}.lo
">${_target}" )
else ()
add_custom_command (
OUTPUT ${_target} DEPENDS ${SOURCEABS}
COMMAND ${BIN2C} ${_source} ">${_target}" )
endif ()
endfunction()

@ -0,0 +1,14 @@
--- This file is part of LuaDist project
name = "dkjson"
version = "2.5"
desc = "dkjson is a module for encoding and decoding JSON data. It supports UTF-8."
author = "David Kolf"
license = "MIT/X11"
url = "http://dkolf.de/src/dkjson-lua.fsl/"
maintainer = "Peter Drahoš"
depends = {
"lua >= 5.1"
}

@ -0,0 +1,30 @@
package = "dkjson"
version = "2.5-2"
source = {
url = "http://dkolf.de/src/dkjson-lua.fsl/tarball/dkjson-2.5.tar.gz?uuid=release_2_5",
file = "dkjson-2.5.tar.gz"
}
description = {
summary = "David Kolf's JSON module for Lua",
detailed = [[
dkjson is a module for encoding and decoding JSON data. It supports UTF-8.
JSON (JavaScript Object Notation) is a format for serializing data based
on the syntax for JavaScript data structures.
dkjson is written in Lua without any dependencies, but
when LPeg is available dkjson uses it to speed up decoding.
]],
homepage = "http://dkolf.de/src/dkjson-lua.fsl/",
license = "MIT/X11"
}
dependencies = {
"lua >= 5.1, < 5.4"
}
build = {
type = "builtin",
modules = {
dkjson = "dkjson.lua"
}
}

@ -0,0 +1,714 @@
-- Module options:
local always_try_using_lpeg = true
local register_global_module_table = false
local global_module_name = 'json'
--[==[
David Kolf's JSON module for Lua 5.1/5.2
Version 2.5
For the documentation see the corresponding readme.txt or visit
<http://dkolf.de/src/dkjson-lua.fsl/>.
You can contact the author by sending an e-mail to 'david' at the
domain 'dkolf.de'.
Copyright (C) 2010-2014 David Heiko Kolf
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--]==]
-- global dependencies:
local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset =
pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset
local error, require, pcall, select = error, require, pcall, select
local floor, huge = math.floor, math.huge
local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat =
string.rep, string.gsub, string.sub, string.byte, string.char,
string.find, string.len, string.format
local strmatch = string.match
local concat = table.concat
local json = { version = "dkjson 2.5" }
if register_global_module_table then
_G[global_module_name] = json
end
local _ENV = nil -- blocking globals in Lua 5.2
pcall (function()
-- Enable access to blocked metatables.
-- Don't worry, this module doesn't change anything in them.
local debmeta = require "debug".getmetatable
if debmeta then getmetatable = debmeta end
end)
json.null = setmetatable ({}, {
__tojson = function () return "null" end
})
local function isarray (tbl)
local max, n, arraylen = 0, 0, 0
for k,v in pairs (tbl) do
if k == 'n' and type(v) == 'number' then
arraylen = v
if v > max then
max = v
end
else
if type(k) ~= 'number' or k < 1 or floor(k) ~= k then
return false
end
if k > max then
max = k
end
n = n + 1
end
end
if max > 10 and max > arraylen and max > n * 2 then
return false -- don't create an array with too many holes
end
return true, max
end
local escapecodes = {
["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"
}
local function escapeutf8 (uchar)
local value = escapecodes[uchar]
if value then
return value
end
local a, b, c, d = strbyte (uchar, 1, 4)
a, b, c, d = a or 0, b or 0, c or 0, d or 0
if a <= 0x7f then
value = a
elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then
value = (a - 0xc0) * 0x40 + b - 0x80
elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then
value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80
elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then
value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80
else
return ""
end
if value <= 0xffff then
return strformat ("\\u%.4x", value)
elseif value <= 0x10ffff then
-- encode as UTF-16 surrogate pair
value = value - 0x10000
local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400)
return strformat ("\\u%.4x\\u%.4x", highsur, lowsur)
else
return ""
end
end
local function fsub (str, pattern, repl)
-- gsub always builds a new string in a buffer, even when no match
-- exists. First using find should be more efficient when most strings
-- don't contain the pattern.
if strfind (str, pattern) then
return gsub (str, pattern, repl)
else
return str
end
end
local function quotestring (value)
-- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js
value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8)
if strfind (value, "[\194\216\220\225\226\239]") then
value = fsub (value, "\194[\128-\159\173]", escapeutf8)
value = fsub (value, "\216[\128-\132]", escapeutf8)
value = fsub (value, "\220\143", escapeutf8)
value = fsub (value, "\225\158[\180\181]", escapeutf8)
value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8)
value = fsub (value, "\226\129[\160-\175]", escapeutf8)
value = fsub (value, "\239\187\191", escapeutf8)
value = fsub (value, "\239\191[\176-\191]", escapeutf8)
end
return "\"" .. value .. "\""
end
json.quotestring = quotestring
local function replace(str, o, n)
local i, j = strfind (str, o, 1, true)
if i then
return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1)
else
return str
end
end
-- locale independent num2str and str2num functions
local decpoint, numfilter
local function updatedecpoint ()
decpoint = strmatch(tostring(0.5), "([^05+])")
-- build a filter that can be used to remove group separators
numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+"
end
updatedecpoint()
local function num2str (num)
return replace(fsub(tostring(num), numfilter, ""), decpoint, ".")
end
local function str2num (str)
local num = tonumber(replace(str, ".", decpoint))
if not num then
updatedecpoint()
num = tonumber(replace(str, ".", decpoint))
end
return num
end
local function addnewline2 (level, buffer, buflen)
buffer[buflen+1] = "\n"
buffer[buflen+2] = strrep (" ", level)
buflen = buflen + 2
return buflen
end
function json.addnewline (state)
if state.indent then
state.bufferlen = addnewline2 (state.level or 0,
state.buffer, state.bufferlen or #(state.buffer))
end
end
local encode2 -- forward declaration
local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder, state)
local kt = type (key)
if kt ~= 'string' and kt ~= 'number' then
return nil, "type '" .. kt .. "' is not supported as a key by JSON."
end
if prev then
buflen = buflen + 1
buffer[buflen] = ","
end
if indent then
buflen = addnewline2 (level, buffer, buflen)
end
buffer[buflen+1] = quotestring (key)
buffer[buflen+2] = ":"
return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder, state)
end
local function appendcustom(res, buffer, state)
local buflen = state.bufferlen
if type (res) == 'string' then
buflen = buflen + 1
buffer[buflen] = res
end
return buflen
end
local function exception(reason, value, state, buffer, buflen, defaultmessage)
defaultmessage = defaultmessage or reason
local handler = state.exception
if not handler then
return nil, defaultmessage
else
state.bufferlen = buflen
local ret, msg = handler (reason, value, state, defaultmessage)
if not ret then return nil, msg or defaultmessage end
return appendcustom(ret, buffer, state)
end
end
function json.encodeexception(reason, value, state, defaultmessage)
return quotestring("<" .. defaultmessage .. ">")
end
encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, state)
local valtype = type (value)
local valmeta = getmetatable (value)
valmeta = type (valmeta) == 'table' and valmeta -- only tables
local valtojson = valmeta and valmeta.__tojson
if valtojson then
if tables[value] then
return exception('reference cycle', value, state, buffer, buflen)
end
tables[value] = true
state.bufferlen = buflen
local ret, msg = valtojson (value, state)
if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end
tables[value] = nil
buflen = appendcustom(ret, buffer, state)
elseif value == nil then
buflen = buflen + 1
buffer[buflen] = "null"
elseif valtype == 'number' then
local s
if value ~= value or value >= huge or -value >= huge then
-- This is the behaviour of the original JSON implementation.
s = "null"
else
s = num2str (value)
end
buflen = buflen + 1
buffer[buflen] = s
elseif valtype == 'boolean' then
buflen = buflen + 1
buffer[buflen] = value and "true" or "false"
elseif valtype == 'string' then
buflen = buflen + 1
buffer[buflen] = quotestring (value)
elseif valtype == 'table' then
if tables[value] then
return exception('reference cycle', value, state, buffer, buflen)
end
tables[value] = true
level = level + 1
local isa, n = isarray (value)
if n == 0 and valmeta and valmeta.__jsontype == 'object' then
isa = false
end
local msg
if isa then -- JSON array
buflen = buflen + 1
buffer[buflen] = "["
for i = 1, n do
buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
if i < n then
buflen = buflen + 1
buffer[buflen] = ","
end
end
buflen = buflen + 1
buffer[buflen] = "]"
else -- JSON object
local prev = false
buflen = buflen + 1
buffer[buflen] = "{"
local order = valmeta and valmeta.__jsonorder or globalorder
if order then
local used = {}
n = #order
for i = 1, n do
local k = order[i]
local v = value[k]
if v then
used[k] = true
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
prev = true -- add a seperator before the next element
end
end
for k,v in pairs (value) do
if not used[k] then
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
prev = true -- add a seperator before the next element
end
end
else -- unordered
for k,v in pairs (value) do
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
prev = true -- add a seperator before the next element
end
end
if indent then
buflen = addnewline2 (level - 1, buffer, buflen)
end
buflen = buflen + 1
buffer[buflen] = "}"
end
tables[value] = nil
else
return exception ('unsupported type', value, state, buffer, buflen,
"type '" .. valtype .. "' is not supported by JSON.")
end
return buflen
end
function json.encode (value, state)
state = state or {}
local oldbuffer = state.buffer
local buffer = oldbuffer or {}
state.buffer = buffer
updatedecpoint()
local ret, msg = encode2 (value, state.indent, state.level or 0,
buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state)
if not ret then
error (msg, 2)
elseif oldbuffer == buffer then
state.bufferlen = ret
return true
else
state.bufferlen = nil
state.buffer = nil
return concat (buffer)
end
end
local function loc (str, where)
local line, pos, linepos = 1, 1, 0
while true do
pos = strfind (str, "\n", pos, true)
if pos and pos < where then
line = line + 1
linepos = pos
pos = pos + 1
else
break
end
end
return "line " .. line .. ", column " .. (where - linepos)
end
local function unterminated (str, what, where)
return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where)
end
local function scanwhite (str, pos)
while true do
pos = strfind (str, "%S", pos)
if not pos then return nil end
local sub2 = strsub (str, pos, pos + 1)
if sub2 == "\239\187" and strsub (str, pos + 2, pos + 2) == "\191" then
-- UTF-8 Byte Order Mark
pos = pos + 3
elseif sub2 == "//" then
pos = strfind (str, "[\n\r]", pos + 2)
if not pos then return nil end
elseif sub2 == "/*" then
pos = strfind (str, "*/", pos + 2)
if not pos then return nil end
pos = pos + 2
else
return pos
end
end
end
local escapechars = {
["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f",
["n"] = "\n", ["r"] = "\r", ["t"] = "\t"
}
local function unichar (value)
if value < 0 then
return nil
elseif value <= 0x007f then
return strchar (value)
elseif value <= 0x07ff then
return strchar (0xc0 + floor(value/0x40),
0x80 + (floor(value) % 0x40))
elseif value <= 0xffff then
return strchar (0xe0 + floor(value/0x1000),
0x80 + (floor(value/0x40) % 0x40),
0x80 + (floor(value) % 0x40))
elseif value <= 0x10ffff then
return strchar (0xf0 + floor(value/0x40000),
0x80 + (floor(value/0x1000) % 0x40),
0x80 + (floor(value/0x40) % 0x40),
0x80 + (floor(value) % 0x40))
else
return nil
end
end
local function scanstring (str, pos)
local lastpos = pos + 1
local buffer, n = {}, 0
while true do
local nextpos = strfind (str, "[\"\\]", lastpos)
if not nextpos then
return unterminated (str, "string", pos)
end
if nextpos > lastpos then
n = n + 1
buffer[n] = strsub (str, lastpos, nextpos - 1)
end
if strsub (str, nextpos, nextpos) == "\"" then
lastpos = nextpos + 1
break
else
local escchar = strsub (str, nextpos + 1, nextpos + 1)
local value
if escchar == "u" then
value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16)
if value then
local value2
if 0xD800 <= value and value <= 0xDBff then
-- we have the high surrogate of UTF-16. Check if there is a
-- low surrogate escaped nearby to combine them.
if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then
value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16)
if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then
value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000
else
value2 = nil -- in case it was out of range for a low surrogate
end
end
end
value = value and unichar (value)
if value then
if value2 then
lastpos = nextpos + 12
else
lastpos = nextpos + 6
end
end
end
end
if not value then
value = escapechars[escchar] or escchar
lastpos = nextpos + 2
end
n = n + 1
buffer[n] = value
end
end
if n == 1 then
return buffer[1], lastpos
elseif n > 1 then
return concat (buffer), lastpos
else
return "", lastpos
end
end
local scanvalue -- forward declaration
local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta)
local len = strlen (str)
local tbl, n = {}, 0
local pos = startpos + 1
if what == 'object' then
setmetatable (tbl, objectmeta)
else
setmetatable (tbl, arraymeta)
end
while true do
pos = scanwhite (str, pos)
if not pos then return unterminated (str, what, startpos) end
local char = strsub (str, pos, pos)
if char == closechar then
return tbl, pos + 1
end
local val1, err
val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
if err then return nil, pos, err end
pos = scanwhite (str, pos)
if not pos then return unterminated (str, what, startpos) end
char = strsub (str, pos, pos)
if char == ":" then
if val1 == nil then
return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")"
end
pos = scanwhite (str, pos + 1)
if not pos then return unterminated (str, what, startpos) end
local val2
val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
if err then return nil, pos, err end
tbl[val1] = val2
pos = scanwhite (str, pos)
if not pos then return unterminated (str, what, startpos) end
char = strsub (str, pos, pos)
else
n = n + 1
tbl[n] = val1
end
if char == "," then
pos = pos + 1
end
end
end
scanvalue = function (str, pos, nullval, objectmeta, arraymeta)
pos = pos or 1
pos = scanwhite (str, pos)
if not pos then
return nil, strlen (str) + 1, "no valid JSON value (reached the end)"
end
local char = strsub (str, pos, pos)
if char == "{" then
return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta)
elseif char == "[" then
return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta)
elseif char == "\"" then
return scanstring (str, pos)
else
local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos)
if pstart then
local number = str2num (strsub (str, pstart, pend))
if number then
return number, pend + 1
end
end
pstart, pend = strfind (str, "^%a%w*", pos)
if pstart then
local name = strsub (str, pstart, pend)
if name == "true" then
return true, pend + 1
elseif name == "false" then
return false, pend + 1
elseif name == "null" then
return nullval, pend + 1
end
end
return nil, pos, "no valid JSON value at " .. loc (str, pos)
end
end
local function optionalmetatables(...)
if select("#", ...) > 0 then
return ...
else
return {__jsontype = 'object'}, {__jsontype = 'array'}
end
end
function json.decode (str, pos, nullval, ...)
local objectmeta, arraymeta = optionalmetatables(...)
return scanvalue (str, pos, nullval, objectmeta, arraymeta)
end
function json.use_lpeg ()
local g = require ("lpeg")
if g.version() == "0.11" then
error "due to a bug in LPeg 0.11, it cannot be used for JSON matching"
end
local pegmatch = g.match
local P, S, R = g.P, g.S, g.R
local function ErrorCall (str, pos, msg, state)
if not state.msg then
state.msg = msg .. " at " .. loc (str, pos)
state.pos = pos
end
return false
end
local function Err (msg)
return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall)
end
local SingleLineComment = P"//" * (1 - S"\n\r")^0
local MultiLineComment = P"/*" * (1 - P"*/")^0 * P"*/"
local Space = (S" \n\r\t" + P"\239\187\191" + SingleLineComment + MultiLineComment)^0
local PlainChar = 1 - S"\"\\\n\r"
local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars
local HexDigit = R("09", "af", "AF")
local function UTF16Surrogate (match, pos, high, low)
high, low = tonumber (high, 16), tonumber (low, 16)
if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then
return true, unichar ((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)
else
return false
end
end
local function UTF16BMP (hex)
return unichar (tonumber (hex, 16))
end
local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit))
local UnicodeEscape = g.Cmt (U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence/UTF16BMP
local Char = UnicodeEscape + EscapeSequence + PlainChar
local String = P"\"" * g.Cs (Char ^ 0) * (P"\"" + Err "unterminated string")
local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0))
local Fractal = P"." * R"09"^0
local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1
local Number = (Integer * Fractal^(-1) * Exponent^(-1))/str2num
local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1)
local SimpleValue = Number + String + Constant
local ArrayContent, ObjectContent
-- The functions parsearray and parseobject parse only a single value/pair
-- at a time and store them directly to avoid hitting the LPeg limits.
local function parsearray (str, pos, nullval, state)
local obj, cont
local npos
local t, nt = {}, 0
repeat
obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state)
if not npos then break end
pos = npos
nt = nt + 1
t[nt] = obj
until cont == 'last'
return pos, setmetatable (t, state.arraymeta)
end
local function parseobject (str, pos, nullval, state)
local obj, key, cont
local npos
local t = {}
repeat
key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state)
if not npos then break end
pos = npos
t[key] = obj
until cont == 'last'
return pos, setmetatable (t, state.objectmeta)
end
local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray) * Space * (P"]" + Err "']' expected")
local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject) * Space * (P"}" + Err "'}' expected")
local Value = Space * (Array + Object + SimpleValue)
local ExpectedValue = Value + Space * Err "value expected"
ArrayContent = Value * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
local Pair = g.Cg (Space * String * Space * (P":" + Err "colon expected") * ExpectedValue)
ObjectContent = Pair * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
local DecodeValue = ExpectedValue * g.Cp ()
function json.decode (str, pos, nullval, ...)
local state = {}
state.objectmeta, state.arraymeta = optionalmetatables(...)
local obj, retpos = pegmatch (DecodeValue, str, pos, nullval, state)
if state.msg then
return nil, state.pos, state.msg
else
return obj, retpos
end
end
-- use this function only once:
json.use_lpeg = function () return json end
json.using_lpeg = true
return json -- so you can get the module using json = require "dkjson".use_lpeg()
end
if always_try_using_lpeg then
pcall (json.use_lpeg)
end
return json

@ -0,0 +1,653 @@
local encode, decode, dkencode, dkdecode
local test_module, opt = ... -- command line argument
--local test_module = 'cmj-json'
--local test_module = 'dkjson'
--local test_module = 'dkjson-nopeg'
--local test_module = 'fleece'
--local test_module = 'jf-json'
--locel test_module = 'lua-yajl'
--local test_module = 'mp-cjson'
--local test_module = 'nm-json'
--local test_module = 'sb-json'
--local test_module = 'th-json'
test_module = test_module or 'dkjson'
--local opt = "esc" -- Test which characters in the BMP get escaped and whether this is correct
--local opt = "esc_full" -- Full range from 0 to 0x10ffff
--local opt = "esc_asc" -- Just 0 to 127
--local opt = "refcycle" -- What happens when a reference cycle gets encoded?
local testlocale = "de_DE.UTF8"
local function inlocale(fn)
local oldloc = os.setlocale(nil, 'numeric')
if not os.setlocale(testlocale, 'numeric') then
print("test could not switch to locale "..testlocale)
else
fn()
end
os.setlocale(oldloc, 'numeric')
end
if test_module == 'dkjson-nopeg' then
test_module = 'dkjson'
package.preload["lpeg"] = function () error "lpeg disabled" end
package.loaded["lpeg"] = nil
lpeg = nil
end
if test_module == 'dkjson-lulpeg' then
test_module = 'dkjson'
package.loaded["lpeg"] = require "lulpeg"
end
do
-- http://chiselapp.com/user/dhkolf/repository/dkjson/
local dkjson = require "dkjson"
dkencode = dkjson.encode
dkdecode = dkjson.decode
end
if test_module == 'cmj-json' then
-- https://github.com/craigmj/json4lua/
-- http://json.luaforge.net/
local json = require "cmjjson" -- renamed, the original file was just 'json'
encode = json.encode
decode = json.decode
elseif test_module == 'dkjson' then
-- http://chiselapp.com/user/dhkolf/repository/dkjson/
encode = dkencode
decode = dkdecode
elseif test_module == 'fleece' then
-- http://www.eonblast.com/fleece/
local fleece = require "fleece"
encode = function(x) return fleece.json(x, "E4") end
elseif test_module == 'jf-json' then
-- http://regex.info/blog/lua/json
local json = require "jfjson" -- renamed, the original file was just 'JSON'
encode = function(x) return json:encode(x) end
decode = function(x) return json:decode(x) end
elseif test_module == 'lua-yajl' then
-- http://github.com/brimworks/lua-yajl
local yajl = require ("yajl")
encode = yajl.to_string
decode = yajl.to_value
elseif test_module == 'mp-cjson' then
-- http://www.kyne.com.au/~mark/software/lua-cjson.php
local json = require "cjson"
encode = json.encode
decode = json.decode
elseif test_module == 'nm-json' then
-- http://luaforge.net/projects/luajsonlib/
local json = require "LuaJSON"
encode = json.encode or json.stringify
decode = json.decode or json.parse
elseif test_module == 'sb-json' then
-- http://www.chipmunkav.com/downloads/Json.lua
local json = require "sbjson" -- renamed, the original file was just 'Json'
encode = json.Encode
decode = json.Decode
elseif test_module == 'th-json' then
-- https://github.com/harningt/luajson
-- http://luaforge.net/projects/luajson/
local json = require "json"
encode = json.encode
decode = json.decode
else
print "No module specified"
return
end
if not encode then
print ("No encode method")
else
local x, r
local escapecodes = {
["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t", ["/"] = "\\/"
}
local function test (x, n, expect)
local enc = encode{ x }:match("^%s*%[%s*%\"(.-)%\"%s*%]%s*$")
if not enc or (escapecodes[x] ~= enc
and ("\\u%04x"):format(n) ~= enc:gsub("[A-F]", string.lower)
and not (expect and enc:match("^"..expect.."$"))) then
print(("U+%04X isn't encoded correctly: %q"):format(n, enc))
end
end
-- necessary escapes for JSON:
for i = 0,31 do
test(string.char(i), i)
end
test("\"", ("\""):byte())
test("\\", ("\\"):byte())
-- necessary escapes for JavaScript:
test("\226\128\168", 0x2028)
test("\226\128\169", 0x2029)
-- invalid escapes that were seen in the wild:
test("'", ("'"):byte(), "%'")
r,x = pcall (encode, { [1000] = "x" })
if not r then
print ("encoding a sparse array (#=0) raises an error:", x)
else
if #x > 30 then
print ("sparse array (#=0) encoded as:", x:sub(1,15).." <...> "..x:sub(-15,-1), "#"..#x)
else
print ("sparse array (#=0) encoded as:", x)
end
end
r,x = pcall (encode, { [1] = "a", [1000] = "x" })
if not r then
print ("encoding a sparse array (#=1) raises an error:", x)
else
if #x > 30 then
print ("sparse array (#=1) encoded as:", x:sub(1,15).." <...> "..x:sub(-15,-1), "#str="..#x)
else
print ("sparse array (#=1) encoded as:", x)
end
end
r,x = pcall (encode, { [1] = "a", [5] = "c", ["x"] = "x" })
if not r then
print ("encoding a mixed table raises an error:", x)
else
print ("mixed table encoded as:", x)
end
r, x = pcall(encode, { math.huge*0 }) -- NaN
if not r then
print ("encoding NaN raises an error:", x)
else
r = dkdecode(x)
if not r then
print ("NaN isn't converted into valid JSON:", x)
elseif type(r[1]) == "number" and r[1] == r[1] then -- a number, but not NaN
print ("NaN is converted into a valid number:", x)
else
print ("NaN is converted to:", x)
end
end
if test_module == 'fleece' then
print ("Fleece (0.3.1) is known to freeze on +/-Inf")
else
r, x = pcall(encode, { math.huge }) -- +Inf
if not r then
print ("encoding +Inf raises an error:", x)
else
r = dkdecode(x)
if not r then
print ("+Inf isn't converted into valid JSON:", x)
else
print ("+Inf is converted to:", x)
end
end
r, x = pcall(encode, { -math.huge }) -- -Inf
if not r then
print ("encoding -Inf raises an error:", x)
else
r = dkdecode(x)
if not r then
print ("-Inf isn't converted into valid JSON:", x)
else
print ("-Inf is converted to:", x)
end
end
end
inlocale(function ()
local r, x = pcall(encode, { 0.5 })
if not r then
print("encoding 0.5 in locale raises an error:", x)
elseif not x:find(".", 1, true) then
print("In locale 0.5 isn't converted into valid JSON:", x)
end
end)
-- special tests for dkjson:
if test_module == 'dkjson' then
do -- encode a function
local why, value, exstate
local state = {
exception = function (w, v, s)
why, value, exstate = w, v, s
return "\"demo\""
end
}
local encfunction = function () end
r, x = pcall(dkencode, { encfunction }, state )
if not r then
print("encoding a function with exception handler raises an error:", x)
else
if x ~= "[\"demo\"]" then
print("expected to see output of exception handler for type exception, but got", x)
end
if why ~= "unsupported type" then
print("expected exception reason to be 'unsupported type' for type exception")
end
if value ~= encfunction then
print("expected to recieve value for type exception")
end
if exstate ~= state then
print("expected to recieve state for type exception")
end
end
r, x = pcall(dkencode, { function () end }, {
exception = function (w, v, s)
return nil, "demo"
end
})
if r or x ~= "demo" then
print("expected custom error for type exception, but got:", r, x)
end
r, x = pcall(dkencode, { function () end }, {
exception = function (w, v, s)
return nil
end
})
if r or x ~= "type 'function' is not supported by JSON." then
print("expected default error for type exception, but got:", r, x)
end
end
do -- encode a reference cycle
local why, value, exstate
local state = {
exception = function (w, v, s)
why, value, exstate = w, v, s
return "\"demo\""
end
}
local a = {}
a[1] = a
r, x = pcall(dkencode, a, state )
if not r then
print("encoding a reference cycle with exception handler raises an error:", x)
else
if x ~= "[\"demo\"]" then
print("expected to see output of exception handler for reference cycle exception, but got", x)
end
if why ~= "reference cycle" then
print("expected exception reason to be 'reference cycle' for reference cycle exception")
end
if value ~= a then
print("expected to recieve value for reference cycle exception")
end
if exstate ~= state then
print("expected to recieve state for reference cycle exception")
end
end
end
do -- example exception handler
r = dkencode(function () end, { exception = require "dkjson".encodeexception })
if r ~= [["<type 'function' is not supported by JSON.>"]] then
print("expected the exception encoder to encode default error message, but got", r)
end
end
do -- test state buffer for custom __tojson function
local origstate = {}
local usedstate, usedbuffer, usedbufferlen
dkencode({ setmetatable({}, {
__tojson = function(self, state)
usedstate = state
usedbuffer = state.buffer
usedbufferlen = state.bufferlen
return true
end
}) }, origstate)
if usedstate ~= origstate then print("expected tojson-function to recieve the original state") end
if type(usedbuffer) ~= 'table' or #usedbuffer < 1 then print("expected buffer in tojson-function to be an array") end
if usedbufferlen ~= 1 then print("expected bufferlen in tojson-function to be 1, but got "..tostring(usedbufferlen)) end
end
do -- do not keep buffer and bufferlen when they were not present initially
local origstate = {}
dkencode(setmetatable({}, {__tojson = function() return true end}), origstate)
if origstate.buffer ~= nil then print("expected buffer to be reset to nil") end
if origstate.bufferlen ~= nil then print("expected bufferlen to be reset to nil") end
end
do -- keep buffer and update bufferlen when they were present initially
local origbuffer = {}
local origstate = { buffer = origbuffer }
dkencode(true, origstate)
if origstate.buffer ~= origbuffer then print("expected original buffer to remain") end
if origstate.bufferlen ~= 1 then print("expected bufferlen to be updated") end
end
end
end
if not decode then
print ("No decode method")
else
local x, r
x = decode[=[ ["\u0000"] ]=]
if x[1] ~= "\000" then
print ("\\u0000 isn't decoded correctly")
end
x = decode[=[ ["\u20AC"] ]=]
if x[1] ~= "\226\130\172" then
print ("\\u20AC isn't decoded correctly")
end
x = decode[=[ ["\uD834\uDD1E"] ]=]
if x[1] ~= "\240\157\132\158" then
print ("\\uD834\\uDD1E isn't decoded correctly")
end
r, x = pcall(decode, [=[
{"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x":
{"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x":
{"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x":
{"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x":
"deep down"
} } } } } } } } } } } } } } }
} } } } } } } } } } } } } } }
} } } } } } } } } } } } } } }
} } } } } } } } } } } } } } }
]=])
if not r then
print ("decoding a deep nested table raises an error:", x)
else
local i = 0
while type(x) == 'table' do
i = i + 1
x = x.x
end
if i ~= 60 or x ~= "deep down" then
print ("deep nested table isn't decoded correctly")
end
end
if false and test_module == 'cmj-json' then
-- unfortunatly the version can't be read
print ("decoding a big array takes ages (or forever?) on cmj-json prior to version 0.9.5")
else
r, x = pcall(decode, "["..("0,"):rep(100000).."0]")
if not r then
print ("decoding a big array raises an error:", x)
else
if type(x) ~= 'table' or #x ~= 100001 then
print ("big array isn't decoded correctly")
end
end
end
r, x = pcall(decode, "{}")
if not r then
print ("decoding an empty object raises an error:", x)
end
r, x = pcall(decode, "[]")
if not r then
print ("decoding an empty array raises an error:", x)
end
r, x = pcall(decode, "[1e+2]")
if not r then
print ("decoding a number with exponential notation raises an error:", x)
elseif x[1] ~= 1e+2 then
print ("1e+2 decoded incorrectly:", r[1])
end
inlocale(function ()
local r, x = pcall(decode, "[0.5]")
if not r then
print("decoding 0.5 in locale raises an error:", x)
elseif not x then
print("cannot decode 0.5 in locale")
elseif x[1] ~= 0.5 then
print("decoded 0.5 incorrectly in locale:", x[1])
end
end)
-- special tests for dkjson:
if test_module == 'dkjson' then
x = dkdecode[=[ [{"x":0}] ]=]
local m = getmetatable(x)
if not m or m.__jsontype ~= 'array' then
print ("<metatable>.__jsontype ~= array")
end
local m = getmetatable(x[1])
if not m or m.__jsontype ~= 'object' then
print ("<metatable>.__jsontype ~= object")
end
local x,p,m = dkdecode" invalid "
if p ~= 2 or type(m) ~= 'string' or not m:find("at line 1, column 2$") then
print (("Invalid location: position=%d, message=%q"):format(p,m))
end
local x,p,m = dkdecode" \n invalid "
if p ~= 4 or type(m) ~= 'string' or not m:find("at line 2, column 2$") then
print (("Invalid location: position=%d, message=%q"):format(p,m))
end
do -- single line comments
local x, p, m = dkdecode [[
{"test://" // comment // --?
: [ // continues
0] //
}
]]
if type(x) ~= 'table' or type(x["test://"]) ~= 'table' or x["test://"][1] ~= 0 then
print("could not decode a string with single line comments: "..tostring(m))
end
end
do -- multi line comments
local x, p, m = dkdecode [[
{"test:/*"/**//*
hi! this is a comment
*/ : [/** / **/ 0]
}
]]
if type(x) ~= 'table' or type(x["test:/*"]) ~= 'table' or x["test:/*"][1] ~= 0 then
print("could not decode a string with multi line comments: "..tostring(m))
end
end
end
end
if encode and opt == "refcycle" then
local a = {}
a.a = a
print ("Trying a reference cycle...")
encode(a)
end
if encode and (opt or ""):sub(1,3) == "esc" then
local strchar, strbyte, strformat = string.char, string.byte, string.format
local floor = math.floor
local function unichar (value)
if value < 0 then
return nil
elseif value <= 0x007f then
return strchar (value)
elseif value <= 0x07ff then
return strchar (0xc0 + floor(value/0x40),
0x80 + (floor(value) % 0x40))
elseif value <= 0xffff then
return strchar (0xe0 + floor(value/0x1000),
0x80 + (floor(value/0x40) % 0x40),
0x80 + (floor(value) % 0x40))
elseif value <= 0x10ffff then
return strchar (0xf0 + floor(value/0x40000),
0x80 + (floor(value/0x1000) % 0x40),
0x80 + (floor(value/0x40) % 0x40),
0x80 + (floor(value) % 0x40))
else
return nil
end
end
local escapecodes = {
["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t", ["/"] = "\\/"
}
local function escapeutf8 (uchar)
local a, b, c, d = strbyte (uchar, 1, 4)
a, b, c, d = a or 0, b or 0, c or 0, d or 0
if a <= 0x7f then
value = a
elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then
value = (a - 0xc0) * 0x40 + b - 0x80
elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then
value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80
elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then
value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80
else
return ""
end
if value <= 0xffff then
return strformat ("\\u%.4x", value)
elseif value <= 0x10ffff then
-- encode as UTF-16 surrogate pair
value = value - 0x10000
local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400)
return strformat ("\\u%.4x\\u%.4x", highsur, lowsur)
else
return ""
end
end
local isspecial = {}
local unifile = io.open("UnicodeData.txt")
if unifile then
-- <http://www.unicode.org/Public/UNIDATA/UnicodeData.txt>
-- each line consists of 15 parts for each defined codepoints
local pat = {}
for i = 1,14 do
pat[i] = "[^;]*;"
end
pat[1] = "([^;]*);" -- Codepoint
pat[3] = "([^;]*);" -- Category
pat[15] = "[^;]*"
pat = table.concat(pat)
for line in unifile:lines() do
local cp, cat = line:match(pat)
if cat:match("^C[^so]") or cat:match("^Z[lp]") then
isspecial[tonumber(cp, 16)] = cat
end
end
unifile:close()
end
local x,xe
local t = {}
local esc = {}
local escerr = {}
local range
if opt == "esc_full" then range = 0x10ffff
elseif opt == "esc_asc" then range = 0x7f
else range = 0xffff end
for i = 0,range do
t[1] = unichar(i)
xe = encode(t)
x = string.match(xe, "^%s*%[%s*%\"(.*)%\"%s*%]%s*$")
if type(x) ~= 'string' then
escerr[i] = xe
elseif string.lower(x) == escapeutf8(t[1]) then
esc[i] = 'u'
elseif x == escapecodes[t[1]] then
esc[i] = 'c'
elseif x:sub(1,1) == "\\" then
escerr[i] = xe
end
end
do
local i = 0
while i <= range do
local first
while i <= range and not (esc[i] or isspecial[i]) do i = i + 1 end
if i > range then break end
first = i
local special = isspecial[i]
if esc[i] and special then
while esc[i] and isspecial[i] == special do i = i + 1 end
if i-1 > first then
print (("Escaped %s characters from U+%04X to U+%04X"):format(special,first,i-1))
else
print (("Escaped %s character U+%04X"):format(special,first))
end
elseif esc[i] then
while esc[i] and not isspecial[i] do i = i + 1 end
if i-1 > first then
print (("Escaped from U+%04X to U+%04X"):format(first,i-1))
else
if first >= 32 and first <= 127 then
print (("Escaped U+%04X (%c)"):format(first,first))
else
print (("Escaped U+%04X"):format(first))
end
end
elseif special then
while not esc[i] and isspecial[i] == special do i = i + 1 end
if i-1 > first then
print (("Unescaped %s characters from U+%04X to U+%04X"):format(special,first,i-1))
else
print (("Unescaped %s character U+%04X"):format(special,first))
end
end
end
end
do
local i = 0
while i <= range do
local first
while i <= range and not escerr[i] do i = i + 1 end
if not escerr[i] then break end
first = i
while escerr[i] do i = i + 1 end
if i-1 > first then
print (("Errors while escaping from U+%04X to U+%04X"):format(first, i-1))
else
print (("Errors while escaping U+%04X"):format(first))
end
end
end
end
-- Copyright (C) 2011 David Heiko Kolf
--
-- Permission is hereby granted, free of charge, to any person obtaining
-- a copy of this software and associated documentation files (the
-- "Software"), to deal in the Software without restriction, including
-- without limitation the rights to use, copy, modify, merge, publish,
-- distribute, sublicense, and/or sell copies of the Software, and to
-- permit persons to whom the Software is furnished to do so, subject to
-- the following conditions:
--
-- The above copyright notice and this permission notice shall be
-- included in all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
-- BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-- ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.

@ -0,0 +1,211 @@
David Kolf's JSON module for Lua 5.1/5.2
========================================
*Version 2.5*
In the default configuration this module writes no global values, not even
the module table. Import it using
json = require ("dkjson")
In environments where `require` or a similiar function are not available
and you cannot receive the return value of the module, you can set the
option `register_global_module_table` to `true`. The module table will
then be saved in the global variable with the name given by the option
`global_module_name`.
Exported functions and values:
`json.encode (object [, state])`
--------------------------------
Create a string representing the object. `Object` can be a table,
a string, a number, a boolean, `nil`, `json.null` or any object with
a function `__tojson` in its metatable. A table can only use strings
and numbers as keys and its values have to be valid objects as
well. It raises an error for any invalid data types or reference
cycles.
`state` is an optional table with the following fields:
- `indent`
When `indent` (a boolean) is set, the created string will contain
newlines and indentations. Otherwise it will be one long line.
- `keyorder`
`keyorder` is an array to specify the ordering of keys in the
encoded output. If an object has keys which are not in this array
they are written after the sorted keys.
- `level`
This is the initial level of indentation used when `indent` is
set. For each level two spaces are added. When absent it is set
to 0.
- `buffer`
`buffer` is an array to store the strings for the result so they
can be concatenated at once. When it isn't given, the encode
function will create it temporary and will return the
concatenated result.
- `bufferlen`
When `bufferlen` is set, it has to be the index of the last
element of `buffer`.
- `tables`
`tables` is a set to detect reference cycles. It is created
temporary when absent. Every table that is currently processed
is used as key, the value is `true`.
- `exception`
When `exception` is given, it will be called whenever the encoder
cannot encode a given value.
The parameters are `reason`, `value`, `state` and `defaultmessage`.
`reason` is either `"reference cycle"`, `"custom encoder failed"` or
`"unsupported type"`. `value` is the original value that caused the
exception, `state` is this state table, `defaultmessage` is the message
of the error that would usually be raised.
You can either return `true` and add directly to the buffer or you can
return the string directly. To keep raising an error return `nil` and
the desired error message.
An example implementation for an exception function is given in
`json.encodeexception`.
When `state.buffer` was set, the return value will be `true` on
success. Without `state.buffer` the return value will be a string.
`json.decode (string [, position [, null]])`
--------------------------------------------
Decode `string` starting at `position` or at 1 if `position` was
omitted.
`null` is an optional value to be returned for null values. The
default is `nil`, but you could set it to `json.null` or any other
value.
The return values are the object or `nil`, the position of the next
character that doesn't belong to the object, and in case of errors
an error message.
Two metatables are created. Every array or object that is decoded gets
a metatable with the `__jsontype` field set to either `array` or
`object`. If you want to provide your own metatables use the syntax
json.decode (string, position, null, objectmeta, arraymeta)
To prevent the assigning of metatables pass `nil`:
json.decode (string, position, null, nil)
`<metatable>.__jsonorder`
-------------------------
`__jsonorder` can overwrite the `keyorder` for a specific table.
`<metatable>.__jsontype`
------------------------
`__jsontype` can be either `"array"` or `"object"`. This value is only
checked for empty tables. (The default for empty tables is `"array"`).
`<metatable>.__tojson (self, state)`
------------------------------------
You can provide your own `__tojson` function in a metatable. In this
function you can either add directly to the buffer and return true,
or you can return a string. On errors nil and a message should be
returned.
`json.null`
-----------
You can use this value for setting explicit `null` values.
`json.version`
--------------
Set to `"dkjson 2.5"`.
`json.quotestring (string)`
---------------------------
Quote a UTF-8 string and escape critical characters using JSON
escape sequences. This function is only necessary when you build
your own `__tojson` functions.
`json.addnewline (state)`
-------------------------
When `state.indent` is set, add a newline to `state.buffer` and spaces
according to `state.level`.
`json.encodeexception (reason, value, state, defaultmessage)`
-------------------------------------------------------------
This function can be used as value to the `exception` option. Instead of
raising an error this function encodes the error message as a string. This
can help to debug malformed input data.
x = json.encode(value, { exception = json.encodeexception })
LPeg support
------------
When the local configuration variable `always_try_using_lpeg` is set,
this module tries to load LPeg to replace the `decode` function. The
speed increase is significant. You can get the LPeg module at
<http://www.inf.puc-rio.br/~roberto/lpeg/>.
When LPeg couldn't be loaded, the pure Lua functions stay active.
In case you don't want this module to require LPeg on its own,
disable the option `always_try_using_lpeg` in the options section at
the top of the module.
In this case you can later load LPeg support using
### `json.use_lpeg ()`
Require the LPeg module and replace the functions `quotestring` and
and `decode` with functions that use LPeg patterns.
This function returns the module table, so you can load the module
using:
json = require "dkjson".use_lpeg()
Alternatively you can use `pcall` so the JSON module still works when
LPeg isn't found.
json = require "dkjson"
pcall (json.use_lpeg)
### `json.using_lpeg`
This variable is set to `true` when LPeg was loaded successfully.
---------------------------------------------------------------------
Contact
-------
You can contact the author by sending an e-mail to 'david' at the
domain 'dkolf.de'.
---------------------------------------------------------------------
*Copyright (C) 2010-2014 David Heiko Kolf*
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,131 @@
local encode, decode
local test_module = ... -- command line argument
--local test_module = 'cmj-json'
--local test_module = 'dkjson'
--local test_module = 'dkjson-nopeg'
--local test_module = 'fleece'
--local test_module = 'jf-json'
--locel test_module = 'lua-yajl'
--local test_module = 'mp-cjson'
--local test_module = 'nm-json'
--local test_module = 'sb-json'
--local test_module = 'th-json'
if test_module == 'cmj-json' then
-- http://json.luaforge.net/
local json = require "cmjjson" -- renamed, the original file was just 'json'
encode = json.encode
decode = json.decode
elseif test_module == 'dkjson' then
-- http://chiselapp.com/user/dhkolf/repository/dkjson/
local dkjson = require "dkjson"
encode = dkjson.encode
decode = dkjson.decode
elseif test_module == 'dkjson-nopeg' then
package.preload["lpeg"] = function () error "lpeg disabled" end
package.loaded["lpeg"] = nil
lpeg = nil
local dkjson = require "dkjson"
encode = dkjson.encode
decode = dkjson.decode
elseif test_module == 'fleece' then
-- http://www.eonblast.com/fleece/
local fleece = require "fleece"
encode = function(x) return fleece.json(x, "E4") end
elseif test_module == 'jf-json' then
-- http://regex.info/blog/lua/json
local json = require "jfjson" -- renamed, the original file was just 'JSON'
encode = function(x) return json:encode(x) end
decode = function(x) return json:decode(x) end
elseif test_module == 'lua-yajl' then
-- http://github.com/brimworks/lua-yajl
local yajl = require ("yajl")
encode = yajl.to_string
decode = yajl.to_value
elseif test_module == 'mp-cjson' then
-- http://www.kyne.com.au/~mark/software/lua-cjson.php
local json = require "cjson"
encode = json.encode
decode = json.decode
elseif test_module == 'nm-json' then
-- http://luaforge.net/projects/luajsonlib/
local json = require "LuaJSON"
encode = json.encode or json.stringify
decode = json.decode or json.parse
elseif test_module == 'sb-json' then
-- http://www.chipmunkav.com/downloads/Json.lua
local json = require "sbjson" -- renamed, the original file was just 'Json'
encode = json.Encode
decode = json.Decode
elseif test_module == 'th-json' then
-- http://luaforge.net/projects/luajson/
local json = require "json"
encode = json.encode
decode = json.decode
else
print "No module specified"
return
end
-- example data taken from
-- http://de.wikipedia.org/wiki/JavaScript_Object_Notation
local str = [[
{
"Herausgeber": "Xema",
"Nummer": "1234-5678-9012-3456",
"Deckung": 26,
"Währung": "EUR",
"Inhaber": {
"Name": "Mustermann",
"Vorname": "Max",
"männlich": true,
"Depot": {},
"Hobbys": [ "Reiten", "Golfen", "Lesen" ],
"Alter": 42,
"Kinder": [0],
"Partner": null
}
}
]]
local tbl = {
Herausgeber= "Xema",
Nummer= "1234-5678-9012-3456",
Deckung= 2e+6,
["Währung"]= "EUR",
Inhaber= {
Name= "Mustermann",
Vorname= "Max",
["männlich"]= true,
Depot= {},
Hobbys= { "Reiten", "Golfen", "Lesen" },
Alter= 42,
Kinder= {},
Partner= nil
--Partner= json.null
}
}
local t1, t2
if decode then
t1 = os.clock ()
for i = 1,100000 do
decode (str)
end
t2 = os.clock ()
print ("Decoding:", t2 - t1)
end
if encode then
t1 = os.clock ()
for i = 1,100000 do
encode (tbl)
end
t2 = os.clock ()
print ("Encoding:", t2 - t1)
end

@ -0,0 +1,107 @@
Version 2.5 (2014-04-28)
===========
Changes since version 2.4:
* Added customizable exception handling.
* Decode input that contains JavaScript comments.
Version 2.4 (2013-09-28)
===========
Changes since version 2.3:
* Fixed encoding and decoding of numbers in different numeric locales.
* Prevent using version 0.11 of LPeg (causes segmentation faults on
some systems).
Version 1.3 (2013-09-28)
===========
Changes since version 1.2:
* Fixed encoding and decoding of numbers in different numeric locales.
Version 2.3 (2013-04-14)
===========
Changes since version 2.2:
* Corrected the range of escaped characters. Among other characters
U+2029 was missing, which would cause trouble when parsed by a
JavaScript interpreter.
* Added options to register the module table in a global variable.
This is useful in environments where functions similar to require are
not available.
Version 1.2 (2013-04-14)
===========
Changes since version 1.1:
* Corrected the range of escaped characters. Among other characters
U+2029 was missing, which would cause trouble when parsed by a
JavaScript interpreter.
* Locations for error messages were off by one in the first line.
Version 2.2 (2012-04-28)
===========
Changes since version 2.1:
* __jsontype is only used for empty tables.
* It is possible to decode tables without assigning metatables.
* Locations for error messages were off by one in the first line.
* There is no LPeg version of json.quotestring anymore.
Version 2.1 (2011-07-08)
===========
Changes since version 2.0:
* Changed the documentation to Markdown format.
* LPeg is now parsing only a single value at a time to avoid running
out of Lua stack for big arrays and objects.
* Read __tojson, __jsontype and __jsonorder even from blocked metatables
through the debug module.
* Fixed decoding single numbers (only affected the non-LPeg mode).
* Corrected the range of escaped Unicode control characters.
Version 1.1 (2011-07-08)
===========
Changes since version 1.0:
* The values NaN/+Inf/-Inf are recognised and encoded as "null" like in
the original JavaScript implementation.
* Read __tojson even from blocked metatables through the debug module.
* Fixed decoding single numbers.
* Corrected the range of escaped Unicode control characters.
Version 2.0 (2011-05-31)
===========
Changes since version 1.0:
* Optional LPeg support.
* Invalid input data for encoding raises errors instead of returning nil
and the error message. (Invalid data for encoding is usually a
programming error. Raising an error removes the work of explicitly
checking the result).
* The metatable field __jsontype can control whether a Lua table is
encoded as a JSON array or object. (Mainly useful for empty tables).
* When decoding, two metatables are created. One is used to mark the arrays
while the other one is used for the objects. (The metatables are
created once for each decoding operation to make sandboxing possible.
However, you can specify your own metatables as arguments).
* There are no spaces added any longer when encoding.
* It is possible to explicitly sort keys for encoding by providing an array with key
names to the option "keyorder" or the metatable field __jsonorder.
* The values NaN/+Inf/-Inf are recognised and encoded as "null" like in
the original JavaScript implementation.
Version 1.0
===========
Initial version, released 2010-08-28.

@ -0,0 +1,34 @@
{
"devDependencies": {
"@types/node": "^20.11.20",
"@typescript-eslint/eslint-plugin": "^6.7.5",
"@typescript-eslint/parser": "^6.7.5",
"@typescript-to-lua/language-extensions": "^1.19.0",
"dotenv": "^16.4.5",
"eslint": "^8.46.0",
"eslint-config-prettier": "^8.9.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-simple-import-sort": "^10.0.0",
"lua-json": "^1.0.1",
"lua-types": "^2.13.1",
"prettier": "^3.0.0",
"tsx": "^4.7.1",
"typescript": "^5.3.3",
"typescript-to-lua": "^1.24.1"
},
"scripts": {
"lint": "eslint -c .eslintrc ./scripts ./src --ext .ts,.tsx",
"lua-docs:get": "lua ./scripts/lua-docs/get-docs.lua ./lib/BlizzardInterfaceCode/Interface/AddOns/Blizzard_APIDocumentationGenerated",
"lua-docs:format": "prettier --write ./scripts/lua-docs/docs/docs.json",
"lua-docs:full": "pnpm run lua-docs:get && pnpm run lua-docs:format",
"files:clean-dist": "tsx ./scripts/clear-dir dist",
"files:clean-tinkr": "tsx ./scripts/clear-dir tinkr",
"files:copy": "tsx ./scripts/copy-dist",
"types:gen": "tsx ./scripts/gen-types/gen-types.ts",
"types:format": "prettier --write ./src/types/Wow/*.d.ts",
"types:full": "pnpm run types:gen && pnpm run types:format",
"type-check": "tsc --skipLibCheck --noEmit",
"build:dist": "pnpm run files:clean-dist && tstl",
"build:full": "pnpm run files:clean-tinkr && pnpm run build:dist && pnpm run files:copy"
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,3 @@
# The path to your tinkr scripts folder relative to this directory.
# E.g if you have Mekanome and Tinkr on your desktop, then this would be "../{your tinkr folder name}/scripts"
TINKR_DIR=""

@ -0,0 +1,17 @@
import 'dotenv/config'
import { rm } from 'fs'
const args = process.argv
if (args.length < 3) throw new Error('No dir arg')
const dirType = args[2]
const dir = dirType === 'dist' ? './dist' : process.env.TINKR_DIR
if (dir === undefined) throw new Error('Missing tinkr directory config.')
rm(dir, { recursive: true }, () => {
console.log(`Done clearing directory ${dir}`)
})

@ -0,0 +1,3 @@
import { cpSync } from 'fs'
cpSync('./dist', '../howdy/scripts', { recursive: true })

@ -0,0 +1,68 @@
import { writeFile } from 'fs/promises'
import { getParsedScriptObjects } from './helpers/generate-script-object'
import { getParsedCallbacks } from './helpers/get-parsed-callbacks'
import { getParsedConstants } from './helpers/get-parsed-constants'
import { getParsedEnums } from './helpers/get-parsed-enums'
import { getParsedEventData } from './helpers/get-parsed-event-data'
import { getParsedStructs } from './helpers/get-parsed-structs'
import { getParsedSystems } from './helpers/get-parsed-systems'
import { Doc } from './shared/types/doc-types'
const data = require('../lua-docs/docs/docs.json') as Doc[]
const writeDeclaration = (declaration: string, name: string) => {
try {
const decoratedDeclaration = `
/* eslint-disable @typescript-eslint/no-duplicate-enum-values */
/* eslint-disable @typescript-eslint/ban-types */
/** @noSelfInFile **/
declare global {
${declaration}
}
export {}
`
return writeFile(`./src/types/Wow/${name}.d.ts`, decoratedDeclaration)
} catch (error) {
throw error as Error
}
}
const main = async () => {
try {
const enums = getParsedEnums(data)
const structs = getParsedStructs(data)
const constants = getParsedConstants(data)
const callbacks = getParsedCallbacks(data)
const eventData = getParsedEventData(data)
const systemData = getParsedSystems(data)
const scriptObjects = getParsedScriptObjects(data)
console.log('Parsed docs, writing declaration.')
try {
await Promise.all([
writeDeclaration(structs.join('\n'), 'structs'),
writeDeclaration(scriptObjects.join('\n'), 'scripts'),
writeDeclaration(callbacks.join('\n'), 'callbacks'),
writeDeclaration(enums.join('\n'), 'enums'),
writeDeclaration(constants.join('\n'), 'constants'),
writeDeclaration(eventData, 'events'),
writeDeclaration(systemData.join('\n'), 'systems')
])
} catch (error) {
console.log(`Encountered error while writting declarations.`)
console.error(error)
}
console.log('Done!')
} catch (error) {
console.log('SCRIPT ENDED IN ERROR')
console.log(error)
}
}
main()

@ -0,0 +1,71 @@
import { parseFunction } from '../shared/helpers/parse-function'
import { Doc, DocType } from '../shared/types/doc-types'
const FRAMES_WITH_FRAMES = [
'MessageFrame',
'Frame',
'FrameScriptObject',
'ScrollFrame',
'FogOfWarFrame',
'UnitPositionFrame',
'ArchaeologyDigSiteFrame'
]
const ANIMS_WITH_ANIM = [
'AnimAlpha',
'AnimFlipBook',
'AnimPath',
'AnimRotation',
'AnimTextureCoordTranslation',
'AnimTranslation',
'AnimScale',
'AnimVertexColor'
]
const SHOULD_IGNORE = ['Frame']
export const getParsedScriptObjects = (data: Doc[]) => {
const PARSED_SCRIPT_OBJECTS: string[] = []
for (const segment of data) {
if (segment.Type === DocType.ScriptObject && segment.Name) {
// For some reason the cooldown frame is documented twice
if (segment.Functions?.length && segment.Name !== 'CooldownFrameAPI') {
const PARSED_FUNCTIONS: string[] = []
let cleanName = segment.Name.replace(/(API|Simple)/gi, '')
if (!FRAMES_WITH_FRAMES.includes(cleanName)) {
cleanName = cleanName.replace('Frame', '')
}
if (cleanName.includes('Anim') && ANIMS_WITH_ANIM.includes(cleanName)) {
cleanName = cleanName.replace('Anim', '')
}
if (cleanName === 'ModelSceneFrameActorBase') cleanName = 'ModelSceneActorBase'
if (cleanName === 'ModelSceneFrameActor') cleanName = 'ModelSceneActor'
if (cleanName === 'ModelSceneFrame') cleanName = 'ModelScene'
if (cleanName === 'AnimGroup') cleanName = 'AnimationGroup'
if (cleanName === 'Anim') cleanName = 'Animation'
if (cleanName === 'Object') cleanName = 'ScriptObject'
if (!SHOULD_IGNORE.includes(cleanName)) {
for (const func of segment.Functions) {
PARSED_FUNCTIONS.push(parseFunction(func, cleanName))
}
PARSED_SCRIPT_OBJECTS.push(`
interface ${cleanName} {
${PARSED_FUNCTIONS.join('\n')}
}
`)
}
}
}
}
console.log(`Parsed ${PARSED_SCRIPT_OBJECTS.length} ScriptObjects`)
return PARSED_SCRIPT_OBJECTS
}

@ -0,0 +1,42 @@
import { parseField } from '../shared/helpers/parse-field'
import { Doc, TableType } from '../shared/types/doc-types'
import { CallBackTable } from '../shared/types/doc-types'
const parseCallback = (callback: CallBackTable) => {
const PARSED_FIELDS: string[] = []
if (callback.Arguments) {
for (const field of callback.Arguments) {
PARSED_FIELDS.push(parseField(field))
}
}
return `
type ${callback.Name} = (${PARSED_FIELDS.join(',')}) => void
`
}
export const getParsedCallbacks = (data: Doc[]) => {
const CALLBACKS: string[] = []
const PARSED_CALLBACKS: string[] = []
for (const segment of data) {
if (segment.Tables.length > 0) {
for (const table of segment.Tables) {
if (
table.Type === TableType.CallbackType &&
table.Name !== 'PendingPingOffScreenCallback' &&
!PARSED_CALLBACKS.includes(table.Name)
) {
CALLBACKS.push(parseCallback(table))
PARSED_CALLBACKS.push(table.Name)
}
}
}
}
// Save Struct declarations
console.log(`Parsed ${CALLBACKS.length} Callbacks`)
return CALLBACKS
}

@ -0,0 +1,42 @@
import { Doc, TableType } from '../shared/types/doc-types'
import { ConstantTable } from '../shared/types/doc-types'
const parseConstant = (table: ConstantTable) => {
const realValues = table.Values.filter((value) => !!value.Value)
if (!realValues.length) {
return
}
return `
const ${table.Name}: { ${table.Values.map(
(value) => `${value.Name}: ${value.Value}`
)} }
`
}
export const getParsedConstants = (data: Doc[]) => {
const CONSTANTS: string[] = []
const PARSED_CONSTANTS: string[] = []
for (const segment of data) {
if (segment.Tables.length > 0) {
for (const table of segment.Tables) {
if (
table.Type === TableType.Constants &&
!PARSED_CONSTANTS.includes(table.Name)
) {
const constant = parseConstant(table)
if (constant) {
CONSTANTS.push(constant)
PARSED_CONSTANTS.push(table.Name)
}
}
}
}
}
console.log(`Parsed ${CONSTANTS.length} Constants`)
return CONSTANTS
}

@ -0,0 +1,28 @@
import { Doc, EnumTable, TableType } from '../shared/types/doc-types'
const parseEnum = (value: EnumTable) => {
return `enum ${value.Name} { ${value.Fields.map(
(field) => `${field.Name} = ${field.EnumValue}`
).join(',')} }`
}
export const getParsedEnums = (data: Doc[]) => {
const ENUMS: string[] = []
// The UnitSex enum has a name conflict with a global function so we skip it.
const PARSED_ENUMS: string[] = ['UnitSex']
for (const segment of data) {
if (segment.Tables.length > 0) {
for (const table of segment.Tables) {
if (table.Type === TableType.Enumeration && !PARSED_ENUMS.includes(table.Name)) {
ENUMS.push(parseEnum(table))
PARSED_ENUMS.push(table.Name)
}
}
}
}
console.log(`Parsed ${ENUMS.length} Enums`)
return ENUMS
}

@ -0,0 +1,37 @@
import { onlyUnique } from '../shared/helpers/only-unique'
import { parseField } from '../shared/helpers/parse-field'
import { Doc } from '../shared/types/doc-types'
export const getParsedEventData = (data: Doc[]) => {
const EVENT_KEYS: string[] = []
const PARSED_EVENTS: string[] = []
for (const segment of data) {
if (segment.Events?.length) {
for (const event of segment.Events) {
EVENT_KEYS.push(event.LiteralName)
PARSED_EVENTS.push(`
${event.LiteralName}: ${
!event.Payload
? undefined
: `LuaMultiReturn<[${event.Payload.map((payload) =>
parseField(payload)
).join(',')}]>`
}
`)
}
}
}
const UNIQUE_KEYS = EVENT_KEYS.filter(onlyUnique)
console.log(`Saving ${UNIQUE_KEYS.length} Events`)
return `
interface WowEventPayload {
${PARSED_EVENTS.join('\n')}
}
type WoWEventKey = keyof WowEventPayload
`
}

@ -0,0 +1,48 @@
import { parseField } from '../shared/helpers/parse-field'
import { Doc, TableType } from '../shared/types/doc-types'
import { StructureTable } from '../shared/types/doc-types'
export const parseStruct = (data: StructureTable) => {
const PARSED_TYPES: string[] = []
for (const field of data.Fields) {
const parsedField = parseField(field)
PARSED_TYPES.push(parsedField)
}
if (PARSED_TYPES.length !== 0) {
return `
interface ${data.Name} {
${PARSED_TYPES.join(',\n')}
}
`
}
return undefined
}
export const getParsedStructs = (data: Doc[]) => {
const STRUCTURES: string[] = []
const PARSED_STRUCTURES: string[] = []
for (const segment of data) {
if (segment.Tables.length > 0) {
for (const table of segment.Tables) {
if (
table.Type === TableType.Structure &&
!PARSED_STRUCTURES.includes(table.Name)
) {
const struct = parseStruct(table)
if (struct) {
STRUCTURES.push(struct)
PARSED_STRUCTURES.push(table.Name)
}
}
}
}
}
// Save Struct declarations
console.log(`Parsed ${STRUCTURES.length} Structures`)
return STRUCTURES
}

@ -0,0 +1,45 @@
import { parseFunction } from '../shared/helpers/parse-function'
import { Doc, DocType } from '../shared/types/doc-types'
const SYSTEMS_TO_SKIP = ['C_Timer']
export const getParsedSystems = (data: Doc[]) => {
const PARSED_SYSTEMS: string[] = [
`function CanInspect(token: UnitToken, showError?: boolean): boolean`,
`function GetSpecializationRoleByID(specId: number): "DAMAGER" | "TANK" | "HEALER" | undefined`,
`function IsPlayerSpell(spellId: number): boolean`,
`function GetSpellCooldown(spellId: number): LuaMultiReturn<[number, number, number, number]>`,
`function GetShapeshiftFormID(): number`,
`function UnitHasIncomingResurrection(token: string): boolean`,
`function CreateFrame<T extends FrameType>(type: T, name?: string, parent?: FrameGeneric<T>, template?: string, id?: number): FrameGeneric<T>`
]
const systemSegments = data.filter((segment) => segment.Type === DocType.System)
for (const segment of systemSegments) {
const PARSED_FUNCTIONS: string[] = []
if (!segment.Namespace || !SYSTEMS_TO_SKIP.includes(segment.Namespace)) {
if (segment.Functions) {
for (const func of segment.Functions) {
PARSED_FUNCTIONS.push(parseFunction(func))
}
if (PARSED_FUNCTIONS.length > 0) {
if (segment.Namespace) {
PARSED_SYSTEMS.push(`
namespace ${segment.Namespace} {
${PARSED_FUNCTIONS.join('\n')}
}
`)
} else {
PARSED_SYSTEMS.push(PARSED_FUNCTIONS.join('\n'))
}
}
}
}
}
console.log(`Parsed ${PARSED_SYSTEMS.length} Systems`)
return PARSED_SYSTEMS
}

@ -0,0 +1,3 @@
export const EVENTS_KEY = 'Events'
export const SCRIPT_OBJECTS_KEY = 'ScriptObjects'
export const SYSTEMS_KEY = 'Systems'

@ -0,0 +1,3 @@
export function onlyUnique(value: unknown, index: number, array: unknown[]) {
return array.indexOf(value) === index
}

@ -0,0 +1,43 @@
import { Doc, Field, TableType } from '../types/doc-types'
import { WOW_TYPE_KEYS, WowTypeKeyAliases } from '../types/wow-type-aliases'
const data = require('../../../lua-docs/docs/docs.json') as Doc[]
const NAMES_TO_SKIP = ['PendingPingOffScreenCallback', 'ScriptObject', 'UnitSex']
const SAFE_TYPES = [TableType.Structure, TableType.Enumeration, TableType.CallbackType]
export const parseField = (field: Field) => {
const typeName = field.Type
if (field.Name === 'in') {
field.Name = 'inValue'
}
const correspondingTypeDef = data
.filter((segment) => segment.Tables.length !== 0)
.flatMap((segment) => segment.Tables)
.find((table) => table.Name === typeName)
// If we found a type for this name in the data, and its an Enum, Struct, or Callback
// then we can assume its safe to declare without aliasing.
if (
correspondingTypeDef &&
!NAMES_TO_SKIP.includes(typeName) &&
SAFE_TYPES.includes(correspondingTypeDef.Type)
) {
return `${field.Name}: ${typeName}${field.Nilable ? ' | undefined' : ''}`
}
if (!WOW_TYPE_KEYS.includes(field.Type)) {
throw new Error(`Encountered unknown string type: "${field.Type}"`)
}
let typeAlias = WowTypeKeyAliases[field.Type]
// The 'table' type requires a bit of special parsing.
if (typeof typeAlias === 'function') {
typeAlias = typeAlias(field)
}
return `${field.Name}: ${typeAlias}${field.Nilable ? ' | undefined' : ''}`
}

@ -0,0 +1,41 @@
import { WowFunction } from '../types/doc-types'
import { parseField } from './parse-field'
const getReturnString = (toReturn: string[]) => {
if (!toReturn.length) return 'void'
const returnString = toReturn.join(',')
if (toReturn.length > 1) {
return `LuaMultiReturn<[${returnString}]>`
}
const splitReturn = returnString.split(':')
return splitReturn[1]
}
export const parseFunction = (func: WowFunction, fieldParent?: string) => {
const ARGUMENT_FIELDS: string[] = []
const RETURN_FIELDS: string[] = []
if (func.Name === 'SetScript' && fieldParent === 'ScriptRegion') {
return ''
}
if (func.Arguments?.length) {
for (const arg of func.Arguments) {
ARGUMENT_FIELDS.push(parseField(arg))
}
}
if (func.Returns?.length) {
for (const returnField of func.Returns) {
RETURN_FIELDS.push(parseField(returnField))
}
}
return `
${fieldParent ? '' : 'function '}${func.Name}(${ARGUMENT_FIELDS.join(
','
)}): ${getReturnString(RETURN_FIELDS)}
`
}

@ -0,0 +1,87 @@
import { WowTypeKeys } from './wow-type-aliases'
export enum TableType {
Enumeration = 'Enumeration',
Structure = 'Structure',
Constants = 'Constants',
CallbackType = 'CallbackType'
}
export enum DocType {
System = 'System',
ScriptObject = 'ScriptObject'
}
export const VALID_TABLE_TYPES = [
TableType.Enumeration,
TableType.Structure,
TableType.Constants,
TableType.CallbackType
]
export interface Field {
Name: string
Type: WowTypeKeys
Nilable: boolean
InnerType: WowTypeKeys
Mixin?: string
}
export interface CallBackTable {
Name: string
Type: TableType.CallbackType
Arguments?: Field[]
}
export interface EnumTable {
MinValue: number
MaxValue: number
NumValues: number
Name: string
Type: TableType.Enumeration
Fields: {
Name: string
Type: string
EnumValue: number
}[]
}
export interface ConstantTable {
Name: string
Type: TableType.Constants
Values: {
Name: string
Type: string
Value?: unknown
}[]
}
export interface StructureTable {
Name: string
Type: TableType.Structure
Fields: Field[]
}
export interface WoWEvent {
Type: 'Event'
LiteralName: string
Name: string
Payload?: Field[]
}
export interface WowFunction {
Returns?: Field[]
Arguments?: Field[]
Type: 'Function'
Name: string
Documentation?: string[]
}
export interface Doc {
Tables: (CallBackTable | EnumTable | ConstantTable | StructureTable)[]
Events?: WoWEvent[]
Type?: DocType
Name?: string
Functions?: WowFunction[]
Namespace?: string
}

@ -0,0 +1,129 @@
import { Field } from './doc-types'
/**
* The wow docs contain a lot of types that dont neatly point to their actual type (e.g "fileID" instead of "number"),
* or in some cases point to a type that we've moved elsewhere in the type heirarchy.
* For this reason we have to take these type names, and map them to one usable by us
*/
export type WowTypeKeys = (typeof WOW_TYPE_KEYS)[number]
export const WowTypeKeyAliases: {
[key: string]: string | ((field: Field) => string)
} = {
string: 'string',
number: 'number',
BigInteger: 'number',
FileAsset: 'string',
ArtifactTiers: 'string',
ConnectionIptype: 'string',
ModelAsset: 'number',
GameMode: 'string',
JustifyVertical: 'number',
GameRule: 'number',
SpellIdentifier: 'number',
ItemCreationContext: 'number',
Vocalerrorsounds: 'number',
UnitSex: 'number',
mouseButton: 'unknown',
JustifyHorizontal: 'string',
normalizedValue: 'number',
stringView: 'string',
PendingPingOffScreenCallback: '(() => void)',
UiMapPoint: 'UiMapPoint',
LuaValueVariant: '{}',
ReportInfo: 'ReportInfoMixin',
ItemSoundType: 'ItemSoundType',
SimpleControlPoint: 'unknown',
size: 'number',
ItemInfo: 'string | number',
ScriptObject: 'ScriptObject',
FilterMode: 'FilterMode',
SimpleMaskTexture: 'ScriptObject',
StatusBarFillStyle: 'StatusBarFillStyle',
Orientation: 'Orientation',
uiAddon: 'string | number',
SimpleLine: 'Line',
InsertMode: 'InsertMode',
SimplePathAnim: 'ScriptObject',
BlendMode: 'BlendMode',
SimpleFontString: 'FontString',
PlayerLocation: 'PlayerLocationMixin',
SimpleWindow: 'unknown',
SimpleFont: 'FontInfo',
SimpleButtonStateToken: 'SimpleButtonStateToken',
LoopType: 'LoopType',
TBFFlags: 'FontFlags[]',
SimpleAnim: 'Animation',
CurveType: 'string',
SingleColorValue: 'number',
IDOrLink: 'string | number',
luaFunction: '(() => void)',
uiRect: '[number, number, number, number]',
TextureAssetDisk: 'string',
TextureAsset: 'string',
SmoothingType: 'string',
uiFontHeight: 'number',
CScriptObject: 'ScriptObject',
SimpleAnimGroup: 'AnimationGroup',
SimpleTexture: 'Texture',
DrawLayer: 'DrawLayer',
ModelSceneFrameActor: 'ModelSceneActor',
ItemTransmogInfo: 'ItemTransmogInfo',
TooltipComparisonItem: 'TooltipComparisonItem',
AnimationDataEnum: 'number',
kstringLfgListChat: 'string',
TransmogLocation: 'TransmogLocationMixin',
NamePlateFrame: 'NamePlateFrame',
UnitToken: 'UnitToken',
WeeklyRewardItemDBID: 'string',
CachedRewardType: 'CachedRewardType',
WeeklyRewardChestThresholdType: 'WeeklyRewardChestThresholdType',
RecruitAcceptanceID: 'string',
kstringLfgListSearch: 'string',
kstringLfgListApplicant: 'string',
GarrisonFollower: 'number',
colorRGBA: 'ColorMixin',
kstringClubMessage: 'string',
CalendarEventID: 'string',
bool: 'boolean',
vector2: 'Vector2DMixin',
ClubId: 'string',
EmptiableItemLocation: 'ItemLocationMixin',
AzeriteItemLocation: 'ItemLocationMixin',
luaIndex: 'number',
NotificationDbId: 'string',
fileID: 'number',
cstring: 'string',
ClubStreamId: 'string',
ClubInvitationId: 'string',
textureAtlas: 'string',
table: (field: Field) => {
if (field.InnerType === 'table') {
throw new Error('String type error: Table of a table.')
}
return `${WowTypeKeyAliases[field.InnerType]}[]`
},
colorRGB: 'ColorMixin',
BigUInteger: 'number',
WOWMONEY: 'number',
TooltipData: 'TooltipData',
time_t: 'number',
AuraData: 'AuraData',
ModelSceneFrame: 'ModelScene',
TransmogPendingInfo: 'TransmogLocationMixin',
WOWGUID: 'string',
textureKit: 'string',
ItemLocation: 'ItemLocationMixin',
vector3: 'Vector3DMixin',
HTMLTextType: 'unknown',
TBFStyleFlags: 'unknown',
FramePoint: 'string',
ScriptRegion: 'unknown',
uiUnit: 'number',
SimpleFrame: 'unknown',
FrameStrata: 'FrameStrata',
AzeriteEmpoweredItemLocation: 'ItemLocationMixin'
} as const
export const WOW_TYPE_KEYS = Object.keys(WowTypeKeyAliases)

File diff suppressed because it is too large Load Diff

@ -0,0 +1,58 @@
local dkjson = require("../../lib/dkjson/dkjson")
-- In their documentation code, blizzard uses some globals that we need to stub in else they'll be nil
CustomOptionTattoo = 0
CustomOptionTattooColor = 0
MAX_STABLE_SLOTS = 200
NUM_PET_SLOTS_THAT_NEED_LEARNED_SPELL = 5
EXTRA_PET_STABLE_SLOT = 5
Enum = {
PlayerCurrencyFlagsDbFlags = {
InBackpack = 0,
UnusedInUI = 0,
},
LFGRoleMeta = {
NumValues = 1
}
}
Constants = {
CharCustomizationConstants = {
CHAR_CUSTOMIZE_CUSTOM_DISPLAY_OPTION_LAST = 0,
CHAR_CUSTOMIZE_CUSTOM_DISPLAY_OPTION_FIRST = 0,
},
PetConsts = {
MAX_STABLE_SLOTS = 200,
NUM_PET_SLOTS_THAT_NEED_LEARNED_SPELL = 5,
EXTRA_PET_STABLE_SLOT = 5
}
}
-- End of stubs
local Tables = {}
APIDocumentation = {
AddDocumentationTable = function(self, doc)
Tables[#Tables+1] = doc
end
}
local directory = ...
for file in io.popen('ls "' .. directory .. '"'):lines() do
if file:match("^.+%.lua$") then
require(directory .. "/" .. file:gsub("%.lua$", ""))
end
end
local encoded = dkjson.encode(Tables)
local file = io.open("./scripts/lua-docs/docs/docs.json", "w")
if file and type(encoded) == 'string' then
file:write(encoded)
file:close()
print("Generated JSON for Lua Types.")
else
print("Unable to find file to write to.")
end

@ -0,0 +1,9 @@
{
"compilerOptions": {
"target": "ESNext",
"lib": ["ESNext"],
"moduleResolution": "Node",
"types": ["node"],
"strict": true
},
}

@ -0,0 +1,95 @@
const [_Tinkr] = [...$vararg]
const Tinkr = _Tinkr as unknown as tinkr
interface PrintArgs {
text: string
prefix?: string
level?: 'info' | 'warn' | 'error'
color?: string
}
function Print(this: void, { text, prefix, level, color }: PrintArgs) {
let textColor: string | undefined = color
if (!textColor) {
textColor =
level === 'error'
? 'FF76CE'
: level === 'info'
? '94FFD8'
: level === 'warn'
? 'FDFFC2'
: 'A3D8FF'
}
print(`|cFF${textColor}[${prefix || 'Mekanome'}]|r |cFFFFFFFF ${text}`)
}
function GetObject(this: void, token: string): WowGameObject | false {
return (Object as typeof TObject)(token)
}
function Error(this: void, text: string) {
Print({ text, prefix: 'ERROR', level: 'error' })
error(text)
}
const Mekanome: Partial<IMekanome> = {
enabled: false,
Print,
Error,
GetObject
}
function Load(this: void, path: string) {
return Tinkr.Util.Evaluator.LoadString(
Tinkr.Util.File.Read(path),
[Tinkr, Mekanome],
path
).default
}
function LoadDirectoryFiles(this: void, path: string) {
const directories = ListDirectories(path)
for (const directory of directories) {
LoadDirectoryFiles(`${path}/${directory}`)
}
const files = ListFiles(path)
for (const file of files) {
Load(`${path}/${file}`)
Print({ text: `${path}/${file}`, prefix: 'Loaded' })
}
}
function Init(this: void) {
Print({ text: '(V) (°,,,,°) (V)' })
LoadDirectoryFiles('scripts/utils')
LoadDirectoryFiles('scripts/managers')
LoadDirectoryFiles('scripts/actions')
// @ts-ignore
const keys: (keyof IMekanome)[] = Object.keys(Mekanome)
keys.forEach((key) => {
const module = Mekanome[key]
if (
module !== undefined &&
typeof module === 'object' &&
// @ts-ignore
module['Init'] !== undefined
) {
// @ts-ignore
module.Init()
Print({ text: key, prefix: 'Initialized', level: 'info' })
}
})
}
Init()
export default {}

@ -0,0 +1,16 @@
const [_, _Mekanome] = [...$vararg]
const Mekanome = _Mekanome as unknown as IMekanome
const ArcaneInt = Mekanome.SpellUtils.Create(1459)
function ArcaneMage(this: void): Castable | undefined {
return Mekanome.SpellUtils.SelfCast(ArcaneInt)
}
Mekanome.ActionManager.Add('arcane_mage', {
run: ArcaneMage,
priority: 1,
loadConditions: { spec: 1, class: 8 }
})
export default undefined

@ -0,0 +1,16 @@
const [_, _Mekanome] = [...$vararg]
const Mekanome = _Mekanome as unknown as IMekanome
function FireMage(this: void): Castable | undefined {
print('Fire!')
return
}
Mekanome.ActionManager.Add('fire_mage', {
run: FireMage,
priority: 1,
loadConditions: { spec: 2, class: 8 }
})
export default undefined

@ -0,0 +1,16 @@
const [_, _Mekanome] = [...$vararg]
const Mekanome = _Mekanome as unknown as IMekanome
function FrostMage(this: void): Castable | undefined {
print('Frost!')
return undefined
}
Mekanome.ActionManager.Add('frost_mage', {
run: FrostMage,
priority: 1,
loadConditions: { spec: 3, class: 8 }
})
export default undefined

@ -0,0 +1,230 @@
const [_, _Mekanome] = [...$vararg]
const Mekanome = _Mekanome as unknown as IMekanome
const actions = new LuaMap<string, Action>()
let isInitialized = false
let isMouseoverSettingEnabled = false
interface ActionLoadConditions {
class?: number
spec?: number
custom?: () => boolean
}
/**
* Evaluates the actions load conditions to tell the ActionManager
* wether or not this action should be loaded.
* @param loadConditions ActionLoadConditions
* @returns boolean
*/
function GetIsActionLoaded(this: void, loadConditions: ActionLoadConditions) {
let matchesClass = true
let matchesSpec = true
let matchesCustom = true
if (loadConditions.class !== undefined) {
const [, , classId] = UnitClass('player')
matchesClass = loadConditions.class === classId
}
if (loadConditions?.spec !== undefined) {
matchesSpec = loadConditions.spec === GetSpecialization()
}
if (loadConditions.custom !== undefined) {
matchesCustom = loadConditions.custom()
}
return matchesClass === true && matchesSpec === true && matchesCustom === true
}
/**
* Returns the actions map in array format, sorted by priority.
*/
function GetSortedActions(this: void) {
const actionsArray: Action[] = []
for (const [, action] of actions) {
actionsArray.push(action)
}
return actionsArray.sort((a, b) => {
return a.priority - b.priority
})
}
/**
* Adds an action to the manager to call inside its ticker if loaded.
* @param name A unique name to identify the action.
* @param action The function to call for the action.
* @param options Options for the action to add.
*/
function Add(
this: void,
name: string,
options: {
run: RunAction
priority?: number
loadConditions: ActionLoadConditions
}
) {
if (actions.has(name)) {
throw Mekanome.Error(`Tried to add action ${name} but it already exists.`)
}
const loaded =
options.loadConditions !== undefined
? GetIsActionLoaded(options.loadConditions)
: false
let priority = options.priority
if (priority === undefined) {
const sortedActions = GetSortedActions()
if (sortedActions.length === 0) {
priority = 1
} else {
const lastAction = sortedActions[sortedActions.length - 1]
priority = lastAction.priority + 1
}
}
actions.set(name, {
priority,
run: options.run,
loadConditions: options.loadConditions,
loaded,
name
})
if (loaded) {
Mekanome.Print({ text: name, prefix: 'Module Loaded', level: 'info' })
}
}
/**
* Removes the action from the list.
* @param name The name of the action to remove.
*/
function Remove(this: void, name: string) {
if (actions.has(name)) {
actions.delete(name)
}
}
/**
* Calls the list of loaded actions, in order.
* If an action returns a binding, that binding is run and the loop is terminted
* for a tick.
*/
function CallActions(this: void) {
const actionList = GetSortedActions()
for (const action of actionList) {
if (action && action.loaded) {
// If an action returns a castable, dont call any other actions this tick.
const castable = action.run()
if (castable !== undefined) {
Mekanome.Cast(castable)
break
}
}
}
}
/**
* In order to cast on targets that arent the active target, we need
* to leverage mouse over casting. If the setting isnt enabled, Mekanome
* will not be able to cast on mobs that aren't the current target.
*/
function CheckSettings(this: void) {
const mouseoverSetting = C_CVar.GetCVar('enableMouseoverCast')
if (mouseoverSetting !== undefined && mouseoverSetting !== '0') {
isMouseoverSettingEnabled = true
} else {
isMouseoverSettingEnabled = false
throw Mekanome.Error('Please enable mouseover casting in the settings menu.')
}
}
/**
* Initializes the action manager.
*/
function Init(this: void) {
if (isInitialized === false) {
// Check for the right settings.
CheckSettings()
// Sets a ticker to call the list of actions.
C_Timer.NewTicker(
0.15,
() => {
if (Mekanome.enabled && isMouseoverSettingEnabled) {
// Refresh the mob & traits managers every tick.
Mekanome.MobManager.Refresh()
Mekanome.PlayerStateManager.Refresh()
CallActions()
}
},
undefined
)
// Listens to the player spec change event, as that might change the load
// status of actions in the list.
Mekanome.EventManager.RegisterEventHandler<'PLAYER_SPECIALIZATION_CHANGED'>(
'PLAYER_SPECIALIZATION_CHANGED',
() => {
for (const [name, action] of actions) {
if (action.loadConditions) {
const loaded = GetIsActionLoaded(action.loadConditions)
if (loaded !== action.loaded) {
actions.set(name, { ...action, loaded })
Mekanome.Print({
text: name,
level: 'info',
prefix: loaded ? 'Loaded Module' : 'Unloaded Module'
})
}
}
}
},
'am_spec_change'
)
// Listens to CVAR updates to make sure that mouseover casting isnt
// disabled while Mekanome is running.
Mekanome.EventManager.RegisterEventHandler<'CVAR_UPDATE'>(
'CVAR_UPDATE',
([eventName]) => {
if (eventName === 'enableMouseoverCast') {
CheckSettings()
}
},
'am_cvar_update'
)
isInitialized = true
}
}
const _ActionManager = {
Add,
Remove,
Init
}
declare global {
type ActionManager = typeof _ActionManager
type RunAction = (this: void) => Castable | undefined
interface Action {
priority: number
name: string
loaded: boolean
loadConditions?: ActionLoadConditions
run: RunAction
}
}
Mekanome.ActionManager = _ActionManager
export default {}

@ -0,0 +1,99 @@
const [_, _Mekanome] = [...$vararg]
const Mekanome = _Mekanome as unknown as IMekanome
function New(this: void, token: string): Auras {
const auras = new LuaMap<number, AuraData>()
const helpfulSlots = C_UnitAuras.GetAuraSlots(
token as UnitToken,
'HELPFUL',
200,
undefined
)
helpfulSlots.forEach((s) => {
if (s !== undefined) {
const aura = C_UnitAuras.GetAuraDataBySlot(token, s)
if (aura) {
auras.set(aura.auraInstanceID, aura)
}
}
})
const harmfulSlots = C_UnitAuras.GetAuraSlots(
token as UnitToken,
'HARMFUL',
200,
undefined
)
harmfulSlots.forEach((s) => {
if (s !== undefined) {
const aura = C_UnitAuras.GetAuraDataBySlot(token, s)
if (aura) {
auras.set(aura.auraInstanceID, aura)
}
}
})
return auras
}
function GetUpdatedAuras(this: void, updateInfo: UnitAuraUpdateInfo, mob: Mob): Auras {
const auras: Auras = new LuaMap()
// If its a full update, just return a fresh aura manager.
if (updateInfo.isFullUpdate) {
return New(mob.token)
}
if (mob.auras !== undefined) {
for (const [instanceId, aura] of mob.auras) {
auras.set(instanceId, aura)
}
}
// Remove any removed auras from the table.
if (updateInfo.removedAuraInstanceIDs !== undefined) {
updateInfo.removedAuraInstanceIDs.forEach((id) => {
if (auras.has(id)) {
auras.delete(id)
}
})
}
// Add any added auras to the table.
if (updateInfo.addedAuras !== undefined) {
updateInfo.addedAuras.forEach((data) => {
auras.set(data.auraInstanceID, data)
})
}
if (updateInfo.updatedAuraInstanceIDs) {
updateInfo.updatedAuraInstanceIDs.forEach((id) => {
const data = C_UnitAuras.GetAuraDataByAuraInstanceID(mob.token, id)
if (data) {
auras.set(data.auraInstanceID, data)
}
})
}
return auras
}
const _AuraManager = {
New,
GetUpdatedAuras
}
declare global {
type Auras = LuaMap<number, AuraData>
type AuraManager = typeof _AuraManager
interface Aura {
id: number
isPlayerAura?: boolean
}
}
Mekanome.AuraManager = _AuraManager
export default {}

@ -0,0 +1,170 @@
const [_, _Mekanome] = [...$vararg]
const Mekanome = _Mekanome as unknown as IMekanome
const ActionBarButtonNames = [
'ActionButton',
'MultiBarBottomLeftButton',
'MultiBarBottomRightButton',
'MultiBarLeftButton',
'MultiBarRightButton',
'MultiBar5Button',
'MultiBar6Button',
'MultiBar7Button'
]
const BarMap = {
ActionButton: 'ACTIONBUTTON',
MultiBarBottomLeftButton: 'MULTIACTIONBAR1BUTTON',
MultiBarBottomRightButton: 'MULTIACTIONBAR2BUTTON',
MultiBarRightButton: 'MULTIACTIONBAR3BUTTON',
MultiBarLeftButton: 'MULTIACTIONBAR4BUTTON',
MultiBar5Button: 'MULTIACTIONBAR5BUTTON',
MultiBar6Button: 'MULTIACTIONBAR6BUTTON'
}
interface Binding {
binding: ActionButtons
id: number | string
type: 'item' | 'spell'
}
const bindings = new Map<number, Binding>()
let isInitialized = false
/**
* Loops through all of the WoW action bar buttons, checks to see if there is a spell
* or item on them, then stores that relationship for later use.
*/
function CheckBindings() {
bindings.clear()
for (const bar of ActionBarButtonNames) {
for (const i of $range(1, 12)) {
const actionButton = `${bar}${i}`
// @ts-ignore
const button = _G[actionButton]
if (
button !== undefined &&
typeof button === 'object' &&
button.action !== undefined
) {
const [type, id] = GetActionInfo(button.action)
if (type === 'spell' || type === 'item') {
// @ts-ignore
const barName = BarMap[bar]
const binding = `${barName}${i}` as ActionButtons
bindings.set(button.action, {
binding,
id,
type
})
}
}
}
}
Mekanome.Print({
text: `${bindings.size}`,
prefix: 'Bindings Update',
level: 'warn'
})
}
/**
* Returns the ActionButtons binding for a given spell.
* @param spell
* @returns ActionButtons
*/
function GetBindingForSpell(this: void, spell: Spell) {
let binding: ActionButtons | undefined = undefined
for (const [, bindingInfo] of bindings) {
if (bindingInfo.id === spell.id) {
binding = bindingInfo.binding
break
}
}
return binding
}
function CallBinding(this: void, binding: ActionButtons) {
RunBinding(binding)
RunBinding(binding, 'up')
}
/**
* Initializes the bindings manager.
*/
function Init(this: void) {
if (isInitialized === false) {
CheckBindings()
// Listen for the evet fired when a spell is added or removed from the action
// bar so that we can update our bindings.
Mekanome.EventManager.RegisterEventHandler<'ACTIONBAR_SLOT_CHANGED'>(
'ACTIONBAR_SLOT_CHANGED',
(args) => {
const slot = args[0]
const binding = bindings.get(slot)
const [type, id] = GetActionInfo(slot)
// This event gets fired for a couple of different reasons.
// 1. A spell is added or removed from the action bar.
// 2. A considtional macro on the action bar is update (e.g a mouseover macro when the player hovers something)
// 3. A cast starts and ends.
// We only care about case 1, so we only update bindings if we either dont have a binding saved
// for this slot, or the binding is changed.
if (binding !== undefined) {
if (type !== binding.type || id !== binding.id) {
CheckBindings()
}
} else {
if (type === 'spell' || type === 'item') {
CheckBindings()
}
}
},
'bm_action_bar_slot_changed'
)
// When you mount a dragon riding mount, and your spells get replaced with dragon
// riding abilities this is called so we update the bindings.
Mekanome.EventManager.RegisterEventHandler<'UPDATE_BONUS_ACTIONBAR'>(
'UPDATE_BONUS_ACTIONBAR',
() => {
CheckBindings()
},
'bm_player_mount_display'
)
// Listens to the player spec change event, as that might change the load
// status of actions in the list.
Mekanome.EventManager.RegisterEventHandler<'PLAYER_SPECIALIZATION_CHANGED'>(
'PLAYER_SPECIALIZATION_CHANGED',
() => {
CheckBindings()
},
'bm_spec_change'
)
isInitialized = true
}
}
const _BindingsManager = {
Init,
GetBindingForSpell,
CallBinding
}
declare global {
type BindingsManager = typeof _BindingsManager
}
Mekanome.BindingsManager = _BindingsManager
export default {}

@ -0,0 +1,51 @@
const [_Tinkr, _Mekanome] = [...$vararg]
const Tinkr = _Tinkr as unknown as tinkr
const Mekanome = _Mekanome as unknown as IMekanome
const meka = Tinkr.Util.Commands.New('meka')
/**
* Registers a commant to be used under the "meka" namespace
* @param prefix /meka {prefix}
* @param callback The function to be called when the command is entered.
* @param helpText Optional text to help describe the command.
*/
function Register(
this: void,
prefix: string,
callback: (this: void) => void,
helpText?: string
) {
meka.Register(prefix, callback, helpText)
}
/**
* Initializes core commands.
*/
function Init(this: void) {
Register(
'toggle',
() => {
if (Mekanome.enabled === false) {
Mekanome.Print({ text: 'Started' })
Mekanome.enabled = true
} else {
Mekanome.Print({ text: 'Stopped' })
Mekanome.enabled = false
}
},
'Toggles Mekanome.'
)
}
const _CommandManager = {
Register,
Init
}
declare global {
type CommandManager = typeof _CommandManager
}
Mekanome.CommandManager = _CommandManager
export default {}

@ -0,0 +1,87 @@
const [_, _Mekanome] = [...$vararg]
const Mekanome = _Mekanome as unknown as IMekanome
type EventCallback<T extends WoWEventKey> = (args: WowEventPayload[T]) => void
const events = new LuaMap<WowEvent, LuaMap<string, (args: any) => void>>()
const frame = CreateFrame<'Frame'>('Frame')
frame.SetScript('OnEvent', (f, event, ...args) => {
const callbacks = events.get(event)
if (callbacks) {
for (const [, cb] of callbacks) {
cb(args)
}
}
})
/**
* Registers an event to listen to.
* @param event The name of the wow event.
* @param callback The function to call when the event triggers.
* @param id The ID specific to this callback, so that multiple handlers can be added per event.
*/
function RegisterEventHandler<T extends WoWEventKey>(
this: void,
event: WowEvent,
callback: EventCallback<T>,
id: string
) {
const existingHandlers = events.get(event)
if (existingHandlers) {
if (existingHandlers.has(id)) {
return
}
existingHandlers.set(id, callback)
events.set(event, existingHandlers)
} else {
frame.RegisterEvent(event)
const handlers = new LuaMap<string, EventCallback<T>>()
handlers.set(id, callback)
events.set(event, handlers)
Mekanome.Print({ text: event, prefix: 'Registered', level: 'info' })
}
}
/**
* Unregisters a Wow event handler.
* @param event The event to unregister from.
* @param id The ID of the handler to unregister.
*/
function UnregisterEventHandler(this: void, event: WowEvent, id: string) {
const handlers = events.get(event)
if (handlers && handlers.has(id)) {
handlers.delete(id)
Mekanome.Print({
text: `Event: ${event} | ID: ${id}`,
prefix: 'Handler',
level: 'error'
})
}
if (handlers && handlers.isEmpty() === true) {
events.delete(event)
frame.UnregisterEvent(event)
Mekanome.Print({
text: event,
prefix: 'Event',
level: 'error'
})
}
}
const _EventManager = {
RegisterEventHandler,
UnregisterEventHandler
}
declare global {
type EventManager = typeof _EventManager
}
Mekanome.EventManager = _EventManager
export default {}

@ -0,0 +1,239 @@
const [_, _Mekanome] = [...$vararg]
const Mekanome = _Mekanome as unknown as IMekanome
let isInitialized = false
// We dont really care about critters, non combat pets, and gas clouds
const CREATURE_TYPES_TO_IGNORE: UnitCreatureType[] = [8, 12, 13, 14]
// Dummys dont really place nice with the combat checking APIs, so we
// just consider th
const DUMMYS = [
// City (SW, Orgrimar, etc)
31146, 31144, 32666, 32667, 46647, 114832, 153285, 153292,
// DragonFlight Valdrakken
198594, 194648, 189632, 194643, 194644, 197833, 189617, 194649,
// DragonFlight Iskaara
193563,
// Other
65310, 66374, 196394, 196406, 199057
]
const mobs = new LuaMap<string, Mob>()
// ------------
// INTERNAL
// ------------
/**
* Creates a "Mob" from a WowGameObject
* @param mobObject A WowGameObject corresponding to the unit
* @returns mob data
*/
function CreateMobFromObject(this: void, mobObject: WowGameObject): Mob {
let type: Mob['type'] = 'unknown'
const id = mobObject.id()
const unit = mobObject.unit()
if (
UnitIsFriend('player', unit as UnitToken) ||
UnitInParty(unit as UnitToken, undefined) ||
UnitInRaid(unit as UnitToken, undefined)
) {
type = 'friend'
}
if (
UnitCanAttack('player', unit as UnitToken) ||
(typeof id === 'number' && DUMMYS.includes(id))
) {
type = 'enemy'
}
return {
type,
token: mobObject.unit(),
guid: mobObject.guid(),
id: mobObject.id(),
auras: Mekanome.AuraManager.New(mobObject.unit())
}
}
function GetMobFromObject(this: void, mobObject: WowGameObject): Mob | undefined {
return mobs.get(mobObject.guid())
}
/**
* Updates the time in which a mob was last in combat.
* @param guid The GUID of the mob to update.
*/
function UpdateCombatTime(this: void, guid: string) {
const mob = GetMob({ guid })
if (mob && mob.type === 'enemy') {
mob.lastCombatTime = GetTime()
mobs.set(guid, mob)
}
}
// ------------
// EXTERNAL
// ------------
/**
* Returns a mob from the mob manager, if it exists
*/
function GetMob(
this: void,
{ token, guid }: { token?: string; guid?: string }
): Mob | undefined {
let id: string | undefined = undefined
if (guid) {
id = guid
} else if (token) {
id = token === 'none' ? token : ObjectGUID(token)
}
if (id === undefined) return undefined
if (mobs.has(id) === false) {
const gameObject = (Object as typeof TObject)(id)
if (gameObject !== false) {
mobs.set(id, CreateMobFromObject(gameObject))
}
}
return mobs.get(id)
}
/**
* Refreshes the list of mobs.
*/
function Refresh(this: void) {
const touchedMobs: string[] = []
const unitObjects = Objects(5)
const playerObjects = Objects(6)
const activePlayerObject = Objects(7)
const objects = unitObjects.concat(playerObjects).concat(activePlayerObject)
for (const gameObject of objects) {
const objectType = ObjectCreatureType(gameObject)
if (CREATURE_TYPES_TO_IGNORE.includes(objectType) === false) {
const existingMob = GetMobFromObject(gameObject)
const mob = CreateMobFromObject(gameObject)
touchedMobs.push(mob.guid)
if (existingMob === undefined) {
mobs.set(mob.guid, mob)
} else {
const isStale = existingMob.id !== mob.id || existingMob.token !== mob.token
if (isStale) {
mobs.set(existingMob.guid, {
...existingMob,
id: mob.id,
token: mob.token
})
}
}
}
}
for (const [guid, _] of mobs) {
if (touchedMobs.includes(guid) === false) {
mobs.delete(guid)
}
}
}
/**
* Iterates over the mob list, and calls a function for each mob.
* @param callback Function called for each mob in the list.
* @param type The type of mobs to iterate over. "enemies_active" iterates over enemies that are in combat.
*/
function Iterate(
this: void,
callback: (mob: Mob) => void,
type?: Mob['type'] | 'enemies_active'
) {
for (const [_, mob] of mobs) {
if (type !== undefined) {
if (type === 'enemies_active') {
const combatOdds = Mekanome.MobUtils.InCombatOdds(mob)
if (combatOdds !== undefined && combatOdds < 0.4) {
callback(mob)
}
} else if (mob.type === type) {
callback(mob)
}
} else {
callback(mob)
}
}
}
/**
* Initializes the mob manager.
*/
function Init(this: void) {
if (isInitialized === false) {
// Update mobs combat times when combat log events come through for them.
// TODO: Only do this if the source is the player, or someone in the players party.
Mekanome.EventManager.RegisterEventHandler<'COMBAT_LOG_EVENT_UNFILTERED'>(
'COMBAT_LOG_EVENT_UNFILTERED',
() => {
if (Mekanome.enabled) {
const [, , , source, , , , dest] = CombatLogGetCurrentEventInfo()
UpdateCombatTime(source)
UpdateCombatTime(dest)
}
},
'mm_combat_log_event_unfiltered'
)
// Update mobs aura info when this event comes through and they're being managed.
Mekanome.EventManager.RegisterEventHandler<'UNIT_AURA'>(
'UNIT_AURA',
([token, auraInfo]) => {
const mob = GetMob({ token })
if (mob) {
const updatedAuras = Mekanome.AuraManager.GetUpdatedAuras(auraInfo, mob)
mob.auras = updatedAuras
mobs.set(mob.guid, mob)
}
},
'mm_unit_aura'
)
isInitialized = true
}
}
const _MobManager = {
Iterate,
Refresh,
GetMob,
Init
}
declare global {
type Mob = {
type: 'friend' | 'enemy' | 'unknown'
lastCombatTime?: number
token: string
guid: string
id: number | boolean
auras: Auras
}
type MobManager = typeof _MobManager
}
Mekanome.MobManager = _MobManager
export default {}

@ -0,0 +1,104 @@
const [_, _Mekanome] = [...$vararg]
const Mekanome = _Mekanome as unknown as IMekanome
let isMoving = false
let isCasting = false
let isChanneling = false
let isAlive = true
let isMounted = false
let Player: Mob | undefined = undefined
let Target: Mob | undefined = undefined
let Mouseover: Mob | undefined = undefined
const sqw = C_CVar.GetCVar('SpellQueueWindow')
let spellQueueWindow = sqw !== undefined ? Number(sqw) : 0
/**
* Refresh the players state
*/
function Refresh(this: void) {
const { MobUtils, MobManager } = Mekanome
const player = MobManager.GetMob({ token: 'player' })
const target = MobManager.GetMob({ token: 'target' })
const mouseover = MobManager.GetMob({ token: 'mouseover' })
if (player === undefined) {
throw Mekanome.Error('Unable to refresh player state as player object doesnt exist.')
}
Player = player
Target = target
Mouseover = mouseover
isMoving = MobUtils.GetIsMoving(player)
isMounted = MobUtils.IsMounted(player)
isAlive = MobUtils.IsAlive(player)
const castEnd = MobUtils.GetCastEndTime(player)
const channelEnd = MobUtils.GetChannelEndTime(player)
if (castEnd) {
const timeUntilEnd = castEnd - GetTime()
isCasting = timeUntilEnd > spellQueueWindow / 1000
} else {
isCasting = false
}
if (channelEnd) {
const timeUntilEnd = channelEnd - GetTime()
isChanneling = timeUntilEnd > spellQueueWindow / 1000
} else {
isChanneling = false
}
}
/**
* Get the players state
*/
function GetPlayerState(this: void) {
if (Player === undefined) {
throw Mekanome.Error('Unable to get player state as player object doesnt exist.')
}
return {
Player,
Target,
Mouseover,
spellQueueWindow,
isAlive,
isMounted,
isMoving,
isCasting,
isChanneling
}
}
function Init(this: void) {
// Listens to CVAR updates to make sure that our local sqw var
// is kept in sync with the players
Mekanome.EventManager.RegisterEventHandler<'CVAR_UPDATE'>(
'CVAR_UPDATE',
([eventName]) => {
if (eventName === 'SpellQueueWindow') {
const newSQW = C_CVar.GetCVar('SpellQueueWindow')
if (newSQW !== undefined) {
spellQueueWindow = Number(newSQW)
}
}
},
'tm_cvar_update'
)
}
const _PlayerStateManager = {
Refresh,
GetPlayerState,
Init
}
declare global {
type PlayerStateManager = typeof _PlayerStateManager
}
Mekanome.PlayerStateManager = _PlayerStateManager
export default {}

@ -0,0 +1,53 @@
interface BaseCastable {
variant: CastableVariant
id: number
binding: ActionButtons
}
interface GroundCastable extends BaseCastable {
position: Vector3
variant: 'ground'
}
interface TargetCastable extends BaseCastable {
target: Mob
variant: 'target'
}
interface SelfCastable extends BaseCastable {
variant: 'self'
}
declare global {
interface IMekanome {
enabled: boolean
CommandManager: CommandManager
ActionManager: ActionManager
EventManager: EventManager
PlayerStateManager: PlayerStateManager
MobManager: MobManager
MobUtils: MobUtils
VectorUtils: VectorUtils
BindingsManager: BindingsManager
SpellUtils: SpellUtils
AuraManager: AuraManager
AuraUtils: AuraUtils
Cast: (this: void, castable: Castable) => void
GetObject: (this: void, token: string) => WowGameObject | false
Require<T>(this: void, file: string): T
Error(this: void, text: string): void
Print(
this: void,
args: {
text: string
prefix?: string
level?: 'info' | 'warn' | 'error'
color?: string
}
): void
}
type CastableVariant = 'self' | 'target' | 'ground'
type Castable = GroundCastable | TargetCastable | SelfCastable
}
export {}

319
src/types/Tinkr.d.ts vendored

@ -0,0 +1,319 @@
declare global {
interface WowGameObject {
/** Returns the Tinkr object managers reference to the object. */
unit: () => string
/** Returns the objects full WoW GUID. */
guid: () => string
/** Returns the units ID (can also be found in the GUID). Players dont have an ID. */
id: () => number | boolean
}
function DirectoryExists(this: void, path: string): boolean
function ListFiles(this: void, directory: string): string[]
function ListDirectories(this: void, path: string): string[]
function SetMouseover(this: void, mob: WowGameObject | undefined): true | false
interface Missile {
// Current X
cx: number
// Current Y
cy: number
// Current Z
cz: number
// Hit X
hx: number
// Hit Y
hy: number
// Hit Z
hz: number
// Init X
ix: number
// Init Y
iy: number
// Init Z
iz: number
// Model X
mx: number
// Model Y
my: number
// Model Z
mz: number
source?: WowGameObject
target?: WowGameObject
spellId?: number
spellVisualId?: number
}
type ObjectType =
// Object
| 0
// Item
| 1
// Container
| 2
// AzeriteEmpoweredItem
| 3
// AzeriteItem
| 4
// Unit
| 5
// Player
| 6
// ActivePlayer
| 7
// GameObject
| 8
// DynamicObject
| 9
// Corpse
| 10
// AreaTrigger
| 11
// SceneObject
| 12
// Conversation
| 13
// AiGroup
| 14
// Scenario
| 15
// Loot
| 16
// Invalid
| 17
type UnitCreatureType =
// Beast
| 1
// Dragonkin
| 2
// Demon
| 3
// Elemental
| 4
// Giant
| 5
// Undead
| 6
// Humanoid
| 7
// Critter
| 8
// Mechanical
| 9
// NotSpecified
| 10
// Totem
| 11
// NonCombatPet
| 12
// GasCloud
| 13
// WildPet
| 14
// Aberration
| 15
/**
* Returns a list of missiles
*/
function Missiles(this: void): LuaPairsIterable<number, Missile>
/**
* Returns whether or not a path exists.
* */
function DirectoryExists(this: void, path: string): boolean
/**
* Returns an iterator of all files in the directory
* */
function ListFiles(this: void, path: string): LuaIterable<string>
/**
* Logs to the tinkr console
* */
function Log(this: void, toLog: string): void
/**
* Will return a tokens GUID
*/
function ObjectGUID(this: void, token: string | WowGameObject): string
/**
* Will return a tokens GUID
*/
function TObject(this: void, token: string): WowGameObject | false
/**
* Returns a list of WowGameObjects, optionally filtered by type
*/
function Objects(this: void, type?: ObjectType): WowGameObject[]
/**
* Returns a gameobject's creature type
*/
function ObjectCreatureType(this: void, gameObject: WowGameObject): UnitCreatureType
/**
* Returns an objects spec number
*/
function ObjectSpecializationID(this: void, gameObject: WowGameObject): number
/**
* Returns an objects height
*/
function ObjectHeight(this: void, token: string | WowGameObject): number
/**
* Returns a units vector3 position
*/
function ObjectPosition(
this: void,
object: WowGameObject | string
): LuaMultiReturn<[number, number, number]>
/**
* Returns the distance between two coordinates
*/
function FastDistance(
this: void,
x: number,
y: number,
z: number,
x2: number,
y2: number,
z2: number
): number
/**
* Returns the coords of the specified attachment position
*/
function GetUnitAttachmentPosition(
this: void,
token: string | WowGameObject,
position: number
): LuaMultiReturn<[number, number, number]>
/**
* Traces a line between two coords, and returns a position if there was a collision while tracing
*/
function TraceLine(
this: void,
x1: number,
y1: number,
z1: number,
x2: number,
y2: number,
z2: number,
flag: number
): LuaMultiReturn<[number, number, number]>
/**
* Returns the object combat reach
*/
function ObjectCombatReach(this: void, token: string): number | false
/**
* Returns the game object of the objects current cast target
*/
function ObjectCastingTarget(
this: void,
token: string | WowGameObject
): WowGameObject | false
/**
* Returns the objects movement bit. See https://docs.tinkr.site/Lua/Objects/ObjectMovementFlag/
*/
function ObjectMovementFlag(this: void, token: string | WowGameObject): number
/**
* Returns the objects rotation
*/
function ObjectRotation(this: void, token: string | WowGameObject): number | false
/**
* Returns wether or not the unit is sitting
*/
function UnitIsSitting(this: void, token: string | WowGameObject): boolean
/**
* Returns the objects model id
*/
function ObjectModelId(this: void, token: string | WowGameObject): number | false
/**
* Returns the objects object manager ID
*/
function ObjectId(this: void, token: string | WowGameObject): number | false
/**
* Returns whether or not the unit is mounted
*/
function UnitIsMounted(this: void, token: string | WowGameObject): boolean
/**
* Returns the target object of the object
*/
function ObjectTarget(
this: void,
token: string | WowGameObject
): WowGameObject | undefined
/**
* Returns whether or not a spell is pending (green cursor)
*/
function IsSpellPending(this: void): number
/**
* Clicks on the specified position.
*/
function Click(this: void, x: number, y: number, z: number): void
interface tinkr {
Util: {
Evaluator: {
LoadString: (toLoad: string, args: any[], filePath: string) => any
}
Commands: {
New(prefix: string): {
Register: (prefix: string, cb: () => void, helpMessage?: string) => void
}
}
File: {
Read: (filePath: string) => string
Run: (filePath: string, args?: any[]) => any
LoadPackage: (filePath: string) => void
}
}
Common: {
GetAnglesBetweenPositions(
x1: number,
y1: number,
z1: number,
x2: number,
y2: number,
z2: number
): number
}
}
// Overrides of WoW Docs
namespace C_Timer {
function After(this: void, seconds: number, callback: () => void): void
function NewTicker(
this: void,
seconds: number,
callback: () => void,
iterations: number | undefined
): { IsCancelled(): boolean; Cancel(): void }
function NewTimer(
this: void,
seconds: number,
callback: () => void
): { IsCancelled(): boolean; Cancel(): void }
}
const SlashCmdList: { [key: string]: (this: void, message: string) => void }
}
export {}

@ -0,0 +1,55 @@
/* eslint-disable @typescript-eslint/no-duplicate-enum-values */
/* eslint-disable @typescript-eslint/ban-types */
/** @noSelfInFile **/
declare global {
type CraftingOrderRequestCallback = (
result: CraftingOrderResult,
orderType: CraftingOrderType,
displayBuckets: boolean,
expectMoreRows: boolean,
offset: number,
isSorted: boolean
) => void
type CraftingOrderRequestMyOrdersCallback = (
result: CraftingOrderResult,
expectMoreRows: boolean,
offset: number,
isSorted: boolean
) => void
type PingCooldownStartedCallback = (info: PingCooldownInfo) => void
type PingPinFrameAddedCallback = (
region: unknown,
uiTextureKit: string,
isWorldPoint: boolean
) => void
type PingPinFrameRemovedCallback = (region: unknown) => void
type PingPinFrameScreenClampStateUpdatedCallback = (
region: unknown,
state: boolean
) => void
type PingRadialWheelCreatedCallback = (region: unknown) => void
type SendMacroPingCallback = (
type: PingSubjectType | undefined,
targetToken: string | undefined
) => void
type TogglePingListenerCallback = (down: boolean) => void
type GetTitleIconTextureCallback = (success: boolean, texture: number) => void
type MacroExecuteLineCallback = (macroLine: string) => void
type TickerCallback = (cb: TimerCallback) => void
type TimerCallback = () => void
}
export {}

@ -0,0 +1,168 @@
/* eslint-disable @typescript-eslint/no-duplicate-enum-values */
/* eslint-disable @typescript-eslint/ban-types */
/** @noSelfInFile **/
declare global {
const AuctionConstants: { DEFAULT_AUCTION_PRICE_MULTIPLIER: 1.5 }
const CharCustomizationConstants: {
CHAR_CUSTOMIZE_CUSTOM_DISPLAY_OPTION_FIRST: 0
CHAR_CUSTOMIZE_CUSTOM_DISPLAY_OPTION_LAST: 0
NUM_CUSTOM_DISPLAY: 1
}
const ContentTrackingConsts: {
MaxTrackedCollectableSources: 15
MaxTrackedAchievements: 10
}
const Callings: { MaxCallings: 3 }
const CraftingOrderConsts: { MAX_CRAFTING_ORDER_FAVORITE_RECIPES: 100 }
const CurrencyConsts: {
PLAYER_CURRENCY_CLIENT_FLAGS: 0
MAX_CURRENCY_QUANTITY: 100000000
CONQUEST_ARENA_AND_BG_META_CURRENCY_ID: 483
CONQUEST_RATED_BG_META_CURRENCY_ID: 484
CONQUEST_ASHRAN_META_CURRENCY_ID: 692
ACCOUNT_WIDE_HONOR_CURRENCY_ID: 1585
ACCOUNT_WIDE_HONOR_LEVEL_CURRENCY_ID: 1586
CONQUEST_CURRENCY_ID: 1602
CONQUEST_POINTS_CURRENCY_ID: 390
CONQUEST_ARENA_META_CURRENCY_ID: 483
CONQUEST_BG_META_CURRENCY_ID: 484
HONOR_CURRENCY_ID: 1792
ARTIFACT_KNOWLEDGE_CURRENCY_ID: 1171
WAR_RESOURCES_CURRENCY_ID: 1560
ECHOES_OF_NYALOTHA_CURRENCY_ID: 1803
DRAGON_ISLES_SUPPLIES_CURRENCY_ID: 2003
QUESTIONMARK_INV_ICON: 134400
PVP_CURRENCY_CONQUEST_ALLIANCE_INV_ICON: 463448
PVP_CURRENCY_CONQUEST_HORDE_INV_ICON: 463449
PVP_CURRENCY_HONOR_ALLIANCE_INV_ICON: 463450
PVP_CURRENCY_HONOR_HORDE_INV_ICON: 463451
CURRENCY_ID_RENOWN: 1822
CURRENCY_ID_RENOWN_KYRIAN: 1829
CURRENCY_ID_RENOWN_VENTHYR: 1830
CURRENCY_ID_RENOWN_NIGHT_FAE: 1831
CURRENCY_ID_RENOWN_NECROLORD: 1832
CURRENCY_ID_WILLING_SOUL: 1810
CURRENCY_ID_RESERVOIR_ANIMA: 1813
CURRENCY_ID_PERKS_PROGRAM_DISPLAY_INFO: 2032
}
const DelvesConsts: {
DELVES_MIN_PLAYER_LEVEL_CONTENT_TUNING_ID: 2677
DELVES_NORMAL_KEY_CURRENCY_ID: 3028
DELVES_COMPANION_TOOLTIP_WIDGET_SET_ID: 1331
DELVES_COMPANION_TRAIT_SYSTEM_ID: 6
BRANN_COMPANION_INFO_ID: 1
}
const EditModeConsts: {
EditModeDefaultGridSpacing: 100
EditModeMinGridSpacing: 20
EditModeMaxGridSpacing: 300
EditModeMaxLayoutsPerType: 5
}
const ItemConsts: {
NUM_ITEM_ENCHANTMENT_SOCKETS: 3
MAX_LOOT_OBJECT_ITEMS: 31
INVALID_TRANSACTION_BANK_TAB_SLOT: 255
}
const ITEM_WEAPON_SUBCLASSConstants: { ITEM_WEAPON_SUBCLASS_NONE: -1 }
const GroupFinderConstants: { MAX_GROUP_FINDER_ACTIVITIES: 41 }
const LFG_ROLEConstants: { LFG_ROLE_NO_ROLE: -1; LFG_ROLE_ANY: 1 }
const LevelConstsExposed: { MIN_RES_SICKNESS_LEVEL: 10; MIN_ACHIEVEMENT_LEVEL: 10 }
const LootConsts: { MasterLootQualityThreshold: 5 }
const MajorFactionsConsts: {
WORLD_STATE_RENOWN_CAP_10_0: 19735
WORLD_STATE_RAPID_RENOWN_CAP_10_0: 20851
PLUNDERSTORM_MAJOR_FACTION_ID: 2593
}
const MoneyFormattingConstants: { GOLD_REWARD_THRESHOLD_TO_HIDE_COPPER: 10 }
const MountDynamicFlightConsts: { TRAIT_SYSTEM_ID: 1; TREE_ID: 672 }
const PetConsts_PostCata: {
MAX_STABLE_SLOTS: 200
MAX_SUMMONABLE_PETS: 25
MAX_SUMMONABLE_HUNTER_PETS: 5
NUM_PET_SLOTS_THAT_NEED_LEARNED_SPELL: 5
NUM_PET_SLOTS: 205
EXTRA_PET_STABLE_SLOT: 5
STABLED_PETS_FIRST_SLOT_INDEX: 6
}
const PetConsts_PreWrath: {
MAX_STABLE_SLOTS: 2
MAX_SUMMONABLE_PETS: 25
MAX_SUMMONABLE_HUNTER_PETS: 1
NUM_PET_SLOTS_THAT_NEED_LEARNED_SPELL: 1
NUM_PET_SLOTS: 205
EXTRA_PET_STABLE_SLOT: 0
STABLED_PETS_FIRST_SLOT_INDEX: 6
}
const PetConsts_Wrath: {
MAX_STABLE_SLOTS: 4
MAX_SUMMONABLE_PETS: 25
MAX_SUMMONABLE_HUNTER_PETS: 1
NUM_PET_SLOTS_THAT_NEED_LEARNED_SPELL: 1
NUM_PET_SLOTS: 205
EXTRA_PET_STABLE_SLOT: 0
STABLED_PETS_FIRST_SLOT_INDEX: 6
}
const ProfessionConsts: {
NUM_PRIMARY_PROFESSIONS: 2
CLASSIC_PROFESSION_PARENT_TIER_INDEX: 4
RUNEFORGING_SKILL_LINE_ID: 960
RUNEFORGING_ROOT_CATEGORY_ID: 210
MAX_CRAFTING_REAGENT_SLOTS: 12
CRAFTING_ORDER_CLAIM_DURATION: 0
PUBLIC_CRAFTING_ORDER_STALE_THRESHOLD: 0
CRAFTING_ORDER_ITEM_RETENTION: 30
}
const PvpInfoConsts: { MaxPlayersPerInstance: 80 }
const QuestWatchConsts: {
MAX_QUEST_WATCHES: 25
MAX_WORLD_QUEST_WATCHES_AUTOMATIC: 1
MAX_WORLD_QUEST_WATCHES_MANUAL: 5
}
const TimerunningConsts: {
TIMERUNNING_SEASON_NONE: 0
TIMERUNNING_SEASON_PANDARIA: 1
TIMERUNNING_ITEM_CTR: 2905
TIMERUNNING_STARTLOC_ID_ALLIANCE: 10211
TIMERUNNING_STARTLOC_ID_HORDE: 10212
}
const TraitConsts: {
MAX_COMBAT_TRAIT_CONFIGS: 40
COMMIT_COMBAT_TRAIT_CONFIG_CHANGES_SPELL_ID: 384255
INSPECT_TRAIT_CONFIG_ID: -1
STARTER_BUILD_TRAIT_CONFIG_ID: -2
VIEW_TRAIT_CONFIG_ID: -3
}
const Transmog: {
NoTransmogID: 0
MainHandTransmogIsIndividualWeapon: -1
MainHandTransmogIsPairedWeapon: 0
}
}
export {}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,982 @@
/** @noSelfInFile **/
declare global {
type ActionButtons =
| 'ACTIONBUTTON1'
| 'ACTIONBUTTON2'
| 'ACTIONBUTTON3'
| 'ACTIONBUTTON4'
| 'ACTIONBUTTON5'
| 'ACTIONBUTTON6'
| 'ACTIONBUTTON7'
| 'ACTIONBUTTON8'
| 'ACTIONBUTTON9'
| 'ACTIONBUTTON10'
| 'ACTIONBUTTON11'
| 'ACTIONBUTTON12'
| 'MULTIACTIONBAR1BUTTON1'
| 'MULTIACTIONBAR1BUTTON2'
| 'MULTIACTIONBAR1BUTTON3'
| 'MULTIACTIONBAR1BUTTON4'
| 'MULTIACTIONBAR1BUTTON5'
| 'MULTIACTIONBAR1BUTTON6'
| 'MULTIACTIONBAR1BUTTON7'
| 'MULTIACTIONBAR1BUTTON8'
| 'MULTIACTIONBAR1BUTTON9'
| 'MULTIACTIONBAR1BUTTON10'
| 'MULTIACTIONBAR1BUTTON11'
| 'MULTIACTIONBAR1BUTTON12'
| 'MULTIACTIONBAR2BUTTON1'
| 'MULTIACTIONBAR2BUTTON2'
| 'MULTIACTIONBAR2BUTTON3'
| 'MULTIACTIONBAR2BUTTON4'
| 'MULTIACTIONBAR2BUTTON5'
| 'MULTIACTIONBAR2BUTTON6'
| 'MULTIACTIONBAR2BUTTON7'
| 'MULTIACTIONBAR2BUTTON8'
| 'MULTIACTIONBAR2BUTTON9'
| 'MULTIACTIONBAR2BUTTON10'
| 'MULTIACTIONBAR2BUTTON11'
| 'MULTIACTIONBAR2BUTTON12'
| 'MULTIACTIONBAR3BUTTON1'
| 'MULTIACTIONBAR3BUTTON2'
| 'MULTIACTIONBAR3BUTTON3'
| 'MULTIACTIONBAR3BUTTON4'
| 'MULTIACTIONBAR3BUTTON5'
| 'MULTIACTIONBAR3BUTTON6'
| 'MULTIACTIONBAR3BUTTON7'
| 'MULTIACTIONBAR3BUTTON8'
| 'MULTIACTIONBAR3BUTTON9'
| 'MULTIACTIONBAR3BUTTON10'
| 'MULTIACTIONBAR3BUTTON11'
| 'MULTIACTIONBAR3BUTTON12'
| 'MULTIACTIONBAR4BUTTON1'
| 'MULTIACTIONBAR4BUTTON2'
| 'MULTIACTIONBAR4BUTTON3'
| 'MULTIACTIONBAR4BUTTON4'
| 'MULTIACTIONBAR4BUTTON5'
| 'MULTIACTIONBAR4BUTTON6'
| 'MULTIACTIONBAR4BUTTON7'
| 'MULTIACTIONBAR4BUTTON8'
| 'MULTIACTIONBAR4BUTTON9'
| 'MULTIACTIONBAR4BUTTON10'
| 'MULTIACTIONBAR4BUTTON11'
| 'MULTIACTIONBAR4BUTTON12'
| 'MULTIACTIONBAR5BUTTON1'
| 'MULTIACTIONBAR5BUTTON2'
| 'MULTIACTIONBAR5BUTTON3'
| 'MULTIACTIONBAR5BUTTON4'
| 'MULTIACTIONBAR5BUTTON5'
| 'MULTIACTIONBAR5BUTTON6'
| 'MULTIACTIONBAR5BUTTON7'
| 'MULTIACTIONBAR5BUTTON8'
| 'MULTIACTIONBAR5BUTTON9'
| 'MULTIACTIONBAR5BUTTON10'
| 'MULTIACTIONBAR5BUTTON11'
| 'MULTIACTIONBAR5BUTTON12'
| 'MULTIACTIONBAR6BUTTON1'
| 'MULTIACTIONBAR6BUTTON2'
| 'MULTIACTIONBAR6BUTTON3'
| 'MULTIACTIONBAR6BUTTON4'
| 'MULTIACTIONBAR6BUTTON5'
| 'MULTIACTIONBAR6BUTTON6'
| 'MULTIACTIONBAR6BUTTON7'
| 'MULTIACTIONBAR6BUTTON8'
| 'MULTIACTIONBAR6BUTTON9'
| 'MULTIACTIONBAR6BUTTON10'
| 'MULTIACTIONBAR6BUTTON11'
| 'MULTIACTIONBAR6BUTTON12'
| 'MULTIACTIONBAR7BUTTON1'
| 'MULTIACTIONBAR7BUTTON2'
| 'MULTIACTIONBAR7BUTTON3'
| 'MULTIACTIONBAR7BUTTON4'
| 'MULTIACTIONBAR7BUTTON5'
| 'MULTIACTIONBAR7BUTTON6'
| 'MULTIACTIONBAR7BUTTON7'
| 'MULTIACTIONBAR7BUTTON8'
| 'MULTIACTIONBAR7BUTTON9'
| 'MULTIACTIONBAR7BUTTON10'
| 'MULTIACTIONBAR7BUTTON11'
| 'MULTIACTIONBAR7BUTTON12'
type UnitToken =
| 'arena1'
| 'arena2'
| 'arena3'
| 'arena4'
| 'arena5'
| 'raid1'
| 'raid2'
| 'raid3'
| 'raid4'
| 'raid5'
| 'raid6'
| 'raid7'
| 'raid8'
| 'raid9'
| 'raid10'
| 'raid11'
| 'raid12'
| 'raid13'
| 'raid14'
| 'raid15'
| 'raid16'
| 'raid17'
| 'raid18'
| 'raid19'
| 'raid20'
| 'raid21'
| 'raid22'
| 'raid23'
| 'raid24'
| 'raid25'
| 'raid26'
| 'raid27'
| 'raid28'
| 'raid29'
| 'raid30'
| 'raid31'
| 'raid32'
| 'raid33'
| 'raid34'
| 'raid35'
| 'raid36'
| 'raid37'
| 'raid38'
| 'raid39'
| 'raid40'
| 'raidpet1'
| 'raidpet2'
| 'raidpet3'
| 'raidpet4'
| 'raidpet5'
| 'raidpet6'
| 'raidpet7'
| 'raidpet8'
| 'raidpet9'
| 'raidpet10'
| 'raidpet11'
| 'raidpet12'
| 'raidpet13'
| 'raidpet14'
| 'raidpet15'
| 'raidpet16'
| 'raidpet17'
| 'raidpet18'
| 'boss1'
| 'boss2'
| 'boss3'
| 'boss4'
| 'boss5'
| 'boss6'
| 'boss7'
| 'boss8'
| 'raidpet19'
| 'raidpet20'
| 'raidpet21'
| 'raidpet22'
| 'raidpet23'
| 'raidpet24'
| 'raidpet25'
| 'raidpet26'
| 'raidpet27'
| 'raidpet28'
| 'raidpet29'
| 'raidpet30'
| 'raidpet31'
| 'raidpet32'
| 'raidpet33'
| 'raidpet34'
| 'raidpet35'
| 'raidpet36'
| 'raidpet37'
| 'raidpet38'
| 'raidpet39'
| 'raidpet40'
| 'party1'
| 'party2'
| 'party3'
| 'party4'
| 'party5'
| 'partypet1'
| 'partypet2'
| 'partypet3'
| 'partypet4'
| 'partypet5'
| 'player'
| 'pet'
| 'focus'
| 'mouseover'
| 'vehicle'
| 'target'
| 'none'
| 'npc'
| 'targettarget'
enum ItemSoundType {
Pickup = 0,
Drop = 1,
Use = 2,
Close = 3
}
type FontShadow = [x: number, y: number, color: ColorMixin]
type UiMapPoint = [uiMapID: number, position: Vector2DMixin, z: number | undefined]
type TooltipData =
| [
type: TooltipDataType | undefined,
id: number | undefined,
dataInstanceID: number, // Correlates to TOOLTIP_DATA_UPDATE
lines: TooltipDataLine[] // The lines in the tooltip
]
| UnitTooltipData
| ItemTooltipData
type UnitTooltipData = [
type: TooltipDataType | undefined,
id: number | undefined,
dataInstanceID: number, // Correlates to TOOLTIP_DATA_UPDATE
lines: TooltipDataLine[], // The lines in the tooltip
guid: string,
healthGUID: string
]
type ItemTooltipData = [
type: TooltipDataType | undefined,
id: number | undefined,
dataInstanceID: number, // Correlates to TOOLTIP_DATA_UPDATE
lines: TooltipDataLine[], // The lines in the tooltip
hyperlink: string | undefined,
guid: string | undefined,
hasDynamicData: boolean | undefined,
isAzeriteItem: boolean,
isAzeriteEmpoweredItem: boolean,
isCorruptedItem: boolean,
overrideItemLevel: number | undefined,
repairCost: number | undefined
]
type TooltipDataLine =
| [
type: TooltipDataLineType,
leftColor: ColorMixin,
leftText: string,
rightColor: ColorMixin,
rightText: string | undefined,
wrapText: boolean | undefined
]
| SellPriceTooltipDataLine
| NestedBlockItemTooltipData
| NestedBlockTooltipDataLine
| UnitTooltipDataLine
| ItemBindingTooltipDataLine
type UnitTooltipDataLine = [
type: TooltipDataLineType,
leftColor: ColorMixin,
leftText: string,
rightColor: ColorMixin,
rightText: string | undefined,
wrapText: boolean | undefined,
unitToken: UnitToken
]
type SellPriceTooltipDataLine = [
type: TooltipDataLineType,
leftColor: ColorMixin,
leftText: string,
rightColor: ColorMixin,
rightText: string | undefined,
wrapText: boolean | undefined,
price: number,
maxPrice: number
]
type TooltipComparisonItem = [guid: string]
type FontInfo = [
height: number,
outline: string,
color: ColorMixin,
shadow: FontShadow | undefined
]
type NestedBlockItemTooltipData = [
type: TooltipDataLineType,
leftColor: ColorMixin,
leftText: string,
rightColor: ColorMixin,
rightText: string | undefined,
wrapText: boolean | undefined,
tooltipType: number,
tooltipID: number
]
type NestedBlockTooltipDataLine = [
type: TooltipDataLineType,
leftColor: ColorMixin,
leftText: string,
rightColor: ColorMixin,
rightText: string | undefined,
wrapText: boolean | undefined,
tooltipType: number,
tooltipID: number
]
type ItemBindingTooltipDataLine = [
type: TooltipDataLineType,
leftColor: ColorMixin,
leftText: string,
rightColor: ColorMixin,
rightText: string | undefined,
wrapText: boolean | undefined,
bonding: number
]
type AuraInfo = [
name: string,
icon: number,
count: number,
dispelType: 'Curse' | 'Disease' | 'Magic' | 'Poison' | undefined,
duration: number,
expirationTime: number,
source: string,
isStealable: boolean,
nameplateShowPersonal: boolean,
spellId: number,
canApplyAura: boolean,
isBossDebuff: boolean,
castByPlayer: boolean,
nameplateShowAll: boolean,
timeMod: number
]
type AuraData = {
applications: number
auraInstanceID: number
canApplyAura: boolean
charges: number
dispelName: 'Curse' | 'Disease' | 'Magic' | 'Poison' | undefined
duration: number
expirationTime: number
icon: number
isBossAura: boolean
isFromPlayerOrPlayerPet: boolean
isHarmful: boolean
isHelpful: boolean
isNameplateOnly: boolean
isRaid: boolean
isStealable: boolean
maxCharges: number
name: string
nameplateShowAll: boolean
nameplateShowPersonal: boolean
points: number[]
sourceUnit: string | undefined
spellId: number
timeMod: number
}
enum CachedRewardType {
None = 0,
Item = 1,
Currency = 2,
Quest = 3
}
enum WeeklyRewardChestThresholdType {
None = 0,
MythicPlus = 1,
RankedPvP = 2,
Raid = 3,
AlsoReceive = 4,
Concession = 5
}
type DrawLayer = 'BACKGROUND' | 'BORDER' | 'ARTWORK' | 'OVERLAY' | 'HIGHLIGHT'
type LoopType = 'BOUNCE' | 'NONE' | 'REPEAT'
type SimpleButtonStateToken = 'DISABLED' | 'NORMAL' | 'PUSHED'
type BlendMode = 'DISABLE' | 'BLEND' | 'ALPHAKEY' | 'ADD' | 'MOD'
type FontFlags = 'MONOCHROME' | 'OUTLINE' | 'THICK'
type FrameStrata =
| 'WORLD'
| 'BACKGROUND'
| 'LOW'
| 'MEDIUM'
| 'HIGH'
| 'DIALOG'
| 'FULLSCREEN'
| 'FULLSCREEN_DIALOG'
| 'TOOLTIP'
type InsertMode = 'TOP' | 'BOTTOM'
type Orientation = 'VERTICAL' | 'HORIZONTAL'
type StatusBarFillStyle = 'STANDARD' | 'STANDARD_NO_RANGE_FILL' | 'CENTER' | 'REVERSE'
type FilterMode = 'LINEAR' | 'TRILINEAR' | 'NEAREST'
type WowScriptEvent =
| 'OnEvent'
| 'OnLoad'
| 'OnUpdate'
| 'OnClick'
| 'OnEnter'
| 'OnLeave'
| 'OnHide'
| 'OnShow'
| 'OnMouseDown'
| 'OnMouseUp'
| 'OnMouseWheel'
| 'OnValueChanged'
| 'OnTextChanged'
type WowEvent = WowScriptEvent | WoWEventKey
type MouseButton = 'LeftButton' | 'RightButton' | 'Middle' | 'Button4' | 'Button5'
interface FunctionContainerMixin {
Cancel: () => void
Invoke: () => void
IsCancelled: () => boolean
}
interface ItemLocationMixin {
Clear: () => void
SetBagAndSlot: (bagID: number, slotIndex: number) => void
GetBagAndSlot: () => LuaMultiReturn<[bagID: number, slotIndex: number]>
SetEquipmentSlot: (equipmentSlotIndex: number) => void
GetEquipmentSlot: () => number
IsEquipmentSlot: () => boolean
IsBagAndSlot: () => boolean
HasAnyLocation: () => boolean
IsValid: () => boolean
IsEqualToBagAndSlot: (bagID: number, slotIndex: number) => boolean
IsEqualToEquipmentSlot: (equipmentSlotIndex: number) => boolean
IsEqualTo: (itemLocation: ItemLocationMixin) => boolean
}
interface TransmogLocationMixin {
Set: (
slotID: number,
transmogType: TransmogType,
modification: TransmogModification
) => void
IsAppearance: () => boolean
IsIllusion: () => boolean
GetSlotID: () => number
GetSlotName: () => string
IsEitherHand: () => boolean
IsMainHand: () => boolean
IsOffHand: () => boolean
IsEqual: (transmogLocation: TransmogLocationMixin) => boolean
GetArmorCategoryID: () => number
GetLookupKey: () => number
IsSecondary: () => boolean
}
interface ItemTransmogInfo {
IsEqual: (itemTransmogInfo: ItemTransmogInfo) => boolean
Clear: () => void
ConfigureSecondaryForMainHand: (isLegionArtifact: boolean) => void
IsMainHandIndividualWeapon: () => boolean
IsMainHandPairedWeapon: () => boolean
}
interface Vector3DMixin {
OnLoad: () => void
IsEqualTo: (otherVector: Vector3DMixin) => boolean
GetXYZ: () => [number, number, number]
SetXYZ: (x: number, y: number, z: number) => void
ScaleBy: (scalar: number) => void
DivideBy: (scalar: number) => void
Add: (other: Vector3DMixin) => void
Subtract: (other: Vector3DMixin) => void
Cross: (other: Vector3DMixin) => void
Dot: (other: Vector3DMixin) => void
GetLengthSquared: () => number
GetLength: () => number
Normalize: () => void
Clone: () => Vector3DMixin
}
interface Vector2DMixin {
IsEqualTo: (vector: Vector2DMixin) => boolean
GetXY: () => [x: number, y: number]
SetXY: (x: number, y: number) => void
ScaleBy: (scalar: number) => void
DivideBy: (scalar: number) => void
Add: (vector: Vector2DMixin) => void
Subtract: (vector: Vector2DMixin) => void
Cross: (vector: Vector2DMixin) => void
Dot: (vector: Vector2DMixin) => number
IsZero: () => boolean
GetLengthSquared: () => number
GetLength: () => number
Normalize: () => void
RotateDirection: (rotationRadians: number) => void
Clone: () => Vector2DMixin
}
interface ColorMixin {
r: number
g: number
b: number
a: number | undefined
IsRGBEqualTo: (color: ColorMixin) => boolean
IsEqualTo: (color: ColorMixin) => boolean
GetRGB: () => LuaMultiReturn<[number, number, number]>
OnLoad: () => void
GetRGBAsBytes: () => LuaMultiReturn<[number, number, number]>
GetRGBA: () => LuaMultiReturn<[number, number, number, number]>
GetRGBAAsBytes: () => LuaMultiReturn<[number, number, number, number]>
SetRGBA: () => void
SetRGB: () => void
GenerateHexColor: () => string
GenerateHexColorNoAlpha: () => string
GenerateHexColorMarkup: () => string
WrapTextInColorCode: (text: string) => string
}
interface NamePlateFrame {
OnAdded: (namePlateUnitToken: UnitToken, driverFrame?: unknown) => void
OnRemoved: () => void
OnOptionsUpdated: () => void
ApplyOffsets: () => void
GetAdditionalInsetPadding: (
insetWidth: number,
insetHeight: number
) => LuaMultiReturn<[number, number]>
GetPreferredInsets: () => LuaMultiReturn<[number, number, number, number]>
OnSizeChanged: () => void
driverFrame: unknown | undefined
UnitFrame: unknown | undefined
namePlateUnitToken: UnitToken
template: string
}
// Sort of guesses with some of the types here.
interface PlayerLocationMixin {
SetGUID: (guid: string) => void
IsValid: () => boolean
IsGUID: () => boolean
IsBattleNetGUID: () => boolean
GetGUID: () => string
SetUnit: (unit: string) => void
IsUnit: () => boolean
GetUnit: () => string
SetChatLineID: (lineID: number) => void
IsChatLineID: () => boolean
GetChatLineID: () => number
SetBattlefieldScoreIndex: (index: number) => void
IsBattlefieldScoreIndex: () => boolean
GetBattlefieldScoreIndex: () => number
SetVoiceID: (memberID: number, channelID: number) => void
IsVoiceID: () => boolean
GetVoiceID: () => [number, number]
SetBattleNetID: (battleNetID: string) => void
IsBattleNetID: () => boolean
GetBattleNetID: () => string
SetCommunityData: (
clubID: number,
streamID: string,
epoch: unknown,
position: number
) => void
IsCommunityData: () => boolean
SetCommunityInvitation: (clubID: number, guid: number) => void
IsCommunityInvitation: () => boolean
Clear: () => void
ClearAndSetField: (fieldName: string, field: string) => void
}
interface ReportInfoMixin {
Clear: () => void
}
type FrameType =
| 'Frame'
| 'Button'
| 'Cooldown'
| 'ColorSelect'
| 'EditBox'
| 'MessageFrame'
| 'Minimap'
| 'Model'
| 'ScrollFrame'
| 'SimpleHTML'
| 'Slider'
| 'StatusBar'
type FrameGeneric<T extends FrameType> = T extends 'Frame'
? Frame
: T extends 'Slider'
? Slider
: T extends 'EditBox'
? EditBox
: T extends 'Button'
? Button
: T extends 'Cooldown'
? Cooldown
: T extends 'ColorSelect'
? ColorSelect
: T extends 'MessageFrame'
? MessageFrame
: T extends 'Minimap'
? Minimap
: T extends 'Model'
? Model
: T extends 'ScrollFrame'
? ScrollFrame
: T extends 'SimpleHTML'
? HTML
: T extends 'Slider'
? Slider
: StatusBar
interface Frame {
GetName(): string
GetObjectType(): string
IsForbidden(): boolean
IsObjectType(objectType: string): boolean
SetForbidden(): void
AbortDrag(): void
CanChangeAttribute(): boolean
CreateFontString(
name: string | undefined,
drawLayer: DrawLayer | undefined,
templateName: string | undefined
): FontString
CreateLine(
name: string | undefined,
drawLayer: DrawLayer | undefined,
templateName: string | undefined,
subLevel: number | undefined
): Line
CreateMaskTexture(
name: string | undefined,
drawLayer: DrawLayer | undefined,
templateName: string | undefined,
subLevel: number | undefined
): ScriptObject
CreateTexture(
name: string | undefined,
drawLayer: DrawLayer | undefined,
templateName: string | undefined,
subLevel: number | undefined
): Texture
DesaturateHierarchy(desaturation: number, excludeRoot: boolean): void
DisableDrawLayer(layer: DrawLayer): void
DoesClipChildren(): boolean
EnableDrawLayer(layer: DrawLayer): void
EnableGamePadButton(enable: boolean): void
EnableGamePadStick(enable: boolean): void
EnableKeyboard(enable: boolean): void
ExecuteAttribute(
attributeName: string,
unpackedPrimitiveType: string
): LuaMultiReturn<[success: boolean, unpackedPrimitiveType: string]>
GetAlpha(): number
GetAttribute(attributeName: string): string
GetBoundsRect(): LuaMultiReturn<
[left: number, bottom: number, width: number, height: number]
>
GetChildren(): ScriptObject
GetClampRectInsets(): LuaMultiReturn<
[left: number, right: number, top: number, bottom: number]
>
GetDontSavePosition(): boolean
GetEffectiveAlpha(): number
GetEffectiveScale(): number
GetEffectivelyFlattensRenderLayers(): boolean
GetFlattensRenderLayers(): boolean
GetFrameLevel(): number
GetFrameStrata(): FrameStrata
GetHitRectInsets(): LuaMultiReturn<
[left: number, right: number, top: number, bottom: number]
>
GetHyperlinksEnabled(): boolean
GetID(): number
GetNumChildren(): number
GetNumRegions(): number
GetPropagateKeyboardInput(): boolean
GetRaisedFrameLevel(): number
GetRegions(): ScriptObject
GetResizeBounds(): LuaMultiReturn<
[minWidth: number, minHeight: number, maxWidth: number, maxHeight: number]
>
GetScale(): number
GetWindow(): unknown
HasFixedFrameLevel(): boolean
HasFixedFrameStrata(): boolean
Hide(): void
InterceptStartDrag(delegate: unknown): void
IsClampedToScreen(): boolean
IsEventRegistered(
eventName: string
): LuaMultiReturn<[isRegistered: boolean, units: string | undefined]>
IsGamePadButtonEnabled(): boolean
IsGamePadStickEnabled(): boolean
IsIgnoringParentAlpha(): boolean
IsIgnoringParentScale(): boolean
IsKeyboardEnabled(): boolean
IsMovable(): boolean
IsObjectLoaded(): boolean
IsResizable(): boolean
IsShown(): boolean
IsToplevel(): boolean
IsUserPlaced(): boolean
IsVisible(): boolean
LockHighlight(): void
Lower(): void
Raise(): void
RegisterAllEvents(): void
RegisterEvent(eventName: string): boolean
RegisterForDrag(unpackedPrimitiveType: number): void
RegisterUnitEvent(eventName: string, units: string): boolean
RotateTextures(radians: number, x: number, y: number): void
SetAlpha(alpha: number): void
SetAttribute(attributeName: string, value: string): void
SetAttributeNoHandler(attributeName: string, value: string): void
SetClampRectInsets(left: number, right: number, top: number, bottom: number): void
SetClampedToScreen(clampedToScreen: boolean): void
SetClipsChildren(clipsChildren: boolean): void
SetDontSavePosition(dontSave: boolean): void
SetDrawLayerEnabled(layer: DrawLayer, isEnabled: boolean): void
SetFixedFrameLevel(isFixed: boolean): void
SetFixedFrameStrata(isFixed: boolean): void
SetFlattensRenderLayers(flatten: boolean): void
SetFrameLevel(frameLevel: number): void
SetFrameStrata(strata: FrameStrata): void
SetHighlightLocked(locked: boolean): void
SetHitRectInsets(left: number, right: number, top: number, bottom: number): void
SetHyperlinksEnabled(enabled: boolean): void
SetID(id: number): void
SetIgnoreParentAlpha(ignore: boolean): void
SetIgnoreParentScale(ignore: boolean): void
SetIsFrameBuffer(isFrameBuffer: boolean): void
SetMovable(movable: boolean): void
SetPropagateKeyboardInput(propagate: boolean): void
SetResizable(resizable: boolean): void
SetResizeBounds(
minWidth: number,
minHeight: number,
maxWidth: number | undefined,
maxHeight: number | undefined
): void
SetScale(scale: number): void
SetShown(shown: boolean): void
SetToplevel(topLevel: boolean): void
SetUserPlaced(userPlaced: boolean): void
SetWindow(window: unknown | undefined): void
Show(): void
StartMoving(alwaysStartFromMouse: boolean): void
StartSizing(resizePoint: string | undefined, alwaysStartFromMouse: boolean): void
StopMovingOrSizing(): void
UnlockHighlight(): void
UnregisterAllEvents(): void
UnregisterEvent(eventName: string): boolean
SetScript(
event: Extract<WowEvent, 'OnEvent'>,
handler: (frame: Frame, eventName: WowEvent, ...args: any[]) => void
): void
}
function GetActionInfo(
id: number
): LuaMultiReturn<[actionType: string, id: number | string, subType: any]>
function GetSpecialization(): number
function GetNumBindings(): number
function GetBinding(
index: number
): LuaMultiReturn<
[command: string, category: string, key1?: string, key2?: string, key3?: string]
>
function GetItemInfo(
id: number | string
): LuaMultiReturn<
[
itemName: string,
itemLink: string,
itemQuality: ItemQuality,
itemLevel: number,
itemMinLevel: number,
itemType: string,
itemSubType: string,
itemStackCount: number,
itemEquipLoc: string,
itemTexture: number,
sellPrice: number,
classID: number,
subclassID: number,
bindType: number,
expacID: number,
setID: number | undefined,
isCraftingReagent: boolean
]
>
function RunBinding(button: ActionButtons, direction?: 'up'): void
function AuraUtil_ForEachAura(
token: string,
type: 'HELPFUL' | 'HARMFUL',
max: number | undefined,
callback: (...aura: AuraInfo) => void
): void
function CombatLogGetCurrentEventInfo(): LuaMultiReturn<
[
timestamp: number,
subevent: string,
hideCaster: boolean,
sourceGUID: string,
sourceName: string,
sourceFlags: number,
sourceRaidFlags: number,
destGUID: string,
destName: string,
destFlags: number,
destRaidFlags: number
]
>
/**
* Returns whether or not the spell is known by the player
*/
function IsSpellKnown(spellId: number, isPetSpell?: boolean): boolean
/**
* Returns whether or not the override spell is known by the player. Override spells
* Are ones that "replace" an existing spell, via talent or equipment or whatever.
*/
function IsSpellKnownOrOverridesKnown(spellId: number, isPetSpell?: boolean): boolean
/**
* Checks whether or not a spell is on cooldown because of loss of control. E.g if the player is silenced
*/
function GetSpellLossOfControlCooldown(
spell: number | string
): LuaMultiReturn<[tart: number, duration: number]>
/**
* Returns the action associated with a binding
*/
function GetBindingAction(binding: string): string
}
export {}

@ -0,0 +1,52 @@
const [_, _Mekanome] = [...$vararg]
const Mekanome = _Mekanome as unknown as IMekanome
/**
* Creates a useable aura
*/
function Create(this: void, id: number, isPlayerAura?: boolean): Aura {
return {
id,
isPlayerAura
}
}
/**
* Returns whether or not an aura is active in a list of auras
*/
function IsUp(this: void, aura: Aura, mob: Mob, secondsToIgnore?: number): boolean {
let isUp = false
for (const [, auraData] of mob.auras) {
const matchesPlayerFilter =
aura.isPlayerAura === undefined || auraData.sourceUnit === 'player'
if (auraData.spellId === aura.id && matchesPlayerFilter) {
if (auraData.expirationTime === 0) {
isUp = true
} else {
const expirationTime = auraData.expirationTime - GetTime()
if (secondsToIgnore !== undefined) {
isUp = expirationTime > secondsToIgnore
} else {
isUp = expirationTime > 0
}
}
break
}
}
return isUp
}
const _AuraUtils = {
IsUp,
Create
}
declare global {
type AuraUtils = typeof _AuraUtils
}
Mekanome.AuraUtils = _AuraUtils
export {}

@ -0,0 +1,75 @@
const [_, _Mekanome] = [...$vararg]
const Mekanome = _Mekanome as unknown as IMekanome
/**
* Casts the provided castable. Can be used for spells, items, macros etc as long as there
* is a corresponding binding and target / position info.
*/
function Cast(this: void, castable: Castable) {
const mouseover = Mekanome.GetObject('mouseover')
function ExecuteBinding(this: void) {
print(castable.binding)
RunBinding(castable.binding)
RunBinding(castable.binding, 'up')
}
function ClearMouseover(this: void) {
SetMouseover(undefined)
}
function RestoreMouseover(this: void) {
if (mouseover !== false) {
SetMouseover(mouseover)
}
}
/**
* Will execute the binding on the player, or active 'target'.
*/
function TargetOrSelfCast(this: void) {
// Because we have mouseover casting enabled, we need to clear the mouseover before casting.
ClearMouseover()
// Then we execute the binding.
ExecuteBinding()
// FInally we restore the mouseover to what it was before the binding was executed.
RestoreMouseover()
}
// Casts as if no target was selected.
if (castable.variant === 'self') {
TargetOrSelfCast()
}
if (castable.variant === 'ground') {
const position = castable.position
// First we execute the binding to get the target reticle active.
ExecuteBinding()
const pending = IsSpellPending()
// Then we check to make sure the spell is pending, and if it is we click on the specefied position to cast it
if (pending === 64) {
Click(position.x, position.y, position.z)
}
}
// Casts the spell at a target.
if (castable.variant === 'target') {
const target = Mekanome.GetObject('target')
const targetGuid = target !== false ? ObjectGUID(target) : undefined
const castTarget = castable.target
if (targetGuid === undefined || castTarget.guid !== targetGuid) {
const targetObj = Mekanome.GetObject(castTarget.token)
if (targetObj !== false) {
SetMouseover(targetObj)
ExecuteBinding()
RestoreMouseover()
}
} else {
TargetOrSelfCast()
}
}
}
Mekanome.Cast = Cast
export default {}

@ -0,0 +1,682 @@
const [, _Mekanome] = [...$vararg]
const Mekanome = _Mekanome as unknown as IMekanome
/**
* Check whether or not the mob is a valid wow mob.
*/
function IsValid(this: void, mob: Mob) {
return (
mob.token !== 'none' &&
mob.token !== undefined &&
Mekanome.GetObject(mob.token) !== false
)
}
/**
* Get whether or not the mob is alive.
*/
function IsAlive(this: void, mob: Mob) {
return UnitIsDeadOrGhost(mob.token as UnitToken) === false
}
/**
* Get whether or not the mob is a pet.
*/
function IsPet(this: void, mob: Mob): boolean {
return UnitIsUnit(mob.token, 'pet')
}
/**
* Get whether or not the player can attack this mob.
*/
function CanAttack(this: void, mob: Mob): boolean {
const token = mob.token as UnitToken
const isFriend = UnitIsFriend('player', token)
if (isFriend) return false
const isAttackable = UnitCanAttack('player', token)
if (isAttackable === false) return false
const reaction = UnitReaction('player', token)
if (reaction === undefined) {
return false
} else {
const isPositiveReaction = reaction >= 5
if (isPositiveReaction) return false
}
return true
}
/**
* Get the name of the mob.
*/
function GetName(this: void, mob: Mob) {
const [unitName] = UnitName(mob.token)
return unitName
}
/**
* Get the health of the mob.
*/
function GetHealth(this: void, mob: Mob) {
const token = mob.token as UnitToken
const absorbAmount = UnitGetTotalHealAbsorbs(token)
const health = UnitHealth(token, false) - absorbAmount
return health
}
/**
* Get the mobs current health expressed as a percentage of their max health.
*/
function GetHealthPercent(this: void, mob: Mob) {
const health = GetHealth(mob)
const maxHealth = UnitHealthMax(mob.token as UnitToken)
return (health / maxHealth) * 100
}
/**
* Get the mobs default powertype, eg 1 for Rage.
*/
function GetPowerType(this: void, mob: Mob) {
const [powerType] = UnitPowerType(mob.token as UnitToken, 0)
return powerType
}
/**
* Get the mobs maximum power of their default power type, or a specified one.
*/
function GetMaxPower(this: void, mob: Mob, powerType?: PowerType) {
const maxPower = UnitPowerMax(
mob.token as UnitToken,
powerType || GetPowerType(mob),
false
)
return maxPower
}
/**
* Get the current value for the mobs default powertype, or a specified one.
*/
function GetCurrentPower(this: void, mob: Mob, powerType?: PowerType) {
const power = UnitPower(mob.token as UnitToken, powerType || GetPowerType(mob), false)
return power
}
/**
* Get the current value for the mobs default powertype, or a specified one, expressed as a percentage of the max for that powertype.
*/
function GetPowerPercent(this: void, mob: Mob, powerType: PowerType) {
const power = GetCurrentPower(mob, powerType)
const maxPower = GetMaxPower(mob, powerType)
return (power / maxPower) * 100
}
type ROLE = 'TANK' | 'HEALER' | 'DAMAGER'
/**
* Gets the group role of the mob if it exists.
*/
function GetRole(this: void, mob: Mob): ROLE | undefined {
return UnitGroupRolesAssigned(mob.token as UnitToken) as ROLE | undefined
}
/**
* Check whether or not the mob is in combat according to the wow api.
*/
function IsAffectingCombat(this: void, mob: Mob) {
return UnitAffectingCombat(mob.token as UnitToken)
}
/**
* Returns the Vector3 coords of the mob.
*/
function GetPosition(this: void, mob: Mob): Vector3 {
const [x, y, z] = ObjectPosition(mob.token)
return Mekanome.VectorUtils.Create({ x, y, z })
}
/**
* Returns distance between two mobs.
*/
function GetDistanceBetween(this: void, referenceMob: Mob, compareMob: Mob): number {
return Mekanome.VectorUtils.Distance(GetPosition(referenceMob), GetPosition(compareMob))
}
/**
* Get whether or not a mob can see another mob un-obstructed.
*/
function CanSee(this: void, reference: Mob, compare: Mob): boolean {
const [ax, ay, az] = ObjectPosition(reference.token)
const ah = ObjectHeight(reference.token)
const [attx, atty, attz] = GetUnitAttachmentPosition(compare.token, 34)
if (attx === undefined || ax === undefined || ah === undefined) return false
if ((ax === 0 && ay === 0 && az === 0) || (attx === 0 && atty === 0 && attz == 0)) {
return true
}
const [x, y, z] = TraceLine(ax, ay, az + ah, attx, atty, attz, 0)
if (x !== 0 || y !== 0 || z !== 0) {
return false
} else {
return true
}
}
/**
* Returns whether or not a mob is casting a spell
*/
function IsCasting(this: void, mob: Mob) {
return UnitCastingInfo(mob.token as UnitToken) !== undefined
}
/**
* Returns whether or not a mob is channeling a spell
*/
function IsChanneling(this: void, mob: Mob) {
return UnitChannelInfo(mob.token as UnitToken) !== undefined
}
/**
* Returns whether or not a mob is channeling or casting a spell
*/
function IsCastingOrChanneling(this: void, mob: Mob) {
return IsChanneling(mob) || IsCasting(mob)
}
function GetEndTime(this: void, startMs: number, endMs: number, percent?: number) {
const castLength = endMs - startMs
const startTime = startMs / 1000
const timeUntil = (castLength / 1000) * ((percent || 100) / 100)
return startTime + timeUntil
}
/**
* If a mob is casting a spell, return the time when it will end.
* @param percent: By default the cast end time is when the cast is complete. If you want to consider
* a spell as being done once it reaches a certain percentage threshold, pass in this value.
*/
function GetCastEndTime(this: void, mob: Mob, percent?: number) {
const castingInfo = UnitCastingInfo(mob.token as UnitToken)
if (castingInfo !== undefined) {
const name = castingInfo[0]
const startTimeMS = castingInfo[3]
const endTimeMs = castingInfo[4]
if (name !== undefined && startTimeMS !== undefined && endTimeMs !== undefined) {
return GetEndTime(startTimeMS, endTimeMs, percent)
}
}
return undefined
}
/**
* If a mob is channeling a spell, return the time when it will end.
* @param percent: By default the cast end time is when the channel ends. If you want to consider
* a spell as being done once it reaches a certain percentage threshold, pass in this value.
*/
function GetChannelEndTime(this: void, mob: Mob, percent?: number) {
const channelInfo = UnitChannelInfo(mob.token as UnitToken)
if (channelInfo !== undefined) {
const name = channelInfo[0]
const startTimeMS = channelInfo[3]
const endTimeMs = channelInfo[4]
if (name !== undefined && startTimeMS !== undefined && endTimeMs !== undefined) {
return GetEndTime(startTimeMS, endTimeMs, percent)
}
}
return undefined
}
function GetCastOrChannelEndTime(
this: void,
mob: Mob,
percent?: number
): number | undefined {
return GetCastEndTime(mob, percent) || GetChannelEndTime(mob, percent)
}
/**
* If the mob is casting or channeling a spell, this returns the target mob of said spell.
*/
function GetCastTarget(this: void, mob: Mob) {
const isMobCasting = IsCastingOrChanneling(mob)
if (isMobCasting) {
const target = ObjectCastingTarget(mob.token)
if (target !== false) {
const targetMob = Mekanome.MobManager.GetMob({ guid: target.guid() })
return targetMob || Mekanome.MobManager.GetMob({ token: 'none' })
} else {
return Mekanome.MobManager.GetMob({ token: 'none' })
}
}
return undefined
}
/**
* If the mob is casting or channeling a spell, this returns how far into the cast the mob is, as a percentage.
*/
function GetCastOrChannelPercentComplete(this: void, mob: Mob) {
let name: string | undefined = undefined
let startTimeMS: number | undefined = undefined
let endTimeMs: number | undefined = undefined
const castingInfo = UnitCastingInfo(mob.token as UnitToken)
if (castingInfo === undefined) {
const channelInfo = UnitChannelInfo(mob.token as UnitToken)
if (channelInfo !== undefined) {
name = channelInfo[0]
startTimeMS = channelInfo[3]
endTimeMs = channelInfo[4]
}
} else {
name = castingInfo[0]
startTimeMS = castingInfo[3]
endTimeMs = castingInfo[4]
}
if (name !== undefined && startTimeMS !== undefined && endTimeMs !== undefined) {
const start = startTimeMS / 1000
const finish = endTimeMs / 1000
const current = GetTime()
return ((current - start) / (finish - start)) * 100
}
return undefined
}
/**
* If the mob is casting or channeling a spell, returns whether or not that spell is interruptible.
*/
function GetIsInterruptible(this: void, mob: Mob) {
let name: string | undefined = undefined
let notInterruptible: boolean | undefined = undefined
const castingInfo = UnitCastingInfo(mob.token as UnitToken)
if (castingInfo === undefined) {
const channelInfo = UnitChannelInfo(mob.token as UnitToken)
if (channelInfo !== undefined) {
name = channelInfo[0]
notInterruptible = channelInfo[6]
}
} else {
name = castingInfo[0]
notInterruptible = castingInfo[7]
}
if (name !== undefined && notInterruptible !== undefined) {
return notInterruptible === false
}
return false
}
/**
* Returns whether or not a mob is interruptible at a specific percentage of their cast.
* @param interruptPercent The percent to check against.
* @param ignoreInterruptible By default this will return false if the active spell is not interruptible. This bypasses that check.
*/
function GetIsInterruptibleAt(
this: void,
mob: Mob,
interruptPercent: number,
ignoreInterruptible?: boolean
) {
if (!ignoreInterruptible && GetIsInterruptible(mob) === false) {
return false
}
const castPercent = GetCastOrChannelPercentComplete(mob)
if (castPercent && castPercent >= interruptPercent) {
return true
}
return false
}
/**
* Gets whether or not the mob is moving.
*/
function GetIsMoving(this: void, mob: Mob) {
const [currentSpeed] = GetUnitSpeed(mob.token as UnitToken)
return currentSpeed > 0
}
/**
* Checks if a mob is facing another using their current positions.
*/
function IsFacingMob(this: void, reference: Mob, compare: Mob) {
const rotation = ObjectRotation(reference.token)
const [x, y] = ObjectPosition(reference.token)
const [x2, y2] = ObjectPosition(compare.token)
if (!x || !x2 || !rotation) {
return false
}
let angle = math.atan2(y2 - y, x2 - x) - rotation
angle = math.deg(angle)
angle = angle % 360
if (angle > 180) {
angle = angle - 360
}
return math.abs(angle) < 90
}
/**
* Checks if a mob is behind another using their current positions.
*/
function IsBehindMob(this: void, reference: Mob, compare: Mob) {
const rotation = ObjectRotation(reference.token)
const [x, y] = ObjectPosition(reference.token)
const [x2, y2] = ObjectPosition(compare.token)
if (!x || !x2 || !rotation) {
return false
}
let angle = math.atan2(y2 - y, x2 - x) - rotation
angle = math.deg(angle)
angle = angle % 360
if (angle > 180) {
angle = angle - 360
}
return math.abs(angle) > 90
}
/**
* Gets the model ID for the mob.
*/
function GetModelId(this: void, mob: Mob) {
return ObjectModelId(mob.token)
}
/**
* Returns whether or not the mob is in a party with the player.
*/
function IsInParty(this: void, mob: Mob) {
return UnitInParty(mob.token as UnitToken, undefined)
}
/**
* Returns whether or not the mob is in a raid with the player.
*/
function IsInRaid(this: void, mob: Mob) {
return UnitInRaid(mob.token as UnitToken, undefined) !== undefined
}
/**
* Returns whether or not the mob is in a party or raid with the player.
*/
function IsInPartyOrRaid(this: void, mob: Mob) {
return IsInParty(mob) || IsInRaid(mob)
}
/**
* Returns whether or not the player is mounted, or shapeshifted into a mount form.
*/
function IsMounted(this: void, mob: Mob) {
const isMounted = UnitIsMounted(mob.token)
if (isMounted) return true
const mountFormIds = [
3, // Mount / Travel form
27, // Swift Flight Form
29 // Flight Form
]
const shapeShiftId = GetShapeshiftFormID()
return shapeShiftId !== undefined && mountFormIds.includes(shapeShiftId)
}
/**
* Returns the "Combat reach" of a mob. The combat reach of a mob is a bounding radius from which distance calcs begin for the purposes of calculating combat distance.
*/
function GetCombatReach(this: void, mob: Mob) {
return ObjectCombatReach(mob.token) || 0
}
/**
* Returns the distance between two mobs, with combat reach accounted for.
*/
function GetCombatDistanceBetween(this: void, reference: Mob, compare: Mob) {
GetDistanceBetween(reference, compare) - GetCombatReach(compare)
}
/**
* Gets whether or not the mob is currently online.
*/
function GetIsOnline(this: void, mob: Mob) {
return UnitIsConnected(mob.token as UnitToken)
}
/**
* Get whether or not the mob is being resurrected.
*/
function HasIncomingRessurection(this: void, mob: Mob) {
return IsAlive(mob) === false && UnitHasIncomingResurrection(mob.token)
}
/**
* Get whether or not the mob is currently targetting something.
*/
function HasTarget(this: void, mob: Mob) {
return ObjectTarget(mob.token) !== undefined
}
/**
* Get the mobs current target.
*/
function GetTarget(this: void, mob: Mob): Mob | undefined {
const objTarget = ObjectTarget(mob.token)
if (!objTarget) return undefined
const mobManagerMob = Mekanome.MobManager.GetMob({ token: objTarget.unit() })
return mobManagerMob
}
/**
* Returns whether or not a mob is within a certain distance from another mob, with combat reach accounted for.
*/
function IsWithinCombatDistance(
this: void,
reference: Mob,
compare: Mob,
distance: number
) {
if (!IsValid(compare)) return false
return GetDistanceBetween(reference, compare) <= distance + GetCombatReach(compare)
}
/**
* Returns whether or not a mob is within a certain distance from another mob.
*/
function IsWithinDistance(this: void, reference: Mob, compare: Mob, distance: number) {
return GetDistanceBetween(reference, compare) <= distance
}
/**
* Returns the number of loss of control effects on the mob. Interrupt lockouts included.
*/
function GetLossOfControlCount(this: void, mob: Mob) {
return C_LossOfControl.GetActiveLossOfControlDataCountByUnit(mob.token as UnitToken)
}
/**
* Returns the mobs outgoing missiles.
*/
function GetOutgoingMissles({
reference,
target,
spellId,
spellVisualId
}: {
reference: Mob
target?: Mob
spellId?: number
spellVisualId?: number
}) {
const missiles = Missiles()
const results: Missile[] = []
if (type(missiles) == 'table' && IsValid(reference)) {
for (const [, missile] of missiles) {
const missileSource = missile.source?.unit()
if (missileSource) {
if (
UnitIsUnit(reference.token, missileSource) &&
(!target || UnitIsUnit(missile.target?.unit() || 'none', target.token)) &&
(!spellId || (!!spellId && spellId === missile.spellId)) &&
(!spellVisualId || (!!spellVisualId && spellVisualId == missile.spellVisualId))
) {
results.push(missile)
}
}
}
}
return results
}
/**
* Returns the mobs incoming missiles.
*/
function GetIncomingMissiles(
this: void,
{
reference,
source,
spellId,
spellVisualId
}: { reference: Mob; source?: Mob; spellId?: number; spellVisualId?: number }
) {
const missiles = Missiles()
const results: Missile[] = []
if (type(missiles) == 'table' && IsValid(reference)) {
for (const [, missile] of missiles) {
const missileTarget = missile.target?.unit()
if (
missileTarget &&
UnitIsUnit(reference.token, missileTarget) &&
(!source || UnitIsUnit(source.token || 'none', reference.token)) &&
(!spellId || spellId == missile.spellId) &&
(!spellVisualId || spellVisualId == missile.spellVisualId)
) {
table.insert(results, missile)
}
}
}
return results
}
/**
* Gets the amount of time the mob has been in combat.
*/
function GetCombatTime(this: void, mob: Mob) {
if (!mob.lastCombatTime) return undefined
return GetTime() - mob.lastCombatTime
}
/**
* Get combat odds (if the last combat time is less than 1 minute ago return 1 / time, else return 0)
* the closer to 0 the more likely the unit is to be in combat (0 = 100%) 60 = 0%
* @param mob The mob to check
* @returns number | undefined
*/
function InCombatOdds(this: void, mob: Mob) {
const combatTime = GetCombatTime(mob)
if (combatTime === undefined) return undefined
const percent = 1 - combatTime / 60
return percent * 100
}
const _MobUtils = {
GetLossOfControlCount,
GetTarget,
IsWithinCombatDistance,
IsWithinDistance,
GetOutgoingMissles,
GetIncomingMissiles,
HasTarget,
HasIncomingRessurection,
GetCombatDistanceBetween,
GetCombatReach,
IsInPartyOrRaid,
IsMounted,
GetModelId,
IsInRaid,
IsInParty,
IsBehindMob,
GetIsOnline,
IsFacingMob,
GetIsMoving,
GetIsInterruptible,
GetCastOrChannelPercentComplete,
IsCasting,
GetCastOrChannelEndTime,
IsChanneling,
GetIsInterruptibleAt,
IsCastingOrChanneling,
IsAffectingCombat,
GetPowerType,
CanSee,
GetMaxPower,
GetCurrentPower,
GetPowerPercent,
GetCastTarget,
InCombatOdds,
GetName,
CanAttack,
GetHealthPercent,
IsAlive,
GetPosition,
GetDistanceBetween,
IsPet,
GetRole,
GetCombatTime,
GetChannelEndTime,
GetCastEndTime
}
declare global {
type MobUtils = typeof _MobUtils
}
Mekanome.MobUtils = _MobUtils
export default {}

@ -0,0 +1,249 @@
const [_, _Mekanome] = [...$vararg]
const Mekanome = _Mekanome as unknown as IMekanome
/**
* Creates a useable spell
*/
function Create(this: void, id: number, traits?: SpellTraits): Spell {
const spellInfo = C_Spell.GetSpellInfo(id)
if (spellInfo === undefined || spellInfo.name === undefined) {
throw Mekanome.Error(`Unable to find info for spell ${id}`)
}
return {
id,
minRange: spellInfo.minRange,
maxRange: spellInfo.maxRange,
name: spellInfo.name,
traits
}
}
/**
* Get cooldown information for the spell.
*/
function GetCooldownInfo(this: void, spell: Spell) {
const [, cdDuration, enabled] = GetSpellCooldown(spell.id)
const [, ccDuration] = GetSpellLossOfControlCooldown(spell.id)
const { spellQueueWindow } = Mekanome.PlayerStateManager.GetPlayerState()
const secondsUntilReady = cdDuration === 0 ? ccDuration : cdDuration
const secondsUntilCastable = secondsUntilReady - spellQueueWindow / 1000
return {
secondsUntilReady,
secondsUntilCastable: secondsUntilCastable < 0 ? 0 : secondsUntilCastable,
isActive: enabled === 0
}
}
/**
* Handles undefined / funciton traits to get a more usuable set of trait values
*/
function GetSpellTraits(this: void, spell: Spell) {
let isCastableWhileMoving = false
let isCastableWhileCasting = false
let isOverride = false
if (spell.traits !== undefined) {
if (spell.traits.isOverride !== undefined) {
isOverride = spell.traits.isOverride
}
if (spell.traits.isCastableWhileCasting !== undefined) {
if (typeof spell.traits.isCastableWhileCasting === 'function') {
isCastableWhileCasting = spell.traits.isCastableWhileCasting()
} else {
isCastableWhileCasting = spell.traits.isCastableWhileCasting
}
}
if (spell.traits.isCastableWhileMoving !== undefined) {
if (typeof spell.traits.isCastableWhileMoving === 'function') {
isCastableWhileMoving = spell.traits.isCastableWhileMoving()
} else {
isCastableWhileMoving = spell.traits.isCastableWhileMoving
}
}
}
return {
isCastableWhileMoving,
isCastableWhileCasting,
isOverride
}
}
interface IsCastableArgs {
spell: Spell
target?: Mob
position?: Vector3
}
/**
* Evaluates the spells traits & conditions to see if it is currently castable.
*/
function IsCastable(this: void, { spell, target, position }: IsCastableArgs): boolean {
const player = Mekanome.MobManager.GetMob({ token: 'player' })
const { isCastableWhileCasting, isCastableWhileMoving, isOverride } =
GetSpellTraits(spell)
if (player === undefined) return false
const traits = Mekanome.PlayerStateManager.GetPlayerState()
// Return false is the spell is not known.
if (isOverride === true) {
if (IsSpellKnownOrOverridesKnown(spell.id) !== true) {
return false
}
} else {
if (IsSpellKnown(spell.id) !== true) {
return false
}
}
const { isActive, secondsUntilCastable } = GetCooldownInfo(spell)
print(`COOLDOWN: ${secondsUntilCastable}`)
// If the spell is current active (e.g Presence of Mind was cast, but has not been consumed yet)
// return false.
if (isActive) return false
// Return false if the spell is on
if (secondsUntilCastable !== 0) {
return false
}
// Return falst is the player is moving and the spell isnt marked
// as castable while moving.
if (traits.isMoving && isCastableWhileMoving === false) {
return false
}
// Return false if the player is currently casting, and this spell
// cant be cast while casting.
if (traits.isCasting === true && isCastableWhileCasting === false) {
return false
}
return true
}
/**
* Get the localized name of the spell.
*/
function GetName(this: void, spell: Spell) {
const name = C_Spell.GetSpellInfo(spell.id).name
return name
}
/**
* Gets the action button binding for the provided spell.
*/
function GetBindingForSpell(this: void, spell: Spell) {
const binding = Mekanome.BindingsManager.GetBindingForSpell(spell)
if (binding === undefined) {
throw Mekanome.Error(`Unable to cast ${GetName(spell)} without binding.`)
}
return binding
}
/**
* Generates a castable to cast the spell on the player
*/
function SelfCast(this: void, spell: Spell): Castable | undefined {
const binding = GetBindingForSpell(spell)
if (IsCastable({ spell }) === false) {
return undefined
}
return {
id: spell.id,
variant: 'self',
binding
}
}
/**
* Generates a castable to cast the spell at the specified target.
*/
function TargetCast(
this: void,
spell: Spell,
_target: Mob | (() => Mob)
): Castable | undefined {
const binding = GetBindingForSpell(spell)
const target = typeof _target === 'function' ? _target() : _target
if (IsCastable({ spell, target }) === false) {
return undefined
}
return {
id: spell.id,
variant: 'target',
binding,
target
}
}
/**
* Generates a castable to cast the spell at the specified location
*/
function GroundCast(
this: void,
spell: Spell,
_position: Vector3 | (() => Vector3)
): Castable | undefined {
const binding = GetBindingForSpell(spell)
const position = typeof _position === 'function' ? _position() : _position
if (IsCastable({ spell, position }) === false) {
return undefined
}
return {
id: spell.id,
variant: 'ground',
binding,
position
}
}
const _SpellUtils = {
Create,
GetName,
GetCooldownInfo,
SelfCast,
GroundCast,
TargetCast
}
declare global {
interface SpellTraits {
isCastableWhileMoving?: boolean | (() => boolean)
isCastableWhileCasting?: boolean | (() => boolean)
isOverride?: boolean
}
interface Spell {
id: number
name: string
minRange: number
maxRange: number
traits?: SpellTraits
}
type SpellUtils = typeof _SpellUtils
}
Mekanome.SpellUtils = _SpellUtils
export default {}

@ -0,0 +1,210 @@
const [_, _Mekanome] = [...$vararg]
const Mekanome = _Mekanome as unknown as IMekanome
function Create(this: void, { x, y, z }: { x: number; y: number; z: number }): Vector3 {
return {
x,
y,
z
}
}
function Add(this: void, a: Vector3, b: Vector3 | number): Vector3 {
if (typeof b === 'number') {
return Create({ x: a.x + b, y: a.y + b, z: a.z + b })
}
return Create({
x: a.x + b.x,
y: a.y + b.y,
z: a.z + b.z
})
}
function Subtract(this: void, a: Vector3, b: Vector3 | number): Vector3 {
if (typeof b === 'number') {
return Create({ x: a.x - b, y: a.y - b, z: a.z - b })
}
return Create({
x: a.x - b.x,
y: a.y - b.y,
z: a.z - b.z
})
}
function Multiply(this: void, a: Vector3, b: Vector3 | number): Vector3 {
if (typeof b === 'number') {
return Create({ x: a.x * b, y: a.y * b, z: a.z * b })
}
return Create({
x: a.x * b.x,
y: a.y * b.y,
z: a.z * b.z
})
}
function Divide(this: void, a: Vector3, b: Vector3 | number): Vector3 {
if (typeof b === 'number') {
return Create({ x: a.x / b, y: a.y / b, z: a.z / b })
}
return Create({
x: a.x / b.x,
y: a.y / b.y,
z: a.z / b.z
})
}
function Equals(this: void, a: Vector3, b: Vector3): boolean {
return a.x === b.x && a.y === b.y && a.z === b.z
}
function LessThan(this: void, a: Vector3, b: Vector3): boolean {
return a.x < b.x && a.y < b.y && a.z < b.z
}
function LessThanOrEqualTo(this: void, a: Vector3, b: Vector3): boolean {
return a.x <= b.x && a.y <= b.y && a.z <= b.z
}
function Dot(this: void, a: Vector3, b: Vector3): number {
return a.x * b.x + a.y * b.y + a.z + b.z
}
function Cross(this: void, a: Vector3, b: Vector3): Vector3 {
return Create({
x: a.y * b.z - a.z * b.y,
y: a.z * b.x - a.x * b.z,
z: a.x * b.y - a.y * b.x
})
}
function Distance(this: void, a: Vector3, b: Vector3): number {
return FastDistance(a.x, a.y, a.z, b.x, b.y, b.z)
}
function NormalizeOrientation(this: void, orientation: number): number {
if (orientation < 0) {
let mod = orientation * -1
mod = mod % (2.0 * Math.PI)
mod = -mod + 2.0 * math.pi
return mod
} else {
return orientation % (2.0 * Math.PI)
}
}
function GetAbsoluteAngle(this: void, a: Vector3, b: Vector3): number {
return NormalizeOrientation(Math.atan2(b.y - a.y, b.x - a.x))
}
function Angle(this: void, a: Vector3, b: Vector3): number {
return Math.acos(
Dot(a, b) /
(Math.sqrt(a.x * a.x + a.y * a.y + a.z * a.z) *
Math.sqrt(b.x * b.x + b.y * b.y + b.z * b.z))
)
}
function _GetNormalized(this: void, vector: Vector3): Vector3 {
const length = Math.sqrt(
vector.x * vector.x + vector.y * vector.y + vector.z * vector.z
)
return Create({ x: vector.x / length, y: vector.y / length, z: vector.z / length })
}
function ClampMagnitude(this: void, vector: Vector3, maxLength: number): Vector3 {
if (Dot(vector, vector) > maxLength * maxLength) {
return Multiply(_GetNormalized(vector), maxLength)
} else {
return vector
}
}
function _GetMagnitude(this: void, vector: Vector3): number {
return Math.sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z)
}
function DirectionOrZero(this: void, vector: Vector3): Vector3 {
const magnitude = _GetMagnitude(vector)
if (magnitude < 0.0000001) {
return Create({ x: 0, y: 0, z: 0 })
} else if (magnitude < 1.00001 && magnitude > 0.99999) {
return vector
} else {
return Multiply(vector, 1.0 / magnitude)
}
}
function Clamp(this: void, x: number, min: number, max: number): number {
return x < min ? min : x > max ? max : x
}
function Lerp(this: void, a: Vector3, b: Vector3, _t: number): Vector3 {
const t = Clamp(_t, 0, 1)
return Create({
x: a.x + (b.x - a.x) * t,
y: a.y + (b.y - a.y) * t,
z: a.z + (b.z - a.z) * t
})
}
function MoveTowards(
this: void,
reference: Vector3,
target: Vector3,
maxDistanceDelta: number
): Vector3 {
const toVector = Subtract(target, reference)
const distance = _GetMagnitude(toVector)
if (distance <= maxDistanceDelta || distance === 0) {
return target
}
return Divide(Add(reference, toVector), distance * maxDistanceDelta)
}
function Project(this: void, reference: Vector3, normal: Vector3): Vector3 {
const num = Dot(normal, normal)
if (num < 1.401298e-45) {
return Create({ x: 0, y: 0, z: 0 })
}
return Multiply(normal, Dot(reference, normal) / num)
}
function ProjectOnPlane(this: void, reference: Vector3, plane: Vector3): Vector3 {
return Subtract(reference, Project(reference, plane))
}
const _VectorUtils = {
Create,
Add,
Subtract,
Multiply,
Divide,
Equals,
LessThan,
LessThanOrEqualTo,
Dot,
Cross,
Distance,
Angle,
GetAbsoluteAngle,
ClampMagnitude,
DirectionOrZero,
Clamp,
Lerp,
MoveTowards,
ProjectOnPlane
}
declare global {
type VectorUtils = typeof _VectorUtils
type Vector3 = {
x: number
y: number
z: number
}
}
Mekanome.VectorUtils = _VectorUtils
export default {}

@ -0,0 +1,26 @@
{
"$schema": "https://raw.githubusercontent.com/TypeScriptToLua/TypeScriptToLua/master/tsconfig-schema.json",
"compilerOptions": {
"target": "ESNext",
"lib": ["ESNext"],
"moduleResolution": "Node",
"baseUrl": ".",
"rootDir": "src",
"outDir": "dist",
"types": ["@typescript-to-lua/language-extensions", "lua-types/5.1"],
"strict": true,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"allowUmdGlobalAccess": true
},
"include": ["src/**/*"],
"tstl": {
"luaTarget": "5.1",
"noImplicitGlobalVariables": true,
"luaLibImport": "inline",
}
}
Loading…
Cancel
Save