-- This file is part of AutoInvite -- -- (C) 2014 Scott Yeskie (Sasky) -- -- This program 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; either version 2 of the License, or -- (at your option) any later version. -- -- This program 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 this program. If not, see <http://www.gnu.org/licenses/>. if AutoInvite == nil then AutoInvite = {} end AutoInvite.AddonId = "AutoInvite" ------------------------------------------------ --- Utility functions ------------------------------------------------ local function b(v) if v then return "T" else return "F" end end local function nn(val) if val == nil then return "NIL" else return val end end local function dbg(msg) if AutoInvite.debug then d("|c999999" .. msg) end end local function echo(msg) CHAT_SYSTEM.primaryContainer.currentBuffer:AddMessage("|CFFFF00"..msg) end AutoInvite.isCyrodiil = function(unit) if unit == nil then unit = "player" end dbg("Current zone: '" .. GetUnitZone(unit) .. "'") return GetUnitZone(unit) == "Cyrodiil" end AutoInvite.guildLookup = function(channel, acctName) local guildId = 0 if channel == CHAT_CHANNEL_GUILD_1 or channel == CHAT_CHANNEL_OFFICER_1 then guildId = GetGuildId(1) end if channel == CHAT_CHANNEL_GUILD_2 or channel == CHAT_CHANNEL_OFFICER_2 then guildId = GetGuildId(2) end if channel == CHAT_CHANNEL_GUILD_3 or channel == CHAT_CHANNEL_OFFICER_3 then guildId = GetGuildId(3) end if channel == CHAT_CHANNEL_GUILD_4 or channel == CHAT_CHANNEL_OFFICER_4 then guildId = GetGuildId(4) end if channel == CHAT_CHANNEL_GUILD_5 or channel == CHAT_CHANNEL_OFFICER_5 then guildId = GetGuildId(5) end if guildId == 0 then d("Error - couldn't invite on channel: " .. channel) end local aName for i=1,GetNumGuildMembers(guildId) do aName = GetGuildMemberInfo(guildId,i) if aName == acctName then local hasChar, charName, zone = GetGuildMemberCharacterInfo(guildId,i) -- Might use zone check to NOT auto-invite people outside current zone if leader in Cyrodiil if not hasChar then echo("Could not find player name for " .. acctName .. ". Please manually invite.") return "" end charName = charName:gsub("%^.+", "") if AutoInvite.cfg.cyrCheck then dbg("In Cyrodiil? " .. b(AutoInvite.isCyrodiil()) .. " / Zone: " .. zone) if AutoInvite.isCyrodiil() and zone ~= "Cyrodiil" then echo("Player " .. charName .. " is not in Cyrodiil but in " .. zone) echo("Blocking invite to prevent crashes.") return "" end end return charName end end end AutoInvite.kickTable = {} function AutoInvite.checkOffline() local now = GetTimeStamp() for i=1,GetGroupSize() do local tag = GetGroupUnitTagByIndex(i) if not IsUnitOnline(tag) then AutoInvite.kickTable[GetUnitName(tag)] = now end end end --Since KickByName doesn't seem to be working function AutoInvite.kickByName(name) AutoInvite.kickTable[name] = nil for i=1,GetGroupSize() do local tag = GetGroupUnitTagByIndex(i) if GetUnitName(tag) == name then GroupKick(tag) return end end d("No one named " .. name .. " found in group scan. Please manually kick.") end ------------------------------------------------ --- Event handlers ------------------------------------------------ AutoInvite.callback = function(_, messageType, from, message) if not AutoInvite.enabled or not AutoInvite.listening then return end --TODO: Switch to queue-based with timeouts so don't send out too many invites if GetGroupSize() >= AutoInvite.cfg.maxSize then echo("Group full. Disabling AutoInvite") AutoInvite.stopListening() end if string.lower(message) == AutoInvite.cfg.watchStr and from ~= nil and from ~= "" then if (messageType >= CHAT_CHANNEL_GUILD_1 and messageType <= CHAT_CHANNEL_OFFICER_5) then from = AutoInvite.guildLookup(messageType, from) if from == "" then return end end --TODO: Add friends list lookup from = from:gsub("%^.+", "") echo("Sending invite to " .. from) GroupInvite(from) GroupInviteByName(from) end --d("Checking message '" .. string.lower(message) .."' ?= '" .. AutoInvite.cfg.watchStr .."'") end AutoInvite.playerLeave = function() if AutoInvite.enabled and AutoInvite.cfg.restart and GetGroupSize() < AutoInvite.cfg.maxSize then echo("Now space in group. Restarted listening.") AutoInvite.startListening() end local unitName = GetUnitName(unitTag):gsub("%^.+", "") AutoInvite.kickTable[unitName] = nil end AutoInvite.offlineEvent = function(_, unitTag, connectStatus) local unitName = GetUnitName(unitTag):gsub("%^.+", "") if connectStatus then dbg(unitTag .. "/" .. unitName .. " has reconnected") AutoInvite.kickTable[unitName] = nil else dbg(unitTag .. "/" .. unitName .. " has disconnected") AutoInvite.kickTable[unitName] = GetTimeStamp() end end function AutoInvite.disbandEvent() if AutoInvite.enabled and AutoInvite.cfg.restart then echo("Group disbanded. Restarted listening.") AutoInvite.startListening() end end -- tick function: called every 15s function AutoInvite.kickCheck() if not AutoInvite.cfg.autoKick then return end local now = GetTimeStamp() --d("Check kick") for p,t in pairs(AutoInvite.kickTable) do local offTime = GetDiffBetweenTimeStamps(now, t) if offTime > AutoInvite.cfg.kickDelay then echo(" KICK: " .. p .. " offline for " .. offTime) AutoInvite.kickByName(p) else dbg(p .. " offline for " .. offTime .. " / " .. AutoInvite.cfg.kickDelay) end end end ------------------------------------------------ --- Invite queue ------------------------------------------------ -- FIFO invite queue AutoInvite.toInvite = { vals = {}, front = 1, back = 1, } local queue = AutoInvite.toInvite function queue:size() return queue.back - queue.front end function queue:push(val) local back = self.back self.vals[back] = val queue.back = back + 1 end function queue:pop() if self:size() == 0 then return nil end local front = self.front local retval = self.vals[front] self.vals[front] = nil self.front = front + 1 return retval end function queue:reset() self.vals = {} self.front = 1 self.back = 1 end -- Key: name / Value: timestamp AutoInvite.sentInvite = {} -- sentInvite not array form, so maintain count AutoInvite.sentInvites = 0 function AutoInvite:processQueue() local now = GetTimeStamp() local effectiveCount = GetGroupSize() + self.sentInvites local numInvites = math.min(queue:size(), self.cfg.maxSize - effectiveCount) for _ = 1,numInvites do local name = queue:pop() self.sentInvites = self.sentInvites + 1 self.sentInvite[name] = now GroupInviteByName(name) end end function AutoInvite:checkSentInvites() local now = GetTimeStamp() local members = {} for i=1,GetGroupSize() do local tag = GetGroupUnitTagByIndex(i) members[GetUnitName(tag)] = true end for name, time in pairs(self.sentInvite) do if members[name] or GetDiffBetweenTimeStamps(now, time) < 15 then self.sentInvite[name] = nil AutoInvite.sentInvites = AutoInvite.sentInvites - 1 end end end function AutoInvite:resetQueues() self.toInvite = {} queue:reset() end function AutoInvite:resetGroup() self:resetQueues() for i=1,GetGroupSize() do local tag = GetGroupUnitTagByIndex(i) local name = GetUnitName(tag) table.insert(self.toInvite, name) GroupDisband() GroupLeave() --for group leader bug end end ------------------------------------------------ --- Main interface ------------------------------------------------ AutoInvite.disable = function() AutoInvite.enabled = false AutoInvite.stopListening() EVENT_MANAGER:UnregisterForUpdate("AutoInviteKickCheck") EVENT_MANAGER:UnregisterForEvent(AutoInvite.AddonId, EVENT_GROUP_DISBANDED) end AutoInvite.stopListening = function() EVENT_MANAGER:UnregisterForEvent(AutoInvite.AddonId, EVENT_CHAT_MESSAGE_CHANNEL) AutoInvite.listening = false end AutoInvite.startListening = function() if not AutoInvite.enabled then AutoInvite.enabled = true AutoInvite.checkOffline() EVENT_MANAGER:RegisterForUpdate("AutoInviteKickCheck", 1000, AutoInvite.kickCheck) EVENT_MANAGER:RegisterForEvent(AutoInvite.AddonId, EVENT_GROUP_DISBANDED) end if not AutoInvite.listening and GetGroupSize() < AutoInvite.cfg.maxSize then --Add handler EVENT_MANAGER:RegisterForEvent(AutoInvite.AddonId, EVENT_CHAT_MESSAGE_CHANNEL, AutoInvite.callback) AutoInvite.listening = true end --First check. When option, will have to check option for disable echo("AutoInvite listening on string '" .. AutoInvite.cfg.watchStr .. "'") end ------------------------------------------------ --- Command line ------------------------------------------------ -- print command usage AutoInvite.help = function() echo("AutoInvite - command '/ai <str>'. Usage") echo(" '/ai foo' - autoInvite on 'foo' command'") echo(" '/ai help' - show this menu") echo(" '/ai' - turn off autoInvite (auto on group full)") return end --Main interaction switch SLASH_COMMANDS["/ai"] = function(str) if not str or str == "" or str == "help" then if not AutoInvite.listening or str == "help" then AutoInvite.help() return end echo("Disabling AutoInvite") AutoInvite.disable() return end AutoInvite.cfg.watchStr = string.lower(str) AutoInvite.startListening() AutoInviteUI.refresh() end ------------------------------------------------ --- Initialization ------------------------------------------------ AutoInvite.init = function() EVENT_MANAGER:UnregisterForEvent("AutoInviteInit", EVENT_PLAYER_ACTIVATED) if AutoInvite.initDone then return end AutoInvite.initDone = true local def = { maxSize = 24, restart = false, cyrCheck = false, autoKick = false, kickDelay = 300, watchStr = "", showPanel = true, } AutoInvite.cfg = ZO_SavedVars:NewAccountWide("AutoInviteSettings", 1.0, "config", def) EVENT_MANAGER:RegisterForEvent(AutoInvite.AddonId, EVENT_GROUP_MEMBER_LEFT, AutoInvite.playerLeave) EVENT_MANAGER:RegisterForEvent(AutoInvite.AddonId, EVENT_GROUP_MEMBER_CONNECTED_STATUS, AutoInvite.offlineEvent) AutoInvite.listening = false AutoInvite.enabled = false AutoInviteUI.init() end EVENT_MANAGER:RegisterForEvent("AutoInviteInit", EVENT_PLAYER_ACTIVATED, AutoInvite.init) -- Debug commands SLASH_COMMANDS["/aik"] = function() local now = GetTimeStamp() --d("Current timestamp: " .. GetTimeStamp()) --d("Offline players:") for p,t in pairs(AutoInvite.kickTable) do local offTime = now - t if offTime > 300 then dbg(" KICK: " .. p .. " offline for " .. now - t) else dbg(" " .. p .. " offline for " .. now - t) end end end SLASH_COMMANDS["/aidebug"] = function() echo("|cFF0000Beginning debug mode for AutoInvite.") AutoInvite.debug = true end SLASH_COMMANDS["/aikick"] = function() local now = GetTimeStamp() d("Offline players:") for p,t in pairs(AutoInvite.kickTable) do local offTime = now - t if offTime > AutoInvite.cfg.kickDelay then echo(" KICK: " .. p .. " offline for " .. now - t) GroupKickByName(p) else dbg(" " .. p .. " offline for " .. now - t) end end end