diff --git a/addons/distance/distance.lua b/addons/distance/distance.lua new file mode 100644 index 0000000..81ebcea --- /dev/null +++ b/addons/distance/distance.lua @@ -0,0 +1,130 @@ +--[[ +* Ashita - Copyright (c) 2014 - 2016 atom0s [atom0s@live.com] +* +* This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. +* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to +* Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. +* +* By using Ashita, you agree to the above license and its terms. +* +* Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were +* made. You must do so in any reasonable manner, but not in any way that suggests the licensor +* endorses you or your use. +* +* Non-Commercial - You may not use the material (Ashita) for commercial purposes. +* +* No-Derivatives - If you remix, transform, or build upon the material (Ashita), you may not distribute the +* modified material. You are, however, allowed to submit the modified works back to the original +* Ashita project in attempt to have it added to the original project. +* +* You may not apply legal terms or technological measures that legally restrict others +* from doing anything the license permits. +* +* No warranties are given. +]]-- + +_addon.author = 'atom0s'; +_addon.name = 'distance'; +_addon.version = '3.0.0'; + +require 'common' + +---------------------------------------------------------------------------------------------------- +-- Configurations +---------------------------------------------------------------------------------------------------- +local default_config = +{ + font = + { + family = 'Arial', + size = 16, + color = 0xFFFFFFFF, + position = { -180, 20 }, + }, + show_name = false, + show_id = false, + show_id_hex = false +}; +local distance_config = default_config; + +---------------------------------------------------------------------------------------------------- +-- func: load +-- desc: Event called when the addon is being loaded. +---------------------------------------------------------------------------------------------------- +ashita.register_event('load', function() + -- Load the configuration file.. + distance_config = ashita.settings.load_merged(_addon.path .. '/settings/settings.json', distance_config); + + -- Create the font object.. + local f = AshitaCore:GetFontManager():Create('__distance_addon'); + f:SetColor(distance_config.font.color); + f:SetFontFamily(distance_config.font.family); + f:SetFontHeight(distance_config.font.size); + f:SetBold(true); + f:SetPositionX(distance_config.font.position[1]); + f:SetPositionY(distance_config.font.position[2]); + f:SetText('0.0'); + f:SetVisibility(true); +end); + +---------------------------------------------------------------------------------------------------- +-- func: unload +-- desc: Event called when the addon is being unloaded. +---------------------------------------------------------------------------------------------------- +ashita.register_event('unload', function() + -- Get the font object.. + local f = AshitaCore:GetFontManager():Get('__distance_addon'); + + -- Update the configuration position.. + distance_config.font.position = { f:GetPositionX(), f:GetPositionY() }; + + -- Save the configuration file.. + ashita.settings.save(_addon.path .. '/settings/settings.json', distance_config); + + -- Delete the font object.. + AshitaCore:GetFontManager():Delete('__distance_addon'); +end); + +---------------------------------------------------------------------------------------------------- +-- func: render +-- desc: Event called when the addon is being rendered. +---------------------------------------------------------------------------------------------------- +ashita.register_event('render', function() + -- Get the font object.. + local f = AshitaCore:GetFontManager():Get('__distance_addon'); + if (f == nil) then return; end + + -- Ensure we have a valid player.. + local party = AshitaCore:GetDataManager():GetParty(); + if (party:GetMemberActive(0) == false or party:GetMemberServerId(0) == 0) then + f:SetText(''); + return; + end + + -- Ensure we have a valid target.. + local target = ashita.ffxi.targets.get_target('t'); + if (target == nil or target.Name == '' or target.TargetIndex == 0) then + f:SetText(''); + return; + end + + local str = string.format('%.1f', math.sqrt(target.Distance)); + + -- Append the name.. + if (distance_config.show_name == true) then + str = str .. ' ' .. target.Name; + end + + -- Append the server id (decimal).. + if (distance_config.show_id == true) then + str = string.format('%s [%d]', str, target.ServerId); + end + + -- Append the server id (hex).. + if (distance_config.show_id_hex == true) then + str = string.format('%s [%08X]', str, target.ServerId); + end + + -- Set the distance text.. + f:SetText(str); +end); \ No newline at end of file diff --git a/addons/fps/fps.lua b/addons/fps/fps.lua new file mode 100644 index 0000000..a45097f --- /dev/null +++ b/addons/fps/fps.lua @@ -0,0 +1,198 @@ +--[[ +* Ashita - Copyright (c) 2014 - 2016 atom0s [atom0s@live.com] +* +* This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. +* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to +* Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. +* +* By using Ashita, you agree to the above license and its terms. +* +* Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were +* made. You must do so in any reasonable manner, but not in any way that suggests the licensor +* endorses you or your use. +* +* Non-Commercial - You may not use the material (Ashita) for commercial purposes. +* +* No-Derivatives - If you remix, transform, or build upon the material (Ashita), you may not distribute the +* modified material. You are, however, allowed to submit the modified works back to the original +* Ashita project in attempt to have it added to the original project. +* +* You may not apply legal terms or technological measures that legally restrict others +* from doing anything the license permits. +* +* No warranties are given. +]]-- + +_addon.author = 'atom0s'; +_addon.name = 'FPS'; +_addon.version = '3.0.0'; + +require 'common' + +---------------------------------------------------------------------------------------------------- +-- FPS Configuration +---------------------------------------------------------------------------------------------------- +local default_config = +{ + font = + { + family = 'Arial', + size = 12, + color = math.d3dcolor(255, 255, 0, 0), + position = { 1, 1 } + }, + format = 'FPS: %.1f' +}; +local fps_config = default_config; + +---------------------------------------------------------------------------------------------------- +-- FPS Variables +---------------------------------------------------------------------------------------------------- +local fps = { }; +fps.count = 0; +fps.timer = 0; +fps.frame = 0; +fps.show = true; + +---------------------------------------------------------------------------------------------------- +-- func: print_help +-- desc: Displays a help block for proper command usage. +---------------------------------------------------------------------------------------------------- +local function print_help(cmd, help) + -- Print the invalid format header.. + print('\31\200[\31\05' .. _addon.name .. '\31\200]\30\01 ' .. '\30\68Invalid format for command:\30\02 ' .. cmd .. '\30\01'); + + -- Loop and print the help commands.. + for k, v in pairs(help) do + print('\31\200[\31\05' .. _addon.name .. '\31\200]\30\01 ' .. '\30\68Syntax:\30\02 ' .. v[1] .. '\30\71 ' .. v[2]); + end +end + +---------------------------------------------------------------------------------------------------- +-- func: load +-- desc: Event called when the addon is being loaded. +---------------------------------------------------------------------------------------------------- +ashita.register_event('load', function() + -- Load the configuration file.. + fps_config = ashita.settings.load_merged(_addon.path .. '/settings/fps.json', fps_config); + + -- Create the font object.. + local f = AshitaCore:GetFontManager():Create('__fps_addon'); + f:SetColor(fps_config.font.color); + f:SetFontFamily(fps_config.font.family); + f:SetFontHeight(fps_config.font.size); + f:SetPositionX(fps_config.font.position[1]); + f:SetPositionY(fps_config.font.position[2]); + f:SetText(''); + f:SetVisibility(fps.show); +end); + +---------------------------------------------------------------------------------------------------- +-- func: unload +-- desc: Event called when the addon is being unloaded. +---------------------------------------------------------------------------------------------------- +ashita.register_event('unload', function() + -- Get the font object.. + local f = AshitaCore:GetFontManager():Get('__fps_addon'); + + -- Update the configuration position.. + fps_config.font.position = { f:GetPositionX(), f:GetPositionY() }; + + -- Save the configuration file.. + ashita.settings.save(_addon.path .. '/settings/fps.json', fps_config); + + -- Delete the font object.. + AshitaCore:GetFontManager():Delete('__fps_addon'); +end); + +---------------------------------------------------------------------------------------------------- +-- func: command +-- desc: Event called when a command was entered. +---------------------------------------------------------------------------------------------------- +ashita.register_event('command', function(command, ntype) + -- Get the arguments of the command.. + local args = command:args(); + if (args[1] ~= '/fps') then + return false; + end + + -- Toggle the FPS visibility.. + if (#args == 1 or args[2] == 'show') then + fps.show = not fps.show; + return true; + end + + -- Set the FPS color.. + if (#args >= 6 and args[2] == 'color') then + font_config.font.color = math.d3dcolor(tonumber(args[3]), tonumber(args[4]), tonumber(args[5]),tonumber(args[6])); + local f = AshitaCore:GetFontManager():Get('__fps_addon'); + if (f ~= nil) then f:SetColor(font_config.font.color); end + return true; + end + + -- Set the font family and height.. + if (#args >= 4 and args[2] == 'font') then + font_config.font.family = args[3]; + font_config.font.size = tonumber(args[4]); + local f = AshitaCore:GetFontManager():Get('__fps_addon'); + if (f ~= nil) then + f:SetFontFamily(font_config.font.family); + f:SetFontHeight(font_config.font.size); + end + return true; + end + + -- Set the FPS divisor.. + if (#args >= 2 and tonumber(args[2]) > 0) then + local pointer = ashita.memory.findpattern('FFXiMain.dll', 0, '81EC000100003BC174218B0D', 0, 0); + if (pointer == 0) then + print('[FPS] Could not locate the required signature to patch the FPS divisor!'); + return true; + end + + -- Read into the pointer.. + local addr = ashita.memory.read_uint32(pointer + 0x0C); + addr = ashita.memory.read_uint32(addr); + + -- Set the new FPS divisor.. + ashita.memory.write_uint32(addr + 0x30, tonumber(args[2])); + print(string.format('\31\200[\31\05FPS\31\200] \31\130Set FPS divisor to: \30\02%d', tonumber(args[2]))); + return true; + end + + -- Prints the addon help.. + print_help('/fps', { + { '/fps show', '- Toggles the FPS display on and off.' }, + { '/fps color [a] [r] [g] [b]', '- Sets the FPS display color.' }, + { '/fps font [name] [size]', '- Sets the FPS display font family and height.' }, + { '/fps [num]', '- Sets the FPS divisor. (1 = 60FPS, 2 = 30FPS, etc.)' }, + }); + return true; +end); + +---------------------------------------------------------------------------------------------------- +-- func: render +-- desc: Event called when the addon is being rendered. +---------------------------------------------------------------------------------------------------- +ashita.register_event('render', function() + -- Get the font object.. + local f = AshitaCore:GetFontManager():Get('__fps_addon'); + if (f == nil) then return; end + + -- Set the font visibility.. + f:SetVisibility(fps.show); + + -- Skip calculations if font is disabled.. + if (fps.show == false) then return; end + + -- Calculate the current FPS.. + fps.count = fps.count + 1; + if (os.time() >= fps.timer + 1) then + fps.frame = fps.count; + fps.count = 0; + fps.timer = os.time(); + end + + -- Update the FPS font.. + f:SetText(string.format(fps_config.format, fps.frame)); +end); \ No newline at end of file diff --git a/addons/libs/common.lua b/addons/libs/common.lua new file mode 100644 index 0000000..3b91720 --- /dev/null +++ b/addons/libs/common.lua @@ -0,0 +1,71 @@ +--[[ +* Ashita - Copyright (c) 2014 - 2016 atom0s [atom0s@live.com] +* +* This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. +* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to +* Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. +* +* By using Ashita, you agree to the above license and its terms. +* +* Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were +* made. You must do so in any reasonable manner, but not in any way that suggests the licensor +* endorses you or your use. +* +* Non-Commercial - You may not use the material (Ashita) for commercial purposes. +* +* No-Derivatives - If you remix, transform, or build upon the material (Ashita), you may not distribute the +* modified material. You are, however, allowed to submit the modified works back to the original +* Ashita project in attempt to have it added to the original project. +* +* You may not apply legal terms or technological measures that legally restrict others +* from doing anything the license permits. +* +* No warranties are given. +]]-- + +---------------------------------------------------------------------------------------------------- +-- Extensions +---------------------------------------------------------------------------------------------------- +require 'imguidef' +require 'logging' +require 'mathex' +require 'settings' +require 'stringex' +require 'tableex' +require 'timer' + +---------------------------------------------------------------------------------------------------- +-- Common FFXI Requires +---------------------------------------------------------------------------------------------------- +require 'ffxi.enums' +require 'ffxi.recast' +require 'ffxi.targets' +require 'ffxi.vanatime' +require 'ffxi.weather' + +---------------------------------------------------------------------------------------------------- +-- func: switch +-- desc: Switch case implementation for Lua. (Credits: Unknown Original Author) +---------------------------------------------------------------------------------------------------- +function switch(c) + local switch_table = + { + casevar = c, + caseof = function(self, code) + local f; + if (self.casevar) then + f = code[self.casevar] or code.default; + else + f = code.missing or code.default; + end + if f then + if (type(f) == 'function') then + return f(self.casevar,self); + else + error('case: ' .. tostring(self.casevar) .. ' is not a function!'); + end + end + end + }; + return switch_table +end \ No newline at end of file diff --git a/addons/libs/ffxi/enums.lua b/addons/libs/ffxi/enums.lua new file mode 100644 index 0000000..9c6fc59 --- /dev/null +++ b/addons/libs/ffxi/enums.lua @@ -0,0 +1,539 @@ +--[[ +* Ashita - Copyright (c) 2014 - 2016 atom0s [atom0s@live.com] +* +* This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. +* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to +* Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. +* +* By using Ashita, you agree to the above license and its terms. +* +* Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were +* made. You must do so in any reasonable manner, but not in any way that suggests the licensor +* endorses you or your use. +* +* Non-Commercial - You may not use the material (Ashita) for commercial purposes. +* +* No-Derivatives - If you remix, transform, or build upon the material (Ashita), you may not distribute the +* modified material. You are, however, allowed to submit the modified works back to the original +* Ashita project in attempt to have it added to the original project. +* +* You may not apply legal terms or technological measures that legally restrict others +* from doing anything the license permits. +* +* No warranties are given. +]]-- + +---------------------------------------------------------------------------------------------------- +-- Holds various definitions, enumerations, etc. specific to FFXI data. +---------------------------------------------------------------------------------------------------- + +AbilityType = +{ + General = 0, + Job = 1, + Pet = 2, + Weapon = 3, + Trait = 4, + BloodPactRage = 5, + Corsair = 6, + CorsairShot = 7, + BloodPactWard = 8, + Samba = 9, + Waltz = 10, + Step = 11, + Florish1 = 12, + Scholar = 13, + Jig = 14, + Flourish2 = 15, + Monster = 16, + Flourish3 = 17, + Weaponskill = 18, + Rune = 19, + Ward = 20, + Effusion = 21 +}; + +CraftRank = +{ + Amateur = 0, + Recruit = 1, + Initiate = 2, + Novice = 3, + Apprentice = 4, + Journeyman = 5, + Craftsman = 6, + Artisan = 7, + Adept = 8, + Veteran = 9 +}; + +CombatType = +{ + Magic = 0x1000, + Combat = 0x2000 +}; + +Containers = +{ + Inventory = 0, + Safe = 1, + Storage = 2, + Temporary = 3, + Locker = 4, + Satchel = 5, + Sack = 6, + Case = 7, + Wardrobe = 8, + Safe2 = 9, + Wardrobe2 = 10, + Wardrobe3 = 11, + Wardrobe4 = 12, +}; + +EquipmentSlotMask = +{ + None = 0x0000, + Main = 0x0001, + Sub = 0x0002, + Range = 0x0004, + Ammo = 0x0008, + Head = 0x0010, + Body = 0x0020, + Hands = 0x0040, + Legs = 0x0080, + Feet = 0x0100, + Neck = 0x0200, + Waist = 0x0400, + LEar = 0x0800, + REar = 0x1000, + LRing = 0x2000, + RRing = 0x4000, + Back = 0x8000, + + -- Slot Groups + Ears = 0x1800, + Rings = 0x6000, + + -- All Slots + All = 0xFFFF +}; + +EquipmentSlots = +{ + Main = 0, + Sub = 1, + Range = 2, + Ammo = 3, + Head = 4, + Body = 5, + Hands = 6, + Legs = 7, + Feet = 8, + Waist = 9, + Ear1 = 10, + Ear2 = 11, + Ring1 = 12, + Ring2 = 13, + Back = 14, +}; + +ElementColor = +{ + Red = 0, + Clear = 1, + Green = 2, + Yellow = 3, + Purple = 4, + Blue = 5, + White = 6, + Black = 7 +}; + +ElementType = +{ + Fire = 0, + Ice = 1, + Air = 2, + Earth = 3, + Thunder = 4, + Water = 5, + Light = 6, + Dark = 7, + Special = 0x0F, + Unknown = 0xFF +}; + +EntityHair = +{ + Hair1A = 0, + Hair1B = 1, + Hair2A = 2, + Hair2B = 3, + Hair3A = 4, + Hair3B = 5, + Hair4A = 6, + Hair4B = 7, + Hair5A = 8, + Hair5B = 9, + Hair6A = 10, + Hair6B = 11, + Hair7A = 12, + Hair7B = 13, + Hair8A = 14, + Hair8B = 15, + + -- Non-Player Hair Styles + Fomar = 29, + Mannequin = 30, +}; + +EntityRace = +{ + Invalid = 0, + HumeMale = 1, + HumeFemale = 2, + ElvaanMale = 3, + ElvaanFemale = 4, + TarutaruMale = 5, + TarutaruFemale = 6, + Mithra = 7, + Galka = 8, + + -- Non-PC Races + MithraChild = 29, + HumeChildFemale = 30, + HumeChildMale = 31, + GoldChocobo = 32, + BlackChocobo = 33, + BlueChocobo = 34, + RedChocobo = 35, + GreenChocobo = 36 +}; + +EntitySpawnFlags = +{ + Player = 0x0001, + Npc = 0x0002, + PartyMember = 0x0004, + AllianceMember = 0x0008, + Monster = 0x0010, + Object = 0x0020, + LocalPlayer = 0x0200, +}; + +EntityType = +{ + Player = 0, + Npc1 = 1, + Npc2 = 2, + Npc3 = 3, + Elevator = 4, + Airship = 5, +}; + +ItemFlags = +{ + None = 0x0000, + WallHanging = 0x0001, + Flag1 = 0x0002, + Flag2 = 0x0004, + Flag3 = 0x0008, + DeliveryInner = 0x0010, + Inscribable = 0x0020, + NoAuction = 0x0040, + Scroll = 0x0080, + Linkshell = 0x0100, + CanUse = 0x0200, + CanTradeNpc = 0x0400, + CanEquip = 0x0800, + NoSale = 0x1000, + NoDelivery = 0x2000, + NoTrade = 0x4000, + Rare = 0x8000, + Exclusive = 0x6040, + Nothing = 0xF140 +}; + +ItemType = +{ + None = 0x0000, + Item = 0x0001, + QuestItem = 0x0002, + Fish = 0x0003, + Weapon = 0x0004, + Armor = 0x0005, + Linkshell = 0x0006, + UsableItem = 0x0007, + Crystal = 0x0008, + Currency = 0x0009, + Furnishing = 0x000A, + Plant = 0x000B, + Flowerpot = 0x000C, + PuppetItem = 0x000D, + Mannequin = 0x000E, + Book = 0x000F, + RacingForm = 0x0010, + BettingSlip = 0x0011, + SoulPlate = 0x0012, + Reflector = 0x0013, + Logs = 0x0014, + LotteryTicket = 0x0015, + TabulaM = 0x0016, + TabulaR = 0x0017, + Voucher = 0x0018, + Rune = 0x0019, + Evolith = 0x001A, + StorageSlip = 0x001B, + Type1 = 0x001C +}; + +JobMask = +{ + None = 0x00000000, + WAR = 0x00000002, + MNK = 0x00000004, + WHM = 0x00000008, + BLM = 0x00000010, + RDM = 0x00000020, + THF = 0x00000040, + PLD = 0x00000080, + DRK = 0x00000100, + BST = 0x00000200, + BRD = 0x00000400, + RNG = 0x00000800, + SAM = 0x00001000, + NIN = 0x00002000, + DRG = 0x00004000, + SMN = 0x00008000, + BLU = 0x00010000, + COR = 0x00020000, + PUP = 0x00040000, + DNC = 0x00080000, + SCH = 0x00100000, + GEO = 0x00200000, + RUN = 0x00400000, + MON = 0x00800000, + JOB24 = 0x01000000, + JOB25 = 0x02000000, + JOB26 = 0x04000000, + JOB27 = 0x08000000, + JOB28 = 0x10000000, + JOB29 = 0x20000000, + JOB30 = 0x40000000, + JOB31 = 0x80000000, + AllJobs = 0x007FFFFE, +}; + +Jobs = +{ + None = 0, + Warrior = 1, + Monk = 2, + WhiteMage = 3, + BlackMage = 4, + RedMage = 5, + Thief = 6, + Paladin = 7, + DarkKnight = 8, + Beastmaster = 9, + Bard = 10, + Ranger = 11, + Samurai = 12, + Ninja = 13, + Dragoon = 14, + Summoner = 15, + BlueMage = 16, + Corsair = 17, + Puppetmaster = 18, + Dancer = 19, + Scholar = 20, + Geomancer = 21, + RuneFencer = 22 +}; + +Language = +{ + Default = 0, + Japanese = 1, + English = 2, + French = 3, -- No longer used. + Deutsch = 4 -- No longer used. +}; + +LoginStatus = +{ + LoginScreen = 0, + Loading = 1, + LoggedIn = 2 +}; + +MagicType = +{ + None = 0, + WhiteMagic = 1, + BlackMagic = 2, + Summon = 3, + Ninjutsu = 4, + Song = 5, + BlueMagic = 6, + Geomancy = 7, + Trust = 8 +}; + +MoonPhase = +{ + New = 0, + WaxingCrescent = 1, + WaxingCrescent2 = 2, + FirstQuarter = 3, + WaxingGibbous = 4, + WaxingGibbous2 = 5, + Full = 6, + WaningGibbous = 7, + WaningGibbous2 = 8, + LastQuarter = 9, + WaningCrescent = 10, + WaningCrescent2 = 11 +}; + +Nation = +{ + SandOria = 0, + Bastok = 1, + Windurst = 2 +}; + +PuppetSlot = +{ + None = 0, + Head = 1, + Body = 2, + Attachment = 3 +}; + +RaceMask = +{ + None = 0x0000, + HumeMale = 0x0002, + HumeFemale = 0x0004, + ElvaanMale = 0x0008, + ElvaanFemale = 0x0010, + TarutaruMale = 0x0020, + TarutaruFemale = 0x0040, + Mithra = 0x0080, + Galka = 0x0100, + Hume = 0x0006, + Elvaan = 0x0018, + Tarutaru = 0x0060, + + Male = 0x012A, + Female = 0x00D4, + + All = 0x01FE, +}; + +SkillTypes = +{ + -- Weapon Skills + HandToHand = 1, + Dagger = 2, + Sword = 3, + GreatSword = 4, + Axe = 5, + GreatAxe = 6, + Scythe = 7, + Polarm = 8, + Katana = 9, + GreatKatana = 10, + Club = 11, + Staff = 12, + -- Combat Skills + Archery = 25, + Marksmanship = 26, + Throwing = 27, + Guard = 28, + Evasion = 29, + Shield = 30, + Parry = 31, + Divine = 32, + Healing = 33, + Enhancing = 34, + Enfeebling = 35, + Elemental = 36, + Dark = 37, + Summoning = 38, + Ninjutsu = 39, + Singing = 40, + String = 41, + Wind = 42, + BlueMagic = 43, + -- Crafting Skills + Fishing = 48, + Woodworking = 49, + Smithing = 50, + Goldsmithing = 51, + Clothcraft = 52, + Leathercraft = 53, + Bonecraft = 54, + Alchemy = 55, + Cooking = 56, + Synergy = 57, + ChocoboDigging = 58, +}; + +TargetType = +{ + None = 0x00, + Self = 0x01, + Player = 0x02, + PartyMember = 0x04, + AllianceMember = 0x08, + Npc = 0x10, + Enemy = 0x20, + Unknown = 0x40, + CorpseOnly = 0x80, + Corpse = 0x9D +}; + +TreasureStatus = +{ + None = 0, + Pass = 1, + Lot = 2 +}; + +WeatherType = +{ + Clear = 0, + Sunny = 1, + Cloudy = 2, + Fog = 3, + Fire = 4, + Fire2 = 5, + Water = 6, + Water2 = 7, + Earth = 8, + Earth2 = 9, + Wind = 10, + Wind2 = 11, + Ice = 12, + Ice2 = 13, + Lightning = 14, + Lightning2 = 15, + Light = 16, + Light2 = 17, + Dark = 18, + Dark2 = 19 +}; + +WeekDay = +{ + Firesday = 0, + Earthsday = 1, + Watersday = 2, + Windsday = 3, + Iceday = 4, + Lightningday = 5, + Lightsday = 6, + Darksday = 7 +}; \ No newline at end of file diff --git a/addons/libs/ffxi/recast.lua b/addons/libs/ffxi/recast.lua new file mode 100644 index 0000000..6af2ccf --- /dev/null +++ b/addons/libs/ffxi/recast.lua @@ -0,0 +1,110 @@ +--[[ +* Ashita - Copyright (c) 2014 - 2016 atom0s [atom0s@live.com] +* +* This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. +* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to +* Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. +* +* By using Ashita, you agree to the above license and its terms. +* +* Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were +* made. You must do so in any reasonable manner, but not in any way that suggests the licensor +* endorses you or your use. +* +* Non-Commercial - You may not use the material (Ashita) for commercial purposes. +* +* No-Derivatives - If you remix, transform, or build upon the material (Ashita), you may not distribute the +* modified material. You are, however, allowed to submit the modified works back to the original +* Ashita project in attempt to have it added to the original project. +* +* You may not apply legal terms or technological measures that legally restrict others +* from doing anything the license permits. +* +* No warranties are given. +]]-- + +ashita = ashita or { }; +ashita.ffxi = ashita.ffxi or { }; +ashita.ffxi.recast = ashita.ffxi.recast or { }; + +-- Scan for patterns.. +ashita.ffxi.recast.a_recast_ptr = ashita.memory.findpattern('FFXiMain.dll', 0, '894124E9????????8B46??6A006A00508BCEE8', 0x19, 0); +ashita.ffxi.recast.s_recast_ptr = ashita.memory.findpattern('FFXiMain.dll', 0, '56BE????????66833E00??????????????662906', 0x02, 0); + +-- Signature validation.. +if (ashita.ffxi.recast.a_recast_ptr == 0 or ashita.ffxi.recast.s_recast_ptr == 0) then + print(string.format('A Pointer: %08X', ashita.ffxi.recast.a_recast_ptr)); + print(string.format('S Pointer: %08X', ashita.ffxi.recast.s_recast_ptr)); + error('recast.lua -- signature validation failed!'); +end + +ashita.ffxi.recast.a_recast_ptr = ashita.memory.read_uint32(ashita.ffxi.recast.a_recast_ptr); +ashita.ffxi.recast.s_recast_ptr = ashita.memory.read_uint32(ashita.ffxi.recast.s_recast_ptr); + +---------------------------------------------------------------------------------------------------- +-- func: get_ability_ids +-- desc: Returns a table containing the current ability recast ids. +---------------------------------------------------------------------------------------------------- +local function get_ability_ids() + local t = { }; + for x = 0, 31 do + t[x + 1] = ashita.memory.read_uint8(ashita.ffxi.recast.a_recast_ptr + (x * 8) + 3); + end + return t; +end +ashita.ffxi.recast.get_ability_ids = get_ability_ids; + +---------------------------------------------------------------------------------------------------- +-- func: get_ability_id_from_index +-- desc: Returns the ability id at the given index in the recast id list. +---------------------------------------------------------------------------------------------------- +local function get_ability_id_from_index(index) + return ashita.memory.read_uint8(ashita.ffxi.recast.a_recast_ptr + (index * 8) + 3); +end +ashita.ffxi.recast.get_ability_id_from_index = get_ability_id_from_index; + +---------------------------------------------------------------------------------------------------- +-- func: get_ability_recast_by_index +-- desc: Returns the raw recast timer for the ability at the given index. +---------------------------------------------------------------------------------------------------- +local function get_ability_recast_by_index(index) + return ashita.memory.read_uint32(ashita.ffxi.recast.a_recast_ptr + (index * 4) + 0xF8); +end +ashita.ffxi.recast.get_ability_recast_by_index = get_ability_recast_by_index; + +---------------------------------------------------------------------------------------------------- +-- func: get_ability_recast_by_id +-- desc: Returns the raw recast timer for the ability of the given id. +---------------------------------------------------------------------------------------------------- +local function get_ability_recast_by_id(id) + local ids = get_ability_ids(); + for k, v in pairs(ids) do + if (v == id) then + return get_ability_recast_by_index(k - 1); + end + end + return -1; +end +ashita.ffxi.recast.get_ability_recast_by_id = get_ability_recast_by_id; + +---------------------------------------------------------------------------------------------------- +-- func: get_spell_recast_by_index +-- desc: Returns the raw recast timer for the spell at the given index. +---------------------------------------------------------------------------------------------------- +local function get_spell_recast_by_index(index) + return ashita.memory.read_uint16(ashita.ffxi.recast.s_recast_ptr + (index * 2)); +end +ashita.ffxi.recast.get_spell_recast_by_index = get_spell_recast_by_index; + +---------------------------------------------------------------------------------------------------- +-- func: format_timestamp +-- desc: Formats a recast timer into a hh:mm:ss timestamp. +---------------------------------------------------------------------------------------------------- +local function format_timestamp(timer) + local t = timer / 60; + local h = math.floor(t / (60 * 60)); + local m = math.floor(t / 60 - h * 60); + local s = math.floor(t - (m + h * 60) * 60); + return string.format('%02i:%02i:%02i', h, m, s); +end +ashita.ffxi.recast.format_timestamp = format_timestamp; \ No newline at end of file diff --git a/addons/libs/ffxi/targets.lua b/addons/libs/ffxi/targets.lua new file mode 100644 index 0000000..eb2298b --- /dev/null +++ b/addons/libs/ffxi/targets.lua @@ -0,0 +1,308 @@ +--[[ +* Ashita - Copyright (c) 2014 - 2016 atom0s [atom0s@live.com] +* +* This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. +* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to +* Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. +* +* By using Ashita, you agree to the above license and its terms. +* +* Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were +* made. You must do so in any reasonable manner, but not in any way that suggests the licensor +* endorses you or your use. +* +* Non-Commercial - You may not use the material (Ashita) for commercial purposes. +* +* No-Derivatives - If you remix, transform, or build upon the material (Ashita), you may not distribute the +* modified material. You are, however, allowed to submit the modified works back to the original +* Ashita project in attempt to have it added to the original project. +* +* You may not apply legal terms or technological measures that legally restrict others +* from doing anything the license permits. +* +* No warranties are given. +]]-- + +ashita = ashita or { }; +ashita.ffxi = ashita.ffxi or { }; +ashita.ffxi.targets = ashita.ffxi.targets or { }; + +-- Scan for patterns.. +ashita.ffxi.targets.pointers = { }; +ashita.ffxi.targets.pointers = +{ + ['lastst'] = + { + pointer = ashita.memory.findpattern('FFXiMain.dll', 0, 'A1????????BF????????8B??B9', 0, 0), + offset1 = 0x01 + }, + ['scan'] = + { + pointer = ashita.memory.findpattern('FFXiMain.dll', 0, 'A1????????85C074??F7', 0, 0), + offset1 = 0x01, + offset2 = 0x350E4 + }, + ['r'] = + { + pointer = ashita.memory.findpattern('FFXiMain.dll', 0, '8B0D????????85C9750333C0C36A00', 0, 0), + offset1 = 0x02, + offset2 = 0x011E + } +}; + +---------------------------------------------------------------------------------------------------- +-- func: get_target_lastst +-- desc: Returns the target. +---------------------------------------------------------------------------------------------------- +local function get_target_lastst() + local lastst = ashita.ffxi.targets.pointers.lastst; + + -- Ensure the pointer is valid.. + if (lastst.pointer == 0) then + return nil; + end + + -- Read the target pointer.. + local pointer = ashita.memory.read_uint32(lastst.pointer + lastst.offset1); + if (pointer == 0) then return nil; end + + -- Read the pointer.. + pointer = ashita.memory.read_uint32(pointer); + if (pointer == 0) then return nil; end + + -- Read the entity index.. + return ashita.memory.read_uint32(pointer + 0xB8); +end +ashita.ffxi.targets.get_target_lastst = get_target_lastst; + +---------------------------------------------------------------------------------------------------- +-- func: get_target_bt +-- desc: Returns the target. +---------------------------------------------------------------------------------------------------- +local function get_target_bt() + -- Obtain the local player.. + local player = GetPlayerEntity(); + if (player == nil) then + return nil; + end + + -- Obtain the players server id.. + local serverId = player.ServerId; + for x = 0, 2303 do + local entity = GetEntity(x); + if (entity ~= nil and entity.WarpPointer ~= 0 and entity.ClaimServerId == serverId) then + if (entity.StatusServer ~= 2 and entity.StatusServer ~= 3) then + return entity; + end + end + end + return nil; +end +ashita.ffxi.targets.get_target_bt = get_target_bt; + +---------------------------------------------------------------------------------------------------- +-- func: get_target_bt +-- desc: Returns the target. +---------------------------------------------------------------------------------------------------- +local function get_target_pet() + -- Obtain the local player.. + local player = GetPlayerEntity(); + if (player == nil) then + return nil; + end + + -- Ensure the players pet index is valid.. + if (player.PetTargetIndex == 0) then + return nil; + end + + -- Return the pet entity.. + return GetEntity(player.PetTargetIndex); +end +ashita.ffxi.targets.get_target_pet = get_target_pet; + +---------------------------------------------------------------------------------------------------- +-- func: get_target_scan +-- desc: Returns the target. +---------------------------------------------------------------------------------------------------- +local function get_target_scan() + local scan = ashita.ffxi.targets.pointers.scan; + + -- Ensure the pointer is valid.. + if (scan.pointer == 0) then + return nil; + end + + -- Read the pointer.. + local pointer = ashita.memory.read_uint32(scan.pointer + scan.offset1); + if (pointer == 0) then return nil; end + + -- Read the pointer.. + pointer = ashita.memory.read_uint32(pointer); + if (pointer == 0) then return nil; end + + -- Validate the scan target is set.. + pointer = pointer + scan.offset2; + if (ashita.memory.read_uint32(pointer) ~= 1) then + return nil; + end + + -- Read the entity index.. + return GetEntity(bit.band(ashita.memory.read_uint32(pointer + 0x10), 0xFFFF)); +end +ashita.ffxi.targets.get_target_scan = get_target_scan; + +---------------------------------------------------------------------------------------------------- +-- func: get_target_ht +-- desc: Returns the target. +---------------------------------------------------------------------------------------------------- +local function get_target_ht() + local e = nil; + + for x = 0, 2303 do + local entity = GetEntity(x); + if (entity ~= nil and entity.WarpPointer ~= 0 and entity.StatusServer ~= 2 and entity.StatusServer ~= 3) then + if (bit.band(bit.rshift(entity.Render.Flags0, 5), 1) == 1) then + if (bit.band(bit.rshift(entity.Render.Flags1, 16), 1) == 1) then + if (e == nil) then + e = entity; + else + if (math.sqrt(entity.Distance) < math.sqrt(e.Distance)) then + e = entity; + end + end + end + end + end + end + + return e; +end +ashita.ffxi.targets.get_target_ht = get_target_ht; + +---------------------------------------------------------------------------------------------------- +-- func: get_target_ft +-- desc: Returns the target. +---------------------------------------------------------------------------------------------------- +local function get_target_ft() + -- Validate the entity map pointer.. + local entity_map = AshitaCore:GetPointerManager():GetPointer('entitymap'); + if (entity_map == 0) then + return nil; + end + + -- Locate the player pointer.. + local player = ashita.memory.read_uint32(ashita.memory.read_uint32(entity_map) + (4 * GetPlayerEntity().TargetIndex)); + + -- Read the follower index.. + local follower = ashita.memory.read_uint16(player + 0x264); + if (follower == 0) then + return nil; + end + + -- Return the follower entity.. + return GetEntity(follower); +end +ashita.ffxi.targets.get_target_ft = get_target_ft; + +---------------------------------------------------------------------------------------------------- +-- func: get_target_normal +-- desc: Returns the target. +---------------------------------------------------------------------------------------------------- +local function get_target_normal() + return GetEntity(AshitaCore:GetDataManager():GetTarget():GetTargetIndex()); +end +ashita.ffxi.targets.get_target_normal = get_target_normal; + +---------------------------------------------------------------------------------------------------- +-- func: get_target_r +-- desc: Returns the target. +---------------------------------------------------------------------------------------------------- +local function get_target_r() + local rtarget = ashita.ffxi.targets.pointers.r; + + -- Ensure the pointer is valid.. + if (rtarget.pointer == 0) then + return nil; + end + + -- Read the pointer.. + local pointer = ashita.memory.read_uint32(rtarget.pointer + rtarget.offset1); + if (pointer == 0) then print('no1');return nil; end + + -- Read the pointer.. + pointer = ashita.memory.read_uint32(pointer); + if (pointer == 0) then print('no2');return nil; end + + -- Offset the pointer.. + pointer = pointer + rtarget.offset2; + + -- Read the response target name.. + local name = ashita.memory.read_string(pointer, 30); + if (name == nil or #name == 0) then + return nil; + end + + -- Locate the matching entity.. + for x = 0, 2303 do + local ent = GetEntity(x); + if (ent ~= nil and ent.Name == name) then + return ent; + end + end + return nil; +end +ashita.ffxi.targets.get_target_r = get_target_r; + +---------------------------------------------------------------------------------------------------- +-- func: get_target +-- desc: Returns the desired target by its short-hand tag. +---------------------------------------------------------------------------------------------------- +local function get_target(name) + -- Convert to lower name.. + name = string.lower(name); + + -- Return the desired target.. + return switch(name) : caseof + { + ['lastst'] = function() return get_target_lastst(); end, + ['bt'] = function() return get_target_bt(); end, + ['me'] = function() return GetPlayerEntity(); end, + ['pet'] = function() return get_target_pet(); end, + ['scan'] = function() return get_target_scan(); end, + ['ht'] = function() return get_target_ht(); end, + ['ft'] = function() return get_target_ft(); end, + ['t'] = function() return get_target_normal(); end, + ['r'] = function() return get_target_r(); end, + ['default'] = function() return nil; end, + }; +end +ashita.ffxi.targets.get_target = get_target; + +---------------------------------------------------------------------------------------------------- +-- func: get_last_teller_name +-- desc: Returns the name of the last person to send the player a whisper. +---------------------------------------------------------------------------------------------------- +local function get_last_teller_name() + local rtarget = ashita.ffxi.targets.pointers.r; + + -- Ensure the pointer is valid.. + if (rtarget.pointer == 0) then + return nil; + end + + -- Read the pointer.. + local pointer = ashita.memory.read_uint32(rtarget.pointer + rtarget.offset1); + if (pointer == 0) then print('no1');return nil; end + + -- Read the pointer.. + pointer = ashita.memory.read_uint32(pointer); + if (pointer == 0) then print('no2');return nil; end + + -- Offset the pointer.. + pointer = pointer + rtarget.offset2; + + -- Read the response target name.. + return ashita.memory.read_string(pointer, 30); +end +ashita.ffxi.targets.get_last_teller_name = get_last_teller_name; \ No newline at end of file diff --git a/addons/libs/ffxi/vanatime.lua b/addons/libs/ffxi/vanatime.lua new file mode 100644 index 0000000..e7871bd --- /dev/null +++ b/addons/libs/ffxi/vanatime.lua @@ -0,0 +1,148 @@ +--[[ +* Ashita - Copyright (c) 2014 - 2016 atom0s [atom0s@live.com] +* +* This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. +* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to +* Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. +* +* By using Ashita, you agree to the above license and its terms. +* +* Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were +* made. You must do so in any reasonable manner, but not in any way that suggests the licensor +* endorses you or your use. +* +* Non-Commercial - You may not use the material (Ashita) for commercial purposes. +* +* No-Derivatives - If you remix, transform, or build upon the material (Ashita), you may not distribute the +* modified material. You are, however, allowed to submit the modified works back to the original +* Ashita project in attempt to have it added to the original project. +* +* You may not apply legal terms or technological measures that legally restrict others +* from doing anything the license permits. +* +* No warranties are given. +]]-- + +ashita = ashita or { }; +ashita.ffxi = ashita.ffxi or { }; +ashita.ffxi.vanatime = ashita.ffxi.vanatime or { }; + +-- Scan for patterns.. +ashita.ffxi.vanatime.pointer = ashita.memory.findpattern('FFXiMain.dll', 0, 'B0015EC390518B4C24088D4424005068', 0x34, 0); + +-- Signature validation.. +if (ashita.ffxi.vanatime.pointer == 0) then + error('vanatime.lua -- signature validation failed!'); +end + +---------------------------------------------------------------------------------------------------- +-- func: get_raw_timestamp +-- desc: Returns the current raw Vana'diel timestamp. +---------------------------------------------------------------------------------------------------- +local function get_raw_timestamp() + local pointer = ashita.memory.read_uint32(ashita.ffxi.vanatime.pointer); + return ashita.memory.read_uint32(pointer + 0x0C); +end +ashita.ffxi.vanatime.get_raw_timestamp = get_raw_timestamp; + +---------------------------------------------------------------------------------------------------- +-- func: get_timestamp +-- desc: Returns the current formatted Vana'diel timestamp. +---------------------------------------------------------------------------------------------------- +local function get_timestamp() + local timestamp = get_raw_timestamp(); + local ts = (timestamp + 92514960) * 25; + local h = (ts / 3600) % 24; + local m = (ts / 60) % 60; + local s = ((ts - (math.floor(ts / 60) * 60))); + + return string.format('%02i:%02i:%02i', h, m, s); +end +ashita.ffxi.vanatime.get_timestamp = get_timestamp; + +---------------------------------------------------------------------------------------------------- +-- func: get_current_time +-- desc: Returns a table with the hour, minutes, and seconds in Vana'diel time. +---------------------------------------------------------------------------------------------------- +local function get_current_time() + local timestamp = get_raw_timestamp(); + local ts = (timestamp + 92514960) * 25; + local h = (ts / 3600) % 24; + local m = (ts / 60) % 60; + local s = ((ts - (math.floor(ts / 60) * 60))); + + local vana = { }; + vana.h = h; + vana.m = m; + vana.s = s; + + return vana; +end +ashita.ffxi.vanatime.get_current_time = get_current_time; + +---------------------------------------------------------------------------------------------------- +-- func: get_current_hour +-- desc: Returns the current Vana'diel hour. +---------------------------------------------------------------------------------------------------- +local function get_current_hour() + local timestamp = get_raw_timestamp(); + local ts = (timestamp + 92514960) * 25; + return (ts / 3600) % 24; +end +ashita.ffxi.vanatime.get_current_hour = get_current_hour; + +---------------------------------------------------------------------------------------------------- +-- func: get_current_minute +-- desc: Returns the current Vana'diel minute. +---------------------------------------------------------------------------------------------------- +local function get_current_minute() + local timestamp = get_raw_timestamp(); + local ts = (timestamp + 92514960) * 25; + return (ts / 60) % 60; +end +ashita.ffxi.vanatime.get_current_minute = get_current_minute; + +---------------------------------------------------------------------------------------------------- +-- func: get_current_second +-- desc: Returns the current Vana'diel second. +---------------------------------------------------------------------------------------------------- +local function get_current_second() + local timestamp = get_raw_timestamp(); + local ts = (timestamp + 92514960) * 25; + return ((ts - (math.floor(ts / 60) * 60))); +end +ashita.ffxi.vanatime.get_current_second = get_current_second; + +---------------------------------------------------------------------------------------------------- +-- func: get_current_date +-- desc: Returns a table with the current Vana'diel date. +---------------------------------------------------------------------------------------------------- +local function get_current_date() + local timestamp = get_raw_timestamp(); + local ts = (timestamp + 92514960) * 25; + local day = math.floor(ts / 86400); + + -- Calculate the moon information.. + local mphase = (day + 26) % 84; + local mpercent = (((42 - mphase) * 100) / 42); + if (0 > mpercent) then + mpercent = math.abs(mpercent); + end + + -- Build the date information.. + local vanadate = { }; + vanadate.weekday = (day % 8); + vanadate.day = (day % 30) + 1; + vanadate.month = ((day % 360) / 30) + 1; + vanadate.year = (day / 360); + vanadate.moon_percent = math.floor(mpercent + 0.5); + + if (38 <= mphase) then + vanadate.moon_phase = math.floor((mphase - 38) / 7); + else + vanadate.moon_phase = math.floor((mphase + 46) / 7); + end + + return vanadate; +end +ashita.ffxi.vanatime.get_current_date = get_current_date; \ No newline at end of file diff --git a/addons/libs/ffxi/weather.lua b/addons/libs/ffxi/weather.lua new file mode 100644 index 0000000..0ab53c2 --- /dev/null +++ b/addons/libs/ffxi/weather.lua @@ -0,0 +1,46 @@ +--[[ +* Ashita - Copyright (c) 2014 - 2016 atom0s [atom0s@live.com] +* +* This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. +* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to +* Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. +* +* By using Ashita, you agree to the above license and its terms. +* +* Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were +* made. You must do so in any reasonable manner, but not in any way that suggests the licensor +* endorses you or your use. +* +* Non-Commercial - You may not use the material (Ashita) for commercial purposes. +* +* No-Derivatives - If you remix, transform, or build upon the material (Ashita), you may not distribute the +* modified material. You are, however, allowed to submit the modified works back to the original +* Ashita project in attempt to have it added to the original project. +* +* You may not apply legal terms or technological measures that legally restrict others +* from doing anything the license permits. +* +* No warranties are given. +]]-- + +ashita = ashita or { }; +ashita.ffxi = ashita.ffxi or { }; +ashita.ffxi.weather = ashita.ffxi.weather or { }; + +-- Scan for patterns.. +ashita.ffxi.weather.pointer = ashita.memory.findpattern('FFXiMain.dll', 0, '66A1????????663D????72', 0x02, 0); + +-- Signature validation.. +if (ashita.ffxi.weather.pointer == 0) then + error('weather.lua -- signature validation failed!'); +end + +---------------------------------------------------------------------------------------------------- +-- func: get_weather +-- desc: Returns the current weather id. +---------------------------------------------------------------------------------------------------- +local function get_weather() + local pointer = ashita.memory.read_uint32(ashita.ffxi.weather.pointer); + return ashita.memory.read_uint8(pointer); +end +ashita.ffxi.weather.get_weather = get_weather; \ No newline at end of file diff --git a/addons/libs/imguidef.lua b/addons/libs/imguidef.lua new file mode 100644 index 0000000..b2b7c50 --- /dev/null +++ b/addons/libs/imguidef.lua @@ -0,0 +1,304 @@ +--[[ +* Ashita - Copyright (c) 2014 - 2016 atom0s [atom0s@live.com] +* +* This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. +* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to +* Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. +* +* By using Ashita, you agree to the above license and its terms. +* +* Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were +* made. You must do so in any reasonable manner, but not in any way that suggests the licensor +* endorses you or your use. +* +* Non-Commercial - You may not use the material (Ashita) for commercial purposes. +* +* No-Derivatives - If you remix, transform, or build upon the material (Ashita), you may not distribute the +* modified material. You are, however, allowed to submit the modified works back to the original +* Ashita project in attempt to have it added to the original project. +* +* You may not apply legal terms or technological measures that legally restrict others +* from doing anything the license permits. +* +* No warranties are given. +]]-- + +ashita = ashita or { }; +ashita.gui = ashita.gui or { }; + +-- Helper To Reduce Nesting +imgui = ashita.gui; + +---------------------------------------------------------------------------------------------------- +-- Common Values Used By ImGui +---------------------------------------------------------------------------------------------------- + +FLT_MAX = 3.402823466e+38; + +---------------------------------------------------------------------------------------------------- +-- Flags for ImGui::Begin() +---------------------------------------------------------------------------------------------------- + +ImGuiWindowFlags_NoTitleBar = 1; +ImGuiWindowFlags_NoResize = 2; +ImGuiWindowFlags_NoMove = 4; +ImGuiWindowFlags_NoScrollbar = 8; +ImGuiWindowFlags_NoScrollWithMouse = 16; +ImGuiWindowFlags_NoCollapse = 32; +ImGuiWindowFlags_AlwaysAutoResize = 64; +ImGuiWindowFlags_ShowBorders = 128; +ImGuiWindowFlags_NoSavedSettings = 256; +ImGuiWindowFlags_NoInputs = 512; +ImGuiWindowFlags_MenuBar = 1024; +ImGuiWindowFlags_HorizontalScrollbar = 2048; +ImGuiWindowFlags_NoFocusOnAppearing = 4096; +ImGuiWindowFlags_NoBringToFrontOnFocus = 8192; +ImGuiWindowFlags_AlwaysVerticalScrollbar = 16384; +ImGuiWindowFlags_AlwaysHorizontalScrollbar = 32768; +ImGuiWindowFlags_AlwaysUseWindowPadding = 65536; +ImGuiWindowFlags_ChildWindow = 1048576; -- Internal use only! +ImGuiWindowFlags_ChildWindowAutoFitX = 2097152; -- Internal use only! +ImGuiWindowFlags_ChildWindowAutoFitY = 4194304; -- Internal use only! +ImGuiWindowFlags_ComboBox = 8388608; -- Internal use only! +ImGuiWindowFlags_Tooltip = 16777216; -- Internal use only! +ImGuiWindowFlags_Popup = 33554432; -- Internal use only! +ImGuiWindowFlags_Modal = 67108864; -- Internal use only! +ImGuiWindowFlags_ChildMenu = 134217728; -- Internal use only! + +---------------------------------------------------------------------------------------------------- +-- Flags for ImGui::InputText() +---------------------------------------------------------------------------------------------------- + +ImGuiInputTextFlags_CharsDecimal = 1; +ImGuiInputTextFlags_CharsHexadecimal = 2; +ImGuiInputTextFlags_CharsUppercase = 4; +ImGuiInputTextFlags_CharsNoBlank = 8; +ImGuiInputTextFlags_AutoSelectAll = 16; +ImGuiInputTextFlags_EnterReturnsTrue = 32; +ImGuiInputTextFlags_CallbackCompletion = 64; +ImGuiInputTextFlags_CallbackHistory = 128; +ImGuiInputTextFlags_CallbackAlways = 256; +ImGuiInputTextFlags_CallbackCharFilter = 512; +ImGuiInputTextFlags_AllowTabInput = 1024; +ImGuiInputTextFlags_CtrlEnterForNewLine = 2048; +ImGuiInputTextFlags_NoHorizontalScroll = 4096; +ImGuiInputTextFlags_AlwaysInsertMode = 8192; +ImGuiInputTextFlags_ReadOnly = 16384; +ImGuiInputTextFlags_Password = 32768; +ImGuiInputTextFlags_Multiline = 1048576; -- Internal use only! + +---------------------------------------------------------------------------------------------------- +-- Flags for ImGui::TreeNodeEx(), ImGui::CollapsingHeader*() +---------------------------------------------------------------------------------------------------- + +ImGuiTreeNodeFlags_Selected = 1; +ImGuiTreeNodeFlags_Framed = 2; +ImGuiTreeNodeFlags_AllowOverlapMode = 4; +ImGuiTreeNodeFlags_NoTreePushOnOpen = 8; +ImGuiTreeNodeFlags_NoAutoOpenOnLog = 16; +ImGuiTreeNodeFlags_DefaultOpen = 32; +ImGuiTreeNodeFlags_OpenOnDoubleClick = 64; +ImGuiTreeNodeFlags_OpenOnArrow = 128; +ImGuiTreeNodeFlags_Leaf = 256; +ImGuiTreeNodeFlags_Bullet = 512; + +ImGuiTreeNodeFlags_CollapsingHeader = 18; -- ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_NoAutoOpenOnLog + +---------------------------------------------------------------------------------------------------- +-- Flags for ImGui::Selectable() +---------------------------------------------------------------------------------------------------- + +ImGuiSelectableFlags_DontClosePopups = 1; +ImGuiSelectableFlags_SpanAllColumns = 2; +ImGuiSelectableFlags_AllowDoubleClick = 4; + +---------------------------------------------------------------------------------------------------- +-- User fill ImGuiIO.KeyMap[] array with indices into the ImGuiIO.KeysDown[512] array +---------------------------------------------------------------------------------------------------- + +ImGuiKey_Tab = 0; +ImGuiKey_LeftArrow = 1; +ImGuiKey_RightArrow = 2; +ImGuiKey_UpArrow = 3; +ImGuiKey_DownArrow = 4; +ImGuiKey_PageUp = 5; +ImGuiKey_PageDown = 6; +ImGuiKey_Home = 7; +ImGuiKey_End = 8; +ImGuiKey_Delete = 9; +ImGuiKey_Backspace = 10; +ImGuiKey_Enter = 11; +ImGuiKey_Escape = 12; +ImGuiKey_A = 13; +ImGuiKey_C = 14; +ImGuiKey_V = 15; +ImGuiKey_X = 16; +ImGuiKey_Y = 17; +ImGuiKey_Z = 18; + +---------------------------------------------------------------------------------------------------- +-- Enumeration for PushStyleColor() / PopStyleColor() +---------------------------------------------------------------------------------------------------- + +ImGuiCol_Text = 0; +ImGuiCol_TextDisabled = 1; +ImGuiCol_WindowBg = 2; +ImGuiCol_ChildWindowBg = 3; +ImGuiCol_PopupBg = 4; +ImGuiCol_Border = 5; +ImGuiCol_BorderShadow = 6; +ImGuiCol_FrameBg = 7; +ImGuiCol_FrameBgHovered = 8; +ImGuiCol_FrameBgActive = 9; +ImGuiCol_TitleBg = 10; +ImGuiCol_TitleBgCollapsed = 11; +ImGuiCol_TitleBgActive = 12; +ImGuiCol_MenuBarBg = 13; +ImGuiCol_ScrollbarBg = 14; +ImGuiCol_ScrollbarGrab = 15; +ImGuiCol_ScrollbarGrabHovered = 16; +ImGuiCol_ScrollbarGrabActive = 17; +ImGuiCol_ComboBg = 18; +ImGuiCol_CheckMark = 19; +ImGuiCol_SliderGrab = 20; +ImGuiCol_SliderGrabActive = 21; +ImGuiCol_Button = 22; +ImGuiCol_ButtonHovered = 23; +ImGuiCol_ButtonActive = 24; +ImGuiCol_Header = 25; +ImGuiCol_HeaderHovered = 26; +ImGuiCol_HeaderActive = 27; +ImGuiCol_Column = 28; +ImGuiCol_ColumnHovered = 29; +ImGuiCol_ColumnActive = 30; +ImGuiCol_ResizeGrip = 31; +ImGuiCol_ResizeGripHovered = 32; +ImGuiCol_ResizeGripActive = 33; +ImGuiCol_CloseButton = 34; +ImGuiCol_CloseButtonHovered = 35; +ImGuiCol_CloseButtonActive = 36; +ImGuiCol_PlotLines = 37; +ImGuiCol_PlotLinesHovered = 38; +ImGuiCol_PlotHistogram = 39; +ImGuiCol_PlotHistogramHovered = 40; +ImGuiCol_TextSelectedBg = 41; +ImGuiCol_ModalWindowDarkening = 42; + +---------------------------------------------------------------------------------------------------- +-- Enumeration for PushStyleVar() / PopStyleVar() +-- NB: the enum only refers to fields of ImGuiStyle() which makes sense to be pushed/poped in UI code. Feel free to add others. +---------------------------------------------------------------------------------------------------- + +ImGuiStyleVar_Alpha = 0; +ImGuiStyleVar_WindowPadding = 1; +ImGuiStyleVar_WindowRounding = 2; +ImGuiStyleVar_WindowMinSize = 3; +ImGuiStyleVar_ChildWindowRounding = 4; +ImGuiStyleVar_FramePadding = 5; +ImGuiStyleVar_FrameRounding = 6; +ImGuiStyleVar_ItemSpacing = 7; +ImGuiStyleVar_ItemInnerSpacing = 8; +ImGuiStyleVar_IndentSpacing = 9; +ImGuiStyleVar_GrabMinSize = 10; + +---------------------------------------------------------------------------------------------------- +-- ImGuiAlign_ +---------------------------------------------------------------------------------------------------- + +ImGuiAlign_Left = 1; +ImGuiAlign_Center = 2; +ImGuiAlign_Right = 4; +ImGuiAlign_Top = 8; +ImGuiAlign_VCenter = 16; +ImGuiAlign_Default = 9; -- ImGuiAlign_Left | ImGuiAlign_Top + +---------------------------------------------------------------------------------------------------- +-- Enumeration for ColorEditMode() +---------------------------------------------------------------------------------------------------- + +ImGuiColorEditMode_UserSelect = -2; +ImGuiColorEditMode_UserSelectShowButton = -1; +ImGuiColorEditMode_RGB = 0; +ImGuiColorEditMode_HSV = 1; +ImGuiColorEditMode_HEX = 2; + +---------------------------------------------------------------------------------------------------- +-- Enumeration for GetMouseCursor() +---------------------------------------------------------------------------------------------------- + +ImGuiMouseCursor_Arrow = 0; +ImGuiMouseCursor_TextInput = 1; +ImGuiMouseCursor_Move = 2; +ImGuiMouseCursor_ResizeNS = 3; +ImGuiMouseCursor_ResizeEW = 4; +ImGuiMouseCursor_ResizeNESW = 5; +ImGuiMouseCursor_ResizeNWSE = 6; + +---------------------------------------------------------------------------------------------------- +-- Condition flags for ImGui::SetWindow***(), SetNextWindow***(), SetNextTreeNode***() functions +-- All those functions treat 0 as a shortcut to ImGuiSetCond_Always +---------------------------------------------------------------------------------------------------- + +ImGuiSetCond_Always = 1; +ImGuiSetCond_Once = 2; +ImGuiSetCond_FirstUseEver = 4; +ImGuiSetCond_Appearing = 8; + +---------------------------------------------------------------------------------------------------- +-- Custom Variable Creation Types +-- Used with imgui.CreateVar +---------------------------------------------------------------------------------------------------- + +ImGuiVar_UNDEF = 0; -- 0 bytes (null) +ImGuiVar_BOOLCPP = 1; -- 1 byte bool +ImGuiVar_BOOL8 = 2; -- 1 byte char +ImGuiVar_BOOL16 = 3; -- 2 bytes short +ImGuiVar_BOOL32 = 4; -- 4 bytes BOOL +ImGuiVar_CHAR = 5; -- 1 byte char +ImGuiVar_INT8 = 6; -- 1 byte char +ImGuiVar_UINT8 = 7; -- 1 byte unsigned char +ImGuiVar_INT16 = 8; -- 2 bytes short +ImGuiVar_UINT16 = 9; -- 2 bytes unsigned short +ImGuiVar_INT32 = 10; -- 4 bytes int +ImGuiVar_UINT32 = 11; -- 4 bytes unsigned int +ImGuiVar_FLOAT = 12; -- 4 bytes float +ImGuiVar_DOUBLE = 13; -- 8 bytes double +ImGuiVar_CDSTRING = 14; -- ? bytes char[] +ImGuiVar_BOOLARRAY = 15; -- ? bytes bool[] +ImGuiVar_INT16ARRAY = 16; -- ? bytes short[] +ImGuiVar_INT32ARRAY = 17; -- ? bytes int[] +ImGuiVar_FLOATARRAY = 18; -- ? bytes float[] +ImGuiVar_DOUBLEARRAY = 19; -- ? bytes double[] + +---------------------------------------------------------------------------------------------------- +-- func: stylecolor +-- desc: Sets a style color of the ImGui system. +---------------------------------------------------------------------------------------------------- +local function stylecolor(idx, color) + if (idx < 0 or idx > ImGuiCol_ModalWindowDarkening) then + error('Attempting to set an invalid style color!'); + return; + end + + local s = imgui.style.Colors; + s[idx] = color; + imgui.style.Colors = s; +end +ashita.gui.stylecolor = stylecolor; + +---------------------------------------------------------------------------------------------------- +-- func: bor +-- desc: Bitwise or operation handler to loop varargs of flags and bitwise or them together. +---------------------------------------------------------------------------------------------------- +local function imguibor(...) + -- Obtain the arguments in a loopable table.. + local args = { n = select('#', ...), ... }; + local ret = 0; + + for x = 1, args.n do + ret = bit.bor(ret, args[x]); + end + + return ret; +end +ashita.gui.bor = imguibor; \ No newline at end of file diff --git a/addons/libs/json/json.lua b/addons/libs/json/json.lua new file mode 100644 index 0000000..f7eb125 --- /dev/null +++ b/addons/libs/json/json.lua @@ -0,0 +1,1544 @@ +-- -*- coding: utf-8 -*- +-- +-- Simple JSON encoding and decoding in pure Lua. +-- +-- Copyright 2010-2016 Jeffrey Friedl +-- http://regex.info/blog/ +-- Latest version: http://regex.info/blog/lua/json +-- +-- This code is released under a Creative Commons CC-BY "Attribution" License: +-- http://creativecommons.org/licenses/by/3.0/deed.en_US +-- +-- It can be used for any purpose so long as: +-- 1) the copyright notice above is maintained +-- 2) the web-page links above are maintained +-- 3) the 'AUTHOR_NOTE' string below is maintained +-- +local VERSION = '20161103.20' -- version history at end of file +local AUTHOR_NOTE = "-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json) version 20161103.20 ]-" + +-- +-- The 'AUTHOR_NOTE' variable exists so that information about the source +-- of the package is maintained even in compiled versions. It's also +-- included in OBJDEF below mostly to quiet warnings about unused variables. +-- +local OBJDEF = { + VERSION = VERSION, + AUTHOR_NOTE = AUTHOR_NOTE, +} + + +-- +-- Simple JSON encoding and decoding in pure Lua. +-- JSON definition: http://www.json.org/ +-- +-- +-- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines +-- +-- local lua_value = JSON:decode(raw_json_text) +-- +-- local raw_json_text = JSON:encode(lua_table_or_value) +-- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability +-- +-- +-- +-- DECODING (from a JSON string to a Lua table) +-- +-- +-- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines +-- +-- local lua_value = JSON:decode(raw_json_text) +-- +-- If the JSON text is for an object or an array, e.g. +-- { "what": "books", "count": 3 } +-- or +-- [ "Larry", "Curly", "Moe" ] +-- +-- the result is a Lua table, e.g. +-- { what = "books", count = 3 } +-- or +-- { "Larry", "Curly", "Moe" } +-- +-- +-- The encode and decode routines accept an optional second argument, +-- "etc", which is not used during encoding or decoding, but upon error +-- is passed along to error handlers. It can be of any type (including nil). +-- +-- +-- +-- ERROR HANDLING +-- +-- With most errors during decoding, this code calls +-- +-- JSON:onDecodeError(message, text, location, etc) +-- +-- with a message about the error, and if known, the JSON text being +-- parsed and the byte count where the problem was discovered. You can +-- replace the default JSON:onDecodeError() with your own function. +-- +-- The default onDecodeError() merely augments the message with data +-- about the text and the location if known (and if a second 'etc' +-- argument had been provided to decode(), its value is tacked onto the +-- message as well), and then calls JSON.assert(), which itself defaults +-- to Lua's built-in assert(), and can also be overridden. +-- +-- For example, in an Adobe Lightroom plugin, you might use something like +-- +-- function JSON:onDecodeError(message, text, location, etc) +-- LrErrors.throwUserError("Internal Error: invalid JSON data") +-- end +-- +-- or even just +-- +-- function JSON.assert(message) +-- LrErrors.throwUserError("Internal Error: " .. message) +-- end +-- +-- If JSON:decode() is passed a nil, this is called instead: +-- +-- JSON:onDecodeOfNilError(message, nil, nil, etc) +-- +-- and if JSON:decode() is passed HTML instead of JSON, this is called: +-- +-- JSON:onDecodeOfHTMLError(message, text, nil, etc) +-- +-- The use of the fourth 'etc' argument allows stronger coordination +-- between decoding and error reporting, especially when you provide your +-- own error-handling routines. Continuing with the the Adobe Lightroom +-- plugin example: +-- +-- function JSON:onDecodeError(message, text, location, etc) +-- local note = "Internal Error: invalid JSON data" +-- if type(etc) = 'table' and etc.photo then +-- note = note .. " while processing for " .. etc.photo:getFormattedMetadata('fileName') +-- end +-- LrErrors.throwUserError(note) +-- end +-- +-- : +-- : +-- +-- for i, photo in ipairs(photosToProcess) do +-- : +-- : +-- local data = JSON:decode(someJsonText, { photo = photo }) +-- : +-- : +-- end +-- +-- +-- +-- If the JSON text passed to decode() has trailing garbage (e.g. as with the JSON "[123]xyzzy"), +-- the method +-- +-- JSON:onTrailingGarbage(json_text, location, parsed_value, etc) +-- +-- is invoked, where: +-- +-- json_text is the original JSON text being parsed, +-- location is the count of bytes into json_text where the garbage starts (6 in the example), +-- parsed_value is the Lua result of what was successfully parsed ({123} in the example), +-- etc is as above. +-- +-- If JSON:onTrailingGarbage() does not abort, it should return the value decode() should return, +-- or nil + an error message. +-- +-- local new_value, error_message = JSON:onTrailingGarbage() +-- +-- The default handler just invokes JSON:onDecodeError("trailing garbage"...), but you can have +-- this package ignore trailing garbage via +-- +-- function JSON:onTrailingGarbage(json_text, location, parsed_value, etc) +-- return parsed_value +-- end +-- +-- +-- DECODING AND STRICT TYPES +-- +-- Because both JSON objects and JSON arrays are converted to Lua tables, +-- it's not normally possible to tell which original JSON type a +-- particular Lua table was derived from, or guarantee decode-encode +-- round-trip equivalency. +-- +-- However, if you enable strictTypes, e.g. +-- +-- JSON = assert(loadfile "JSON.lua")() --load the routines +-- JSON.strictTypes = true +-- +-- then the Lua table resulting from the decoding of a JSON object or +-- JSON array is marked via Lua metatable, so that when re-encoded with +-- JSON:encode() it ends up as the appropriate JSON type. +-- +-- (This is not the default because other routines may not work well with +-- tables that have a metatable set, for example, Lightroom API calls.) +-- +-- +-- ENCODING (from a lua table to a JSON string) +-- +-- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines +-- +-- local raw_json_text = JSON:encode(lua_table_or_value) +-- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability +-- local custom_pretty = JSON:encode(lua_table_or_value, etc, { pretty = true, indent = "| ", align_keys = false }) +-- +-- On error during encoding, this code calls: +-- +-- JSON:onEncodeError(message, etc) +-- +-- which you can override in your local JSON object. +-- +-- The 'etc' in the error call is the second argument to encode() +-- and encode_pretty(), or nil if it wasn't provided. +-- +-- +-- ENCODING OPTIONS +-- +-- An optional third argument, a table of options, can be provided to encode(). +-- +-- encode_options = { +-- -- options for making "pretty" human-readable JSON (see "PRETTY-PRINTING" below) +-- pretty = true, +-- indent = " ", +-- align_keys = false, +-- +-- -- other output-related options +-- null = "\0", -- see "ENCODING JSON NULL VALUES" below +-- stringsAreUtf8 = false, -- see "HANDLING UNICODE LINE AND PARAGRAPH SEPARATORS FOR JAVA" below +-- } +-- +-- json_string = JSON:encode(mytable, etc, encode_options) +-- +-- +-- +-- For reference, the defaults are: +-- +-- pretty = false +-- null = nil, +-- stringsAreUtf8 = false, +-- +-- +-- +-- PRETTY-PRINTING +-- +-- Enabling the 'pretty' encode option helps generate human-readable JSON. +-- +-- pretty = JSON:encode(val, etc, { +-- pretty = true, +-- indent = " ", +-- align_keys = false, +-- }) +-- +-- encode_pretty() is also provided: it's identical to encode() except +-- that encode_pretty() provides a default options table if none given in the call: +-- +-- { pretty = true, align_keys = false, indent = " " } +-- +-- For example, if +-- +-- JSON:encode(data) +-- +-- produces: +-- +-- {"city":"Kyoto","climate":{"avg_temp":16,"humidity":"high","snowfall":"minimal"},"country":"Japan","wards":11} +-- +-- then +-- +-- JSON:encode_pretty(data) +-- +-- produces: +-- +-- { +-- "city": "Kyoto", +-- "climate": { +-- "avg_temp": 16, +-- "humidity": "high", +-- "snowfall": "minimal" +-- }, +-- "country": "Japan", +-- "wards": 11 +-- } +-- +-- The following three lines return identical results: +-- JSON:encode_pretty(data) +-- JSON:encode_pretty(data, nil, { pretty = true, align_keys = false, indent = " " }) +-- JSON:encode (data, nil, { pretty = true, align_keys = false, indent = " " }) +-- +-- An example of setting your own indent string: +-- +-- JSON:encode_pretty(data, nil, { pretty = true, indent = "| " }) +-- +-- produces: +-- +-- { +-- | "city": "Kyoto", +-- | "climate": { +-- | | "avg_temp": 16, +-- | | "humidity": "high", +-- | | "snowfall": "minimal" +-- | }, +-- | "country": "Japan", +-- | "wards": 11 +-- } +-- +-- An example of setting align_keys to true: +-- +-- JSON:encode_pretty(data, nil, { pretty = true, indent = " ", align_keys = true }) +-- +-- produces: +-- +-- { +-- "city": "Kyoto", +-- "climate": { +-- "avg_temp": 16, +-- "humidity": "high", +-- "snowfall": "minimal" +-- }, +-- "country": "Japan", +-- "wards": 11 +-- } +-- +-- which I must admit is kinda ugly, sorry. This was the default for +-- encode_pretty() prior to version 20141223.14. +-- +-- +-- HANDLING UNICODE LINE AND PARAGRAPH SEPARATORS FOR JAVA +-- +-- If the 'stringsAreUtf8' encode option is set to true, consider Lua strings not as a sequence of bytes, +-- but as a sequence of UTF-8 characters. +-- +-- Currently, the only practical effect of setting this option is that Unicode LINE and PARAGRAPH +-- separators, if found in a string, are encoded with a JSON escape instead of being dumped as is. +-- The JSON is valid either way, but encoding this way, apparently, allows the resulting JSON +-- to also be valid Java. +-- +-- AMBIGUOUS SITUATIONS DURING THE ENCODING +-- +-- During the encode, if a Lua table being encoded contains both string +-- and numeric keys, it fits neither JSON's idea of an object, nor its +-- idea of an array. To get around this, when any string key exists (or +-- when non-positive numeric keys exist), numeric keys are converted to +-- strings. +-- +-- For example, +-- JSON:encode({ "one", "two", "three", SOMESTRING = "some string" })) +-- produces the JSON object +-- {"1":"one","2":"two","3":"three","SOMESTRING":"some string"} +-- +-- To prohibit this conversion and instead make it an error condition, set +-- JSON.noKeyConversion = true +-- +-- +-- ENCODING JSON NULL VALUES +-- +-- Lua tables completely omit keys whose value is nil, so without special handling there's +-- no way to get a field in a JSON object with a null value. For example +-- JSON:encode({ username = "admin", password = nil }) +-- produces +-- {"username":"admin"} +-- +-- In order to actually produce +-- {"username":"admin", "password":null} +-- one can include a string value for a "null" field in the options table passed to encode().... +-- any Lua table entry with that value becomes null in the JSON output: +-- JSON:encode({ username = "admin", password = "xyzzy" }, nil, { null = "xyzzy" }) +-- produces +-- {"username":"admin", "password":null} +-- +-- Just be sure to use a string that is otherwise unlikely to appear in your data. +-- The string "\0" (a string with one null byte) may well be appropriate for many applications. +-- +-- The "null" options also applies to Lua tables that become JSON arrays. +-- JSON:encode({ "one", "two", nil, nil }) +-- produces +-- ["one","two"] +-- while +-- NULL = "\0" +-- JSON:encode({ "one", "two", NULL, NULL}, nil, { null = NULL }) +-- produces +-- ["one","two",null,null] +-- +-- +-- +-- +-- HANDLING LARGE AND/OR PRECISE NUMBERS +-- +-- +-- Without special handling, numbers in JSON can lose precision in Lua. +-- For example: +-- +-- T = JSON:decode('{ "small":12345, "big":12345678901234567890123456789, "precise":9876.67890123456789012345 }') +-- +-- print("small: ", type(T.small), T.small) +-- print("big: ", type(T.big), T.big) +-- print("precise: ", type(T.precise), T.precise) +-- +-- produces +-- +-- small: number 12345 +-- big: number 1.2345678901235e+28 +-- precise: number 9876.6789012346 +-- +-- Precision is lost with both 'big' and 'precise'. +-- +-- This package offers ways to try to handle this better (for some definitions of "better")... +-- +-- The most precise method is by setting the global: +-- +-- JSON.decodeNumbersAsObjects = true +-- +-- When this is set, numeric JSON data is encoded into Lua in a form that preserves the exact +-- JSON numeric presentation when re-encoded back out to JSON, or accessed in Lua as a string. +-- +-- (This is done by encoding the numeric data with a Lua table/metatable that returns +-- the possibly-imprecise numeric form when accessed numerically, but the original precise +-- representation when accessed as a string. You can also explicitly access +-- via JSON:forceString() and JSON:forceNumber()) +-- +-- Consider the example above, with this option turned on: +-- +-- JSON.decodeNumbersAsObjects = true +-- +-- T = JSON:decode('{ "small":12345, "big":12345678901234567890123456789, "precise":9876.67890123456789012345 }') +-- +-- print("small: ", type(T.small), T.small) +-- print("big: ", type(T.big), T.big) +-- print("precise: ", type(T.precise), T.precise) +-- +-- This now produces: +-- +-- small: table 12345 +-- big: table 12345678901234567890123456789 +-- precise: table 9876.67890123456789012345 +-- +-- However, within Lua you can still use the values (e.g. T.precise in the example above) in numeric +-- contexts. In such cases you'll get the possibly-imprecise numeric version, but in string contexts +-- and when the data finds its way to this package's encode() function, the original full-precision +-- representation is used. +-- +-- Even without using the JSON.decodeNumbersAsObjects option, you can encode numbers +-- in your Lua table that retain high precision upon encoding to JSON, by using the JSON:asNumber() +-- function: +-- +-- T = { +-- imprecise = 123456789123456789.123456789123456789, +-- precise = JSON:asNumber("123456789123456789.123456789123456789") +-- } +-- +-- print(JSON:encode_pretty(T)) +-- +-- This produces: +-- +-- { +-- "precise": 123456789123456789.123456789123456789, +-- "imprecise": 1.2345678912346e+17 +-- } +-- +-- +-- +-- A different way to handle big/precise JSON numbers is to have decode() merely return +-- the exact string representation of the number instead of the number itself. +-- This approach might be useful when the numbers are merely some kind of opaque +-- object identifier and you want to work with them in Lua as strings anyway. +-- +-- This approach is enabled by setting +-- +-- JSON.decodeIntegerStringificationLength = 10 +-- +-- The value is the number of digits (of the integer part of the number) at which to stringify numbers. +-- +-- Consider our previous example with this option set to 10: +-- +-- JSON.decodeIntegerStringificationLength = 10 +-- +-- T = JSON:decode('{ "small":12345, "big":12345678901234567890123456789, "precise":9876.67890123456789012345 }') +-- +-- print("small: ", type(T.small), T.small) +-- print("big: ", type(T.big), T.big) +-- print("precise: ", type(T.precise), T.precise) +-- +-- This produces: +-- +-- small: number 12345 +-- big: string 12345678901234567890123456789 +-- precise: number 9876.6789012346 +-- +-- The long integer of the 'big' field is at least JSON.decodeIntegerStringificationLength digits +-- in length, so it's converted not to a Lua integer but to a Lua string. Using a value of 0 or 1 ensures +-- that all JSON numeric data becomes strings in Lua. +-- +-- Note that unlike +-- JSON.decodeNumbersAsObjects = true +-- this stringification is simple and unintelligent: the JSON number simply becomes a Lua string, and that's the end of it. +-- If the string is then converted back to JSON, it's still a string. After running the code above, adding +-- print(JSON:encode(T)) +-- produces +-- {"big":"12345678901234567890123456789","precise":9876.6789012346,"small":12345} +-- which is unlikely to be desired. +-- +-- There's a comparable option for the length of the decimal part of a number: +-- +-- JSON.decodeDecimalStringificationLength +-- +-- This can be used alone or in conjunction with +-- +-- JSON.decodeIntegerStringificationLength +-- +-- to trip stringification on precise numbers with at least JSON.decodeIntegerStringificationLength digits after +-- the decimal point. +-- +-- This example: +-- +-- JSON.decodeIntegerStringificationLength = 10 +-- JSON.decodeDecimalStringificationLength = 5 +-- +-- T = JSON:decode('{ "small":12345, "big":12345678901234567890123456789, "precise":9876.67890123456789012345 }') +-- +-- print("small: ", type(T.small), T.small) +-- print("big: ", type(T.big), T.big) +-- print("precise: ", type(T.precise), T.precise) +-- +-- produces: +-- +-- small: number 12345 +-- big: string 12345678901234567890123456789 +-- precise: string 9876.67890123456789012345 +-- +-- +-- +-- +-- +-- SUMMARY OF METHODS YOU CAN OVERRIDE IN YOUR LOCAL LUA JSON OBJECT +-- +-- assert +-- onDecodeError +-- onDecodeOfNilError +-- onDecodeOfHTMLError +-- onTrailingGarbage +-- onEncodeError +-- +-- If you want to create a separate Lua JSON object with its own error handlers, +-- you can reload JSON.lua or use the :new() method. +-- +--------------------------------------------------------------------------- + +local default_pretty_indent = " " +local default_pretty_options = { pretty = true, align_keys = false, indent = default_pretty_indent } + +local isArray = { __tostring = function() return "JSON array" end } isArray.__index = isArray +local isObject = { __tostring = function() return "JSON object" end } isObject.__index = isObject + +function OBJDEF:newArray(tbl) + return setmetatable(tbl or {}, isArray) +end + +function OBJDEF:newObject(tbl) + return setmetatable(tbl or {}, isObject) +end + + + + +local function getnum(op) + return type(op) == 'number' and op or op.N +end + +local isNumber = { + __tostring = function(T) return T.S end, + __unm = function(op) return getnum(op) end, + + __concat = function(op1, op2) return tostring(op1) .. tostring(op2) end, + __add = function(op1, op2) return getnum(op1) + getnum(op2) end, + __sub = function(op1, op2) return getnum(op1) - getnum(op2) end, + __mul = function(op1, op2) return getnum(op1) * getnum(op2) end, + __div = function(op1, op2) return getnum(op1) / getnum(op2) end, + __mod = function(op1, op2) return getnum(op1) % getnum(op2) end, + __pow = function(op1, op2) return getnum(op1) ^ getnum(op2) end, + __lt = function(op1, op2) return getnum(op1) < getnum(op2) end, + __eq = function(op1, op2) return getnum(op1) == getnum(op2) end, + __le = function(op1, op2) return getnum(op1) <= getnum(op2) end, +} +isNumber.__index = isNumber + +function OBJDEF:asNumber(item) + + if getmetatable(item) == isNumber then + -- it's already a JSON number object. + return item + elseif type(item) == 'table' and type(item.S) == 'string' and type(item.N) == 'number' then + -- it's a number-object table that lost its metatable, so give it one + return setmetatable(item, isNumber) + else + -- the normal situation... given a number or a string representation of a number.... + local holder = { + S = tostring(item), -- S is the representation of the number as a string, which remains precise + N = tonumber(item), -- N is the number as a Lua number. + } + return setmetatable(holder, isNumber) + end +end + +-- +-- Given an item that might be a normal string or number, or might be an 'isNumber' object defined above, +-- return the string version. This shouldn't be needed often because the 'isNumber' object should autoconvert +-- to a string in most cases, but it's here to allow it to be forced when needed. +-- +function OBJDEF:forceString(item) + if type(item) == 'table' and type(item.S) == 'string' then + return item.S + else + return tostring(item) + end +end + +-- +-- Given an item that might be a normal string or number, or might be an 'isNumber' object defined above, +-- return the numeric version. +-- +function OBJDEF:forceNumber(item) + if type(item) == 'table' and type(item.N) == 'number' then + return item.N + else + return tonumber(item) + end +end + + +local function unicode_codepoint_as_utf8(codepoint) + -- + -- codepoint is a number + -- + if codepoint <= 127 then + return string.char(codepoint) + + elseif codepoint <= 2047 then + -- + -- 110yyyxx 10xxxxxx <-- useful notation from http://en.wikipedia.org/wiki/Utf8 + -- + local highpart = math.floor(codepoint / 0x40) + local lowpart = codepoint - (0x40 * highpart) + return string.char(0xC0 + highpart, + 0x80 + lowpart) + + elseif codepoint <= 65535 then + -- + -- 1110yyyy 10yyyyxx 10xxxxxx + -- + local highpart = math.floor(codepoint / 0x1000) + local remainder = codepoint - 0x1000 * highpart + local midpart = math.floor(remainder / 0x40) + local lowpart = remainder - 0x40 * midpart + + highpart = 0xE0 + highpart + midpart = 0x80 + midpart + lowpart = 0x80 + lowpart + + -- + -- Check for an invalid character (thanks Andy R. at Adobe). + -- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070 + -- + if ( highpart == 0xE0 and midpart < 0xA0 ) or + ( highpart == 0xED and midpart > 0x9F ) or + ( highpart == 0xF0 and midpart < 0x90 ) or + ( highpart == 0xF4 and midpart > 0x8F ) + then + return "?" + else + return string.char(highpart, + midpart, + lowpart) + end + + else + -- + -- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx + -- + local highpart = math.floor(codepoint / 0x40000) + local remainder = codepoint - 0x40000 * highpart + local midA = math.floor(remainder / 0x1000) + remainder = remainder - 0x1000 * midA + local midB = math.floor(remainder / 0x40) + local lowpart = remainder - 0x40 * midB + + return string.char(0xF0 + highpart, + 0x80 + midA, + 0x80 + midB, + 0x80 + lowpart) + end +end + +function OBJDEF:onDecodeError(message, text, location, etc) + if text then + if location then + message = string.format("%s at byte %d of: %s", message, location, text) + else + message = string.format("%s: %s", message, text) + end + end + + if etc ~= nil then + message = message .. " (" .. OBJDEF:encode(etc) .. ")" + end + + if self.assert then + self.assert(false, message) + else + assert(false, message) + end +end + +function OBJDEF:onTrailingGarbage(json_text, location, parsed_value, etc) + return self:onDecodeError("trailing garbage", json_text, location, etc) +end + +OBJDEF.onDecodeOfNilError = OBJDEF.onDecodeError +OBJDEF.onDecodeOfHTMLError = OBJDEF.onDecodeError + +function OBJDEF:onEncodeError(message, etc) + if etc ~= nil then + message = message .. " (" .. OBJDEF:encode(etc) .. ")" + end + + if self.assert then + self.assert(false, message) + else + assert(false, message) + end +end + +local function grok_number(self, text, start, options) + -- + -- Grab the integer part + -- + local integer_part = text:match('^-?[1-9]%d*', start) + or text:match("^-?0", start) + + if not integer_part then + self:onDecodeError("expected number", text, start, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible + end + + local i = start + integer_part:len() + + -- + -- Grab an optional decimal part + -- + local decimal_part = text:match('^%.%d+', i) or "" + + i = i + decimal_part:len() + + -- + -- Grab an optional exponential part + -- + local exponent_part = text:match('^[eE][-+]?%d+', i) or "" + + i = i + exponent_part:len() + + local full_number_text = integer_part .. decimal_part .. exponent_part + + if options.decodeNumbersAsObjects then + return OBJDEF:asNumber(full_number_text), i + end + + -- + -- If we're told to stringify under certain conditions, so do. + -- We punt a bit when there's an exponent by just stringifying no matter what. + -- I suppose we should really look to see whether the exponent is actually big enough one + -- way or the other to trip stringification, but I'll be lazy about it until someone asks. + -- + if (options.decodeIntegerStringificationLength + and + (integer_part:len() >= options.decodeIntegerStringificationLength or exponent_part:len() > 0)) + + or + + (options.decodeDecimalStringificationLength + and + (decimal_part:len() >= options.decodeDecimalStringificationLength or exponent_part:len() > 0)) + then + return full_number_text, i -- this returns the exact string representation seen in the original JSON + end + + + + local as_number = tonumber(full_number_text) + + if not as_number then + self:onDecodeError("bad number", text, start, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible + end + + return as_number, i +end + + +local function grok_string(self, text, start, options) + + if text:sub(start,start) ~= '"' then + self:onDecodeError("expected string's opening quote", text, start, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible + end + + local i = start + 1 -- +1 to bypass the initial quote + local text_len = text:len() + local VALUE = "" + while i <= text_len do + local c = text:sub(i,i) + if c == '"' then + return VALUE, i + 1 + end + if c ~= '\\' then + VALUE = VALUE .. c + i = i + 1 + elseif text:match('^\\b', i) then + VALUE = VALUE .. "\b" + i = i + 2 + elseif text:match('^\\f', i) then + VALUE = VALUE .. "\f" + i = i + 2 + elseif text:match('^\\n', i) then + VALUE = VALUE .. "\n" + i = i + 2 + elseif text:match('^\\r', i) then + VALUE = VALUE .. "\r" + i = i + 2 + elseif text:match('^\\t', i) then + VALUE = VALUE .. "\t" + i = i + 2 + else + local hex = text:match('^\\u([0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) + if hex then + i = i + 6 -- bypass what we just read + + -- We have a Unicode codepoint. It could be standalone, or if in the proper range and + -- followed by another in a specific range, it'll be a two-code surrogate pair. + local codepoint = tonumber(hex, 16) + if codepoint >= 0xD800 and codepoint <= 0xDBFF then + -- it's a hi surrogate... see whether we have a following low + local lo_surrogate = text:match('^\\u([dD][cdefCDEF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) + if lo_surrogate then + i = i + 6 -- bypass the low surrogate we just read + codepoint = 0x2400 + (codepoint - 0xD800) * 0x400 + tonumber(lo_surrogate, 16) + else + -- not a proper low, so we'll just leave the first codepoint as is and spit it out. + end + end + VALUE = VALUE .. unicode_codepoint_as_utf8(codepoint) + + else + + -- just pass through what's escaped + VALUE = VALUE .. text:match('^\\(.)', i) + i = i + 2 + end + end + end + + self:onDecodeError("unclosed string", text, start, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible +end + +local function skip_whitespace(text, start) + + local _, match_end = text:find("^[ \n\r\t]+", start) -- [http://www.ietf.org/rfc/rfc4627.txt] Section 2 + if match_end then + return match_end + 1 + else + return start + end +end + +local grok_one -- assigned later + +local function grok_object(self, text, start, options) + + if text:sub(start,start) ~= '{' then + self:onDecodeError("expected '{'", text, start, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible + end + + local i = skip_whitespace(text, start + 1) -- +1 to skip the '{' + + local VALUE = self.strictTypes and self:newObject { } or { } + + if text:sub(i,i) == '}' then + return VALUE, i + 1 + end + local text_len = text:len() + while i <= text_len do + local key, new_i = grok_string(self, text, i, options) + + i = skip_whitespace(text, new_i) + + if text:sub(i, i) ~= ':' then + self:onDecodeError("expected colon", text, i, options.etc) + return nil, i -- in case the error method doesn't abort, return something sensible + end + + i = skip_whitespace(text, i + 1) + + local new_val, new_i = grok_one(self, text, i, options) + + VALUE[key] = new_val + + -- + -- Expect now either '}' to end things, or a ',' to allow us to continue. + -- + i = skip_whitespace(text, new_i) + + local c = text:sub(i,i) + + if c == '}' then + return VALUE, i + 1 + end + + if text:sub(i, i) ~= ',' then + self:onDecodeError("expected comma or '}'", text, i, options.etc) + return nil, i -- in case the error method doesn't abort, return something sensible + end + + i = skip_whitespace(text, i + 1) + end + + self:onDecodeError("unclosed '{'", text, start, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible +end + +local function grok_array(self, text, start, options) + if text:sub(start,start) ~= '[' then + self:onDecodeError("expected '['", text, start, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible + end + + local i = skip_whitespace(text, start + 1) -- +1 to skip the '[' + local VALUE = self.strictTypes and self:newArray { } or { } + if text:sub(i,i) == ']' then + return VALUE, i + 1 + end + + local VALUE_INDEX = 1 + + local text_len = text:len() + while i <= text_len do + local val, new_i = grok_one(self, text, i, options) + + -- can't table.insert(VALUE, val) here because it's a no-op if val is nil + VALUE[VALUE_INDEX] = val + VALUE_INDEX = VALUE_INDEX + 1 + + i = skip_whitespace(text, new_i) + + -- + -- Expect now either ']' to end things, or a ',' to allow us to continue. + -- + local c = text:sub(i,i) + if c == ']' then + return VALUE, i + 1 + end + if text:sub(i, i) ~= ',' then + self:onDecodeError("expected comma or ']'", text, i, options.etc) + return nil, i -- in case the error method doesn't abort, return something sensible + end + i = skip_whitespace(text, i + 1) + end + self:onDecodeError("unclosed '['", text, start, options.etc) + return nil, i -- in case the error method doesn't abort, return something sensible +end + + +grok_one = function(self, text, start, options) + -- Skip any whitespace + start = skip_whitespace(text, start) + + if start > text:len() then + self:onDecodeError("unexpected end of string", text, nil, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible + end + + if text:find('^"', start) then + return grok_string(self, text, start, options.etc) + + elseif text:find('^[-0123456789 ]', start) then + return grok_number(self, text, start, options) + + elseif text:find('^%{', start) then + return grok_object(self, text, start, options) + + elseif text:find('^%[', start) then + return grok_array(self, text, start, options) + + elseif text:find('^true', start) then + return true, start + 4 + + elseif text:find('^false', start) then + return false, start + 5 + + elseif text:find('^null', start) then + return nil, start + 4 + + else + self:onDecodeError("can't parse JSON", text, start, options.etc) + return nil, 1 -- in case the error method doesn't abort, return something sensible + end +end + +function OBJDEF:decode(text, etc, options) + -- + -- If the user didn't pass in a table of decode options, make an empty one. + -- + if type(options) ~= 'table' then + options = {} + end + + -- + -- If they passed in an 'etc' argument, stuff it into the options. + -- (If not, any 'etc' field in the options they passed in remains to be used) + -- + if etc ~= nil then + options.etc = etc + end + + + if type(self) ~= 'table' or self.__index ~= OBJDEF then + local error_message = "JSON:decode must be called in method format" + OBJDEF:onDecodeError(error_message, nil, nil, options.etc) + return nil, error_message -- in case the error method doesn't abort, return something sensible + end + + if text == nil then + local error_message = "nil passed to JSON:decode()" + self:onDecodeOfNilError(error_message, nil, nil, options.etc) + return nil, error_message -- in case the error method doesn't abort, return something sensible + + elseif type(text) ~= 'string' then + local error_message = "expected string argument to JSON:decode()" + self:onDecodeError(string.format("%s, got %s", error_message, type(text)), nil, nil, options.etc) + return nil, error_message -- in case the error method doesn't abort, return something sensible + end + + if text:match('^%s*$') then + -- an empty string is nothing, but not an error + return nil + end + + if text:match('^%s*<') then + -- Can't be JSON... we'll assume it's HTML + local error_message = "HTML passed to JSON:decode()" + self:onDecodeOfHTMLError(error_message, text, nil, options.etc) + return nil, error_message -- in case the error method doesn't abort, return something sensible + end + + -- + -- Ensure that it's not UTF-32 or UTF-16. + -- Those are perfectly valid encodings for JSON (as per RFC 4627 section 3), + -- but this package can't handle them. + -- + if text:sub(1,1):byte() == 0 or (text:len() >= 2 and text:sub(2,2):byte() == 0) then + local error_message = "JSON package groks only UTF-8, sorry" + self:onDecodeError(error_message, text, nil, options.etc) + return nil, error_message -- in case the error method doesn't abort, return something sensible + end + + -- + -- apply global options + -- + if options.decodeNumbersAsObjects == nil then + options.decodeNumbersAsObjects = self.decodeNumbersAsObjects + end + if options.decodeIntegerStringificationLength == nil then + options.decodeIntegerStringificationLength = self.decodeIntegerStringificationLength + end + if options.decodeDecimalStringificationLength == nil then + options.decodeDecimalStringificationLength = self.decodeDecimalStringificationLength + end + + -- + -- Finally, go parse it + -- + local success, value, next_i = pcall(grok_one, self, text, 1, options) + + if success then + if next_i ~= #text + 1 then + -- something's left over after we parsed the first thing.... whitespace is allowed. + next_i = skip_whitespace(text, next_i) + + -- if we have something left over now, it's trailing garbage + local error_message + if next_i ~= #text + 1 then + value, error_message = self:onTrailingGarbage(text, next_i, value, options.etc) + end + end + return value, error_message + + else + + -- If JSON:onDecodeError() didn't abort out of the pcall, we'll have received + -- the error message here as "value", so pass it along as an assert. + local error_message = value + if self.assert then + self.assert(false, error_message) + else + assert(false, error_message) + end + -- and if we're still here, return a nil and throw the error message on as a second arg + return nil, error_message + + end +end + +local function backslash_replacement_function(c) + if c == "\n" then + return "\\n" + elseif c == "\r" then + return "\\r" + elseif c == "\t" then + return "\\t" + elseif c == "\b" then + return "\\b" + elseif c == "\f" then + return "\\f" + elseif c == '"' then + return '\\"' + elseif c == '\\' then + return '\\\\' + else + return string.format("\\u%04x", c:byte()) + end +end + +local chars_to_be_escaped_in_JSON_string + = '[' + .. '"' -- class sub-pattern to match a double quote + .. '%\\' -- class sub-pattern to match a backslash + .. '%z' -- class sub-pattern to match a null + .. '\001' .. '-' .. '\031' -- class sub-pattern to match control characters + .. ']' + + +local LINE_SEPARATOR_as_utf8 = unicode_codepoint_as_utf8(0x2028) +local PARAGRAPH_SEPARATOR_as_utf8 = unicode_codepoint_as_utf8(0x2029) +local function json_string_literal(value, options) + local newval = value:gsub(chars_to_be_escaped_in_JSON_string, backslash_replacement_function) + if options.stringsAreUtf8 then + -- + -- This feels really ugly to just look into a string for the sequence of bytes that we know to be a particular utf8 character, + -- but utf8 was designed purposefully to make this kind of thing possible. Still, feels dirty. + -- I'd rather decode the byte stream into a character stream, but it's not technically needed so + -- not technically worth it. + -- + newval = newval:gsub(LINE_SEPARATOR_as_utf8, '\\u2028'):gsub(PARAGRAPH_SEPARATOR_as_utf8,'\\u2029') + end + return '"' .. newval .. '"' +end + +local function object_or_array(self, T, etc) + -- + -- We need to inspect all the keys... if there are any strings, we'll convert to a JSON + -- object. If there are only numbers, it's a JSON array. + -- + -- If we'll be converting to a JSON object, we'll want to sort the keys so that the + -- end result is deterministic. + -- + local string_keys = { } + local number_keys = { } + local number_keys_must_be_strings = false + local maximum_number_key + + for key in pairs(T) do + if type(key) == 'string' then + table.insert(string_keys, key) + elseif type(key) == 'number' then + table.insert(number_keys, key) + if key <= 0 or key >= math.huge then + number_keys_must_be_strings = true + elseif not maximum_number_key or key > maximum_number_key then + maximum_number_key = key + end + else + self:onEncodeError("can't encode table with a key of type " .. type(key), etc) + end + end + + if #string_keys == 0 and not number_keys_must_be_strings then + -- + -- An empty table, or a numeric-only array + -- + if #number_keys > 0 then + return nil, maximum_number_key -- an array + elseif tostring(T) == "JSON array" then + return nil + elseif tostring(T) == "JSON object" then + return { } + else + -- have to guess, so we'll pick array, since empty arrays are likely more common than empty objects + return nil + end + end + + table.sort(string_keys) + + local map + if #number_keys > 0 then + -- + -- If we're here then we have either mixed string/number keys, or numbers inappropriate for a JSON array + -- It's not ideal, but we'll turn the numbers into strings so that we can at least create a JSON object. + -- + + if self.noKeyConversion then + self:onEncodeError("a table with both numeric and string keys could be an object or array; aborting", etc) + end + + -- + -- Have to make a shallow copy of the source table so we can remap the numeric keys to be strings + -- + map = { } + for key, val in pairs(T) do + map[key] = val + end + + table.sort(number_keys) + + -- + -- Throw numeric keys in there as strings + -- + for _, number_key in ipairs(number_keys) do + local string_key = tostring(number_key) + if map[string_key] == nil then + table.insert(string_keys , string_key) + map[string_key] = T[number_key] + else + self:onEncodeError("conflict converting table with mixed-type keys into a JSON object: key " .. number_key .. " exists both as a string and a number.", etc) + end + end + end + + return string_keys, nil, map +end + +-- +-- Encode +-- +-- 'options' is nil, or a table with possible keys: +-- +-- pretty -- If true, return a pretty-printed version. +-- +-- indent -- A string (usually of spaces) used to indent each nested level. +-- +-- align_keys -- If true, align all the keys when formatting a table. +-- +-- null -- If this exists with a string value, table elements with this value are output as JSON null. +-- +-- stringsAreUtf8 -- If true, consider Lua strings not as a sequence of bytes, but as a sequence of UTF-8 characters. +-- (Currently, the only practical effect of setting this option is that Unicode LINE and PARAGRAPH +-- separators, if found in a string, are encoded with a JSON escape instead of as raw UTF-8. +-- The JSON is valid either way, but encoding this way, apparently, allows the resulting JSON +-- to also be valid Java.) +-- +-- +local encode_value -- must predeclare because it calls itself +function encode_value(self, value, parents, etc, options, indent, for_key) + + -- + -- keys in a JSON object can never be null, so we don't even consider options.null when converting a key value + -- + if value == nil or (not for_key and options and options.null and value == options.null) then + return 'null' + + elseif type(value) == 'string' then + return json_string_literal(value, options) + + elseif type(value) == 'number' then + if value ~= value then + -- + -- NaN (Not a Number). + -- JSON has no NaN, so we have to fudge the best we can. This should really be a package option. + -- + return "null" + elseif value >= math.huge then + -- + -- Positive infinity. JSON has no INF, so we have to fudge the best we can. This should + -- really be a package option. Note: at least with some implementations, positive infinity + -- is both ">= math.huge" and "<= -math.huge", which makes no sense but that's how it is. + -- Negative infinity is properly "<= -math.huge". So, we must be sure to check the ">=" + -- case first. + -- + return "1e+9999" + elseif value <= -math.huge then + -- + -- Negative infinity. + -- JSON has no INF, so we have to fudge the best we can. This should really be a package option. + -- + return "-1e+9999" + else + return tostring(value) + end + + elseif type(value) == 'boolean' then + return tostring(value) + + elseif type(value) ~= 'table' then + self:onEncodeError("can't convert " .. type(value) .. " to JSON", etc) + + elseif getmetatable(value) == isNumber then + return tostring(value) + else + -- + -- A table to be converted to either a JSON object or array. + -- + local T = value + + if type(options) ~= 'table' then + options = {} + end + if type(indent) ~= 'string' then + indent = "" + end + + if parents[T] then + self:onEncodeError("table " .. tostring(T) .. " is a child of itself", etc) + else + parents[T] = true + end + + local result_value + + local object_keys, maximum_number_key, map = object_or_array(self, T, etc) + if maximum_number_key then + -- + -- An array... + -- + local ITEMS = { } + for i = 1, maximum_number_key do + table.insert(ITEMS, encode_value(self, T[i], parents, etc, options, indent)) + end + + if options.pretty then + result_value = "[ " .. table.concat(ITEMS, ", ") .. " ]" + else + result_value = "[" .. table.concat(ITEMS, ",") .. "]" + end + + elseif object_keys then + -- + -- An object + -- + local TT = map or T + + if options.pretty then + + local KEYS = { } + local max_key_length = 0 + for _, key in ipairs(object_keys) do + local encoded = encode_value(self, tostring(key), parents, etc, options, indent, true) + if options.align_keys then + max_key_length = math.max(max_key_length, #encoded) + end + table.insert(KEYS, encoded) + end + local key_indent = indent .. tostring(options.indent or "") + local subtable_indent = key_indent .. string.rep(" ", max_key_length) .. (options.align_keys and " " or "") + local FORMAT = "%s%" .. string.format("%d", max_key_length) .. "s: %s" + + local COMBINED_PARTS = { } + for i, key in ipairs(object_keys) do + local encoded_val = encode_value(self, TT[key], parents, etc, options, subtable_indent) + table.insert(COMBINED_PARTS, string.format(FORMAT, key_indent, KEYS[i], encoded_val)) + end + result_value = "{\n" .. table.concat(COMBINED_PARTS, ",\n") .. "\n" .. indent .. "}" + + else + + local PARTS = { } + for _, key in ipairs(object_keys) do + local encoded_val = encode_value(self, TT[key], parents, etc, options, indent) + local encoded_key = encode_value(self, tostring(key), parents, etc, options, indent, true) + table.insert(PARTS, string.format("%s:%s", encoded_key, encoded_val)) + end + result_value = "{" .. table.concat(PARTS, ",") .. "}" + + end + else + -- + -- An empty array/object... we'll treat it as an array, though it should really be an option + -- + result_value = "[]" + end + + parents[T] = false + return result_value + end +end + +local function top_level_encode(self, value, etc, options) + local val = encode_value(self, value, {}, etc, options) + if val == nil then + --PRIVATE("may need to revert to the previous public verison if I can't figure out what the guy wanted") + return val + else + return val + end +end + +function OBJDEF:encode(value, etc, options) + if type(self) ~= 'table' or self.__index ~= OBJDEF then + OBJDEF:onEncodeError("JSON:encode must be called in method format", etc) + end + + -- + -- If the user didn't pass in a table of decode options, make an empty one. + -- + if type(options) ~= 'table' then + options = {} + end + + return top_level_encode(self, value, etc, options) +end + +function OBJDEF:encode_pretty(value, etc, options) + if type(self) ~= 'table' or self.__index ~= OBJDEF then + OBJDEF:onEncodeError("JSON:encode_pretty must be called in method format", etc) + end + + -- + -- If the user didn't pass in a table of decode options, use the default pretty ones + -- + if type(options) ~= 'table' then + options = default_pretty_options + end + + return top_level_encode(self, value, etc, options) +end + +function OBJDEF.__tostring() + return "JSON encode/decode package" +end + +OBJDEF.__index = OBJDEF + +function OBJDEF:new(args) + local new = { } + + if args then + for key, val in pairs(args) do + new[key] = val + end + end + + return setmetatable(new, OBJDEF) +end + +return OBJDEF:new() + +-- +-- Version history: +-- +-- 20161103.20 Used to silently ignore trailing garbage when decoding. Now fails via JSON:onTrailingGarbage() +-- http://seriot.ch/parsing_json.php +-- +-- Built-in error message about "expected comma or ']'" had mistakenly referred to '[' +-- +-- Updated the built-in error reporting to refer to bytes rather than characters. +-- +-- The decode() method no longer assumes that error handlers abort. +-- +-- Made the VERSION string a string instead of a number +-- +-- +-- 20160916.19 Fixed the isNumber.__index assignment (thanks to Jack Taylor) +-- +-- 20160730.18 Added JSON:forceString() and JSON:forceNumber() +-- +-- 20160728.17 Added concatenation to the metatable for JSON:asNumber() +-- +-- 20160709.16 Could crash if not passed an options table (thanks jarno heikkinen ). +-- +-- Made JSON:asNumber() a bit more resilient to being passed the results of itself. +-- +-- 20160526.15 Added the ability to easily encode null values in JSON, via the new "null" encoding option. +-- (Thanks to Adam B for bringing up the issue.) +-- +-- Added some support for very large numbers and precise floats via +-- JSON.decodeNumbersAsObjects +-- JSON.decodeIntegerStringificationLength +-- JSON.decodeDecimalStringificationLength +-- +-- Added the "stringsAreUtf8" encoding option. (Hat tip to http://lua-users.org/wiki/JsonModules ) +-- +-- 20141223.14 The encode_pretty() routine produced fine results for small datasets, but isn't really +-- appropriate for anything large, so with help from Alex Aulbach I've made the encode routines +-- more flexible, and changed the default encode_pretty() to be more generally useful. +-- +-- Added a third 'options' argument to the encode() and encode_pretty() routines, to control +-- how the encoding takes place. +-- +-- Updated docs to add assert() call to the loadfile() line, just as good practice so that +-- if there is a problem loading JSON.lua, the appropriate error message will percolate up. +-- +-- 20140920.13 Put back (in a way that doesn't cause warnings about unused variables) the author string, +-- so that the source of the package, and its version number, are visible in compiled copies. +-- +-- 20140911.12 Minor lua cleanup. +-- Fixed internal reference to 'JSON.noKeyConversion' to reference 'self' instead of 'JSON'. +-- (Thanks to SmugMug's David Parry for these.) +-- +-- 20140418.11 JSON nulls embedded within an array were being ignored, such that +-- ["1",null,null,null,null,null,"seven"], +-- would return +-- {1,"seven"} +-- It's now fixed to properly return +-- {1, nil, nil, nil, nil, nil, "seven"} +-- Thanks to "haddock" for catching the error. +-- +-- 20140116.10 The user's JSON.assert() wasn't always being used. Thanks to "blue" for the heads up. +-- +-- 20131118.9 Update for Lua 5.3... it seems that tostring(2/1) produces "2.0" instead of "2", +-- and this caused some problems. +-- +-- 20131031.8 Unified the code for encode() and encode_pretty(); they had been stupidly separate, +-- and had of course diverged (encode_pretty didn't get the fixes that encode got, so +-- sometimes produced incorrect results; thanks to Mattie for the heads up). +-- +-- Handle encoding tables with non-positive numeric keys (unlikely, but possible). +-- +-- If a table has both numeric and string keys, or its numeric keys are inappropriate +-- (such as being non-positive or infinite), the numeric keys are turned into +-- string keys appropriate for a JSON object. So, as before, +-- JSON:encode({ "one", "two", "three" }) +-- produces the array +-- ["one","two","three"] +-- but now something with mixed key types like +-- JSON:encode({ "one", "two", "three", SOMESTRING = "some string" })) +-- instead of throwing an error produces an object: +-- {"1":"one","2":"two","3":"three","SOMESTRING":"some string"} +-- +-- To maintain the prior throw-an-error semantics, set +-- JSON.noKeyConversion = true +-- +-- 20131004.7 Release under a Creative Commons CC-BY license, which I should have done from day one, sorry. +-- +-- 20130120.6 Comment update: added a link to the specific page on my blog where this code can +-- be found, so that folks who come across the code outside of my blog can find updates +-- more easily. +-- +-- 20111207.5 Added support for the 'etc' arguments, for better error reporting. +-- +-- 20110731.4 More feedback from David Kolf on how to make the tests for Nan/Infinity system independent. +-- +-- 20110730.3 Incorporated feedback from David Kolf at http://lua-users.org/wiki/JsonModules: +-- +-- * When encoding lua for JSON, Sparse numeric arrays are now handled by +-- spitting out full arrays, such that +-- JSON:encode({"one", "two", [10] = "ten"}) +-- returns +-- ["one","two",null,null,null,null,null,null,null,"ten"] +-- +-- In 20100810.2 and earlier, only up to the first non-null value would have been retained. +-- +-- * When encoding lua for JSON, numeric value NaN gets spit out as null, and infinity as "1+e9999". +-- Version 20100810.2 and earlier created invalid JSON in both cases. +-- +-- * Unicode surrogate pairs are now detected when decoding JSON. +-- +-- 20100810.2 added some checking to ensure that an invalid Unicode character couldn't leak in to the UTF-8 encoding +-- +-- 20100731.1 initial public release +-- diff --git a/addons/libs/logging.lua b/addons/libs/logging.lua new file mode 100644 index 0000000..20612ac --- /dev/null +++ b/addons/libs/logging.lua @@ -0,0 +1,91 @@ +--[[ +* Ashita - Copyright (c) 2014 - 2016 atom0s [atom0s@live.com] +* +* This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. +* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to +* Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. +* +* By using Ashita, you agree to the above license and its terms. +* +* Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were +* made. You must do so in any reasonable manner, but not in any way that suggests the licensor +* endorses you or your use. +* +* Non-Commercial - You may not use the material (Ashita) for commercial purposes. +* +* No-Derivatives - If you remix, transform, or build upon the material (Ashita), you may not distribute the +* modified material. You are, however, allowed to submit the modified works back to the original +* Ashita project in attempt to have it added to the original project. +* +* You may not apply legal terms or technological measures that legally restrict others +* from doing anything the license permits. +* +* No warranties are given. +]]-- + +ashita = ashita or { }; +ashita.logging = ashita.logging or { }; +ashita.logging.level = ashita.logging.level or { }; + +---------------------------------------------------------------------------------------------------- +-- Logging Level +---------------------------------------------------------------------------------------------------- +ashita.logging.level.None = 0; -- Logs a normal message. +ashita.logging.level.Information = 1; -- Logs a warning. +ashita.logging.level.Warning = 2; -- Logs a warning. +ashita.logging.level.Error = 3; -- Logs an error. +ashita.logging.level.Debug = 4; -- Logs a debug message. + +---------------------------------------------------------------------------------------------------- +-- func: ashita.logging.log +-- desc: Prints a string to the current log file. +---------------------------------------------------------------------------------------------------- +local function log(lvl, src, str) + LogManager:Log(lvl, src, str); +end +ashita.logging.log = log; + +---------------------------------------------------------------------------------------------------- +-- func: ashita.logging.normal +-- desc: Logs an normal line to the log file. +---------------------------------------------------------------------------------------------------- +local function log_normal(src, str) + ashita.logging.log(ashita.logging.level.None, src, str); +end +ashita.logging.normal = log_normal; + +---------------------------------------------------------------------------------------------------- +-- func: ashita.logging.info +-- desc: Logs an information line to the log file. +---------------------------------------------------------------------------------------------------- +local function log_info(src, str) + ashita.logging.log(ashita.logging.level.Information, src, str); +end +ashita.logging.info = log_info; + +---------------------------------------------------------------------------------------------------- +-- func: ashita.logging.warn +-- desc: Logs a warning line to the log file. +---------------------------------------------------------------------------------------------------- +local function log_warning(src, str) + ashita.logging.log(ashita.logging.level.Warning, src, str); +end +ashita.logging.warn = log_warning; + +---------------------------------------------------------------------------------------------------- +-- func: ashita.logging.error +-- desc: Logs an error line to the log file. +---------------------------------------------------------------------------------------------------- +local function log_error(src, str) + ashita.logging.log(ashita.logging.level.Error, src, str); +end +ashita.logging.error = log_error; + +---------------------------------------------------------------------------------------------------- +-- func: ashita.logging.debug +-- desc: Logs a debug line to the log file. +---------------------------------------------------------------------------------------------------- +local function log_debug(src, str) + ashita.logging.log(ashita.logging.level.Debug, src, str); +end +ashita.logging.debug = log_debug; \ No newline at end of file diff --git a/addons/libs/mathex.lua b/addons/libs/mathex.lua new file mode 100644 index 0000000..c5cf0b2 --- /dev/null +++ b/addons/libs/mathex.lua @@ -0,0 +1,130 @@ +--[[ +* Ashita - Copyright (c) 2014 - 2016 atom0s [atom0s@live.com] +* +* This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. +* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to +* Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. +* +* By using Ashita, you agree to the above license and its terms. +* +* Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were +* made. You must do so in any reasonable manner, but not in any way that suggests the licensor +* endorses you or your use. +* +* Non-Commercial - You may not use the material (Ashita) for commercial purposes. +* +* No-Derivatives - If you remix, transform, or build upon the material (Ashita), you may not distribute the +* modified material. You are, however, allowed to submit the modified works back to the original +* Ashita project in attempt to have it added to the original project. +* +* You may not apply legal terms or technological measures that legally restrict others +* from doing anything the license permits. +* +* No warranties are given. +]]-- + +---------------------------------------------------------------------------------------------------- +-- func: math.distance2d +-- desc: Returns the 2D distance between two sets of coords. +---------------------------------------------------------------------------------------------------- +function math.distance2d(x1, y1, x2, y2) + local x = x2 - x1; + local y = y2 - y1; + return math.sqrt((x * x) + (y * y)); +end + +---------------------------------------------------------------------------------------------------- +-- func: math.distance3d +-- desc: Returns the 3D distance between two sets of coords. +---------------------------------------------------------------------------------------------------- +function math.distance3d(x1, y1, z1, x2, y2, z2) + local x = x2 - x1; + local y = y2 - y1; + local z = z2 - z1; + return math.sqrt((x * x) + (y * y) + (z * z)); +end + +---------------------------------------------------------------------------------------------------- +-- func: math.degree2rad +-- desc: Converts a degree to a radian. +---------------------------------------------------------------------------------------------------- +function math.degree2rad(d) + local pi = 3.14159265359; + return d * (pi / 180); +end + +---------------------------------------------------------------------------------------------------- +-- func: math.rad2degree +-- desc: Converts a radian to a degree. +---------------------------------------------------------------------------------------------------- +function math.rad2degree(r) + local pi = 3.14159265359; + return r * (180 / pi); +end + +---------------------------------------------------------------------------------------------------- +-- func: math.clamp +-- desc: Clamps a number between a min and max value. +---------------------------------------------------------------------------------------------------- +function math.clamp(n, min, max) + if (n < min) then return min; end + if (n > max) then return max; end + return n; +end + +---------------------------------------------------------------------------------------------------- +-- func: math.round +-- desc: Rounds a number to the given decimal places. +---------------------------------------------------------------------------------------------------- +function math.round(n, dp) + local m = 10 ^ (dp or 0); + return math.floor(n * m + 0.5) / m; +end + +---------------------------------------------------------------------------------------------------- +-- func: math.d3dcolor +-- desc: Converts the given ARGB values to make a D3DCOLOR. +---------------------------------------------------------------------------------------------------- +function math.d3dcolor(a, r, g, b) + local a = bit.lshift(bit.band(a, 0xFF), 24); + local r = bit.lshift(bit.band(r, 0xFF), 16); + local g = bit.lshift(bit.band(g, 0xFF), 08); + local b = bit.band(b, 0xFF); + return bit.bor(bit.bor(a, r), bit.bor(g, b)); +end + +---------------------------------------------------------------------------------------------------- +-- func: math.bin2int +-- desc: Converts a binary number to an integer. +---------------------------------------------------------------------------------------------------- +function math.bin2int(b) + return tonumber(b, 2); +end + +---------------------------------------------------------------------------------------------------- +-- func: math.int2bin +-- desc: Converts an integer to a binary number. +---------------------------------------------------------------------------------------------------- +function math.int2bin(i) + local s = string.format('%o', i); + local a = { ["0"] = "000", ["1"] = "001", ["2"] = "010", ["3"] = "011", ["4"] = "100", ["5"] = "101", ["6"] = "110", ["7"] = "111" }; + local b = string.gsub(s, "(.)", function(d) return a[d]; end); + return b; +end + +---------------------------------------------------------------------------------------------------- +-- func: math.rngrnd +-- desc: Generates a ranged random number between a min and max point. +---------------------------------------------------------------------------------------------------- +function math.rngrnd(l, h) + return l + (h - l) * math.random(); +end +---------------------------------------------------------------------------------------------------- +-- func: math.truncate +-- desc: Returns a number towards zero. +---------------------------------------------------------------------------------------------------- +function math.truncate(n, dp) + local m = 10 ^ (dp or 0); + local f = num < 0 and math.ceil or math.floor; + return f(n * m) / m; +end \ No newline at end of file diff --git a/addons/libs/settings.lua b/addons/libs/settings.lua new file mode 100644 index 0000000..1210fda --- /dev/null +++ b/addons/libs/settings.lua @@ -0,0 +1,156 @@ +--[[ +* Ashita - Copyright (c) 2014 - 2016 atom0s [atom0s@live.com] +* +* This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. +* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to +* Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. +* +* By using Ashita, you agree to the above license and its terms. +* +* Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were +* made. You must do so in any reasonable manner, but not in any way that suggests the licensor +* endorses you or your use. +* +* Non-Commercial - You may not use the material (Ashita) for commercial purposes. +* +* No-Derivatives - If you remix, transform, or build upon the material (Ashita), you may not distribute the +* modified material. You are, however, allowed to submit the modified works back to the original +* Ashita project in attempt to have it added to the original project. +* +* You may not apply legal terms or technological measures that legally restrict others +* from doing anything the license permits. +* +* No warranties are given. +]]-- + +ashita = ashita or { }; +ashita.settings = ashita.settings or { }; + +if (ashita.settings.JSON == nil) then + ashita.settings.JSON = require('json.json'); +end +---------------------------------------------------------------------------------------------------- +-- func: normalize_path +-- desc: Normalizes a paths slashes to a single format. +---------------------------------------------------------------------------------------------------- +local function normalize_path(path) + local p = path:gsub('/', '\\'); + local i = p:find('\\'); + + while (i ~= nil) do + if (p:sub(i + 1, i + 1) == '\\') then + p = p:remove(i); + i = p:find('\\'); + else + i = p:find('\\', i + 1); + end + end + return p; +end + +---------------------------------------------------------------------------------------------------- +-- func: path_from_file +-- desc: Obtains a path from a file location. +---------------------------------------------------------------------------------------------------- +local function path_from_file(f) + -- Ensure the given data has a file extension.. + if (f:find('%.') == nil) then + return f; + end + + -- Find the location of the first slash.. + local s = f:find('\\'); + if (s == nil) then return f; end + + -- Find the last slash location.. + while (true) do + local c = f:find('\\', s + 1); + if (c == nil) then break; end + s = c; + end + + -- Pull and return the file path.. + return f:sub(0, s - 1); +end + +---------------------------------------------------------------------------------------------------- +-- func: save_settings +-- desc: Saves a table, as JSON, to the given file. +---------------------------------------------------------------------------------------------------- +local function save_settings(name, t) + -- Convert the table to json.. + local data = ashita.settings.JSON:encode_pretty(t, nil, { pretty = true, align_keys = false, indent = ' ' }); + if (data == nil) then + error('Failed to convert data to JSON for saving.'); + return false; + end + + -- Normalize the path.. + local name = normalize_path(name); + + -- Ensure the path exists.. + local dir = path_from_file(name); + if (not ashita.file.dir_exists(dir)) then + ashita.file.create_dir(dir); + end + + -- Save the config file.. + local f = io.open(name, 'w'); + if (f == nil) then + error('Failed to save configuration.'); + return false; + end + + -- Write and close the file.. + f:write(data); + f:close(); + + return true; +end +ashita.settings.save = save_settings; + +---------------------------------------------------------------------------------------------------- +-- func: load_settings +-- desc: Loads a json settings file and converts its data to a Lua table. +---------------------------------------------------------------------------------------------------- +local function load_settings(name) + -- Load the file for reading.. + local f = io.open(name, 'r'); + if (f == nil) then + return nil; + end + + -- Read the full file contents.. + local raw = f:read('*a'); + f:close(); + + -- Convert the JSON to a Lua table.. + local data = ashita.settings.JSON:decode(raw); + if (type(data) == 'table') then return data; end + + -- Failed to convert.. + return nil; +end +ashita.settings.load = load_settings; + +---------------------------------------------------------------------------------------------------- +-- func: load_settings_merged +-- desc: Loads a json settings file and converts its data to a Lua table. Afterward, merges the +-- loaded settings into the parent table. +---------------------------------------------------------------------------------------------------- +local function load_settings_merged(name, parent) + -- Ensure the parent table is valid.. + if (parent == nil or type(parent) ~= 'table') then + return nil; + end + + -- Load the settings.. + local data = ashita.settings.load(name); + if (data == nil) then + return parent; + end + + -- Merge the tables.. + return table.merge(parent, data); +end +ashita.settings.load_merged = load_settings_merged; \ No newline at end of file diff --git a/addons/libs/stringex.lua b/addons/libs/stringex.lua new file mode 100644 index 0000000..32a4548 --- /dev/null +++ b/addons/libs/stringex.lua @@ -0,0 +1,344 @@ +--[[ +* Ashita - Copyright (c) 2014 - 2016 atom0s [atom0s@live.com] +* +* This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. +* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to +* Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. +* +* By using Ashita, you agree to the above license and its terms. +* +* Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were +* made. You must do so in any reasonable manner, but not in any way that suggests the licensor +* endorses you or your use. +* +* Non-Commercial - You may not use the material (Ashita) for commercial purposes. +* +* No-Derivatives - If you remix, transform, or build upon the material (Ashita), you may not distribute the +* modified material. You are, however, allowed to submit the modified works back to the original +* Ashita project in attempt to have it added to the original project. +* +* You may not apply legal terms or technological measures that legally restrict others +* from doing anything the license permits. +* +* No warranties are given. +]]-- + +---------------------------------------------------------------------------------------------------- +-- func: string.contains +-- desc: Determines if a string contains the given sub-string. +---------------------------------------------------------------------------------------------------- +function string.contains(s, v) + return s:find(v, nil, true) ~= nil; +end + +---------------------------------------------------------------------------------------------------- +-- func: string.startswith +-- desc: Determines if a string begins with a specific string. +---------------------------------------------------------------------------------------------------- +function string.startswith(s, v) + return s:sub(1, #v) == v; +end + +---------------------------------------------------------------------------------------------------- +-- func: string.endswith +-- desc: Determines if a string ends with a specific string. +---------------------------------------------------------------------------------------------------- +function string.endswith(s, v) + return s:sub(-#v) == v; +end + +---------------------------------------------------------------------------------------------------- +-- func: string.upperfirst +-- desc: Uppercases the first letter of a string. +---------------------------------------------------------------------------------------------------- +function string.upperfirst(s) + return s:sub(1, 1):upper() .. s:sub(2); +end + +---------------------------------------------------------------------------------------------------- +-- func: string.toproper +-- desc: Converts a string to proper casing. +---------------------------------------------------------------------------------------------------- +function string.toproper(s) + local ret = ''; + local t = { }; + + for x = 1, s:len() do + t[x] = s:sub(x, x); + if (t[x - 1] == ' ' or x == 1) then + t[x] = t[x]:upperfirst(); + end + ret = ret .. t[x]; + end + return ret; +end + +---------------------------------------------------------------------------------------------------- +-- func: string.insert +-- desc: Inserts data into the current string at the given position. +---------------------------------------------------------------------------------------------------- +function string.insert(s, p, v) + local part = s:sub(1, p - 1); + return part .. v .. s:sub(#part + 1); +end + +---------------------------------------------------------------------------------------------------- +-- func: string.remove +-- desc: Removes the character at the given index. +---------------------------------------------------------------------------------------------------- +function string.remove(s, index) + return s:sub(0, index - 1) .. s:sub(index + 1); +end + +---------------------------------------------------------------------------------------------------- +-- func: string.lpad +-- desc: Pads a string 'n' times with the given string. +---------------------------------------------------------------------------------------------------- +function string.lpad(s, v, n) + return (v:rep(n) .. s):sub(-(n > #s and n or #s)); +end + +---------------------------------------------------------------------------------------------------- +-- func: string.rpad +-- desc: Pads a string 'n' times with the given string. +---------------------------------------------------------------------------------------------------- +function string.rpad(s, v, n) + return (s .. v:rep(n)):sub(1, -(n > #s and n or #s)); +end + +---------------------------------------------------------------------------------------------------- +-- func: string.hex +-- desc: Converts a strings value to a hex string. +---------------------------------------------------------------------------------------------------- +function string.hex(s, sep) + sep = sep or ' '; + + local ret = ''; + for _, v in pairs(s:totable()) do + ret = ret .. string.format('%02X', v) .. sep; + end + return ret:trim(); +end + +---------------------------------------------------------------------------------------------------- +-- func: string.fromhex +-- desc: Converts a hex value to a string. +---------------------------------------------------------------------------------------------------- +function string.fromhex(s) + s = s:gsub('%s*0x', ''):gsub('[^%w]', ''); + return (s:gsub('%w%w', function(c) return string.char(tonumber(c, 16)); end)); +end + +---------------------------------------------------------------------------------------------------- +-- func: string.totable +-- desc: Converts the characters of a string to a table. +---------------------------------------------------------------------------------------------------- +function string.totable(s) + local ret = { }; + for x = 1, string.len(s) do + ret[x] = string.byte(s, x); + end + return ret; +end + +---------------------------------------------------------------------------------------------------- +-- func: string.clean +-- desc: Cleans a string of whitespace. +---------------------------------------------------------------------------------------------------- +function string.clean(s, trimend) + if (trimend == nil) then trimend = true; end + if (trimend) then + return s:gsub('%s+', ' '):trim(); + else + return (s:gsub('%s+', ' ')); + end +end + +---------------------------------------------------------------------------------------------------- +-- func: string.trimstart +-- desc: Trims the start of a string for whitespace. +---------------------------------------------------------------------------------------------------- +function string.trimstart(s, c) + if (not c) then c = ' '; end + s = string.reverse(s); + s = string.trimend(s, c); + return string.reverse(s); +end + +---------------------------------------------------------------------------------------------------- +-- func: string.trimend +-- desc: Trims the end of a string for whitespace. +---------------------------------------------------------------------------------------------------- +function string.trimend(s, c) + if (not c) then c = ' '; end + if (string.sub(s, -1) == c) then + s = string.sub(s, 0, -2); + s = string.trimend(s, c); + end + return s; +end + +---------------------------------------------------------------------------------------------------- +-- func: string.trim +-- desc: Trims a string of whitespace. +---------------------------------------------------------------------------------------------------- +function string.trim(s, c) + if (not c) then c = ' '; end + s = string.trimstart(s, c); + s = string.trimend(s, c); + return s; +end + +---------------------------------------------------------------------------------------------------- +-- func: string.args +-- desc: Returns a table of arguments parsed from a string. +---------------------------------------------------------------------------------------------------- +function string:args() + local STATE_NONE = 0; -- Currently within nothing.. + local STATE_WORD = 1; -- Currently within a word.. + local STATE_QUOTE = 2; -- Currently within a quote.. + + local currentState = STATE_NONE; + local currentChar = nil; + local nextChar = nil; + local stringStart = nil; + local args = { }; + + -- Loop the string and self any arguments.. + for x = 1, string.len(self) do + -- Read the current characters.. + currentChar = string.sub(self, x, x); + nextChar = string.sub(self, x + 1, x+1); + + -- Handle non-state.. + if (currentState == STATE_NONE) then + if (currentChar == '"') then + stringStart = x+1; + currentState = STATE_QUOTE; + else + if (currentChar ~= ' ') then + stringStart = x; + currentState = STATE_WORD; + end + end + + -- Handle quoted string state.. + elseif (currentState == STATE_QUOTE) then + if (currentChar == '"') then + currentState = STATE_NONE; + table.insert(args, #args+1, string.sub(self, stringStart, x - 1)); + end + + -- Handle word string state.. + elseif (currentState == STATE_WORD) then + if (currentChar == ' ' or nextChar == nil or nextChar == '\0') then + currentState = STATE_NONE; + table.insert(args, #args+1, string.sub(self, stringStart, x - 1)); + end + else + print('args - Unknown state.'); + end + end + + -- If in a word insert into the args table.. + if (currentState == STATE_WORD) then + table.insert(args, #args + 1, string.sub(self, stringStart, #self + 1)); + end + + -- Return the found arguments.. + return args; +end + +---------------------------------------------------------------------------------------------------- +-- func: string.is_quoted_arg +-- desc: Determines if the string is quoted. +---------------------------------------------------------------------------------------------------- +function string.is_quoted_arg() + local arg = string.match(self, "^\"(.*)\"$"); + return (arg ~= nil), arg; +end + +---------------------------------------------------------------------------------------------------- +-- func: string.parseargs +-- desc: Returns a table of arguments parsed from a string. +---------------------------------------------------------------------------------------------------- +function string:parseargs() + local STATE_NONE = 0; -- Currently within nothing.. + local STATE_WORD = 1; -- Currently within a word.. + local STATE_QUOTE = 2; -- Currently within a quote.. + + local currentState = STATE_NONE; + local currentChar = nil; + local nextChar = nil; + local stringStart = nil; + local prefix = nil; + local args = { }; + + -- Loop the string and self any arguments.. + for x = 1, string.len(self) do + -- Read the current characters.. + currentChar = string.sub(self, x, x); + nextChar = string.sub(self, x + 1, x + 1); + + -- Ensure the command starts with a slash.. + if (x == 1 and currentChar ~= '/') then + return nil; + end + + -- Handle non-state.. + if (currentState == STATE_NONE) then + if (currentChar == '"') then + stringStart = x; + currentState = STATE_QUOTE; + elseif (currentChar ~= ' ') then + stringStart = x; + currentState = STATE_WORD; + end + + -- Handle quoted string state.. + elseif (currentState == STATE_QUOTE) then + if (currentChar == '"') then + table.insert(args, #args + 1, string.sub(self, stringStart, x)); + currentState = STATE_NONE; + end + + -- Handle word string state.. + elseif (currentState == STATE_WORD) then + if (currentChar == ' ') then + table.insert(args, #args+1, string.sub(self, stringStart, x - 1)); + if (prefix == nil) then + prefix = args[#args]; + end + currentState = STATE_NONE; + elseif (nextChar == nil or nextChar == '\0') then + -- This section never actually seems to get hit during processing. + -- Regardless, it needs to use a different endpoint than the block above. + table.insert(args, #args + 1, string.sub(self, stringStart, x)); + if (prefix == nil) then + prefix = args[#args]; + end + currentState = STATE_NONE; + elseif (prefix == nil and currentChar == '/' and x == (stringStart + 1)) then + -- If command line starts with //, put that in its own argument field + table.insert(args, #args + 1, string.sub(self, stringStart, x)); + prefix = args[#args]; + currentState = STATE_NONE; + elseif (currentChar == '"') then + -- A quote mark should start a new quote arg, even if there is no space delimiter. + table.insert(args, #args + 1, string.sub(self, stringStart, x - 1)); + currentState = STATE_QUOTE; + stringStart = x; + end + else + print('parseargs - Unknown state.'); + end + end + + -- If in a word insert into the args table.. + if (currentState == STATE_WORD) then + table.insert(args, #args + 1, string.sub(self, stringStart, #self)); + end + + -- Return the found arguments.. + return args; +end \ No newline at end of file diff --git a/addons/libs/tableex.lua b/addons/libs/tableex.lua new file mode 100644 index 0000000..544df2d --- /dev/null +++ b/addons/libs/tableex.lua @@ -0,0 +1,273 @@ +--[[ +* Ashita - Copyright (c) 2014 - 2016 atom0s [atom0s@live.com] +* +* This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. +* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to +* Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. +* +* By using Ashita, you agree to the above license and its terms. +* +* Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were +* made. You must do so in any reasonable manner, but not in any way that suggests the licensor +* endorses you or your use. +* +* Non-Commercial - You may not use the material (Ashita) for commercial purposes. +* +* No-Derivatives - If you remix, transform, or build upon the material (Ashita), you may not distribute the +* modified material. You are, however, allowed to submit the modified works back to the original +* Ashita project in attempt to have it added to the original project. +* +* You may not apply legal terms or technological measures that legally restrict others +* from doing anything the license permits. +* +* No warranties are given. +]]-- + +---------------------------------------------------------------------------------------------------- +-- func: table.copy +-- desc: Creates a new copy of the given table. +---------------------------------------------------------------------------------------------------- +function table.copy(t) + -- Ensure the incoming object is a table.. + if (type(t) ~= 'table') then + return 't'; + end + + local t_mt = getmetatable(t); + local copy = { }; + + -- Make a copy of all inner-tables.. + for k, v in pairs(t) do + if (type(v) == 'table') then + v = table.copy(v); + end + copy[k] = v; + end + + -- Update the metatable.. + setmetatable(copy, t_mt); + return copy; +end + +---------------------------------------------------------------------------------------------------- +-- func: table.count +-- desc: Returns the count of elements in the table. +---------------------------------------------------------------------------------------------------- +function table.count(t) + local count = 0; + for _, _ in pairs(t) do + count = count + 1; + end + return count; +end + +---------------------------------------------------------------------------------------------------- +-- func: table.haskey +-- desc: Determines if the table has a given key. +---------------------------------------------------------------------------------------------------- +function table.haskey(t, key) + for k, _ in pairs(t) do + if (k == key) then + return true; + end + end + return false; +end + +---------------------------------------------------------------------------------------------------- +-- func: table.hasvalue +-- desc: Determines if the table has the given value. +---------------------------------------------------------------------------------------------------- +function table.hasvalue(t, val) + for _, v in pairs(t) do + if (v == val) then + return true; + end + end + return false; +end + +---------------------------------------------------------------------------------------------------- +-- func: table.merge +-- desc: Merges a table into another one, populating missing entries. +---------------------------------------------------------------------------------------------------- +function table.merge(src, dest) + for k, v in pairs(src) do + if (type(v) == 'table') then + if (dest[k] == nil) then + dest[k] = v; + else + table.merge(v, dest[k]); + end + else + if (dest[k] == nil) then + dest[k] = v; + end + end + end + return dest; +end + +---------------------------------------------------------------------------------------------------- +-- func: table.null +-- desc: Nils all values of the given table. +---------------------------------------------------------------------------------------------------- +function table.null(t) + for k, _ in pairs(t) do + t[k] = nil; + end +end + +---------------------------------------------------------------------------------------------------- +-- func: table.reverse +-- desc: Reverses the order of elements in a table. +---------------------------------------------------------------------------------------------------- +function table.reverse(t) + local len = #t; + local ret = { }; + + for x = len, 1, -1 do + ret[len - x + 1] = t[x]; + end + + return ret; +end + +---------------------------------------------------------------------------------------------------- +-- func: table.sortbykey +-- desc: Sorts the given table by its keys. +---------------------------------------------------------------------------------------------------- +function table.sortbykey(t, desc) + local ret = { }; + + for k, _ in pairs(t) do + table.insert(ret, k); + end + + if (desc) then + table.sort(ret, function(a, b) return t[a] < t[b]; end); + else + table.sort(ret, function(a, b) return t[a] > t[b]; end); + end + + return ret; +end + +---------------------------------------------------------------------------------------------------- +-- func: table.sum +-- desc: Gets the sum of all number elements of a table. +---------------------------------------------------------------------------------------------------- +function table.sum(t) + local val = 0; + for _, v in ipairs(t) do + if (type(v) == 'number') then + val = val + v; + end + end + return val; +end + +---------------------------------------------------------------------------------------------------- +-- func: table.mult +-- desc: Gets the product of all number elements of a table. +---------------------------------------------------------------------------------------------------- +function table.mult(t) + local val = 0; + for _, v in ipairs(t) do + if (type(v) == 'number') then + val = val * v; + end + end + return val; +end + +---------------------------------------------------------------------------------------------------- +-- func: table.min +-- desc: Returns the lowest numeric value within a table. +---------------------------------------------------------------------------------------------------- +function table.min(t) + local val = nil; + for _, v in ipairs(t) do + if (type(v) == 'number') then + if (val == nil) then + val = v; + else + if (v < val) then + val = v; + end + end + end + end + return val; +end + +---------------------------------------------------------------------------------------------------- +-- func: table.max +-- desc: Returns the highest numeric value within a table. +---------------------------------------------------------------------------------------------------- +function table.max(t) + local val = nil; + for _, v in ipairs(t) do + if (type(v) == 'number') then + if (val == nil) then + val = v; + else + if (v > val) then + val = v; + end + end + end + end + return val; +end + +---------------------------------------------------------------------------------------------------- +-- func: table.join +-- desc: Joins a tables values together into a string with the given separator. +---------------------------------------------------------------------------------------------------- +function table.join(t, sep) + local ret = ''; + sep = sep or ''; + for k, v in pairs(t) do + if (#ret == 0) then + ret = tostring(v); + else + ret = ret .. sep .. tostring(v); + end + end + return ret; +end + +---------------------------------------------------------------------------------------------------- +-- func: table.foreach +-- desc: Executes the given function against each table value. +---------------------------------------------------------------------------------------------------- +function table.foreach(t, func) + local ret = T{ }; + for k, v in pairs(t) do + ret[k] = func(k, v); + end + return ret; +end + +---------------------------------------------------------------------------------------------------- +-- func: table.keys +-- desc: Returns a table of keys from the given table. +---------------------------------------------------------------------------------------------------- +function table.keys(t) + local ret = { }; + local count = 1; + for k, _ in pairs(t) do + ret[count] = k; + count = count + 1; + end + return ret; +end + +---------------------------------------------------------------------------------------------------- +-- func: T +-- desc: Creates a metatable enabled table object. +---------------------------------------------------------------------------------------------------- +function T(t) + return setmetatable(t, { __index = table }); +end \ No newline at end of file diff --git a/addons/libs/timer.lua b/addons/libs/timer.lua new file mode 100644 index 0000000..61deb91 --- /dev/null +++ b/addons/libs/timer.lua @@ -0,0 +1,240 @@ +--[[ +* Ashita - Copyright (c) 2014 - 2016 atom0s [atom0s@live.com] +* +* This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. +* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to +* Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. +* +* By using Ashita, you agree to the above license and its terms. +* +* Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were +* made. You must do so in any reasonable manner, but not in any way that suggests the licensor +* endorses you or your use. +* +* Non-Commercial - You may not use the material (Ashita) for commercial purposes. +* +* No-Derivatives - If you remix, transform, or build upon the material (Ashita), you may not distribute the +* modified material. You are, however, allowed to submit the modified works back to the original +* Ashita project in attempt to have it added to the original project. +* +* You may not apply legal terms or technological measures that legally restrict others +* from doing anything the license permits. +* +* No warranties are given. +]]-- + +ashita = ashita or { }; +ashita.timer = ashita.timer or { }; + +-- Timer Status Definition +local TIMER_PAUSED = 0; +local TIMER_STOPPED = 1; +local TIMER_RUNNING = 2; + +-- Timer Table Definitions +ashita.timer.timers = { }; +ashita.timer.timersonce = { }; + +---------------------------------------------------------------------------------------------------- +-- func: create_timer +-- desc: Creates a timer object inside of the timer table. +---------------------------------------------------------------------------------------------------- +local function create_timer(name) + if (ashita.timer.timers[name] == nil) then + ashita.timer.timers[name] = { }; + ashita.timer.timers[name].Status = TIMER_STOPPED; + return true; + end + return false; +end + +---------------------------------------------------------------------------------------------------- +-- func: remove_timer +-- desc: Removes a timer from the timer table. +---------------------------------------------------------------------------------------------------- +local function remove_timer(name) + ashita.timer.timers[name] = nil; +end + +---------------------------------------------------------------------------------------------------- +-- func: is_timer +-- desc: Determines if the given name is a valid timer. +---------------------------------------------------------------------------------------------------- +local function is_timer(name) + return ashita.timer.timers[name] ~= nil; +end + +---------------------------------------------------------------------------------------------------- +-- func: once +-- desc: Creates a one-time timer that fires after the given delay. +---------------------------------------------------------------------------------------------------- +local function once(delay, func, ...) + local t = { }; + t.Finish = os.time() + delay; + if (func) then + t.Function = func; + end + f.Args = {...}; + + table.insert(ashita.timer.timersonce, t); + return true; +end + +---------------------------------------------------------------------------------------------------- +-- func: create +-- desc: Creates a timer. +---------------------------------------------------------------------------------------------------- +local function create(name, delay, reps, func, ...) + if (ashita.timer.is_timer(name)) then + ashita.timer.remove_timer(name); + end + + ashita.timer.adjust_timer(name, delay, reps, func, unpack({...})); + ashita.timer.start_timer(name); +end + +---------------------------------------------------------------------------------------------------- +-- func: start_timer +-- desc: Starts a timer by its name. +---------------------------------------------------------------------------------------------------- +local function start_timer(name) + if (ashita.timer.is_timer(name)) then + ashita.timer.timers[name].n = 0; + ashita.timer.timers[name].Status = TIMER_RUNNING; + ashita.timer.timers[name].Last = os.time(); + return true; + end + return false; +end + +---------------------------------------------------------------------------------------------------- +-- func: adjust_timer +-- desc: Updates a timer objects properties. +---------------------------------------------------------------------------------------------------- +local function adjust_timer(name, delay, reps, func, ...) + ashita.timer.create_timer(name); + + ashita.timer.timers[name].Delay = delay; + ashita.timer.timers[name].Reps = reps; + ashita.timer.timers[name].Args = {...}; + + if (func ~= nil) then + ashita.timer.timers[name].Function = func; + end + + return true; +end + +---------------------------------------------------------------------------------------------------- +-- func: pause +-- desc: Pauses a timer. +---------------------------------------------------------------------------------------------------- +local function pause(name) + if (ashita.timer.is_timer(name)) then + if (ashita.timer.timers[name].Status == TIMER_RUNNING) then + ashita.timer.timers[name].Diff = os.time() - ashita.timer.timers[name].Last; + ashita.timer.timers[name].Status = TIMER_PAUSED; + return true; + end + end + return false; +end + +---------------------------------------------------------------------------------------------------- +-- func: unpause +-- desc: Unpauses a timer. +---------------------------------------------------------------------------------------------------- +local function unpause(name) + if (ashita.timer.is_timer(name)) then + if (ashita.timer.timers[name].Status == TIMER_RUNNING) then + ashita.timer.timers[name].Diff = nil; + ashita.timer.timers[name].Status = TIMER_RUNNING; + return true; + end + end + return false; +end + +---------------------------------------------------------------------------------------------------- +-- func: toggle +-- desc: Toggles a timers paused state. +---------------------------------------------------------------------------------------------------- +local function toggle(name) + if (ashita.timer.is_timer(name)) then + if (ashita.timer.timers[name].Status == TIMER_PAUSED) then + return ashita.timer.unpause(name); + elseif (ashita.timer.timers[name].Status == TIMER_RUNNING) then + return ashita.timer.pause(name); + end + end + return false; +end + +---------------------------------------------------------------------------------------------------- +-- func: stop +-- desc: Stops a timer. +---------------------------------------------------------------------------------------------------- +local function stop(name) + if (ashita.timer.is_timer(name)) then + ashita.timer.timers[name].Status = TIMER_STOPPED; + return true; + end + return false; +end + +---------------------------------------------------------------------------------------------------- +-- func: pulse +-- desc: Pulses the timer tables to allow timers to run. +---------------------------------------------------------------------------------------------------- +local function pulse(name) + -- Handle the normal timers.. + for k, v in pairs(ashita.timer.timers) do + if (v.Status == TIMER_PAUSED) then + v.Last = os.timer() - v.Diff; + elseif (v.Status == TIMER_RUNNING and (v.Last + v.Delay) <= os.time()) then + v.Last = os.time(); + v.n = v.n + 1; + + -- Call the timer function.. + local a, b, c, d, e, f = pcall(v.Function, unpack(v.Args)); + if (a == nil or a == false) then + print(_addon.name .. ' - timer.lua pcall error: ' .. tostring(b)); + end + + -- Stop the timer after its reps were met.. + if (v.n >= v.Reps and v.Reps > 0) then + ashita.timer.stop(k); + end + end + end + + -- Handle the once timers.. + for k, v in pairs(ashita.timer.timersonce) do + if (v.Finish <= os.timer()) then + local a, b, c, d, e, f = pcall(v.Function, unpack(v.Args)); + if (a == nil or a == false) then + print(_addon.name .. ' - timer.lua pcall error: ' .. tostring(b)); + end + + -- Remove the timer.. + ashita.timer.timersonce[name] = nil; + end + end +end +ashita.register_event('timerpulse', pulse); + +---------------------------------------------------------------------------------------------------- +-- Expose The Functions +---------------------------------------------------------------------------------------------------- +ashita.timer.create_timer = create_timer; +ashita.timer.remove_timer = remove_timer; +ashita.timer.is_timer = is_timer; +ashita.timer.once = once; +ashita.timer.create = create; +ashita.timer.start_timer = start_timer; +ashita.timer.adjust_timer = adjust_timer; +ashita.timer.pause = pause; +ashita.timer.unpause = unpause; +ashita.timer.toggle = toggle; +ashita.timer.stop = stop; +ashita.timer.pulse = pulse; \ No newline at end of file diff --git a/addons/timestamp/timestamp.lua b/addons/timestamp/timestamp.lua new file mode 100644 index 0000000..b0d1b43 --- /dev/null +++ b/addons/timestamp/timestamp.lua @@ -0,0 +1,90 @@ +--[[ +* Ashita - Copyright (c) 2014 - 2016 atom0s [atom0s@live.com] +* +* This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. +* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to +* Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. +* +* By using Ashita, you agree to the above license and its terms. +* +* Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were +* made. You must do so in any reasonable manner, but not in any way that suggests the licensor +* endorses you or your use. +* +* Non-Commercial - You may not use the material (Ashita) for commercial purposes. +* +* No-Derivatives - If you remix, transform, or build upon the material (Ashita), you may not distribute the +* modified material. You are, however, allowed to submit the modified works back to the original +* Ashita project in attempt to have it added to the original project. +* +* You may not apply legal terms or technological measures that legally restrict others +* from doing anything the license permits. +* +* No warranties are given. +]]-- + +_addon.author = 'atom0s'; +_addon.name = 'timestamp'; +_addon.version = '3.0.0'; + +require 'common' + +---------------------------------------------------------------------------------------------------- +-- Configurations +---------------------------------------------------------------------------------------------------- +local default_config = +{ + color = 200, + format = '%H:%M:%S', + open_bracket = '[', + close_bracket = ']' +}; +local configs = default_config; + +---------------------------------------------------------------------------------------------------- +-- func: load +-- desc: Event called when the addon is being loaded. +---------------------------------------------------------------------------------------------------- +ashita.register_event('load', function() + -- Load the configuration file.. + configs = ashita.settings.load_merged(_addon.path .. '/settings/settings.json', configs); +end); + +---------------------------------------------------------------------------------------------------- +-- func: unload +-- desc: Event called when the addon is being unloaded. +---------------------------------------------------------------------------------------------------- +ashita.register_event('unload', function() + -- Save the configuration file.. + ashita.settings.save(_addon.path .. '/settings/settings.json', configs); +end); + +--------------------------------------------------------------------------------------------------- +-- func: incoming_text +-- desc: Event called when the addon is asked to handle an incoming chat line. +--------------------------------------------------------------------------------------------------- +ashita.register_event('incoming_text', function(mode, message, modifiedmode, modifiedmessage, blocked) + -- Do nothing if the line is already blocked.. + if (blocked) then return false; end + + -- Handle the modified message if its set.. + if (modifiedmessage ~= nil and #modifiedmessage > 0) then + message = modifiedmessage; + end + + -- Skip ignored chat modes.. + local ignored = T{ + -- Synergy + 600, 702, 909, 919, + + -- Linkshell / System Messages + 200, 973 + }; + if (ignored:hasvalue(mode)) then + return false; + end + + -- Create and apply the timestamp.. + local timestamp = os.date(string.format('\31\%c[%s]\30\01 ', configs.color, configs.format)); + return timestamp .. message; +end); \ No newline at end of file diff --git a/addons/tparty/tparty.lua b/addons/tparty/tparty.lua new file mode 100644 index 0000000..b493805 --- /dev/null +++ b/addons/tparty/tparty.lua @@ -0,0 +1,204 @@ +--[[ +* Ashita - Copyright (c) 2014 - 2016 atom0s [atom0s@live.com] +* +* This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. +* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to +* Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. +* +* By using Ashita, you agree to the above license and its terms. +* +* Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were +* made. You must do so in any reasonable manner, but not in any way that suggests the licensor +* endorses you or your use. +* +* Non-Commercial - You may not use the material (Ashita) for commercial purposes. +* +* No-Derivatives - If you remix, transform, or build upon the material (Ashita), you may not distribute the +* modified material. You are, however, allowed to submit the modified works back to the original +* Ashita project in attempt to have it added to the original project. +* +* You may not apply legal terms or technological measures that legally restrict others +* from doing anything the license permits. +* +* No warranties are given. +]]-- + +_addon.author = 'atom0s'; +_addon.name = 'tparty'; +_addon.version = '3.0.0'; + +require 'common' + +---------------------------------------------------------------------------------------------------- +-- Variables +---------------------------------------------------------------------------------------------------- +local tparty = { }; +tparty.targethpp_str = '__tparty_targethpp'; -- The base name used for a string object. +tparty.partytpp_str = '__tparty_partytpp'; -- The base name used for a string object. + +---------------------------------------------------------------------------------------------------- +-- func: load +-- desc: Event called when the addon is being loaded. +---------------------------------------------------------------------------------------------------- +ashita.register_event('load', function() + -- Pull the current configuration info for position calculations.. + tparty.window_x = AshitaCore:GetConfigurationManager():get_int32('boot_config', 'window_x', 800); + tparty.window_y = AshitaCore:GetConfigurationManager():get_int32('boot_config', 'window_y', 800); + tparty.menu_x = AshitaCore:GetConfigurationManager():get_int32('boot_config', 'menu_x', 0); + tparty.menu_y = AshitaCore:GetConfigurationManager():get_int32('boot_config', 'menu_y', 0); + + -- Ensure the menu sizes have a valid resolution.. + if (tparty.menu_x <= 0) then + tparty.menu_x = tparty.window_x; + end + if (tparty.menu_y <= 0) then + tparty.menu_y = tparty.window_y; + end + + -- Calculate the scaling based on the resolution.. + tparty.scale_x = tparty.window_x / tparty.menu_x; + tparty.scale_y = tparty.window_y / tparty.menu_y; + + -- Create the target hp % text object.. + local f = AshitaCore:GetFontManager():Create(tparty.targethpp_str); + f:SetColor(0xFFFFFFFF); + f:SetFontFamily('Arial'); + f:SetFontHeight(8 * tparty.scale_y); + f:SetBold(true); + f:SetRightJustified(true); + f:SetPositionX(0); + f:SetPositionY(0); + f:SetText('0.0'); + f:SetLocked(true); + f:SetVisibility(true); + + -- Create the individual party member labels.. + for x = 0, 17 do + f = AshitaCore:GetFontManager():Create(string.format('%s%d', tparty.partytpp_str, x)); + f:SetColor(0xFFFFFFFF); + f:SetFontFamily('Arial'); + f:SetFontHeight(8 * tparty.scale_y); + f:SetBold(true); + f:SetRightJustified(true); + f:SetPositionX(0); + f:SetPositionY(0); + f:SetText(string.format('TP: %d', x)); + f:SetLocked(true); + f:SetVisibility(true); + end +end); + +---------------------------------------------------------------------------------------------------- +-- func: unload +-- desc: Event called when the addon is being unloaded. +---------------------------------------------------------------------------------------------------- +ashita.register_event('unload', function() + -- Cleanup the font objects.. + AshitaCore:GetFontManager():Delete(tparty.targethpp_str); + for x = 0, 17 do + AshitaCore:GetFontManager():Delete(string.format('%s%d', tparty.partytpp_str, x)); + end +end); + +---------------------------------------------------------------------------------------------------- +-- func: render +-- desc: Event called when the addon is being rendered. +---------------------------------------------------------------------------------------------------- +ashita.register_event('render', function() + -- Obtain needed information managers.. + local party = AshitaCore:GetDataManager():GetParty(); + local target = AshitaCore:GetDataManager():GetTarget(); + + -- Calculate offset position starting points.. + local posx = tparty.window_x - (107 * tparty.scale_x); + local posy = tparty.window_y - (34 * tparty.scale_y); + local curx = 0; + local cury = 0; + + -- Locate the players zone.. + local zone = party:GetMemberZone(0); + + -- Handle the players local party.. + for x = 0, 5 do + -- Get the current player slot font object.. + local f = AshitaCore:GetFontManager():Get(string.format('%s%d', tparty.partytpp_str, x)); + + -- Ensure the player is valid to render information for.. + if (x ~= 0 and zone ~= party:GetMemberZone(x) or party:GetMemberActive(x) == 0) then + f:SetVisibility(false); + else + -- Calculate the needed position of the font.. + curx = posx; + cury = posy - ((party:GetAllianceParty0MemberCount() - 1 - x) * (20 * tparty.scale_y)); + + -- Update the font object.. + local tp = party:GetMemberCurrentTP(x); + f:SetPositionX(curx); + f:SetPositionY(cury); + f:SetText(tostring(tp)); + f:SetVisibility(true); + if (tp >= 1000) then + f:SetColor(0xFF00FF00); + else + f:SetColor(0xFFFFFFFF); + end + end + end + + -- Calculate the alliance offsets.. + local posx_a0 = posx; + local posy_a0 = tparty.window_y - (389 * tparty.scale_y); + local posx_a1 = posx; + local posy_a1 = tparty.window_y - (288 * tparty.scale_y); + + -- Handle the alliance party information.. + for x = 6, 17 do + curx = 0; + cury = 0; + + -- Calculate the alliance position.. + if ((x >= 6) and (x < 12)) then + curx = posx_a0; + cury = posy_a0 + (((x - 6) * 16) * tparty.scale_y); + else + curx = posx_a1; + cury = posy_a1 + (((x - 12) * 16) * tparty.scale_y); + end + + -- Get the current player slot font object.. + local f = AshitaCore:GetFontManager():Get(string.format('%s%d', tparty.partytpp_str, x)); + local tp = party:GetMemberCurrentTP(x); + f:SetPositionX(curx); + f:SetPositionY(cury); + f:SetText(tostring(tp)); + if (tp >= 1000) then + f:SetColor(0xFF00FF00); + else + f:SetColor(0xFFFFFFFF); + end + + -- Set the visibility of the party members information.. + if (party:GetMemberActive(x) == 0 or zone ~= party:GetMemberZone(x)) then + f:SetVisibility(false); + else + f:SetVisibility(true); + end + end + + -- Obtain the current target.. + local target = ashita.ffxi.targets.get_target('t'); + if (target == nil) then + f = AshitaCore:GetFontManager():Get(tparty.targethpp_str); + f:SetText('0'); + f:SetVisibility(false); + else + curx = posx; + cury = posy - (((party:GetAllianceParty0MemberCount() - 1) * 21) + 34) * tparty.scale_y; + + f = AshitaCore:GetFontManager():Get(tparty.targethpp_str); + f:SetPositionX(curx); + f:SetPositionY(cury); + f:SetText(tostring(target.HealthPercent)); + f:SetVisibility(true); + end +end); \ No newline at end of file