diff --git a/Chatreader.lua b/Chatreader.lua new file mode 100644 index 0000000..3190c04 --- /dev/null +++ b/Chatreader.lua @@ -0,0 +1,300 @@ +--This file is part of Game Master Genie. +--Copyright 2011-2014 Chocochaos + +--Game Master Genie is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 of the License. +--Game Master Genie is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +--You should have received a copy of the GNU General Public License along with Game Master Genie. If not, see . + +TicketTab = "General"; + +-- 1d2h3m4s to number in seconds +function GMGenie.timeStrToSeconds(timeStr) + local days = string.match(timeStr, "([0-9]*)d"); + if not days then + days = 0; + end + local hours = string.match(timeStr, "([0-9]*)h"); + if not hours then + hours = 0; + end + local minutes = string.match(timeStr, "([0-9]*)m"); + if not minutes then + minutes = 0; + end + local seconds = string.match(timeStr, "([0-9]*)s"); + if not seconds then + seconds = 0; + end + return (((((tonumber(days) * 24) + tonumber(hours)) * 60) + tonumber(minutes)) * 60) + tonumber(seconds); +end + +-- Read from chat +local ORIG_ChatFrame_MessageEventHandler = ChatFrame_MessageEventHandler; +function ChatFrame_MessageEventHandler(self, event, ...) + local arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15 = ...; + + local ActionTaken = false; + + -- development code to analize chat messages + --local excapedarg = string.gsub(arg1, "%|", "%%"); + --GMGenie.showGMMessage("1: " .. excapedarg); + + -- check for system messages of interest + if (event == "CHAT_MSG_SYSTEM") then + -- Showing list of open tickets whose creator is online. + if string.find(arg1, "Showing list of open tickets") then + Chronos.scheduleByName('ticketreupdate', 0.5, GMGenie.Tickets.update); + ActionTaken = true; + end + -- ticket list or reading ticket + local ticketId, name, createStr, lastModifiedStr, rest = string.match(arg1, "^%|cffaaffaaTicket%|r:%|cffaaccff%s([0-9]+).%|r%s%|cff00ff00Created%sby%|r:%|cff00ccff%s(.+)%|r%s%|cff00ff00Created%|r:%|cff00ccff%s([a-zA-Z0-9%s]+)%sago%|r%s%|cff00ff00Last%schange%|r:%|cff00ccff%s([a-zA-Z0-9%s]+)%sago%|r%s(.*)$"); + + if ticketId and name and createStr and lastModifiedStr then + ticketId = tonumber(ticketId); + local createStamp = GMGenie.timeStrToSeconds(createStr); + local lastModifiedStamp = GMGenie.timeStrToSeconds(lastModifiedStr); + if GMGenie.Tickets.tempList then + GMGenie.Tickets.listTicket(ticketId, name, createStr, createStamp, lastModifiedStr, lastModifiedStamp); + end + + local assignedTo = string.match(rest, "%|cff00ff00Assigned%sto%|r:%|cff00ccff%s([a-zA-Z]+)%|r%s"); + if assignedTo then + GMGenie.Tickets.setAssigned(ticketId, assignedTo); + end + local message = string.match(rest, "%|cff00ff00Ticket%sMessage%|r:%s%[(.-)%]%|r"); + local ticketCorrect = false; + if message then + ticketCorrect = GMGenie.Tickets.readTicket(ticketId, message); + else + local message = string.match(rest, "%|cff00ff00Ticket%sMessage%|r:%s%[(.*)"); + if message then + ticketCorrect = GMGenie.Tickets.readTicket(ticketId, message); + if ticketCorrect then + GMGenie.Tickets.messageOpen = true; + end + end + end + + local comment = string.match(rest, "%|cff00ff00GM%sComment%|r:%s%[(.*)%]%|r"); + if comment then + GMGenie.Tickets.comment(ticketId, comment); + end + + if ticketCorrect or GMGenie.Tickets.tempList then + ActionTaken = true; + end + elseif GMGenie.Tickets.messageOpen then + ActionTaken = true; + local message, rest = string.match(arg1, "(.-)%]%|r(.*)"); + if message then + GMGenie.Tickets.messageOpen = false; + GMGenie.Tickets.addLine(message); + else + if string.find(arg1, "%]%|r") then + rest = string.match(arg1, "%]%|r(.*)"); + GMGenie.Tickets.messageOpen = false; + else + GMGenie.Tickets.addLine(arg1); + end + end + + if rest then + local comment = string.match(rest, "%|cff00ff00GM%sComment%|r:%s%[(.*)%]%|r"); + if comment and GMGenie.Tickets.currentTicket['ticketId'] then + GMGenie.Tickets.comment(GMGenie.Tickets.currentTicket['ticketId'], comment); + end + end + else + -- Ticket edited + local name, ticketId = string.match(arg1, "^%|cff00ff00Character%|r%|cffff00ff%s([a-zA-Z]+)%s%|r%|cff00ff00edited%shis/her%sticket:%|r%|cffff00ff%s([0-9]+).%|r$"); + if name and ticketId then + if GMGenie.Tickets.isOpen() then + GMGenie.Tickets.refresh(); + end + ActionTaken = true; + end + -- Ticket abandoned + local name, ticketId = string.match(arg1, "^%|cff00ff00Character%|r%|cffff00ff%s([a-zA-Z]+)%s%|r%|cff00ff00abandoned%sticket%sentry:%|r%|cffff00ff%s([0-9]+).%|r$"); + if name and ticketId then + if GMGenie.Tickets.isOpen() then + GMGenie.Tickets.refresh(); + end + ActionTaken = true; + end + -- New Ticket + local name, ticketId = string.match(arg1, "^%|cff00ff00New%sticket%sfrom%|r%|cffff00ff%s([a-zA-Z]+).%|r%s%|cff00ff00Ticket%sentry:%|r%|cffff00ff%s([0-9]+).%|r$"); + if name and ticketId then + if GMGenie.Tickets.isOpen() then + GMGenie.Tickets.refresh(); + end + ActionTaken = true; + end + end + + -- read coords from chat + if GMGenie.Spawns.waitingForGps == 1 then + if string.find(arg1, "^You are outdoors") or string.find(arg1, "^no VMAP available for area info") then + ActionTaken = true; + end + local map = string.match(arg1, "^Map:%s([0-9]+)%s"); + if map then + GMGenie.Spawns.waitingForGps = 2; + GMGenie.Spawns.setMap(map); + ActionTaken = true; + end + end + if GMGenie.Spawns.waitingForGps == 2 then + local x, y, z, o = string.match(arg1, "^X:%s([0-9%.%-]+)%sY:%s([0-9%.%-]+)%sZ:%s([0-9%.%-]+)%sOrientation:%s([0-9%.%-]+)$"); + if x and y and z and o then + GMGenie.Spawns.waitingForGps = 3; + GMGenie.Spawns.move(x, y, z, o); + ActionTaken = true; + end + end + if GMGenie.Spawns.waitingForGps == 3 then + if string.find(arg1, "^grid") or string.find(arg1, "^ ZoneX") then + ActionTaken = true; + end + if string.find(arg1, "^GroundZ") then + GMGenie.Spawns.waitingForGps = 0; + ActionTaken = true; + end + end + + if GMGenie.Spy.waitingForPin or GMGenie.Macros.Discipline.IpBan.waitingForPin then + if string.find(arg1, "Player not found!") then + GMGenie.Spy.waitingForPin = false; + GMGenie.Macros.Discipline.IpBan.waitingForPin = false; + else + if GMGenie.Spy.waitingForPin then + local offline, name1, name2, guid = string.match(arg1, "Player ?(.*) %|cffffffff%|Hplayer:(.*)%|h%[(.*)%]%|h%|r %(guid: (.*)%)"); + local phase = string.match(arg1, "Phase: (.*)"); + local account, accountId, gmLevel = string.match(arg1, "Account: (.*) %(ID: (.*)%), GMLevel: (.*)"); + local login, failedLogins = string.match(arg1, "Last Login: (.*) %(Failed Logins: (.*)%)"); + local os, latency, email = string.match(arg1, "OS: (.*) %- Latency: (.*) ms %- Mail: (.*)"); + local ip, locked = string.match(arg1, "Last IP: (.*) %(Locked: (.*)%)"); + local level, xpCurrent, xpMax = string.match(arg1, "Level: (.*) %((.*)/(.*) XP"); + local race, class = string.match(arg1, "Race: (.*), (.*)"); + local alive = string.match(arg1, "Alive %?: (.*)"); + local money = string.match(arg1, "Money: (.*)"); + local map, area, zone = string.match(arg1, "Map: (.*), Area: (.*), Zone: (.*)"); + local guild, guildId = string.match(arg1, "Guild: (.*) %(ID: (.*)%)"); + local guildRank = string.match(arg1, "Rank: (.*)"); + local playedTime = string.match(arg1, "Played time: (.*)"); + + + if offline then + GMGenie.Spy.processPin01(offline, name1, guid, arg1); + ActionTaken = true; + end + if phase then + GMGenie.Spy.processPin02(phase, arg1); + ActionTaken = true; + end + if account then + GMGenie.Spy.processPin03(account, accountId, gmLevel, arg1); + ActionTaken = true; + end + if login then + GMGenie.Spy.processPin04(login, failedLogins, arg1); + ActionTaken = true; + end + if os then + GMGenie.Spy.processPin05(os, latency, email, arg1); + ActionTaken = true; + end + if ip then + GMGenie.Spy.processPin06(ip, locked, arg1); + ActionTaken = true; + end + if level then + GMGenie.Spy.processPin07(level, xpCurrent, xpMax, arg1); + ActionTaken = true; + end + if race then + GMGenie.Spy.processPin08(race, class, arg1); + ActionTaken = true; + end + if alive then + GMGenie.Spy.processPin09(alive, arg1); + ActionTaken = true; + end + if money then + GMGenie.Spy.processPin10(money, arg1); + ActionTaken = true; + end + if map then + GMGenie.Spy.processPin11(map, area, zone, arg1); + ActionTaken = true; + end + if guild then + GMGenie.Spy.processPin12(guild, guildId, arg1); + ActionTaken = true; + end + if guildRank then + GMGenie.Spy.processPin13(guildRank, arg1); + ActionTaken = true; + end + if playedTime then + GMGenie.Spy.processPin14(playedTime, arg1); + ActionTaken = true; + end + else + local ip, locked = string.match(arg1, "Last IP: (.*) %(Locked: (.*)%)") + + if ip then + GMGenie.Macros.Discipline.IpBan.processPin(ip); + ActionTaken = true; + end + end + end + end + + if GMGenie.Spawns.waitingForObject then + local name, guid, id = string.match(arg1, "%|cffffffff%|Hgameobject:.*%|h%[(.*)%]%|h%|r%sGUID:%s(.*)%sID:%s(.*)"); + if name and guid and id then + GMGenie.Spawns.deleteObject(name, guid, id); + ActionTaken = true; + elseif string.find(arg1, "X:%s.*%sY:%s.*%sZ:%s.*%sMapId:%s.*") or string.find(arg1, "Orientation:%s.*") or string.find(arg1, "Phasemask%s.*") then + ActionTaken = true; + elseif string.find(arg1, "SpawnTime:%sFull:.*%sRemain:.*") then + ActionTaken = true; + GMGenie.Spawns.waitingForObject = false; + elseif string.find(arg1, "Nothing found!") then + GMGenie.Spawns.waitingForObject = false; + end + end + + if GMGenie.Spawns.waitingForObjectDelete then + if string.find(arg1, "Game Object %(GUID: .*%) removed") then + ActionTaken = true; + GMGenie.Spawns.waitingForObjectDelete = false; + end + end + + local charName = UnitName("player"); + if string.match(arg1, "%|cffffffff%|Hplayer:" .. charName .. "%|h%[" .. charName .. "%]%|h%|r%'s Fly Mode on") then + GMGenie.Hud.flyStatus(true); + elseif string.match(arg1, "%|cffffffff%|Hplayer:" .. charName .. "%|h%[" .. charName .. "%]%|h%|r%'s Fly Mode off") then + GMGenie.Hud.flyStatus(false); + elseif arg1 == "Accepting Whisper: ON" or arg1 == "Accepting Whisper: on" then + GMGenie.Hud.whisperStatus(true); + elseif arg1 == "Accepting Whisper: OFF" or arg1 == "Accepting Whisper: off" then + GMGenie.Hud.whisperStatus(false); + elseif arg1 == "You are: visible" then + GMGenie.Hud.visibilityStatus(true); + elseif arg1 == "You are: invisible" then + GMGenie.Hud.visibilityStatus(false); + end + + local characterName = string.match(arg1, "%|cFFFFBF00%[AntiCheat%]%:%|cFFFFFFFF %[(.*)%] %|cFF00FFFFdetected as possible cheater%."); + if characterName then + arg1 = "|cFFFFBF00[AntiCheat]:|r |Hanticheat:" .. characterName .. "|h[" .. characterName .. "]|h detected as possible cheater."; + end + end + + -- if nothing was done, just display the message + if not ActionTaken then + ORIG_ChatFrame_MessageEventHandler(self, event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15); + end +end \ No newline at end of file diff --git a/Chronos/Chronos.lua b/Chronos/Chronos.lua new file mode 100644 index 0000000..d36e99f --- /dev/null +++ b/Chronos/Chronos.lua @@ -0,0 +1,860 @@ +--[[ +-- +-- Chronos +-- Keeper of Time +-- +-- By AnduinLothar, Alexander Brazie, and Thott +-- +-- Chronos manages time. You can schedule a function to be called +-- in X seconds, with or without an id. You can request a timer, +-- which tracks the elapsed duration since the timer was started. +-- +-- To use as an embeddable addon: +-- - Put the Chronos folder inside your Interface/AddOns// folder. +-- - Add Chronos\Chronos.xml to your toc or load it in your xml before your localization files. +-- - Add Chronos to the OptionalDeps in your toc +-- +-- To use as an addon library: +-- - Put the Chronos folder inside your Interface/AddOns/ folder. +-- - Add Chronos to the Dependencies in your toc +-- +-- Please see below or see http://www.wowwiki.com/Chronos_(addon) for details. +-- +-- $LastChangedBy: karlkfi $ +-- $Date: 2006-12-21 06:19:14 -0600 (Thu, 21 Dec 2006) $ +-- $Rev: 4467 $ +-- +--]] + +local CHRONOS_REV = 2.12; + +local isBetterInstanceLoaded = (Chronos and Chronos.version and Chronos.version >= CHRONOS_REV); + +if (not isBetterInstanceLoaded) then + + if (not Chronos) then + Chronos = {}; + end + + Chronos.version = CHRONOS_REV; + + ------------------------------------------------------------------------------ + --[[ Variables ]] -- + ------------------------------------------------------------------------------ + + Chronos.online = true; + + CHRONOS_DEBUG = false; + CHRONOS_DEBUG_WARNINGS = false; + + -- Chronos Data + if (not ChronosData) then + ChronosData = {}; + end + + -- Chronos Recycled Tables Storage + if (not Chronos.tables) then + Chronos.tables = {}; + end + + -- Initialize the Timers + if (not ChronosData.timers) then + ChronosData.timers = {}; + end + + -- Initialize the perform-over-time task list + if (not ChronosData.tasks) then + ChronosData.tasks = {}; + end + + -- Maximum items per frame + Chronos.MAX_TASKS_PER_FRAME = 100; + + -- Maximum steps per task + Chronos.MAX_STEPS_PER_TASK = 300; + + -- Maximum time delay per frame + Chronos.MAX_TIME_PER_STEP = .3; + + Chronos.emptyTable = {}; + + ------------------------------------------------------------------------------ + --[[ User Functions ]] -- + ------------------------------------------------------------------------------ + + --[[ + -- debug(boolean) + -- + -- Toggles debug mode + ]] -- + function Chronos.debug(enable) + if (enable) then + ChronosFrame:SetScript("OnUpdate", Chronos.OnUpdate_Debug); + CHRONOS_DEBUG = true; + CHRONOS_DEBUG_WARNINGS = true; + else + ChronosFrame:SetScript("OnUpdate", Chronos.OnUpdate_Quick); + CHRONOS_DEBUG = false; + CHRONOS_DEBUG_WARNINGS = false; + end + end + + --[[ + -- Scheduling functions + -- Parts rewritten by AnduinLothar for efficiency + -- Parts rewritten by Thott for speed + -- Written by Alexander + -- Original by Thott + -- + -- Usage: Chronos.schedule(when,handler,arg1,arg2,etc) + -- + -- After seconds pass (values less than one and fractional values are + -- fine), handler is called with the specified arguments, i.e.: + -- handler(arg1,arg2,etc) + -- + -- If you'd like to have something done every X seconds, reschedule + -- it each time in the handler or preferably use scheduleRepeating. + -- + -- Also, please note that there is a limit to the number of + -- scheduled tasks that can be performed per xml object at the + -- same time. + --]] + function Chronos.schedule(when, handler, ...) + if (not Chronos.online) then + return; + end + if (not handler) then + Chronos.printError("ERROR: nil handler passed to Chronos.schedule()"); + return; + end + + --local memstart = collectgarbage("count"); + -- -- Assign an id + -- local id = ""; + -- if ( not this ) then + -- id = "Keybinding"; + -- else + -- id = self:GetName(); + -- end + -- if ( not id ) then + -- id = "_DEFAULT"; + -- end + -- if ( not when ) then + -- Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS", "Chronos Error Detection: ", id , " has sent no interval for this function. ", when ); + -- return; + -- end + + -- -- Ensure we're not looping ChronosFrame + -- if ( id == "ChronosFrame" and ChronosData.lastID ) then + -- id = ChronosData.lastID; + -- end + + -- use recycled tables to avoid excessive garbage collection -AnduinLothar + --tinsert(ChronosData.sched, Chronos.getTable()) + --local i = #ChronosData.sched + local recTable = Chronos.getTable() + -- ChronosData.sched[i].id = id; + recTable.time = when + GetTime(); + recTable.handler = handler; + recTable.args = Chronos.getArgTable(...); + + -- task list is a heap, add new + local i = #ChronosData.sched + 1 + while (i > 1) do + if (recTable.time < ChronosData.sched[i - 1].time) then + i = i - 1; + else + break + end + end + tinsert(ChronosData.sched, i, recTable) + + -- Debug print + --Chronos.printDebugError("CHRONOS_DEBUG", "Scheduled "..handler.." in "..when.." seconds from "..id ); + --Chronos.printError("Memory change in schedule: "..memstart.."->"..memend.." = "..memend-memstart); + end + + + --[[ + -- Chronos.scheduleByName(name, delay, function, arg1, ... ); + -- + -- Same as Chronos.schedule, except it takes a schedule name argument. + -- Only one event can be scheduled with a given name at any one time. + -- Thus if one exists, and another one is scheduled, the first one + -- is deleted, then the second one added. + -- + --]] + function Chronos.scheduleByName(name, when, handler, ...) + if (not name) then + Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS", "Chronos Error Detection: No name specified to Chronos.scheduleByName"); + return; + end + local namedSchedule = ChronosData.byName[name]; + if (namedSchedule and handler) then + Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS", "Chronos Error Detection: scheduleByName is reasigning \"" .. name .. "\"."); + Chronos.releaseTable(ChronosData.byName[name]); + else + if (not handler) then + if (not namedSchedule) then + Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS", "Chronos Error Detection: No handler specified to Chronos.scheduleByName, no previous entry found for scheduled entry \"" .. name .. "\"."); + return; + end + if (not namedSchedule.handler) then + Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS", "Chronos Error Detection: No handler specified to Chronos.scheduleByName, no handler could be found in previous entry of \"" .. name .. "\" either."); + return; + end + handler = namedSchedule.handler; + Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS", "Chronos: scheduleByName is updating \"" .. name .. "\" to time: " .. when); + else + Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS", "Chronos: scheduleByName is asigning \"" .. name .. "\"."); + end + end + ChronosData.byName[name] = Chronos.getTable(); + namedSchedule = ChronosData.byName[name]; + namedSchedule.time = when + GetTime() + namedSchedule.handler = handler; + namedSchedule.args = Chronos.getArgTable(...); + end + + --[[ + -- unscheduleByName(name); + -- + -- Removes an entry that was created with scheduleByName() + -- + -- Args: + -- name - the name used + -- + --]] + function Chronos.unscheduleByName(name) + if (not Chronos.online) then + return; + end + if (not name) then + Chronos.printError("No name specified to Chronos.unscheduleByName"); + return; + end + if (ChronosData.byName[name]) then + Chronos.releaseTable(ChronosData.byName[name]); + ChronosData.byName[name] = nil; + end + + -- Debug print + --Chronos.printDebugError("CHRONOS_DEBUG", "Cancelled scheduled timer of name ",name); + end + + --[[ + -- unscheduleRepeating(name); + -- Mirrors unscheduleByName for backwards compatibility + --]] + Chronos.unscheduleRepeating = Chronos.unscheduleByName; + + --[[ + -- isScheduledByName(name) + -- Returns the amount of time left if it is indeed scheduled by name! + -- + -- returns: + -- number - time remaining + -- nil - not scheduled + -- + --]] + function Chronos.isScheduledByName(name) + if (not Chronos.online) then + return; + end + if (not name) then + Chronos.printError("No name specified to Chronos.isScheduledByName " .. (self:GetName() or "unknown")); + return; + end + local namedSchedule = ChronosData.byName[name]; + if (namedSchedule) then + return namedSchedule.time - GetTime(); + end + + -- Debug print + --Chronos.printDebugError("CHRONOS_DEBUG", "Did not find timer of name ",name); + return nil; + end + + --[[ + -- isScheduledRepeating(name) + -- Mirrors isScheduledByName for backwards compatibility + --]] + Chronos.isScheduledRepeating = Chronos.isScheduledByName; + + --[[ + -- Chronos.scheduleRepeating(name, delay, function); + -- + -- Same as Chronos.scheduleByName, except it repeats without recalling and takes no arguments. + -- + --]] + function Chronos.scheduleRepeating(name, when, handler) + if (not name) then + Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS", "Chronos Error Detection: No name specified to Chronos.scheduleRepeating"); + return; + end + local namedSchedule = ChronosData.byName[name]; + if (namedSchedule and handler) then + Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS", "Chronos Error Detection: scheduleRepeating is reasigning " .. name); + Chronos.releaseTable(ChronosData.byName[name]); + else + if (not handler) then + if (not namedSchedule) then + Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS", "Chronos Error Detection: No handler specified to Chronos.scheduleRepeating, no previous entry found for scheduled entry '" .. name .. "'."); + return; + end + if (not namedSchedule.handler) then + Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS", "Chronos Error Detection: No handler specified to Chronos.scheduleRepeating, no handler could be found in previous entry '" .. name .. "' either."); + return; + end + handler = namedSchedule.handler; + Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS", "Chronos: scheduleRepeating is updating '" .. name .. "' to time: " .. when); + else + Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS", "Chronos: scheduleRepeating is asigning '" .. name .. "'."); + end + end + ChronosData.byName[name] = Chronos.getTable(); + namedSchedule = ChronosData.byName[name]; + namedSchedule.time = when + GetTime(); + namedSchedule.period = when; + namedSchedule.handler = handler; + namedSchedule.repeating = true; + end + + --[[ + -- Chronos.flushByName(name, when); + -- + -- Updates the ByName or Repeating event to flush at the time specified. If no time is specified flush will be immediate. If it is a Repeating event the timer will be reset. + -- + --]] + function Chronos.flushByName(name, when) + if (not name) then + Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS", "Chronos Error Detection: No name specified to Chronos.flushByName"); + return; + elseif (not ChronosData.byName[name]) then + Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS", "Chronos Error Detection: no previous entry found for Chronos.flushByName entry '" .. name .. "'."); + return; + end + if (not when) then + Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS", "Chronos: flushing '" .. name .. "'."); + when = GetTime(); + else + Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS", "Chronos: flushing '" .. name .. "' in " .. when .. " seconds."); + when = when + GetTime(); + end + ChronosData.byName[name].time = when; + end + + --[[ + -- Chronos.startTimer([ID]); + -- Starts a timer on a particular + -- + -- Args + -- ID - optional parameter to identify who is asking for a timer. + -- + -- If ID does not exist, self:GetName() is used. + -- + -- When you want to get the amount of time passed since startTimer(ID) is called, + -- call getTimer(ID) and it will return the number in seconds. + -- + --]] + function Chronos.startTimer(id) + if (not Chronos.online) then + return; + end + + if (not id) then + id = self:GetName(); + end + + -- Create a table for this id's timers + if (not ChronosData.timers[id]) then + ChronosData.timers[id] = Chronos.getTable(); + end + + -- Clear out an entry if the table is too big. + if (#ChronosData.timers[id] > Chronos.MAX_TASKS_PER_FRAME) then + Chronos.printError("Too many Chronos timers created for id " .. tostring(id)); + return; + end + + -- Add a new timer entry + table.insert(ChronosData.timers[id], GetTime()); + end + + + --[[ + -- endTimer([id]); + -- + -- Ends the timer and returns the amount of time passed. + -- + -- args: + -- id - ID for the timer. If not specified, then ID will + -- be self:GetName() + -- + -- returns: + -- (Number delta, Number start, Number end) + -- + -- delta - the amount of time passed in seconds. + -- start - the starting time + -- now - the time the endTimer was called. + --]] + + function Chronos.endTimer(id) + if (not Chronos.online) then + return; + end + + if (not id) then + id = self:GetName(); + end + + if (not ChronosData.timers[id] or #ChronosData.timers[id] == 0) then + return nil; + end + + local now = GetTime(); + + -- Grab the last timer called + local startTime = tremove(ChronosData.timers[id]); + + return (now - startTime), startTime, now; + end + + + --[[ + -- getTimer([id]); + -- + -- Gets the timer and returns the amount of time passed. + -- Does not terminate the timer. + -- + -- args: + -- id - ID for the timer. If not specified, then ID will + -- be self:GetName() + -- + -- returns: + -- (Number delta, Number start, Number end) + -- + -- delta - the amount of time passed in seconds. + -- start - the starting time + -- now - the time the endTimer was called. + --]] + + function Chronos.getTimer(id) + if (not Chronos.online) then + return; + end + + if (not id) then + id = self:GetName(); + end + + local now = GetTime(); + if (not ChronosData.timers[id] or #ChronosData.timers[id] == 0) then + return 0, 0, now; + end + + -- Grab the last timer called + local startTime = ChronosData.timers[id][#ChronosData.timers[id]]; + + return (now - startTime), startTime, now; + end + + --[[ + -- isTimerActive([id]) + -- returns true if the timer exists. + -- + -- args: + -- id - ID for the timer. If not specified, then ID will + -- be self:GetName() + -- + -- returns: + -- true - exists + -- false - does not + --]] + function Chronos.isTimerActive(id) + if (not Chronos.online) then + return; + end + + if (not id) then + id = self:GetName(); + end + + -- Create a table for this id's timers + if (not ChronosData.timers[id]) then + return false; + end + + return true; + end + + --[[ + -- getTime() + -- + -- returns the Chronos internal elapsed time. + -- + -- returns: + -- (elapsedTime) + -- + -- elapsedTime - time in seconds since Chronos initialized + --]] + function Chronos.getTime() + return ChronosData.elapsedTime; + end + + --[[ + -- Chronos.afterInit(func, ...) + -- Performs func after the game has truely started. + -- By Thott + --]] + function Chronos.afterInit(func, ...) + local id; + if (this) then + id = self:GetName(); + else + id = "unknown"; + end + --if(id == "SkyFrame") then + -- Chronos.printError("Ignoring Sky init"); + -- return; + --end + if (ChronosData.initialized) then + func(...); + else + if (not ChronosData.afterInit) then + ChronosData.afterInit = Chronos.getTable(); + Chronos.schedule(0.2, Chronos.initCheck); + end + local recTable = Chronos.getTable(); + recTable.func = func; + recTable.args = Chronos.getArgTable(...); + recTable.id = id; + tinsert(ChronosData.afterInit, recTable); + end + end + + + ------------------------------------------------------------------------------ + --[[ Table Recycling ]] -- + ------------------------------------------------------------------------------ + function Chronos.getTable(...) + local stack = Chronos.tables; + if (not stack) then + Chronos.tables = {}; + stack = Chronos.tables; + return {}; + end + local recTable; + if (#stack >= 1) then + recTable = tremove(stack) + else + recTable = {}; + end + for i = 1, select("#", ...) do + recTable[i] = select(i, ...); + end + return recTable; + end + + -- Release a table to be nilled and used again. + -- Optionally pass in an unpack(...) as the 2nd arg so that you can return the args: + -- return Chronos.releaseTable(t1, unpack(t1)) + function Chronos.releaseTable(t1, ...) + if (type(t1) ~= "table") then + return; + end + + local stack = Chronos.tables; + if (not stack) then + Chronos.tables = {}; + stack = Chronos.tables; + end + + for k, v in pairs(t1) do + t1[k] = nil; + end + + tinsert(stack, t1); + return ...; + end + + + ------------------------------------------------------------------------------ + --[[ Helpers Functions ]] -- + ------------------------------------------------------------------------------ + function Chronos.getArgTable(...) + if (select('#', ...) == 0) then + return Chronos.emptyTable; + else + return Chronos.getTable(...); + end + end + + function Chronos.run(func, args) + if (func) then + if (args) then + return func(unpack(args)); + else + return func(); + end + end + end + + function Chronos.printError(text) + ChatFrame1:AddMessage(text, RED_FONT_COLOR.r, RED_FONT_COLOR.g, RED_FONT_COLOR.b, 1.0, UIERRORS_HOLD_TIME); + end + + function Chronos.printDebugError(var, text) + if (var) and (getglobal(var)) then + Chronos.printError(text); + end + end + + ------------------------------------------------------------------------------ + --[[ Frame Script Helpers ]] -- + ------------------------------------------------------------------------------ + function Chronos.chatColorsInit() + ChronosData.chatColorsInitialized = true; + ChronosFrame:UnregisterEvent("UPDATE_CHAT_COLOR"); + end + + function Chronos.initCheck() + if (not ChronosData.initialized) then + if (UnitName("player") and UnitName("player") ~= UKNOWNBEING and UnitName("player") ~= UNKNOWNBEING and UnitName("player") ~= UNKNOWNOBJECT and ChronosData.variablesLoaded and ChronosData.enteredWorld and ChronosData.chatColorsInitialized) then + ChronosData.initialized = true; + Chronos.schedule(1, Chronos.initCheck); + return; + else + Chronos.schedule(0.2, Chronos.initCheck); + return; + end + end + if (ChronosData.afterInit) then + local i = ChronosData.afterInit_i; + if (not i) then + i = 1; + end + ChronosData.afterInit_i = i + 1; + --Chronos.printError("afterInit: processing ",i," of ",ChronosData.afterInit.n," initialization functions, id: ",ChronosData.afterInit[i].id); + Chronos.run(ChronosData.afterInit[i].func, ChronosData.afterInit[i].args); + if (i == #ChronosData.afterInit) then + for i, v in ipairs(ChronosData.afterInit) do + Chronos.releaseTable(v); + end + Chronos.releaseTable(ChronosData.afterInit); + ChronosData.afterInit = nil; + ChronosData.afterInit_i = nil; + else + Chronos.schedule(0.1, Chronos.initCheck); + return; + end + end + end + + --[[ + -- Sends a chat command through the standard editbox + --]] + function Chronos.SendChatCommand(command) + local text = ChatFrameEditBox:GetText(); + ChatFrameEditBox:SetText(command); + ChatEdit_SendText(ChatFrameEditBox); + ChatFrameEditBox:SetText(text); + end + + function Chronos.RegisterSlashCommands() + --Needs to be able Variables load if you want to use Sky + local chronosFunc = function(msg) + local _, _, seconds, command = string.find(msg, "([%d\.]+)%s+(.*)"); + if (seconds and command) then + Chronos.schedule(seconds, Chronos.SendChatCommand, command); + else + Chronos.printError(SCHEDULE_USAGE1); + Chronos.printError(SCHEDULE_USAGE2); + end + end + if (Satellite) then + Satellite.registerSlashCommand({ + id = "Schedule"; + commands = SCHEDULE_COMM; + onExecute = chronosFunc; + helpText = SCHEDULE_DESC; + replace = true; + }); + else + SlashCmdList["CHRONOS_SCHEDULE"] = chronosFunc; + for i = 1, #SCHEDULE_COMM do setglobal("SLASH_CHRONOS_SCHEDULE" .. i, SCHEDULE_COMM[i]); end + end + end + + ------------------------------------------------------------------------------ + --[[ Frame Scripts ]] -- + ------------------------------------------------------------------------------ + function Chronos.OnLoad() + Chronos.framecount = 0; + + if (not ChronosData.byName) then + ChronosData.byName = {}; + end + if (not ChronosData.repeating) then + ChronosData.repeating = {}; + end + if (not ChronosData.sched) then + ChronosData.sched = {}; + end + ChronosData.elapsedTime = 0; + + Chronos.afterInit(Chronos.RegisterSlashCommands); + end + + function Chronos.OnEvent(self, event, ...) + if (event == "ADDON_LOADED") then + ChronosData.variablesLoaded = true; + ChronosFrame:Show(); + elseif (event == "PLAYER_ENTERING_WORLD") then + ChronosData.enteredWorld = true; + Chronos.online = true; + elseif (event == "PLAYER_LEAVING_WORLD") then + Chronos.online = false; + elseif (event == "UPDATE_CHAT_COLOR") then + Chronos.scheduleByName("ChronosAfterChatColorInit", 1, Chronos.chatColorsInit); + end + end + + function Chronos.OnUpdate_Quick(self, arg1) + if (not Chronos.online) then + return; + end + if (not ChronosData.variablesLoaded) then + return; + end + + if (ChronosData.elapsedTime) then + ChronosData.elapsedTime = ChronosData.elapsedTime + arg1; + else + ChronosData.elapsedTime = arg1; + end + + -- Execute scheduled tasks that are ready, pulling them off the front of the list queue. + local now = GetTime(); + local i; + local task; + while (#ChronosData.sched > 0) do + if (not ChronosData.sched[1].time) then + --Sea.io.printTable(ChronosData.sched[1]); + tremove(ChronosData.sched, 1); + elseif (ChronosData.sched[1].time <= now) then + task = tremove(ChronosData.sched, 1); + Chronos.run(task.handler, task.args); + Chronos.releaseTable(task); + else + break; + end + end + + -- Execute named scheduled tasks that are ready. + local k, v = next(ChronosData.byName); + local newK, newV; + while (k ~= nil) do + newK, newV = next(ChronosData.byName, k); + if (not v.time) then + --Sea.io.printTable(v); + ChronosData.byName[k] = nil; + elseif (v.time <= now) then + if (v.repeating) then + ChronosData.byName[k].time = now + v.period; + v.handler(); + else + Chronos.run(v.handler, v.args); + Chronos.releaseTable(ChronosData.byName[k]); + ChronosData.byName[k] = nil; + end + end + k, v = newK, newV; + end + end + + function Chronos.OnUpdate_Debug(self, arg1) + if (not Chronos.online) then + return; + end + if (not ChronosData.variablesLoaded) then + return; + end + local memstart = collectgarbage("count"); + + if (ChronosData.elapsedTime) then + ChronosData.elapsedTime = ChronosData.elapsedTime + arg1; + else + ChronosData.elapsedTime = arg1; + end + + local now = GetTime(); + local i; + local task; + -- Execute scheduled tasks that are ready, popping them off the heap. + while (#ChronosData.sched > 0) do + if (ChronosData.sched[1].time <= now) then + task = tremove(ChronosData.sched, 1); + Chronos.run(task.handler, task.args); + Chronos.releaseTable(task); + else + break; + end + end + + local memend = collectgarbage("count"); + if (memend - memstart > 0) then + Chronos.printError("gcmemleak from ChronosData.sched in OnUpdate: " .. (memend - memstart)); + end + + -- Execute named scheduled tasks that are ready. + memstart = memend; + local k, v = next(ChronosData.byName); + local newK, newV; + while (k ~= nil) do + newK, newV = next(ChronosData.byName, k); + if (v.time <= now) then + local m = collectgarbage("count"); + if (v.repeating) then + ChronosData.byName[k].time = now + v.period; + v.handler(); + else + Chronos.run(v.handler, v.args); + Chronos.releaseTable(ChronosData.byName[k]); + ChronosData.byName[k] = nil; + end + local mm = collectgarbage("count"); + memstart = memstart + mm - m; + end + k, v = newK, newV; + end + + memend = collectgarbage("count"); + if (memend - memstart > 0) then + Chronos.printError("gcmemleak from ChronosData.byName in OnUpdate: " .. (memend - memstart)); + end + end + + ------------------------------------------------------------------------------ + --[[ Frame Script Assignment ]] -- + ------------------------------------------------------------------------------ + + Chronos.OnUpdate_Quick(); + + --Event Driver + if (not ChronosFrame) then + CreateFrame("Frame", "ChronosFrame"); + end + ChronosFrame:Hide(); + --Event Registration + ChronosFrame:RegisterEvent("ADDON_LOADED"); + ChronosFrame:RegisterEvent("PLAYER_ENTERING_WORLD"); + ChronosFrame:RegisterEvent("PLAYER_LEAVING_WORLD"); + ChronosFrame:RegisterEvent("UPDATE_CHAT_COLOR"); + --Frame Scripts + ChronosFrame:SetScript("OnEvent", Chronos.OnEvent); + ChronosFrame:SetScript("OnUpdate", Chronos.OnUpdate_Quick); + --OnLoad Call + Chronos.OnLoad(); +end + diff --git a/Chronos/Chronos.toc b/Chronos/Chronos.toc new file mode 100644 index 0000000..52d3cb2 --- /dev/null +++ b/Chronos/Chronos.toc @@ -0,0 +1,7 @@ +## Interface: 20400 +## Title: Chronos +## Notes: Embeddable Time Keeping and Scheduling System +## Notes-deDE: Sammlung nützlicher Funktionen zum Zeitmanagement, welche von einigen anderen AddOns verwendet werden. +## Author: Alexander Brazie, Thott, AnduinLothar +## X-Website: http://www.wowinterface.com/downloads/info4328-Chronos.html +Chronos.xml \ No newline at end of file diff --git a/Chronos/Chronos.xml b/Chronos/Chronos.xml new file mode 100644 index 0000000..f727726 --- /dev/null +++ b/Chronos/Chronos.xml @@ -0,0 +1,24 @@ + + + + + + + + +