commit
b92831e815
@ -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 💅. |
@ -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 {} |
@ -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…
Reference in new issue