SHARE
TWEET

pChat

Antisenil Dec 6th, 2019 3 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. --[[
  2. Author: DesertDwellers
  3. Filename: pChat.lua
  4. Version: 9.3.11.26
  5. ]]--
  6.  
  7. --  pChat object will receive functions
  8. pChat = pChat or {}
  9.  
  10. -- Common
  11. local ADDON_NAME        = "pChat"
  12. local ADDON_VERSION = "9.3.11.26"
  13. local ADDON_AUTHOR  = "DesertDwellers, Ayantir, Puddy"
  14. local ADDON_WEBSITE = "http://www.esoui.com/downloads/info93-pChat.html"
  15.  
  16. -- Init
  17. local isAddonLoaded         = false -- OnAddonLoaded() done
  18. local isAddonInitialized    = false
  19.  
  20. -- Default variables to push in SavedVars
  21. local defaults = {
  22.     -- LAM handled
  23.     showGuildNumbers = false,
  24.     allGuildsSameColour = false,
  25.     allZonesSameColour = true,
  26.     allNPCSameColour = true,
  27.     delzonetags = true,
  28.     carriageReturn = false,
  29.     alwaysShowChat = false,
  30.     augmentHistoryBuffer = true,
  31.     oneColour = false,
  32.     showTagInEntry = true,
  33.     showTimestamp = true,
  34.     timestampcolorislcol = false,
  35.     useESOcolors = true,
  36.     diffforESOcolors = 40,
  37.     timestampFormat = "HH:m",
  38.     guildTags = {},
  39.     officertag = {},
  40.     switchFor = {},
  41.     officerSwitchFor = {},
  42.     formatguild = {},
  43.     defaultchannel = CHAT_CHANNEL_GUILD_1,
  44.     soundforincwhisps = SOUNDS.NEW_NOTIFICATION,
  45.     enablecopy = true,
  46.     enableChatTabChannel = true,
  47.     enablepartyswitch = true,
  48.     enableWhisperTab = false,
  49.     groupLeader = false,
  50.     disableBrackets = true,
  51.     chatMinimizedAtLaunch = false,
  52.     chatMinimizedInMenus = false,
  53.     chatMaximizedAfterMenus = false,
  54.     windowDarkness = 6,
  55.     chatSyncConfig = true,
  56.     floodProtect = true,
  57.     floodGracePeriod = 30,
  58.     lookingForProtect = false,
  59.     wantToProtect = false,
  60.     restoreOnReloadUI = true,
  61.     restoreOnLogOut = true,
  62.     restoreOnQuit = false,
  63.     restoreOnAFK = true,
  64.     restoreSystem = true,
  65.     restoreSystemOnly = false,
  66.     restoreWhisps = true,
  67.     restoreTextEntryHistoryAtLogOutQuit = false,
  68.     addChannelAndTargetToHistory = true,
  69.     timeBeforeRestore = 2,
  70.     notifyIM = false,
  71.     nicknames = "",
  72.     defaultTab = 1,
  73.     defaultTabName = "",
  74.     groupNames = 1,
  75.     geoChannelsFormat = 2,
  76.     urlHandling = true,
  77.     -- guildRecruitProtect = false,
  78.     spamGracePeriod = 5,
  79.     fonts = "ESO Standard Font",
  80.     colours =
  81.     {
  82.         [2*CHAT_CHANNEL_SAY] = "|cFFFFFF", -- say Left
  83.         [2*CHAT_CHANNEL_SAY + 1] = "|cFFFFFF", -- say Right
  84.         [2*CHAT_CHANNEL_YELL] = "|cE974D8", -- yell Left
  85.         [2*CHAT_CHANNEL_YELL + 1] = "|cFFB5F4", -- yell Right
  86.         [2*CHAT_CHANNEL_WHISPER] = "|cB27BFF", -- tell in Left
  87.         [2*CHAT_CHANNEL_WHISPER + 1] = "|cB27BFF", -- tell in Right
  88.         [2*CHAT_CHANNEL_PARTY] = "|c6EABCA", -- group Left
  89.         [2*CHAT_CHANNEL_PARTY + 1] = "|cA1DAF7", -- group Right
  90.         [2*CHAT_CHANNEL_WHISPER_SENT] = "|c7E57B5", -- tell out Left
  91.         [2*CHAT_CHANNEL_WHISPER_SENT + 1] = "|c7E57B5", -- tell out Right
  92.         [2*CHAT_CHANNEL_EMOTE] = "|cA5A5A5", -- emote Left
  93.         [2*CHAT_CHANNEL_EMOTE + 1] = "|cA5A5A5", -- emote Right
  94.         [2*CHAT_CHANNEL_MONSTER_SAY] = "|c879B7D", -- npc Left
  95.         [2*CHAT_CHANNEL_MONSTER_SAY + 1] = "|c879B7D", -- npc Right
  96.         [2*CHAT_CHANNEL_MONSTER_YELL] = "|c879B7D", -- npc yell Left
  97.         [2*CHAT_CHANNEL_MONSTER_YELL + 1] = "|c879B7D", -- npc yell Right
  98.         [2*CHAT_CHANNEL_MONSTER_WHISPER] = "|c879B7D", -- npc whisper Left
  99.         [2*CHAT_CHANNEL_MONSTER_WHISPER + 1] = "|c879B7D", -- npc whisper Right
  100.         [2*CHAT_CHANNEL_MONSTER_EMOTE] = "|c879B7D", -- npc emote Left
  101.         [2*CHAT_CHANNEL_MONSTER_EMOTE + 1] = "|c879B7D", -- npc emote Right
  102.         [2*CHAT_CHANNEL_GUILD_1] = "|c94E193", -- guild Left
  103.         [2*CHAT_CHANNEL_GUILD_1 + 1] = "|cC3F0C2", -- guild Right
  104.         [2*CHAT_CHANNEL_GUILD_2] = "|c94E193",
  105.         [2*CHAT_CHANNEL_GUILD_2 + 1] = "|cC3F0C2",
  106.         [2*CHAT_CHANNEL_GUILD_3] = "|c94E193",
  107.         [2*CHAT_CHANNEL_GUILD_3 + 1] = "|cC3F0C2",
  108.         [2*CHAT_CHANNEL_GUILD_4] = "|c94E193",
  109.         [2*CHAT_CHANNEL_GUILD_4 + 1] = "|cC3F0C2",
  110.         [2*CHAT_CHANNEL_GUILD_5] = "|c94E193",
  111.         [2*CHAT_CHANNEL_GUILD_5 + 1] = "|cC3F0C2",
  112.         [2*CHAT_CHANNEL_OFFICER_1] = "|cC3F0C2", -- guild officers Left
  113.         [2*CHAT_CHANNEL_OFFICER_1 + 1] = "|cC3F0C2", -- guild officers Right
  114.         [2*CHAT_CHANNEL_OFFICER_2] = "|cC3F0C2",
  115.         [2*CHAT_CHANNEL_OFFICER_2 + 1] = "|cC3F0C2",
  116.         [2*CHAT_CHANNEL_OFFICER_3] = "|cC3F0C2",
  117.         [2*CHAT_CHANNEL_OFFICER_3 + 1] = "|cC3F0C2",
  118.         [2*CHAT_CHANNEL_OFFICER_4] = "|cC3F0C2",
  119.         [2*CHAT_CHANNEL_OFFICER_4 + 1] = "|cC3F0C2",
  120.         [2*CHAT_CHANNEL_OFFICER_5] = "|cC3F0C2",
  121.         [2*CHAT_CHANNEL_OFFICER_5 + 1] = "|cC3F0C2",
  122.         [2*CHAT_CHANNEL_ZONE] = "|cCEB36F", -- zone Left
  123.         [2*CHAT_CHANNEL_ZONE + 1] = "|cB0A074", -- zone Right
  124.         [2*CHAT_CHANNEL_ZONE_LANGUAGE_1] = "|cCEB36F", -- EN zone Left
  125.         [2*CHAT_CHANNEL_ZONE_LANGUAGE_1 + 1] = "|cB0A074", -- EN zone Right
  126.         [2*CHAT_CHANNEL_ZONE_LANGUAGE_2] = "|cCEB36F", -- FR zone Left
  127.         [2*CHAT_CHANNEL_ZONE_LANGUAGE_2 + 1] = "|cB0A074", -- FR zone Right
  128.         [2*CHAT_CHANNEL_ZONE_LANGUAGE_3] = "|cCEB36F", -- DE zone Left
  129.         [2*CHAT_CHANNEL_ZONE_LANGUAGE_3 + 1] = "|cB0A074", -- DE zone Right
  130.         [2*CHAT_CHANNEL_ZONE_LANGUAGE_4] = "|cCEB36F", -- JP zone Left
  131.         [2*CHAT_CHANNEL_ZONE_LANGUAGE_4 + 1] = "|cB0A074", -- JP zone Right
  132.         ["timestamp"] = "|c8F8F8F", -- timestamp
  133.         ["tabwarning"] = "|c76BCC3", -- tab Warning ~ "Azure" (ZOS default)
  134.         ["groupleader"] = "|cC35582", --
  135.         ["groupleader1"] = "|c76BCC3", --
  136.     },
  137.     -- Not LAM
  138.     chatConfSync = {},
  139. }
  140.  
  141. -- SV
  142. local db
  143. local targetToWhisp
  144. local guild1 = 1
  145. local guild2 = 2
  146. local guild3 = 3
  147. local guild4 = 4
  148. local guild5 = 5
  149. local guildName1 = ""
  150. local guildName2 = ""
  151. local guildName3 = ""
  152. local guildName4 = ""
  153. local guildName5 = ""
  154. local guildId1 = nil
  155. local guildId2 = nil
  156. local guildId3 = nil
  157. local guildId4 = nil
  158. local guildId5 = nil
  159. local guildMaxNum = 0
  160.  
  161.  
  162.  
  163. -- pChatData will receive variables and objects.
  164. local pChatData = {
  165.     cachedMessages = {}, -- This one must be init before OnAddonLoaded because it will receive data before this event.
  166. }
  167.  
  168. -- Used for pChat LinkHandling
  169. local PCHAT_LINK = "p"
  170. local PCHAT_URL_CHAN = 97
  171. local PCHAT_CHANNEL_SAY = 98
  172. local PCHAT_CHANNEL_NONE = 99
  173.  
  174. -- Init Automated Messages
  175. local automatedMessagesList = ZO_SortFilterList:Subclass()
  176.  
  177. -- Backuping AddMessage for internal debug - AVOID DOING A CHAT_SYSTEM:AddMessage() in pChat, it can cause recursive calls
  178. CHAT_SYSTEM.Zo_AddMessage = CHAT_SYSTEM.AddMessage
  179.  
  180. --[[
  181. PCHAT_LINK format : ZO_LinkHandler_CreateLink(message, nil, PCHAT_LINK, data)
  182. message = message to display, nil (ignored by ZO_LinkHandler_CreateLink), PCHAT_LINK : declaration
  183. data : strings separated by ":"
  184. 1st arg is chancode like CHAT_CHANNEL_GUILD_1
  185. ]]--
  186.  
  187. local ChanInfoArray = ZO_ChatSystem_GetChannelInfo()
  188.  
  189. pChatData.chatCategories = {
  190.     CHAT_CATEGORY_SAY,
  191.     CHAT_CATEGORY_YELL,
  192.     CHAT_CATEGORY_WHISPER_INCOMING,
  193.     CHAT_CATEGORY_WHISPER_OUTGOING,
  194.     CHAT_CATEGORY_ZONE,
  195.     CHAT_CATEGORY_PARTY,
  196.     CHAT_CATEGORY_EMOTE,
  197.     CHAT_CATEGORY_SYSTEM,
  198.     CHAT_CATEGORY_GUILD_1,
  199.     CHAT_CATEGORY_GUILD_2,
  200.     CHAT_CATEGORY_GUILD_3,
  201.     CHAT_CATEGORY_GUILD_4,
  202.     CHAT_CATEGORY_GUILD_5,
  203.     CHAT_CATEGORY_OFFICER_1,
  204.     CHAT_CATEGORY_OFFICER_2,
  205.     CHAT_CATEGORY_OFFICER_3,
  206.     CHAT_CATEGORY_OFFICER_4,
  207.     CHAT_CATEGORY_OFFICER_5,
  208.     CHAT_CATEGORY_ZONE_ENGLISH,
  209.     CHAT_CATEGORY_ZONE_FRENCH,
  210.     CHAT_CATEGORY_ZONE_GERMAN,
  211.     CHAT_CATEGORY_ZONE_JAPANESE,
  212.     CHAT_CATEGORY_MONSTER_SAY,
  213.     CHAT_CATEGORY_MONSTER_YELL,
  214.     CHAT_CATEGORY_MONSTER_WHISPER,
  215.     CHAT_CATEGORY_MONSTER_EMOTE,
  216. }
  217.  
  218. pChatData.guildCategories = {
  219.     CHAT_CATEGORY_GUILD_1,
  220.     CHAT_CATEGORY_GUILD_2,
  221.     CHAT_CATEGORY_GUILD_3,
  222.     CHAT_CATEGORY_GUILD_4,
  223.     CHAT_CATEGORY_GUILD_5,
  224.     CHAT_CATEGORY_OFFICER_1,
  225.     CHAT_CATEGORY_OFFICER_2,
  226.     CHAT_CATEGORY_OFFICER_3,
  227.     CHAT_CATEGORY_OFFICER_4,
  228.     CHAT_CATEGORY_OFFICER_5,
  229. }
  230.  
  231. pChatData.defaultChannels = {
  232.     PCHAT_CHANNEL_NONE,
  233.     CHAT_CHANNEL_ZONE,
  234.     CHAT_CHANNEL_SAY,
  235.     CHAT_CHANNEL_GUILD_1,
  236.     CHAT_CHANNEL_GUILD_2,
  237.     CHAT_CHANNEL_GUILD_3,
  238.     CHAT_CHANNEL_GUILD_4,
  239.     CHAT_CHANNEL_GUILD_5,
  240.     CHAT_CHANNEL_OFFICER_1,
  241.     CHAT_CHANNEL_OFFICER_2,
  242.     CHAT_CHANNEL_OFFICER_3,
  243.     CHAT_CHANNEL_OFFICER_4,
  244.     CHAT_CHANNEL_OFFICER_5,
  245. }
  246.  
  247. local chatStrings =
  248. {
  249.     standard = "%s%s: |r%s%s%s|r", -- standard format: say, yell, group, npc, npc yell, npc whisper, zone
  250.     esostandart = "%s%s %s: |r%s%s%s|r", -- standard format: say, yell, group, npc, npc yell, npc whisper, zone with tag (except for monsters)
  251.     esoparty = "%s%s%s: |r%s%s%s|r", -- standard format: party
  252.     tellIn = "%s%s: |r%s%s%s|r", -- tell in
  253.     tellOut = "%s-> %s: |r%s%s%s|r", -- tell out
  254.     emote = "%s%s |r%s%s|r", -- emote
  255.     guild = "%s%s %s: |r%s%s%s|r", -- guild
  256.     language = "%s[%s] %s: |r%s%s%s|r", -- language zones
  257.    
  258.     -- For copy System, only Handle "From part"
  259.    
  260.     copystandard = "[%s]: ", -- standard format: say, yell, group, npc, npc yell, npc whisper, zone
  261.     copyesostandard = "[%s] %s: ", -- standard format: say, yell, group, npc, npc yell, npc whisper, zone with tag (except for monsters)
  262.     copyesoparty = "[%s]%s: ", -- standard format: party
  263.     copytellIn = "[%s]: ", -- tell in
  264.     copytellOut = "-> [%s]: ", -- tell out
  265.     copyemote = "%s ", -- emote
  266.     copyguild = "[%s] [%s]: ", -- guild
  267.     copylanguage = "[%s] %s: ", -- language zones
  268.     copynpc = "%s: ", -- language zones
  269. }
  270.  
  271. -- Registering librairies
  272. local LAM = LibStub("LibAddonMenu-2.0")
  273. local PCHATLC = LibStub("libChat-1.0")
  274. local LMP = LibStub("LibMediaProvider-1.0")
  275. local LMM = LibStub("LibMainMenu")
  276. local LCT = LibStub("LibCustomTitles",true)
  277. local MENU_CATEGORY_PCHAT = nil
  278.  
  279. -- Return true/false if text is a flood
  280. local function SpamFlood(from, text, chanCode)
  281.  
  282.     -- 2+ messages identiqual in less than 30 seconds on Character channels = spam
  283.        
  284.     -- Should not happen
  285.     if db.LineStrings then
  286.        
  287.         if db.lineNumber then
  288.             -- 1st message cannot be a spam
  289.             if db.lineNumber > 1 then
  290.                
  291.                 local checkSpam = true
  292.                 local previousLine = db.lineNumber - 1
  293.                 local ourMessageTimestamp = GetTimeStamp()
  294.                
  295.                 while checkSpam do
  296.                    
  297.                     -- Previous line can be a ChanSystem one
  298.                     if db.LineStrings[previousLine].channel ~= CHAT_CHANNEL_SYSTEM then
  299.                         if (ourMessageTimestamp - db.LineStrings[previousLine].rawTimestamp) < db.floodGracePeriod then
  300.                             -- if our message is sent by our chatter / will be break by "Character" channels and "UserID" Channels
  301.                             if from == db.LineStrings[previousLine].rawFrom then
  302.                                 -- if our message is eq of last message
  303.                                 if text == db.LineStrings[previousLine].rawText then
  304.                                     -- Previous and current must be in zone(s), yell, say, emote (Character Channels except party)
  305.                                     -- TODO: Find a characterchannel func
  306.                                    
  307.                                     -- CHAT_CHANNEL_SAY = 0 == nil in lua, will broke the array, so use PCHAT_CHANNEL_SAY
  308.                                     local spamChanCode = chanCode
  309.                                     if spamChanCode == CHAT_CHANNEL_SAY then
  310.                                         spamChanCode = PCHAT_CHANNEL_SAY
  311.                                     end
  312.                                    
  313.                                     local spammableChannels = {}
  314.                                     spammableChannels[CHAT_CHANNEL_ZONE_LANGUAGE_1] = true
  315.                                     spammableChannels[CHAT_CHANNEL_ZONE_LANGUAGE_2] = true
  316.                                     spammableChannels[CHAT_CHANNEL_ZONE_LANGUAGE_3] = true
  317.                                     spammableChannels[CHAT_CHANNEL_ZONE_LANGUAGE_4] = true
  318.                                     spammableChannels[PCHAT_CHANNEL_SAY] = true
  319.                                     spammableChannels[CHAT_CHANNEL_YELL] = true
  320.                                     spammableChannels[CHAT_CHANNEL_ZONE] = true
  321.                                     spammableChannels[CHAT_CHANNEL_EMOTE] = true
  322.                                    
  323.                                     -- spammableChannels[spamChanCode] = return true if our message was sent in a spammable channel
  324.                                     -- spammableChannels[db.LineStrings[previousLine].channel] = return true if previous message was sent in a spammable channel
  325.                                     if spammableChannels[spamChanCode] and spammableChannels[db.LineStrings[previousLine].channel] then
  326.                                         -- Spam
  327.                                         --CHAT_SYSTEM:Zo_AddMessage("Spam detected ( " .. text ..  " )")
  328.                                         return true
  329.                                     end
  330.                                 end
  331.                             end
  332.                         else
  333.                             -- > 30s, stop analyzis
  334.                             checkSpam = false
  335.                         end
  336.                     end
  337.                    
  338.                     if previousLine > 1 then
  339.                         previousLine = previousLine - 1
  340.                     else
  341.                         checkSpam = false
  342.                     end
  343.                
  344.                 end
  345.                
  346.             end
  347.         end
  348.        
  349.     end
  350.    
  351.     return false
  352.        
  353. end
  354.  
  355. -- Return true/false if text is a LFG message
  356. local function SpamLookingFor(text)
  357.  
  358.     local spamStrings = {
  359.         [1] = "[lL][%s.]?[fF][%s.]?[%d]?[%s.]?[mMgG]",
  360.         [2] = "[lL][%s.]?[fF][%s.]?[%d]?[%s.]?[hH][eE][aA][lL]",
  361.         [3] = "[lL][%s.]?[fF][%s.]?[%d]?[%s.]?[dD][dD]",
  362.         [4] = "[lL][%s.]?[fF][%s.]?[%d]?[%s.]?[dD][pP][sS]",
  363.         [5] = "[lL][%s.]?[fF][%s.]?[%d]?[%s.]?[tT][aA][nN][kK]",
  364.         [6] = "[lL][%s.]?[fF][%s.]?[%d]?[%s.]?[dD][aA][iI][lL][yY]",
  365.         [7] = "[lL][%s.]?[fF][%s.]?[%d]?[%s.]?[sS][iI][lL][vV][eE][rR]",
  366.         [8] = "[lL][%s.]?[fF][%s.]?[%d]?[%s.]?[gG][oO][lL][dD]", -- bypassed by first rule
  367.         [9] = "[lL][%s.]?[fF][%s.]?[%d]?[%s.]?[dD][uU][nN][gG][eE][oO][nN]",
  368.     }
  369.    
  370.     for _, spamString in ipairs(spamStrings) do
  371.         if string.find(text, spamString) then
  372.             --CHAT_SYSTEM:Zo_AddMessage("spamLookingFor:" .. text .." ;spamString=" .. spamString)
  373.             return true
  374.         end
  375.     end
  376.  
  377.     return false
  378.  
  379. end
  380.  
  381. -- Return true/false if text is a WTT message
  382. local function SpamWantTo(text)
  383.  
  384.     -- "w.T S"
  385.     if string.find(text, "[wW][%s.]?[tT][%s.]?[bBsStT]") then
  386.        
  387.         -- Item Handler
  388.         if string.find(text, "|H(.-):item:(.-)|h(.-)|h") then
  389.             -- Match
  390.             --CHAT_SYSTEM:Zo_AddMessage("WT detected ( " .. text .. " )")
  391.             return true
  392.         elseif string.find(text, "[Ww][Ww][%s]+[Bb][Ii][Tt][Ee]") then
  393.             -- Match
  394.             --CHAT_SYSTEM:Zo_AddMessage("WT WW Bite detected ( " .. text .. " )")
  395.             return true
  396.         end
  397.    
  398.     end
  399.    
  400.     return false
  401.    
  402. end
  403.  
  404. -- Return true/false if text is a Guild recruitment one
  405. local function SpamGuildRecruit(text, chanCode)
  406.  
  407.     -- Guild Recruitment message are too complex to only use 1/2 patterns, an heuristic method must be used
  408.    
  409.     -- 1st is channel. only check geographic channels (character ones)
  410.     -- 2nd is text len, they're generally long ones
  411.     -- Then, presence of certain words
  412.    
  413.     --CHAT_SYSTEM:Zo_AddMessage("GR analizis")
  414.    
  415.     local spamChanCode = chanCode
  416.     if spamChanCode == CHAT_CHANNEL_SAY then
  417.         spamChanCode = PCHAT_CHANNEL_SAY
  418.     end
  419.    
  420.     local spammableChannels = {}
  421.     spammableChannels[CHAT_CHANNEL_ZONE_LANGUAGE_1] = true
  422.     spammableChannels[CHAT_CHANNEL_ZONE_LANGUAGE_2] = true
  423.     spammableChannels[CHAT_CHANNEL_ZONE_LANGUAGE_3] = true
  424.     spammableChannels[CHAT_CHANNEL_ZONE_LANGUAGE_4] = true
  425.     spammableChannels[PCHAT_CHANNEL_SAY] = true
  426.     spammableChannels[CHAT_CHANNEL_YELL] = true
  427.     spammableChannels[CHAT_CHANNEL_ZONE] = true
  428.     spammableChannels[CHAT_CHANNEL_EMOTE] = true
  429.    
  430.     local spamStrings = {
  431.         ["[Ll]ooking [Ff]or ([Nn]ew+) [Mm]embers"] = 5, -- looking for (new) members
  432.         ["%d%d%d\+"] = 5, -- 398+
  433.         ["%d%d%d\/500"] = 5, -- 398/500
  434.         ["gilde"] = 1, -- 398/500
  435.         ["guild"] = 1, -- 398/500
  436.         ["[Tt][Ee][Aa][Mm][Ss][Pp][Ee][Aa][Kk]"] = 1,
  437.     }
  438.    
  439.     --[[
  440.    
  441. Polska gildia <OUTLAWS> po stronie DC rekrutuje! Oferujemy triale, eventy pvp, dungeony, hardmody, porady doswiadczonych graczy oraz mila atmosfere. Wymagamy TS-a i mikrofonu. "OUTLAWS gildia dla ludzi chcacych tworzyc gildie, a nie tylko byc w gildii!" W sprawie rekrutacji zloz podanie na www.outlawseso.guildlaunch.com lub napisz w grze.
  442. Seid gegrüßt liebe Abenteurer.  Eine der ältesten aktiven Handelsgilden (seit 30.03.14), Caveat Emptor (deutschsprachig), hat wieder Plätze frei. (490+ Mitglieder). Gildenhändler vorhanden! /w bei Interesse
  443. Salut. L'esprit d'équipe est quelque chose qui vous parle ? Relever les plus haut défis en PvE comme en PvP et  RvR vous tente, mais vous ne voulez pas sacrifier votre IRL pour cela ? Empirium League est construite autours de tous ces principes. /w pour plus d'infos ou sur Empiriumleague.com
  444. Die Gilde Drachenstern sucht noch Aktive Member ab V1 für gemeinsamen Spaß und Erfolg:) unserer aktueller Raidcontent ist Sanctum/HelRah und AA unsere Raidtage sind Mittwoch/Freitags und Sonntag bei Fragen/Intresse einfach tell/me:)
  445. Anyone wants to join a BIG ACTIVE TRADE GUILD? Then join Daggerfall Trade Masters 493/500 MEMBERS--5DAYS ACTIVITY--_TEAMSPEAK--CRAGLORN FRONT ROW TRADER (SHELZAKA) Whisper me for invite and start BUYING and SELLING right NOW!
  446. The Cambridge Alliance is a multi-faction social guild based in the UK.  Send me a whisper if you would like to join!  www.cambridge-alliance.co.uk
  447. Rejoignez le Comptoir de Bordeciel, Guilde Internationale de Trade (490+) présente depuis la release. Bénéficiez de nos prices check grâce à un historique de vente et bénéficiez d’estimations réelles. Les taxes sont investies dans le marchand de guilde. Envoyez-moi par mp le code « CDC » pour invitation auto
  448. Valeureux soldats de l'Alliance, rejoignez l'Ordre des Ombres, combattez pour la gloire de Daguefillante, pour la victoire, et la conquète du trône impérial ! -- Mumble et site -- Guilde PVE-PVP -- 18+ -- MP pour info
  449. Join Honour. A well established guild with over 10 years gaming experience and almost 500 active members. We run a full week of Undaunted, Trials, Cyrodiil and low level helper nights. With a social and helpful community and great crafting support. Check out our forums www.honourforever.com. /w for invite  
  450. {L'Ordre des Ombres} construite sur l'entraide, la sympathie et bonne ambiance vous invite a rejoindre ses rangs afin de profiter au maximum de TESO. www.ordredesombres.fr Cordialement - Chems
  451. The new guild Auctionhouse Extended is recruiting players, who want to buy or sell items! (300+ member
  452. Totalitarnaya Destructivnaya Sekta "Cadwell's Honor" nabiraet otvazhnyh i bezbashennyh rakov, krabov i drugie moreprodukty v svoi tesnye ryady!! PvE, PvP, TS, neobychnyi RP i prochie huliganstva. Nam nuzhny vashi kleshni!! TeamSpeak dlya priema cadwells-honor.ru
  453. you look for a guild (united trade post / 490+ member) for trade, pve and all other things in eso without obligations? /w me for invitation --- du suchst eine gilde (united trade post / 490+ mitglieder) für handel, pve und alle anderen dinge in eso ohne verpflichtungen. /w me
  454. [The Warehouse] Trading Guild Looking for Members! /w me for Invite :)
  455. Russian guild Daggerfall Bandits is looking for new members! We are the biggest and the most active russian community in Covenant. Regular PvE and PvP raids to normal and hard mode content! 490+ members. Whisper me.
  456.   Bonjour, la Guilde La Flibuste recherche des joueurs francophones.  Sans conditions et pour jouer dans la bonne humeur, notre guilde est ouverte à tous.  On recherche des joueurs de toutes les classes et vétérang pour compléter nos raids.  Pour plus d'infos, c'est ici :) Merci et bon jeu.  
  457.    
  458.     ]]--
  459.    
  460.     -- Our note. Per default, each message get its heuristic level to 0
  461.     local guildHeuristics = 0
  462.    
  463.     -- spammableChannels[db.LineStrings[previousLine].channel] = return true if previous message was sent in a spammable channel
  464.     if spammableChannels[spamChanCode] then
  465.        
  466.         local textLen = string.len(text)
  467.         local text300 = (string.len(text) > 300) -- 50
  468.         local text250 = (string.len(text) > 250) -- 40
  469.         local text200 = (string.len(text) > 200) -- 30
  470.         local text150 = (string.len(text) > 150) -- 20
  471.         local text100 = (string.len(text) > 100) -- 10
  472.         local text30  = (string.len(text) > 30)  -- 0
  473.        
  474.         -- 30 chars are needed to make a phrase of guild recruitment. If recruiter spam in SMS, pChat won't handle it.
  475.         if text30 then
  476.            
  477.             -- Each message can possibly be a spam, let's wrote our checklist
  478.             --CHAT_SYSTEM:Zo_AddMessage("GR Len ( " .. textLen .. " )")
  479.            
  480.             if text300 then
  481.                 guildHeuristics = 50
  482.             elseif text250 then
  483.                 guildHeuristics = 40
  484.             elseif text200 then
  485.                 guildHeuristics = 30
  486.             elseif text150 then
  487.                 guildHeuristics = 20
  488.             elseif text100 then
  489.                 guildHeuristics = 10
  490.             end
  491.            
  492.             -- Still to do
  493.            
  494.             for spamString, value in ipairs(spamStrings) do
  495.                 if string.find(text, spamString) then
  496.                     --CHAT_SYSTEM:Zo_AddMessage(spamString)
  497.                     guildHeuristics = guildHeuristics + value
  498.                 end
  499.             end
  500.            
  501.             if guildHeuristics > 60 then
  502.                 --CHAT_SYSTEM:Zo_AddMessage("GR : true (score=" .. guildHeuristics .. ")")
  503.                 return true
  504.             end
  505.        
  506.         end
  507.    
  508.     end
  509.    
  510.     --CHAT_SYSTEM:Zo_AddMessage("GR : false (score=" .. guildHeuristics .. ")")
  511.     return false
  512.    
  513. end
  514.  
  515. -- Return true/false if anti spam is enabled for a certain category
  516. -- Categories must be : Flood, LookingFor, WantTo, GuildRecruit
  517. local function IsSpamEnabledForCategory(category)
  518.    
  519.     if category == "Flood" then
  520.    
  521.         -- Enabled in Options?
  522.         if db.floodProtect then
  523.             --CHAT_SYSTEM:Zo_AddMessage("floodProtect enabled")
  524.             -- AntiSpam is enabled
  525.             return true
  526.         end
  527.        
  528.         --CHAT_SYSTEM:Zo_AddMessage("floodProtect disabled")
  529.         -- AntiSpam is disabled
  530.         return false
  531.    
  532.     -- LFG
  533.     elseif category == "LookingFor" then
  534.         -- Enabled in Options?
  535.         if db.lookingForProtect then
  536.             -- Enabled in reality?
  537.             if pChatData.spamLookingForEnabled then
  538.                 --CHAT_SYSTEM:Zo_AddMessage("lookingForProtect enabled")
  539.                 -- AntiSpam is enabled
  540.                 return true
  541.             else
  542.            
  543.                 --CHAT_SYSTEM:Zo_AddMessage("lookingForProtect is temporary disabled since " .. pChat.spamTempLookingForStopTimestamp)
  544.                
  545.                 -- AntiSpam is disabled .. since -/+ grace time ?
  546.                 if GetTimeStamp() - pChatData.spamTempLookingForStopTimestamp > (db.spamGracePeriod * 60) then
  547.                     --CHAT_SYSTEM:Zo_AddMessage("lookingForProtect enabled again")
  548.                     -- Grace period outdatted -> we need to re-enable it
  549.                     pChatData.spamLookingForEnabled = true
  550.                     return true
  551.                 end
  552.             end
  553.         end
  554.        
  555.         --CHAT_SYSTEM:Zo_AddMessage("lookingForProtect disabled")
  556.         -- AntiSpam is disabled
  557.         return false
  558.    
  559.     -- WTT
  560.     elseif category == "WantTo" then
  561.         -- Enabled in Options?
  562.         if db.wantToProtect then
  563.             -- Enabled in reality?
  564.             if pChatData.spamWantToEnabled then
  565.                 --CHAT_SYSTEM:Zo_AddMessage("wantToProtect enabled")
  566.                 -- AntiSpam is enabled
  567.                 return true
  568.             else
  569.                 -- AntiSpam is disabled .. since -/+ grace time ?
  570.                 if GetTimeStamp() - pChatData.spamTempWantToStopTimestamp > (db.spamGracePeriod * 60) then
  571.                     --CHAT_SYSTEM:Zo_AddMessage("wantToProtect enabled again")
  572.                     -- Grace period outdatted -> we need to re-enable it
  573.                     pChatData.spamWantToEnabled = true
  574.                     return true
  575.                 end
  576.             end
  577.         end
  578.        
  579.         --CHAT_SYSTEM:Zo_AddMessage("wantToProtect disabled")
  580.         -- AntiSpam is disabled
  581.         return false
  582.    
  583.     -- Join my Awesome guild
  584.     elseif category == "GuildRecruit" then
  585.         -- Enabled in Options?
  586.         if db.guildRecruitProtect then
  587.             -- Enabled in reality?
  588.             if pChatData.spamGuildRecruitEnabled then
  589.                 -- AntiSpam is enabled
  590.                 return true
  591.             else
  592.                 -- AntiSpam is disabled .. since -/+ grace time ?
  593.                 if GetTimeStamp() - pChatData.spamTempGuildRecruitStopTimestamp > (db.spamGracePeriod * 60) then
  594.                     -- Grace period outdatted -> we need to re-enable it
  595.                     pChatData.spamGuildRecruitEnabled = true
  596.                     return true
  597.                 end
  598.             end
  599.         end
  600.        
  601.         -- AntiSpam is disabled
  602.         return false
  603.    
  604.     end
  605.    
  606. end
  607.  
  608. -- Return true is message is a spam depending on MANY parameters
  609. local function SpamFilter(chanCode, from, text, isCS)
  610.  
  611.     -- 5 options for spam : Spam (multiple messages) ; LFM/LFG ; WT(T/S/B) ; Guild Recruitment ; Gold Spamming for various websites
  612.    
  613.     -- ZOS GM are NEVER blocked
  614.     if isCS then
  615.         return false
  616.     end
  617.    
  618.     -- CHAT_CHANNEL_PARTY is not spamfiltered, party leader get its own antispam tool (= kick)
  619.     if chanCode == CHAT_CHANNEL_PARTY then
  620.         return false
  621.     end
  622.    
  623.     -- "I" or anyone do not flood
  624.     if IsSpamEnabledForCategory("Flood") then
  625.         if SpamFlood(from, text, chanCode) then return true end
  626.     end
  627.    
  628.     -- But "I" can have exceptions
  629.     if zo_strformat(SI_UNIT_NAME, from) == pChatData.localPlayer or from == GetDisplayName() then
  630.    
  631.         --CHAT_SYSTEM:Zo_AddMessage("I say something ( " .. text .. " )")
  632.        
  633.         if IsSpamEnabledForCategory("LookingFor") then
  634.             -- "I" can look for a group
  635.             if SpamLookingFor(text) then
  636.                
  637.                 --CHAT_SYSTEM:Zo_AddMessage("I say a LF Message ( " .. text .. " )")
  638.                
  639.                 -- If I break myself the rule, disable it few minutes
  640.                 pChatData.spamTempLookingForStopTimestamp = GetTimeStamp()
  641.                 pChatData.spamLookingForEnabled = false
  642.                
  643.             end
  644.         end
  645.        
  646.         if IsSpamEnabledForCategory("WantTo") then
  647.             -- "I" can be a trader
  648.             if SpamWantTo(text) then
  649.                
  650.                 --CHAT_SYSTEM:Zo_AddMessage("I say a WT Message ( " .. text .. " )")
  651.                
  652.                 -- If I break myself the rule, disable it few minutes
  653.                 pChatData.spamTempWantToStopTimestamp = GetTimeStamp()
  654.                 pChatData.spamWantToStop = true
  655.                
  656.             end
  657.         end
  658.        
  659.         --[[
  660.         if IsSpamEnabledForCategory("GuildRecruit") then
  661.             -- "I" can do some guild recruitment
  662.             if SpamGuildRecruit(text, chanCode) then
  663.                
  664.                 --CHAT_SYSTEM:Zo_AddMessage("I say a GR Message ( " .. text .. " )")
  665.                
  666.                 -- If I break myself the rule, disable it few minutes
  667.                 pChatData.spamTempGuildRecruitStopTimestamp = GetTimeStamp()
  668.                 pChatData.spamGuildRecruitStop = true
  669.                
  670.             end
  671.         end
  672.         ]]--
  673.        
  674.         -- My message will be displayed in any case
  675.         return false
  676.        
  677.     end
  678.    
  679.     -- Spam
  680.     if IsSpamEnabledForCategory("Flood") then
  681.         if SpamFlood(from, text, chanCode) then return true end
  682.     end
  683.    
  684.     -- Looking For
  685.     if IsSpamEnabledForCategory("LookingFor") then
  686.         if SpamLookingFor(text) then return true end
  687.     end
  688.    
  689.     -- Want To
  690.     if IsSpamEnabledForCategory("WantTo") then
  691.         if SpamWantTo(text) then return true end
  692.     end
  693.    
  694.     -- Guild Recruit
  695.     --[[
  696.     if IsSpamEnabledForCategory("GuildRecruit") then
  697.         if SpamGuildRecruit(text, chanCode) then return true end
  698.     end
  699.     ]]--
  700.    
  701.     return false
  702.  
  703. end
  704.  
  705. -- Turn a ([0,1])^3 RGB colour to "|cABCDEF" form. We could use ZO_ColorDef, but we have so many colors so we don't do it.
  706. local function ConvertRGBToHex(r, g, b)
  707.     return string.format("|c%.2x%.2x%.2x", zo_floor(r * 255), zo_floor(g * 255), zo_floor(b * 255))
  708. end
  709.  
  710. -- Convert a colour from "|cABCDEF" form to [0,1] RGB form.
  711. local function ConvertHexToRGBA(colourString)
  712.     local r=tonumber(string.sub(colourString, 3, 4), 16) or 255
  713.     local g=tonumber(string.sub(colourString, 5, 6), 16) or 255
  714.     local b=tonumber(string.sub(colourString, 7, 8), 16) or 255
  715.     return r/255, g/255, b/255, 1
  716. end
  717.  
  718. -- Return a formatted time
  719. local function CreateTimestamp(timeStr, formatStr)
  720.     formatStr = formatStr or db.timestampFormat
  721.    
  722.     -- split up default timestamp
  723.     local hours, minutes, seconds = timeStr:match("([^%:]+):([^%:]+):([^%:]+)")
  724.     local hoursNoLead = tonumber(hours) -- hours without leading zero
  725.     local hours12NoLead = (hoursNoLead - 1)%12 + 1
  726.     local hours12
  727.     if (hours12NoLead < 10) then
  728.         hours12 = "0" .. hours12NoLead
  729.     else
  730.         hours12 = hours12NoLead
  731.     end
  732.     local pUp = "AM"
  733.     local pLow = "am"
  734.     if (hoursNoLead >= 12) then
  735.         pUp = "PM"
  736.         pLow = "pm"
  737.     end
  738.    
  739.     -- create new one
  740.     local timestamp = formatStr
  741.     timestamp = timestamp:gsub("HH", hours)
  742.     timestamp = timestamp:gsub("H", hoursNoLead)
  743.     timestamp = timestamp:gsub("hh", hours12)
  744.     timestamp = timestamp:gsub("h", hours12NoLead)
  745.     timestamp = timestamp:gsub("m", minutes)
  746.     timestamp = timestamp:gsub("s", seconds)
  747.     timestamp = timestamp:gsub("A", pUp)
  748.     timestamp = timestamp:gsub("a", pLow)
  749.    
  750.     return timestamp
  751.    
  752. end
  753.  
  754. -- Format from name
  755. local function ConvertName(chanCode, from, isCS, fromDisplayName)
  756.    
  757.     local function DisplayWithOrWoBrackets(realFrom, displayed, linkType)
  758.         if not displayed then -- reported. Should not happen, maybe parser error with nicknames.
  759.             displayed = realFrom
  760.         end
  761.         if db.disableBrackets then
  762.             return ZO_LinkHandler_CreateLinkWithoutBrackets(displayed, nil, linkType, realFrom)
  763.         else
  764.             return ZO_LinkHandler_CreateLink(displayed, nil, linkType, realFrom)
  765.         end
  766.     end
  767.    
  768.     -- From can be UserID or Character name depending on wich channel we are
  769.     local new_from = from
  770.    
  771.     -- Messages from @Someone (guild / whisps)
  772.     if IsDecoratedDisplayName(from) then
  773.        
  774.         -- Guild / Officer chat only
  775.         if chanCode >= CHAT_CHANNEL_GUILD_1 and chanCode <= CHAT_CHANNEL_OFFICER_5 then
  776.            
  777.             -- Get guild ID based on channel id
  778.             local guildId = GetGuildId((chanCode - CHAT_CHANNEL_GUILD_1) % 5 + 1)
  779.             local guildName = GetGuildName(guildId)
  780.            
  781.             if pChatData.nicknames[new_from] then -- @UserID Nicknammed
  782.                 db.LineStrings[db.lineNumber].rawFrom = pChatData.nicknames[new_from]
  783.                 new_from = DisplayWithOrWoBrackets(new_from, pChatData.nicknames[new_from], DISPLAY_NAME_LINK_TYPE)
  784.             elseif db.formatguild[guildName] == 2 then -- Char
  785.                 local _, characterName = GetGuildMemberCharacterInfo(guildId, GetGuildMemberIndexFromDisplayName(guildId, new_from))
  786.                 characterName = zo_strformat(SI_UNIT_NAME, characterName)
  787.                 local nickNamedName
  788.                 if pChatData.nicknames[characterName] then -- Char Nicknammed
  789.                     nickNamedName = pChatData.nicknames[characterName]
  790.                 end
  791.                 db.LineStrings[db.lineNumber].rawFrom = nickNamedName or characterName
  792.                 new_from = DisplayWithOrWoBrackets(characterName, nickNamedName or characterName, CHARACTER_LINK_TYPE)
  793.             elseif db.formatguild[guildName] == 3 then -- Char@UserID
  794.                 local _, characterName = GetGuildMemberCharacterInfo(guildId, GetGuildMemberIndexFromDisplayName(guildId, new_from))
  795.                 characterName = zo_strformat(SI_UNIT_NAME, characterName)
  796.                 if characterName == "" then characterName = new_from end -- Some buggy rosters
  797.                
  798.                 if pChatData.nicknames[characterName] then -- Char Nicknammed
  799.                     characterName = pChatData.nicknames[characterName]
  800.                 else
  801.                     characterName = characterName .. new_from
  802.                 end
  803.                
  804.                 db.LineStrings[db.lineNumber].rawFrom = characterName
  805.                 new_from = DisplayWithOrWoBrackets(new_from, characterName, DISPLAY_NAME_LINK_TYPE)
  806.             else
  807.                 db.LineStrings[db.lineNumber].rawFrom = new_from
  808.                 new_from = DisplayWithOrWoBrackets(new_from, new_from, DISPLAY_NAME_LINK_TYPE)
  809.             end
  810.  
  811.         else
  812.             -- Wisps with @ We can't guess characterName for those ones
  813.            
  814.             if pChatData.nicknames[new_from] then -- @UserID Nicknammed
  815.                 db.LineStrings[db.lineNumber].rawFrom = pChatData.nicknames[new_from]
  816.                 new_from = DisplayWithOrWoBrackets(new_from, pChatData.nicknames[new_from], DISPLAY_NAME_LINK_TYPE)
  817.             else
  818.                 db.LineStrings[db.lineNumber].rawFrom = new_from
  819.                 new_from = DisplayWithOrWoBrackets(new_from, new_from, DISPLAY_NAME_LINK_TYPE)
  820.             end
  821.  
  822.         end
  823.        
  824.     -- Geo chat, Party, Whisps with characterName
  825.     else
  826.        
  827.         new_from = zo_strformat(SI_UNIT_NAME, new_from)
  828.        
  829.         local nicknamedFrom
  830.         if pChatData.nicknames[new_from] then -- Character or Account Nicknammed
  831.             nicknamedFrom = pChatData.nicknames[new_from]
  832.         elseif pChatData.nicknames[fromDisplayName] then
  833.             nicknamedFrom = pChatData.nicknames[fromDisplayName]
  834.         end
  835.        
  836.         db.LineStrings[db.lineNumber].rawFrom = nicknamedFrom or new_from
  837.        
  838.         -- No brackets / UserID for emotes
  839.         if chanCode == CHAT_CHANNEL_EMOTE then
  840.             new_from = ZO_LinkHandler_CreateLinkWithoutBrackets(nicknamedFrom or new_from, nil, CHARACTER_LINK_TYPE, from)
  841.         -- zones / whisps / say / tell. No Handler for NPC
  842.         elseif not (chanCode == CHAT_CHANNEL_MONSTER_SAY or chanCode == CHAT_CHANNEL_MONSTER_YELL or chanCode == CHAT_CHANNEL_MONSTER_WHISPER or chanCode == CHAT_CHANNEL_MONSTER_EMOTE) then
  843.            
  844.             if chanCode == CHAT_CHANNEL_PARTY then
  845.                 if db.groupNames == 1 then
  846.                     db.LineStrings[db.lineNumber].rawFrom = nicknamedFrom or fromDisplayName
  847.                     new_from = DisplayWithOrWoBrackets(fromDisplayName, nicknamedFrom or fromDisplayName, DISPLAY_NAME_LINK_TYPE)
  848.                 elseif db.groupNames == 3 then
  849.                     new_from = new_from .. fromDisplayName
  850.                     db.LineStrings[db.lineNumber].rawFrom = nicknamedFrom or new_from
  851.                     new_from = DisplayWithOrWoBrackets(from, nicknamedFrom or new_from, CHARACTER_LINK_TYPE)
  852.                 else
  853.                     db.LineStrings[db.lineNumber].rawFrom = nicknamedFrom or new_from
  854.                     new_from = DisplayWithOrWoBrackets(from, nicknamedFrom or new_from, CHARACTER_LINK_TYPE)
  855.                 end
  856.             else
  857.                 if db.geoChannelsFormat == 1 then
  858.                     db.LineStrings[db.lineNumber].rawFrom = nicknamedFrom or fromDisplayName
  859.                     new_from = DisplayWithOrWoBrackets(fromDisplayName, nicknamedFrom or fromDisplayName, DISPLAY_NAME_LINK_TYPE)
  860.                 elseif db.geoChannelsFormat == 3 then
  861.                     new_from = new_from .. fromDisplayName
  862.                     db.LineStrings[db.lineNumber].rawFrom = nicknamedFrom or new_from
  863.                     new_from = DisplayWithOrWoBrackets(from, nicknamedFrom or new_from, CHARACTER_LINK_TYPE)
  864.                 else
  865.                     db.LineStrings[db.lineNumber].rawFrom = nicknamedFrom or new_from
  866.                     new_from = DisplayWithOrWoBrackets(from, nicknamedFrom or new_from, CHARACTER_LINK_TYPE)
  867.                 end
  868.             end
  869.            
  870.         end
  871.        
  872.     end
  873.    
  874.     if isCS then -- ZOS icon
  875.         new_from = "|t16:16:EsoUI/Art/ChatWindow/csIcon.dds|t" .. new_from
  876.     end
  877.    
  878.     return new_from
  879.    
  880. end
  881.  
  882. local function UndockTextEntry()
  883.    
  884.     -- Unfinshed
  885.    
  886.     if not db.chatConfSync[pChatData.localPlayer].TextEntryPoint then
  887.         db.chatConfSync[pChatData.localPlayer].TextEntryPoint = CENTER
  888.         db.chatConfSync[pChatData.localPlayer].TextEntryRelPoint = CENTER
  889.         db.chatConfSync[pChatData.localPlayer].TextEntryX = 0
  890.         db.chatConfSync[pChatData.localPlayer].TextEntryY = -300
  891.         db.chatConfSync[pChatData.localPlayer].TextEntryWidth = 200
  892.     end
  893.        
  894.     ZO_ChatWindowTextEntry:ClearAnchors()
  895.     ZO_ChatWindowTextEntry:SetAnchor(db.chatConfSync[pChatData.localPlayer].TextEntryPoint, GuiRoot, db.chatConfSync[pChatData.localPlayer].TextEntryRelPoint, db.chatConfSync[pChatData.localPlayer].TextEntryX, 300)
  896.     ZO_ChatWindowTextEntry:SetDimensions(400, 27)
  897.     ZO_ChatWindowTextEntry:SetMovable(false)
  898.    
  899.     local undockedTexture = WINDOW_MANAGER:CreateControl("UndockedBackground", ZO_ChatWindowTextEntry, CT_TEXTURE)
  900.     undockedTexture:SetTexture("EsoUI/Art/Performance/StatusMeterMunge.dds")
  901.     undockedTexture:SetAnchor(CENTER, ZO_ChatWindowTextEntry, CENTER, 0, 0)
  902.     undockedTexture:SetDimensions(800, 250)
  903.    
  904. end
  905.  
  906. local function RedockTextEntry()
  907.  
  908.     -- Unfinished
  909.  
  910.     ZO_ChatWindowTextEntry:ClearAnchors()
  911.     ZO_ChatWindowTextEntry:SetAnchor(BOTTOMLEFT, ZO_ChatWindowMinimize, BOTTOMRIGHT, -6, -13)
  912.     ZO_ChatWindowTextEntry:SetAnchor(BOTTOMRIGHT, ZO_ChatWindow, BOTTOMRIGHT, -23, -13)
  913.     ZO_ChatWindowTextEntry:SetMovable(false)
  914.  
  915. end
  916.  
  917.  
  918. -- For console
  919. function pChat.CMD_DEBUG()
  920.     if pChat.DEBUG ~= 1 then
  921.         d("pChat Debug : On")
  922.         pChat.DEBUG = 1
  923.     else
  924.         d("pChat Debug : Off")
  925.         pChat.DEBUG = 0
  926.     end
  927. end
  928.  
  929. -- For console quest time
  930. function pChat.CMD_DEBUG1()
  931.     if pChat.DEBUG ~= 2 then
  932.         d("pChat Debug 2: On")
  933.         pChat.DEBUG = 2
  934.     else
  935.         d("pChat Debug 2: Off")
  936.         pChat.DEBUG = 0
  937.     end
  938. end
  939.  
  940. function pChat.CMD_DEBUG2()
  941.     if pChat.DEBUG ~=3 then
  942.         d("pChat Debug 3 : On")
  943.         pChat.DEBUG = 3
  944.     else
  945.         d("pChat Debug 3 : Off")
  946.         pChat.DEBUG = 0
  947.     end
  948. end
  949.  
  950.  
  951. -- Also called by bindings
  952. function pChat_ShowAutoMsg()
  953.     LMM:ToggleCategory(MENU_CATEGORY_PCHAT)
  954. end
  955.  
  956. -- Register Slash commands
  957. SLASH_COMMANDS["/msg"] = pChat_ShowAutoMsg
  958. SLASH_COMMANDS["/pchat_debug"] = pChat.CMD_DEBUG
  959. SLASH_COMMANDS["/pchat_debug1"] = pChat.CMD_DEBUG1
  960. SLASH_COMMANDS["/pchat_debug2"] = pChat.CMD_DEBUG2
  961.  
  962. ZO_CreateStringId("PCHAT_AUTOMSG_NAME_DEFAULT_TEXT", GetString(PCHAT_PCHAT_AUTOMSG_NAME_DEFAULT_TEXT))
  963. ZO_CreateStringId("PCHAT_AUTOMSG_MESSAGE_DEFAULT_TEXT", GetString(PCHAT_PCHAT_AUTOMSG_MESSAGE_DEFAULT_TEXT))
  964. ZO_CreateStringId("PCHAT_AUTOMSG_MESSAGE_TIP1_TEXT", GetString(PCHAT_PCHAT_AUTOMSG_MESSAGE_TIP1_TEXT))
  965. ZO_CreateStringId("PCHAT_AUTOMSG_MESSAGE_TIP2_TEXT", GetString(PCHAT_PCHAT_AUTOMSG_MESSAGE_TIP2_TEXT))
  966. ZO_CreateStringId("PCHAT_AUTOMSG_MESSAGE_TIP3_TEXT", GetString(PCHAT_PCHAT_AUTOMSG_MESSAGE_TIP3_TEXT))
  967. ZO_CreateStringId("PCHAT_AUTOMSG_NAME_HEADER", GetString(PCHAT_PCHAT_AUTOMSG_NAME_HEADER))
  968. ZO_CreateStringId("PCHAT_AUTOMSG_MESSAGE_HEADER", GetString(PCHAT_PCHAT_AUTOMSG_MESSAGE_HEADER))
  969. ZO_CreateStringId("PCHAT_AUTOMSG_ADD_TITLE_HEADER", GetString(PCHAT_PCHAT_AUTOMSG_ADD_TITLE_HEADER))
  970. ZO_CreateStringId("PCHAT_AUTOMSG_EDIT_TITLE_HEADER", GetString(PCHAT_PCHAT_AUTOMSG_EDIT_TITLE_HEADER))
  971. ZO_CreateStringId("PCHAT_AUTOMSG_ADD_AUTO_MSG", GetString(PCHAT_PCHAT_AUTOMSG_ADD_AUTO_MSG))
  972. ZO_CreateStringId("PCHAT_AUTOMSG_EDIT_AUTO_MSG", GetString(PCHAT_PCHAT_AUTOMSG_EDIT_AUTO_MSG))
  973. ZO_CreateStringId("SI_BINDING_NAME_PCHAT_SHOW_AUTO_MSG", GetString(PCHAT_SI_BINDING_NAME_PCHAT_SHOW_AUTO_MSG))
  974. ZO_CreateStringId("PCHAT_AUTOMSG_REMOVE_AUTO_MSG", GetString(PCHAT_PCHAT_AUTOMSG_REMOVE_AUTO_MSG))
  975.  
  976.  
  977.  
  978.  
  979. function automatedMessagesList:New(control)
  980.    
  981.     ZO_SortFilterList.InitializeSortFilterList(self, control)
  982.    
  983.     local AutomatedMessagesSorterKeys =
  984.     {
  985.         ["name"] = {},
  986.         ["message"] = {tiebreaker = "name"}
  987.     }
  988.    
  989.     self.masterList = {}
  990.     ZO_ScrollList_AddDataType(self.list, 1, "pChatXMLAutoMsgRowTemplate", 32, function(control, data) self:SetupEntry(control, data) end)
  991.     ZO_ScrollList_EnableHighlight(self.list, "ZO_ThinListHighlight")
  992.     self.sortFunction = function(listEntry1, listEntry2) return ZO_TableOrderingFunction(listEntry1.data, listEntry2.data, self.currentSortKey, AutomatedMessagesSorterKeys, self.currentSortOrder) end
  993.     --self:SetAlternateRowBackgrounds(true)
  994.    
  995.     return self
  996.    
  997. end
  998.  
  999. -- format ESO text to raw text
  1000. -- IE : Transforms LinkHandlers into their displayed value
  1001. local function FormatRawText(text)
  1002.  
  1003.     -- Strip colors from chat
  1004.     local newtext = string.gsub(text, "|[cC]%x%x%x%x%x%x", ""):gsub("|r", "")
  1005.    
  1006.     -- Transforms a LinkHandler into its localized displayed value
  1007.     -- "|H(.-):(.-)|h(.-)|h" = pattern for Linkhandlers
  1008.     -- Item : |H1:item:33753:25:1:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0|h|h = [Battaglir] in french -> Link to item 33753, etc
  1009.     -- Achievement |H1:achievement:33753|h|h etc (not searched a lot, was easy)
  1010.     -- DisplayName = |H1:display:Ayantir|h[@Ayantir]|h = [@Ayantir] -> link to DisplayName @Ayantir
  1011.     -- Book = |H1:book:186|h|h = [Climat de guerre] in french -> Link to book 186 .. GetLoreBookTitleFromLink()
  1012.     -- pChat = |H1:PCHAT_LINK:124:11|h[06:18]|h = [06:18] (here 0 is the line number reference and 11 is the chanCode) - URL handling : if chanCode = 97, it will popup a dialog to open internet browser
  1013.     -- Character = |H1:character:salhamandhil^Mx|h[salhamandhil]|h = text (is there anything which link Characters into a message ?) (here salhamandhil is with brackets volontary)
  1014.     -- Need to do quest_items too. |H1:quest_item:4275|h|h
  1015.    
  1016.     newtext = string.gsub(newtext, "|H(.-):(.-)|h(.-)|h", function (linkStyle, data, text)
  1017.         -- linkStyle = style (ignored by game, seems to be often 1)
  1018.         -- data = data saparated by ":"
  1019.         -- text = text displayed, used for Channels, DisplayName, Character, and some fake itemlinks (used by addons)
  1020.        
  1021.         -- linktype is : item, achievement, character, channel, book, display, pchatlink
  1022.         -- DOES NOT HANDLE ADDONS LINKTYPES
  1023.        
  1024.         -- for all types, only param1 is important
  1025.         local linkType, param1 = zo_strsplit(':', data)
  1026.        
  1027.         -- param1 : itemID
  1028.         -- Need to get
  1029.         if linkType == ITEM_LINK_TYPE or linkType == COLLECTIBLE_LINK_TYPE then
  1030.             -- Fakelink and GetItemLinkName
  1031.             return "[" .. zo_strformat(SI_TOOLTIP_ITEM_NAME, GetItemLinkName("|H" .. linkStyle ..":" .. data .. "|h|h")) .. "]"
  1032.         -- param1 : achievementID
  1033.         elseif linkType == ACHIEVEMENT_LINK_TYPE then
  1034.             -- zo_strformat to avoid masculine/feminine problems
  1035.             return "[" .. zo_strformat(GetAchievementInfo(param1)) .. "]"
  1036.         -- SysMessages Links CharacterNames
  1037.         elseif linkType == CHARACTER_LINK_TYPE then
  1038.             return text
  1039.         elseif linkType == CHANNEL_LINK_TYPE then
  1040.             return text
  1041.         elseif linkType == BOOK_LINK_TYPE then
  1042.             return "[" .. GetLoreBookTitleFromLink(newtext) .. "]"
  1043.         -- SysMessages Links DysplayNames
  1044.         elseif linkType == DISPLAY_NAME_LINK_TYPE then
  1045.             -- No formatting here
  1046.             return "[@" .. param1 .. "]"
  1047.         -- Used for Sysmessages
  1048.         elseif linkType == "quest_item" then
  1049.             -- No formatting here
  1050.             return "[" .. zo_strformat(SI_TOOLTIP_ITEM_NAME, GetQuestItemNameFromLink(newtext)) .. "]"
  1051.         elseif linkType == PCHAT_LINK then
  1052.             -- No formatting here .. maybe yes ?..
  1053.             return text
  1054.         end
  1055.     end)
  1056.  
  1057.     return newtext
  1058.    
  1059. end
  1060.  
  1061. function automatedMessagesList:SetupEntry(control, data)
  1062.    
  1063.     control.data = data
  1064.     control.name = GetControl(control, "Name")
  1065.     control.message = GetControl(control, "Message")
  1066.    
  1067.     local messageTrunc = FormatRawText(data.message)
  1068.     if string.len(messageTrunc) > 53 then
  1069.         messageTrunc = string.sub(messageTrunc, 1, 53) .. " .."
  1070.     end
  1071.    
  1072.     control.name:SetText(data.name)
  1073.     control.message:SetText(messageTrunc)
  1074.    
  1075.     ZO_SortFilterList.SetupRow(self, control, data)
  1076.    
  1077. end
  1078.  
  1079. function automatedMessagesList:BuildMasterList()
  1080.     self.masterList = {}
  1081.     local messages = db.automatedMessages
  1082.     if messages then
  1083.         for k, v in ipairs(messages) do
  1084.             local data = v
  1085.             table.insert(self.masterList, data)
  1086.         end
  1087.     end
  1088.    
  1089. end
  1090.  
  1091. function automatedMessagesList:SortScrollList()
  1092.     local scrollData = ZO_ScrollList_GetDataList(self.list)
  1093.     table.sort(scrollData, self.sortFunction)
  1094. end
  1095.  
  1096. function automatedMessagesList:FilterScrollList()
  1097.     local scrollData = ZO_ScrollList_GetDataList(self.list)
  1098.     ZO_ClearNumericallyIndexedTable(scrollData)
  1099.  
  1100.     for i = 1, #self.masterList do
  1101.         local data = self.masterList[i]
  1102.         table.insert(scrollData, ZO_ScrollList_CreateDataEntry(1, data))
  1103.     end
  1104. end
  1105.  
  1106. local function GetDataByNameInSavedAutomatedMessages(name)
  1107.     local dataList = db.automatedMessages
  1108.     for index, data in ipairs(dataList) do
  1109.         if(data.name == name) then
  1110.             return data, index
  1111.         end
  1112.     end
  1113. end
  1114.  
  1115. local function GetDataByNameInAutomatedMessages(name)
  1116.     local dataList = pChatData.automatedMessagesList.list.data
  1117.     for i = 1, #dataList do
  1118.         local data = dataList[i].data
  1119.         if(data.name == name) then
  1120.             return data, index
  1121.         end
  1122.     end
  1123. end
  1124.  
  1125. local function SaveAutomatedMessage(name, message, isNew)
  1126.    
  1127.     if db then
  1128.        
  1129.         local alreadyFav = false
  1130.        
  1131.         if isNew then
  1132.             for k, v in pairs(db.automatedMessages) do
  1133.                 if "!" .. name == v.name then
  1134.                     alreadyFav = true
  1135.                 end
  1136.             end
  1137.         end
  1138.        
  1139.         if not alreadyFav then
  1140.            
  1141.             pChatXMLAutoMsg:GetNamedChild("Warning"):SetHidden(true)
  1142.             pChatXMLAutoMsg:GetNamedChild("Warning"):SetText("")
  1143.            
  1144.             if string.len(name) > 12 then
  1145.                 name = string.sub(name, 1, 12)
  1146.             end
  1147.            
  1148.             if string.len(message) > 351 then
  1149.                 message = string.sub(message, 1, 351)
  1150.             end
  1151.            
  1152.             local entryList = ZO_ScrollList_GetDataList(pChatData.automatedMessagesList.list)
  1153.            
  1154.             if isNew then
  1155.                 local data = {name = "!" .. name, message = message}
  1156.                 local entry = ZO_ScrollList_CreateDataEntry(1, data)
  1157.                 table.insert(entryList, entry)
  1158.                 table.insert(db.automatedMessages, {name = "!" .. name, message = message}) -- "data" variable is modified by ZO_ScrollList_CreateDataEntry and will crash eso if saved to savedvars
  1159.             else
  1160.                
  1161.                 local data = GetDataByNameInAutomatedMessages(name)
  1162.                 local _, index = GetDataByNameInSavedAutomatedMessages(name)
  1163.                
  1164.                 data.message = message
  1165.                 db.automatedMessages[index].message = message
  1166.             end
  1167.            
  1168.             ZO_ScrollList_Commit(pChatData.automatedMessagesList.list)
  1169.            
  1170.         else
  1171.             pChatXMLAutoMsg:GetNamedChild("Warning"):SetHidden(false)
  1172.             pChatXMLAutoMsg:GetNamedChild("Warning"):SetText(GetString(PCHAT_SAVMSGERRALREADYEXISTS))
  1173.             pChatXMLAutoMsg:GetNamedChild("Warning"):SetColor(1, 0, 0)
  1174.             zo_callLater(function() pChatXMLAutoMsg:GetNamedChild("Warning"):SetHidden(true) end, 5000)
  1175.         end
  1176.        
  1177.     end
  1178.  
  1179. end
  1180.  
  1181. local function CleanAutomatedMessageListForDB()
  1182.     -- :RefreshData() adds dataEntry recursively, delete it to avoid overflow in SavedVars
  1183.     for k, v in ipairs(db.automatedMessages) do
  1184.         if v.dataEntry then
  1185.             v.dataEntry = nil
  1186.         end
  1187.     end
  1188. end
  1189.  
  1190. local function RemoveAutomatedMessage()
  1191.  
  1192.     local data = ZO_ScrollList_GetData(WINDOW_MANAGER:GetMouseOverControl())
  1193.     local _, index = GetDataByNameInSavedAutomatedMessages(data.name)
  1194.     table.remove(db.automatedMessages, index)
  1195.     pChatData.automatedMessagesList:RefreshData()
  1196.     CleanAutomatedMessageListForDB()
  1197.  
  1198. end
  1199.  
  1200. -- Init Automated messages, build the scene and handle array of automated strings
  1201. local function InitAutomatedMessages()
  1202.    
  1203.     -- Create Scene
  1204.     PCHAT_AUTOMSG_SCENE = ZO_Scene:New("pChatAutomatedMessagesScene", SCENE_MANAGER)   
  1205.    
  1206.     -- Mouse standard position and background
  1207.     PCHAT_AUTOMSG_SCENE:AddFragmentGroup(FRAGMENT_GROUP.MOUSE_DRIVEN_UI_WINDOW)
  1208.     PCHAT_AUTOMSG_SCENE:AddFragmentGroup(FRAGMENT_GROUP.FRAME_TARGET_STANDARD_RIGHT_PANEL)
  1209.    
  1210.     -- Background Right, it will set ZO_RightPanelFootPrint and its stuff.
  1211.     PCHAT_AUTOMSG_SCENE:AddFragment(RIGHT_BG_FRAGMENT)
  1212.    
  1213.     -- The title fragment
  1214.     PCHAT_AUTOMSG_SCENE:AddFragment(TITLE_FRAGMENT)
  1215.    
  1216.     -- Set Title
  1217.     ZO_CreateStringId("SI_PCHAT_AUTOMSG_TITLE", ADDON_NAME)
  1218.     PCHAT_AUTOMSG_TITLE_FRAGMENT = ZO_SetTitleFragment:New(SI_PCHAT_AUTOMSG_TITLE)
  1219.     PCHAT_AUTOMSG_SCENE:AddFragment(PCHAT_AUTOMSG_TITLE_FRAGMENT)
  1220.    
  1221.     -- Add the XML to our scene
  1222.     PCHAT_AUTOMSG_SCENE_WINDOW = ZO_FadeSceneFragment:New(pChatXMLAutoMsg)
  1223.     PCHAT_AUTOMSG_SCENE:AddFragment(PCHAT_AUTOMSG_SCENE_WINDOW)
  1224.    
  1225.     -- Register Scenes and the group name
  1226.     SCENE_MANAGER:AddSceneGroup("pChatSceneGroup", ZO_SceneGroup:New("pChatAutomatedMessagesScene"))
  1227.    
  1228.     -- Its infos
  1229.     PCHAT_MAIN_MENU_CATEGORY_DATA =
  1230.     {
  1231.         binding = "PCHAT_SHOW_AUTO_MSG",
  1232.         categoryName = PCHAT_SHOW_AUTO_MSG,
  1233.         normal = "EsoUI/Art/MainMenu/menuBar_champion_up.dds",
  1234.         pressed = "EsoUI/Art/MainMenu/menuBar_champion_down.dds",
  1235.         highlight = "EsoUI/Art/MainMenu/menuBar_champion_over.dds",
  1236.     }
  1237.    
  1238.     MENU_CATEGORY_PCHAT = LMM:AddCategory(PCHAT_MAIN_MENU_CATEGORY_DATA)
  1239.    
  1240.     local iconData = {
  1241.         {
  1242.         categoryName = SI_BINDING_NAME_PCHAT_SHOW_AUTO_MSG,
  1243.         descriptor = "pChatAutomatedMessagesScene",
  1244.         normal = "EsoUI/Art/MainMenu/menuBar_champion_up.dds",
  1245.         pressed = "EsoUI/Art/MainMenu/menuBar_champion_down.dds",
  1246.         highlight = "EsoUI/Art/MainMenu/menuBar_champion_over.dds",
  1247.         },
  1248.     }
  1249.    
  1250.     -- Register the group and add the buttons (we cannot all AddRawScene, only AddSceneGroup, so we emulate both functions).
  1251.     LMM:AddSceneGroup(MENU_CATEGORY_PCHAT, "pChatSceneGroup", iconData)
  1252.    
  1253.     pChatData.autoMsgDescriptor = {
  1254.         alignment = KEYBIND_STRIP_ALIGN_CENTER,
  1255.         {
  1256.             name = GetString(PCHAT_AUTOMSG_ADD_AUTO_MSG),
  1257.             keybind = "UI_SHORTCUT_PRIMARY",
  1258.             control = self,
  1259.             callback = function(descriptor) ZO_Dialogs_ShowDialog("PCHAT_AUTOMSG_SAVE_MSG", nil, {mainTextParams = {functionName}}) end,
  1260.             visible = function(descriptor) return true end
  1261.         },
  1262.         {
  1263.             name = GetString(PCHAT_AUTOMSG_EDIT_AUTO_MSG),
  1264.             keybind = "UI_SHORTCUT_SECONDARY",
  1265.             control = self,
  1266.             callback = function(descriptor) ZO_Dialogs_ShowDialog("PCHAT_AUTOMSG_EDIT_MSG", nil, {mainTextParams = {functionName}}) end,
  1267.             visible = function(descriptor)
  1268.                 if pChatData.autoMessagesShowKeybind then
  1269.                     return true
  1270.                 else
  1271.                     return false
  1272.                 end
  1273.             end
  1274.         },
  1275.         {
  1276.             name = GetString(PCHAT_AUTOMSG_REMOVE_AUTO_MSG),
  1277.             keybind = "UI_SHORTCUT_NEGATIVE",
  1278.             control = self,
  1279.             callback = function(descriptor) RemoveAutomatedMessage() end,
  1280.             visible = function(descriptor)
  1281.                 if pChatData.autoMessagesShowKeybind then
  1282.                     return true
  1283.                 else
  1284.                     return false
  1285.                 end
  1286.             end
  1287.         },
  1288.     }
  1289.    
  1290.     pChatData.pChatAutomatedMessagesScene = SCENE_MANAGER:GetScene("pChatAutomatedMessagesScene")
  1291.     pChatData.pChatAutomatedMessagesScene:RegisterCallback("StateChange", function(oldState, newState)
  1292.         if newState == SCENE_SHOWING then
  1293.             KEYBIND_STRIP:AddKeybindButtonGroup(pChatData.autoMsgDescriptor)
  1294.         elseif newState == SCENE_HIDDEN then
  1295.             if KEYBIND_STRIP:HasKeybindButtonGroup(pChatData.autoMsgDescriptor) then
  1296.                 KEYBIND_STRIP:RemoveKeybindButtonGroup(pChatData.autoMsgDescriptor)
  1297.             end
  1298.         end
  1299.     end)
  1300.    
  1301.     if not db.automatedMessages then
  1302.         db.automatedMessages = {}
  1303.     end
  1304.    
  1305.     pChatData.automatedMessagesList = automatedMessagesList:New(pChatXMLAutoMsg)
  1306.     pChatData.automatedMessagesList:RefreshData()
  1307.     CleanAutomatedMessageListForDB()
  1308.    
  1309.     pChatData.automatedMessagesList.keybindStripDescriptor = pChatData.autoMsgDescriptor
  1310.    
  1311. end
  1312.  
  1313. -- Called by XML
  1314. function pChat_HoverRowOfAutomatedMessages(control)
  1315.     pChatData.autoMessagesShowKeybind = true
  1316.     pChatData.automatedMessagesList:Row_OnMouseEnter(control)
  1317. end
  1318.  
  1319. -- Called by XML
  1320. function pChat_ExitRowOfAutomatedMessages(control)
  1321.     pChatData.autoMessagesShowKeybind = false
  1322.     pChatData.automatedMessagesList:Row_OnMouseExit(control)
  1323. end
  1324.  
  1325. -- Called by XML
  1326. function pChat_BuildAutomatedMessagesDialog(control)
  1327.  
  1328.     local function AddDialogSetup(dialog, data)
  1329.        
  1330.         local name = GetControl(dialog, "NameEdit")
  1331.         local message = GetControl(dialog, "MessageEdit")
  1332.        
  1333.         name:SetText("")
  1334.         message:SetText("")
  1335.         name:SetEditEnabled(true)
  1336.        
  1337.     end
  1338.  
  1339.     ZO_Dialogs_RegisterCustomDialog("PCHAT_AUTOMSG_SAVE_MSG",
  1340.     {
  1341.         customControl = control,
  1342.         setup = AddDialogSetup,
  1343.         title =
  1344.         {
  1345.             text = PCHAT_AUTOMSG_ADD_TITLE_HEADER,
  1346.         },
  1347.         buttons =
  1348.         {
  1349.             [1] =
  1350.             {
  1351.                 control  = GetControl(control, "Request"),
  1352.                 text     = PCHAT_AUTOMSG_ADD_AUTO_MSG,
  1353.                 callback = function(dialog)
  1354.                     local name = GetControl(dialog, "NameEdit"):GetText()
  1355.                     local message = GetControl(dialog, "MessageEdit"):GetText()
  1356.                     if(name ~= "") and (message ~= "") then
  1357.                         SaveAutomatedMessage(name, message, true)
  1358.                     end
  1359.                 end,
  1360.             },
  1361.             [2] =
  1362.             {
  1363.                 control = GetControl(control, "Cancel"),
  1364.                 text = SI_DIALOG_CANCEL,
  1365.             }
  1366.         }
  1367.     })
  1368.    
  1369.     local function EditDialogSetup(dialog)
  1370.         local data = ZO_ScrollList_GetData(WINDOW_MANAGER:GetMouseOverControl())
  1371.         local name = GetControl(dialog, "NameEdit")
  1372.         local edit = GetControl(dialog, "MessageEdit")
  1373.        
  1374.        
  1375.         name:SetText(data.name)
  1376.         name:SetEditEnabled(false)
  1377.         edit:SetText(data.message)
  1378.         edit:TakeFocus()
  1379.        
  1380.     end
  1381.  
  1382.     ZO_Dialogs_RegisterCustomDialog("PCHAT_AUTOMSG_EDIT_MSG",
  1383.     {
  1384.         customControl = control,
  1385.         setup = EditDialogSetup,
  1386.         title =
  1387.         {
  1388.             text = PCHAT_AUTOMSG_EDIT_TITLE_HEADER,
  1389.         },
  1390.         buttons =
  1391.         {
  1392.             [1] =
  1393.             {
  1394.                 control  = GetControl(control, "Request"),
  1395.                 text     = PCHAT_AUTOMSG_EDIT_AUTO_MSG,
  1396.                 callback = function(dialog)
  1397.                     local name = GetControl(dialog, "NameEdit"):GetText()
  1398.                     local message = GetControl(dialog, "MessageEdit"):GetText()
  1399.                     if(name ~= "") and (message ~= "") then
  1400.                         SaveAutomatedMessage(name, message, false)
  1401.                     end
  1402.                 end,
  1403.             },
  1404.             [2] =
  1405.             {
  1406.                 control = GetControl(control, "Cancel"),
  1407.                 text = SI_DIALOG_CANCEL,
  1408.             }
  1409.         }
  1410.     })
  1411.  
  1412. end
  1413. -- **************************************************************************
  1414. -- Chat Tab Functions
  1415. -- **************************************************************************
  1416. local function getTabNames()
  1417.     local totalTabs = CHAT_SYSTEM.tabPool.m_Active
  1418.     if totalTabs ~= nil and #totalTabs >= 1 then
  1419.         tabNames = {}
  1420.         for idx, tmpTab in pairs(totalTabs) do
  1421.             local tabLabel = tmpTab:GetNamedChild("Text")
  1422.             local tmpTabName = tabLabel:GetText()
  1423.             if tmpTabName ~= nil and tmpTabName ~= "" then
  1424.                 tabNames[idx] = tmpTabName
  1425.             end
  1426.         end
  1427.     end
  1428. end
  1429.  
  1430. local function getTabIdx (tabName)
  1431.     local tabIdx = 0
  1432.     local totalTabs = CHAT_SYSTEM.tabPool.m_Active
  1433.     for i = 1, #totalTabs do
  1434.         if tabNames[i] == tabName then
  1435.             tabIdx = i
  1436.         end
  1437.     end
  1438.     return tabIdx
  1439. end
  1440.  
  1441. -- Rewrite of a core function
  1442. function CHAT_SYSTEM.textEntry:AddCommandHistory(text)
  1443.    
  1444.     local currentChannel = CHAT_SYSTEM.currentChannel
  1445.     local currentTarget = CHAT_SYSTEM.currentTarget
  1446.     local rewritedText = text
  1447.    
  1448.     -- Don't add the switch when chat is restored
  1449.     if db.addChannelAndTargetToHistory and isAddonInitialized then
  1450.    
  1451.         local switch = CHAT_SYSTEM.switchLookup[0]
  1452.        
  1453.         -- It's a message
  1454.         switch = CHAT_SYSTEM.switchLookup[currentChannel]
  1455.         -- Below code suspected issue fix under comment - Bug ticket 2253 6/12/2018
  1456.         --[[
  1457.                 rewritedText = string.format("%s ", switch)
  1458.         if currentTarget then
  1459.             rewritedText = string.format("%s%s ", rewritedText, currentTarget)
  1460.         end
  1461.         rewritedText = string.format("%s%s", rewritedText, text)
  1462.         ]]--
  1463.         -- New code for bug ticket 2253 6/12/2018
  1464.         if switch ~= nil then
  1465.             rewritedText = string.format("%s ", switch)
  1466.             if currentTarget then
  1467.                 rewritedText = string.format("%s%s ", rewritedText, currentTarget)
  1468.             end
  1469.             rewritedText = string.format("%s%s", rewritedText, text)
  1470.         end
  1471.     end
  1472.    
  1473.     CHAT_SYSTEM.textEntry.commandHistory:Add(rewritedText)
  1474.     CHAT_SYSTEM.textEntry.commandHistoryCursor = CHAT_SYSTEM.textEntry.commandHistory:Size() + 1
  1475.    
  1476. end
  1477.  
  1478. -- Rewrite of a core function
  1479. function CHAT_SYSTEM.textEntry:GetText()
  1480.     local text = self.editControl:GetText()
  1481.    
  1482.     if text ~= "" then
  1483.         if string.sub(text,1,1) == "!" then
  1484.             if string.len(text) <= 12 then
  1485.                
  1486.                 local automatedMessage = true
  1487.                 for k, v in ipairs(db.automatedMessages) do
  1488.                     if v.name == text then
  1489.                         text = db.automatedMessages[k].message
  1490.                         automatedMessage = true
  1491.                     end
  1492.                 end
  1493.            
  1494.                 if automatedMessage then
  1495.                    
  1496.                     if string.len(text) < 1 or string.len(text) > 351 then
  1497.                         text = self.editControl:GetText()
  1498.                     end
  1499.                    
  1500.                     if self.ignoreTextEntryChangedEvent then return end
  1501.                     self.ignoreTextEntryChangedEvent = true
  1502.                    
  1503.                     local spaceStart, spaceEnd = zo_strfind(text, " ", 1, true)
  1504.                    
  1505.                     if spaceStart and spaceStart > 1 then
  1506.                         local potentialSwitch = zo_strsub(text, 1, spaceStart - 1)
  1507.                         local switch = CHAT_SYSTEM.switchLookup[potentialSwitch:lower()]
  1508.  
  1509.                         local valid, switchArg, deferredError, spaceStartOverride = CHAT_SYSTEM:ValidateSwitch(switch, text, spaceStart)
  1510.  
  1511.                         if valid then
  1512.                             if(deferredError) then
  1513.                                 self.requirementErrorMessage = switch.requirementErrorMessage
  1514.                                 if self.requirementErrorMessage then
  1515.                                     if type(self.requirementErrorMessage) == "string" then
  1516.                                         CHAT_SYSTEM:AddMessage(self.requirementErrorMessage)
  1517.                                     elseif type(self.requirementErrorMessage) == "function" then
  1518.                                         CHAT_SYSTEM:AddMessage(self.requirementErrorMessage())
  1519.                                     end
  1520.                                 end
  1521.                             else
  1522.                                 self.requirementErrorMessage = nil
  1523.                             end
  1524.  
  1525.                             CHAT_SYSTEM:SetChannel(switch.id, switchArg)
  1526.                             local oldCursorPos = CHAT_SYSTEM.textEntry:GetCursorPosition()
  1527.  
  1528.                             spaceStart = spaceStartOverride or spaceStart
  1529.                             CHAT_SYSTEM.textEntry:SetText(zo_strsub(text, spaceStart + 1))
  1530.                             text = zo_strsub(text, spaceStart + 1)
  1531.                             CHAT_SYSTEM.textEntry:SetCursorPosition(oldCursorPos - spaceStart)
  1532.                         end
  1533.                     end
  1534.                    
  1535.                     self.ignoreTextEntryChangedEvent = false
  1536.                    
  1537.                 end
  1538.             end
  1539.         end
  1540.     end
  1541.    
  1542.     return text
  1543.    
  1544. end
  1545.  
  1546. -- Change ChatWindow Darkness by modifying its <Center> & <Edge>. Originally defined in virtual object ZO_ChatContainerTemplate in sharedchatsystem.xml
  1547. local function ChangeChatWindowDarkness(changeSetting)
  1548.  
  1549.     if dbWindowDarkness == 0 then
  1550.         ZO_ChatWindowBg:SetCenterTexture("EsoUI/Art/ChatWindow/chat_BG_center.dds")
  1551.         ZO_ChatWindowBg:SetEdgeTexture("EsoUI/Art/ChatWindow/chat_BG_edge.dds", 256, 256, 32)
  1552.     else
  1553.         ZO_ChatWindowBg:SetCenterColor(0, 0, 0, 1)
  1554.         ZO_ChatWindowBg:SetEdgeColor(0, 0, 0, 1)
  1555.         if db.windowDarkness == 11 and changeSetting then
  1556.             ZO_ChatWindowBg:SetCenterTexture("pChat/dds/chat_bg_center_100.dds")
  1557.             ZO_ChatWindowBg:SetEdgeTexture("pChat/dds/chat_bg_edge_100.dds", 256, 256, 32)
  1558.         elseif db.windowDarkness == 10 then
  1559.             ZO_ChatWindowBg:SetCenterTexture("pChat/dds/chat_bg_center_90.dds")
  1560.             ZO_ChatWindowBg:SetEdgeTexture("pChat/dds/chat_bg_edge_90.dds", 256, 256, 32)
  1561.         elseif db.windowDarkness == 9 then
  1562.             ZO_ChatWindowBg:SetCenterTexture("pChat/dds/chat_bg_center_80.dds")
  1563.             ZO_ChatWindowBg:SetEdgeTexture("pChat/dds/chat_bg_edge_80.dds", 256, 256, 32)
  1564.         elseif db.windowDarkness == 8 then
  1565.             ZO_ChatWindowBg:SetCenterTexture("pChat/dds/chat_bg_center_70.dds")
  1566.             ZO_ChatWindowBg:SetEdgeTexture("pChat/dds/chat_bg_edge_70.dds", 256, 256, 32)
  1567.         elseif db.windowDarkness == 7 then
  1568.             ZO_ChatWindowBg:SetCenterTexture("pChat/dds/chat_bg_center_60.dds")
  1569.             ZO_ChatWindowBg:SetEdgeTexture("pChat/dds/chat_bg_edge_60.dds", 256, 256, 32)
  1570.         elseif db.windowDarkness == 6 then
  1571.             ZO_ChatWindowBg:SetCenterTexture("pChat/dds/chat_bg_center_50.dds")
  1572.             ZO_ChatWindowBg:SetEdgeTexture("pChat/dds/chat_bg_edge_50.dds", 256, 256, 32)
  1573.         elseif db.windowDarkness == 5 then
  1574.             ZO_ChatWindowBg:SetCenterTexture("pChat/dds/chat_bg_center_40.dds")
  1575.             ZO_ChatWindowBg:SetEdgeTexture("pChat/dds/chat_bg_edge_40.dds", 256, 256, 32)
  1576.         elseif db.windowDarkness == 4 then
  1577.             ZO_ChatWindowBg:SetCenterTexture("pChat/dds/chat_bg_center_30.dds")
  1578.             ZO_ChatWindowBg:SetEdgeTexture("pChat/dds/chat_bg_edge_30.dds", 256, 256, 32)
  1579.         elseif db.windowDarkness == 3 then
  1580.             ZO_ChatWindowBg:SetCenterTexture("pChat/dds/chat_bg_center_20.dds")
  1581.             ZO_ChatWindowBg:SetEdgeTexture("pChat/dds/chat_bg_edge_20.dds", 256, 256, 32)
  1582.         elseif db.windowDarkness == 2 then
  1583.             ZO_ChatWindowBg:SetCenterTexture("pChat/dds/chat_bg_center_10.dds")
  1584.             ZO_ChatWindowBg:SetEdgeTexture("pChat/dds/chat_bg_edge_10.dds", 256, 256, 32)
  1585.         elseif db.windowDarkness == 1 then
  1586.             ZO_ChatWindowBg:SetCenterColor(0, 0, 0, 0)
  1587.             ZO_ChatWindowBg:SetEdgeColor(0, 0, 0, 0)
  1588.         end
  1589.     end
  1590. end
  1591.  
  1592. -- Add IM label on XML Initialization, set anchor and set it hidden
  1593. function pChat_AddIMLabel(control)
  1594.    
  1595.     control:SetParent(CHAT_SYSTEM.control)
  1596.     control:ClearAnchors()
  1597.     control:SetAnchor(RIGHT, ZO_ChatWindowOptions, LEFT, -5, 32)
  1598.     CHAT_SYSTEM.IMLabel = control
  1599.  
  1600. end
  1601.  
  1602. -- Add IM label on XML Initialization, set anchor and set it hidden. Used for Chat Minibar
  1603. function pChat_AddIMLabelMin(control)
  1604.    
  1605.     control:SetParent(CHAT_SYSTEM.control)
  1606.     control:ClearAnchors()
  1607.     control:SetAnchor(BOTTOM, CHAT_SYSTEM.minBar.maxButton, TOP, 2, 0)
  1608.     CHAT_SYSTEM.IMLabelMin = control
  1609.  
  1610. end
  1611.  
  1612. -- Add IM close button on XML Initialization, set anchor and set it hidden
  1613. function pChat_AddIMButton(control)
  1614.    
  1615.     control:SetParent(CHAT_SYSTEM.control)
  1616.     control:ClearAnchors()
  1617.     control:SetAnchor(RIGHT, ZO_ChatWindowOptions, LEFT, 2, 35)
  1618.     CHAT_SYSTEM.IMbutton = control
  1619.  
  1620. end
  1621.  
  1622. -- Hide it
  1623. local function HideIMTooltip()
  1624.     ClearTooltip(InformationTooltip)
  1625. end
  1626.  
  1627. -- Show IM notification tooltip
  1628. local function ShowIMTooltip(self, lineNumber)
  1629.  
  1630.     if db.LineStrings[lineNumber] then
  1631.  
  1632.         local sender = db.LineStrings[lineNumber].rawFrom
  1633.         local text = db.LineStrings[lineNumber].rawMessage
  1634.        
  1635.         if (not IsDecoratedDisplayName(sender)) then
  1636.             sender = zo_strformat(SI_UNIT_NAME, sender)
  1637.         end
  1638.        
  1639.         InitializeTooltip(InformationTooltip, self, BOTTOMLEFT, 0, 0, TOPRIGHT)
  1640.         InformationTooltip:AddLine(sender, "ZoFontGame", 1, 1, 1, TOPLEFT, MODIFY_TEXT_TYPE_NONE, TEXT_ALIGN_LEFT, true)
  1641.        
  1642.         local r, g, b = ZO_NORMAL_TEXT:UnpackRGB()
  1643.         InformationTooltip:AddLine(text, "ZoFontGame", r, g, b, TOPLEFT, MODIFY_TEXT_TYPE_NONE, TEXT_ALIGN_LEFT, true)
  1644.        
  1645.     end
  1646.  
  1647. end
  1648.  
  1649. -- When an incoming Whisper is received
  1650. local function OnIMReceived(from, lineNumber)
  1651.    
  1652.     -- Display visual notification
  1653.     if db.notifyIM then
  1654.        
  1655.         -- If chat minimized, show the minified button
  1656.         if (CHAT_SYSTEM:IsMinimized()) then
  1657.             CHAT_SYSTEM.IMLabelMin:SetHandler("OnMouseEnter", function(self) ShowIMTooltip(self, lineNumber) end)
  1658.             CHAT_SYSTEM.IMLabelMin:SetHandler("OnMouseExit", HideIMTooltip)
  1659.             CHAT_SYSTEM.IMLabelMin:SetHidden(false)
  1660.         else
  1661.             -- Chat maximized
  1662.             local _, scrollMax = CHAT_SYSTEM.primaryContainer.scrollbar:GetMinMax()
  1663.            
  1664.             -- If whispers not show in the tab or we don't scroll at bottom
  1665.             if ((not IsChatContainerTabCategoryEnabled(1, pChatData.activeTab, CHAT_CATEGORY_WHISPER_INCOMING)) or (IsChatContainerTabCategoryEnabled(1, pChatData.activeTab, CHAT_CATEGORY_WHISPER_INCOMING) and CHAT_SYSTEM.primaryContainer.scrollbar:GetValue() < scrollMax)) then
  1666.                
  1667.                 -- Undecorate (^F / ^M)
  1668.                 if (not IsDecoratedDisplayName(from)) then
  1669.                     from = zo_strformat(SI_UNIT_NAME, from)
  1670.                 end
  1671.                
  1672.                 -- Split if name too long
  1673.                 local displayedFrom = from
  1674.                 if string.len(displayedFrom) > 8 then
  1675.                     displayedFrom = string.sub(from, 1, 7) .. ".."
  1676.                 end
  1677.                
  1678.                 -- Show
  1679.                 CHAT_SYSTEM.IMLabel:SetText(displayedFrom)
  1680.                 CHAT_SYSTEM.IMLabel:SetHidden(false)
  1681.                 CHAT_SYSTEM.IMbutton:SetHidden(false)
  1682.                
  1683.                 -- Add handler
  1684.                 CHAT_SYSTEM.IMLabel:SetHandler("OnMouseEnter", function(self) ShowIMTooltip(self, lineNumber) end)
  1685.                 CHAT_SYSTEM.IMLabel:SetHandler("OnMouseExit", function(self) HideIMTooltip() end)
  1686.             end
  1687.         end
  1688.        
  1689.     end
  1690.  
  1691. end
  1692.  
  1693. -- Hide IM notification when click on it. Can be Called by XML
  1694. function pChat_RemoveIMNotification()
  1695.     CHAT_SYSTEM.IMLabel:SetHidden(true)
  1696.     CHAT_SYSTEM.IMLabelMin:SetHidden(true)
  1697.     CHAT_SYSTEM.IMbutton:SetHidden(true)
  1698. end
  1699.  
  1700. -- Will try to display the notified IM. Called by XML
  1701. function pChat_TryToJumpToIm(isMinimized)
  1702.    
  1703.     -- Show chat first
  1704.     if isMinimized then
  1705.         CHAT_SYSTEM:Maximize()
  1706.         CHAT_SYSTEM.IMLabelMin:SetHidden(true)
  1707.     end
  1708.    
  1709.     -- Tab get IM, scroll
  1710.     if IsChatContainerTabCategoryEnabled(1, pChatData.activeTab, CHAT_CATEGORY_WHISPER_INCOMING) then
  1711.         ZO_ChatSystem_ScrollToBottom(CHAT_SYSTEM.control)
  1712.         pChat_RemoveIMNotification()
  1713.     else
  1714.        
  1715.         -- Tab don't have IM's, switch to next
  1716.         local numTabs = #CHAT_SYSTEM.primaryContainer.windows
  1717.         local actualTab = pChatData.activeTab + 1
  1718.         local oldActiveTab = pChatData.activeTab
  1719.         local PRESSED = 1
  1720.         local UNPRESSED = 2
  1721.         local hasSwitched = false
  1722.         local maxt = 1
  1723.        
  1724.         while actualTab <= numTabs and (not hasSwitched) do
  1725.             if IsChatContainerTabCategoryEnabled(1, actualTab, CHAT_CATEGORY_WHISPER_INCOMING) then
  1726.            
  1727.                 CHAT_SYSTEM.primaryContainer:HandleTabClick(CHAT_SYSTEM.primaryContainer.windows[actualTab].tab)
  1728.                
  1729.                 local tabText = GetControl("ZO_ChatWindowTabTemplate" .. actualTab .. "Text")
  1730.                 tabText:SetColor(GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_SELECTED))
  1731.                 tabText:GetParent().state = PRESSED
  1732.                 local oldTabText = GetControl("ZO_ChatWindowTabTemplate" .. oldActiveTab .. "Text")
  1733.                 oldTabText:SetColor(GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_CONTRAST))
  1734.                 oldTabText:GetParent().state = UNPRESSED
  1735.                 ZO_ChatSystem_ScrollToBottom(CHAT_SYSTEM.control)
  1736.                
  1737.                 hasSwitched = true
  1738.             else
  1739.                 actualTab = actualTab + 1
  1740.             end
  1741.         end
  1742.            
  1743.         actualTab = 1
  1744.        
  1745.         -- If we were on tab #3 and IM are show on tab #2, need to restart from 1
  1746.         while actualTab < oldActiveTab and (not hasSwitched) do
  1747.             if IsChatContainerTabCategoryEnabled(1, actualTab, CHAT_CATEGORY_WHISPER_INCOMING) then
  1748.            
  1749.                 CHAT_SYSTEM.primaryContainer:HandleTabClick(CHAT_SYSTEM.primaryContainer.windows[actualTab].tab)
  1750.                
  1751.                 local tabText = GetControl("ZO_ChatWindowTabTemplate" .. actualTab .. "Text")
  1752.                 tabText:SetColor(GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_SELECTED))
  1753.                 tabText:GetParent().state = PRESSED
  1754.                 local oldTabText = GetControl("ZO_ChatWindowTabTemplate" .. oldActiveTab .. "Text")
  1755.                 oldTabText:SetColor(GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_CONTRAST))
  1756.                 oldTabText:GetParent().state = UNPRESSED
  1757.                 ZO_ChatSystem_ScrollToBottom(CHAT_SYSTEM.control)
  1758.                
  1759.                 hasSwitched = true
  1760.             else
  1761.                 actualTab = actualTab + 1
  1762.             end
  1763.         end
  1764.        
  1765.     end
  1766.    
  1767. end
  1768.  
  1769. -- Rewrite of a core function, if user click on the scroll to bottom button, Hide IM notification
  1770. -- Todo: Hide IM when user manually scroll to the bottom
  1771. function ZO_ChatSystem_ScrollToBottom(control)
  1772.  
  1773.     CHAT_SYSTEM.IMbutton:SetHidden(true)
  1774.     CHAT_SYSTEM.IMLabel:SetHidden(true)
  1775.     control.container:ScrollToBottom()
  1776.    
  1777. end
  1778.  
  1779. -- Set copied text into text entry, if possible
  1780. local function CopyToTextEntry(message)
  1781.  
  1782.     -- Max of inputbox is 351 chars
  1783.     if string.len(message) < 351 then
  1784.         if CHAT_SYSTEM.textEntry:GetText() == "" then
  1785.             CHAT_SYSTEM.textEntry:Open(message)
  1786.             ZO_ChatWindowTextEntryEditBox:SelectAll()
  1787.         end
  1788.     end
  1789.  
  1790. end
  1791.  
  1792. -- Copy message (only message)
  1793. local function CopyMessage(numLine)
  1794.     -- Args are passed as string trought LinkHandlerSystem
  1795.     CopyToTextEntry(db.LineStrings[numLine].rawMessage)
  1796. end
  1797.  
  1798. --Copy line (including timestamp, from, channel, message, etc)
  1799. local function CopyLine(numLine)
  1800.     -- Args are passed as string trought LinkHandlerSystem
  1801.     CopyToTextEntry(db.LineStrings[numLine].rawLine)
  1802. end
  1803.  
  1804. -- Popup a dialog message with text to copy
  1805. local function ShowCopyDialog(message)
  1806.  
  1807.     -- Split text, courtesy of LibOrangUtils, modified to handle multibyte characters
  1808.     local function str_lensplit(text, maxChars)
  1809.  
  1810.         local ret                   = {}
  1811.         local text_len              = string.len(text)
  1812.         local UTFAditionalBytes = 0
  1813.         local fromWithUTFShift  = 0
  1814.         local doCut                 = true
  1815.        
  1816.         if(text_len <= maxChars) then
  1817.             ret[#ret+1] = text
  1818.         else
  1819.        
  1820.             local splittedStart = 0
  1821.             local splittedEnd = splittedStart + maxChars - 1
  1822.        
  1823.             while doCut do
  1824.                
  1825.                 if UTFAditionalBytes > 0 then
  1826.                     fromWithUTFShift = UTFAditionalBytes
  1827.                 else
  1828.                     fromWithUTFShift = 0
  1829.                 end
  1830.                
  1831.                 local UTFAditionalBytes = 0
  1832.                
  1833.                 splittedEnd = splittedStart + maxChars - 1
  1834.                
  1835.                 if(splittedEnd >= text_len) then
  1836.                     splittedEnd = text_len
  1837.                     doCut = false
  1838.                 elseif (string.byte(text, splittedEnd, splittedEnd)) > 128 then
  1839.                     UTFAditionalBytes = 1
  1840.                    
  1841.                     local lastByte = string.byte(splittedString, -1)
  1842.                     local beforeLastByte = string.byte(splittedString, -2, -2)
  1843.                    
  1844.                     if (lastByte < 128) then
  1845.                         --
  1846.                     elseif lastByte >= 128 and lastByte < 192 then
  1847.                        
  1848.                         if beforeLastByte >= 192 and beforeLastByte < 224 then
  1849.                             --
  1850.                         elseif beforeLastByte >= 128 and beforeLastByte < 192 then
  1851.                             --
  1852.                         elseif beforeLastByte >= 224 and beforeLastByte < 240 then
  1853.                             UTFAditionalBytes = 1
  1854.                         end
  1855.                        
  1856.                         splittedEnd = splittedEnd + UTFAditionalBytes
  1857.                         splittedString = text:sub(splittedStart, splittedEnd)
  1858.                        
  1859.                     elseif lastByte >= 192 and lastByte < 224 then
  1860.                         UTFAditionalBytes = 1
  1861.                         splittedEnd = splittedEnd + UTFAditionalBytes
  1862.                     elseif lastByte >= 224 and lastByte < 240 then
  1863.                         UTFAditionalBytes = 2
  1864.                         splittedEnd = splittedEnd + UTFAditionalBytes
  1865.                     end
  1866.                    
  1867.                 end
  1868.                
  1869.                 --ret = ret+1
  1870.                 ret[#ret+1] = string.sub(text, splittedStart, splittedEnd)
  1871.                
  1872.                 splittedStart = splittedEnd + 1
  1873.                
  1874.             end
  1875.         end
  1876.        
  1877.         return ret
  1878.        
  1879.     end
  1880.    
  1881.     local maxChars      = 20000
  1882.    
  1883.     -- editbox is 20000 chars max
  1884.     if string.len(message) < maxChars then
  1885.         pChatCopyDialogTLCTitle:SetText(GetString(PCHAT_COPYXMLTITLE))
  1886.         pChatCopyDialogTLCLabel:SetText(GetString(PCHAT_COPYXMLLABEL))
  1887.         pChatCopyDialogTLCNoteEdit:SetText(message)
  1888.         pChatCopyDialogTLCNoteNext:SetHidden(true)
  1889.         pChatCopyDialogTLC:SetHidden(false)
  1890.         pChatCopyDialogTLCNoteEdit:SetEditEnabled(false)
  1891.         pChatCopyDialogTLCNoteEdit:SelectAll()
  1892.     else
  1893.        
  1894.         pChatCopyDialogTLCTitle:SetText(GetString(PCHAT_COPYXMLTITLE))
  1895.         pChatCopyDialogTLCLabel:SetText(GetString(PCHAT_COPYXMLTOOLONG))
  1896.        
  1897.         pChatData.messageTableId = 1
  1898.         pChatData.messageTable = str_lensplit(message, maxChars)
  1899.         pChatCopyDialogTLCNoteNext:SetText(GetString(PCHAT_COPYXMLNEXT) .. " ( " .. pChatData.messageTableId .. " / " .. #pChatData.messageTable .. " )")
  1900.         pChatCopyDialogTLCNoteEdit:SetText(pChatData.messageTable[pChatData.messageTableId])
  1901.         pChatCopyDialogTLCNoteEdit:SetEditEnabled(false)
  1902.         pChatCopyDialogTLCNoteEdit:SelectAll()
  1903.        
  1904.         pChatCopyDialogTLC:SetHidden(false)
  1905.         pChatCopyDialogTLCNoteNext:SetHidden(false)
  1906.        
  1907.         pChatCopyDialogTLCNoteEdit:TakeFocus()
  1908.        
  1909.     end
  1910.    
  1911. end
  1912.  
  1913. -- Copy discussion
  1914. -- It will copy all text mark with the same chanCode
  1915. -- Todo : Whisps by person
  1916. local function CopyDiscussion(chanNumber, numLine)
  1917.  
  1918.     -- Args are passed as string trought LinkHandlerSystem
  1919.     local numChanCode = tonumber(chanNumber)
  1920.     -- Whispers sent and received together
  1921.     if numChanCode == CHAT_CHANNEL_WHISPER_SENT then
  1922.         numChanCode = CHAT_CHANNEL_WHISPER
  1923.     elseif numChanCode == PCHAT_URL_CHAN then
  1924.         numChanCode = db.LineStrings[numLine].channel
  1925.     end
  1926.    
  1927.     local stringToCopy = ""
  1928.     for k, data in ipairs(db.LineStrings) do
  1929.         if numChanCode == CHAT_CHANNEL_WHISPER or numChanCode == CHAT_CHANNEL_WHISPER_SENT then
  1930.             if data.channel == CHAT_CHANNEL_WHISPER or data.channel == CHAT_CHANNEL_WHISPER_SENT then
  1931.                 if stringToCopy == "" then
  1932.                     stringToCopy = db.LineStrings[k].rawLine
  1933.                 else
  1934.                     stringToCopy = stringToCopy .. "\r\n" .. db.LineStrings[k].rawLine
  1935.                 end
  1936.             end
  1937.         elseif data.channel == numChanCode then
  1938.             if stringToCopy == "" then
  1939.                 stringToCopy = db.LineStrings[k].rawLine
  1940.             else
  1941.                 stringToCopy = stringToCopy .. "\r\n" .. db.LineStrings[k].rawLine
  1942.             end
  1943.         end
  1944.     end
  1945.    
  1946.     ShowCopyDialog(stringToCopy)
  1947.    
  1948. end
  1949.  
  1950. -- Copy Whole chat (not tab)
  1951. local function CopyWholeChat()
  1952.  
  1953.     local stringToCopy = ""
  1954.     for k, data in ipairs(db.LineStrings) do
  1955.         if stringToCopy == "" then
  1956.             stringToCopy = db.LineStrings[k].rawLine
  1957.         else
  1958.             stringToCopy = stringToCopy .. "\r\n" .. db.LineStrings[k].rawLine
  1959.         end
  1960.     end
  1961.    
  1962.     ShowCopyDialog(stringToCopy)
  1963.    
  1964. end
  1965.  
  1966. -- Show contextualMenu when clicking on a pChatLink
  1967. local function ShowContextMenuOnHandlers(numLine, chanNumber)
  1968.    
  1969.     ClearMenu()
  1970.    
  1971.     if not ZO_Dialogs_IsShowingDialog() then
  1972.         AddMenuItem(GetString(PCHAT_COPYMESSAGECT), function() CopyMessage(numLine) end)
  1973.         AddMenuItem(GetString(PCHAT_COPYLINECT), function() CopyLine(numLine) end)
  1974.         AddMenuItem(GetString(PCHAT_COPYDISCUSSIONCT), function() CopyDiscussion(chanNumber, numLine) end)
  1975.         AddMenuItem(GetString(PCHAT_ALLCT), CopyWholeChat)
  1976.     end
  1977.    
  1978.     ShowMenu()
  1979.        
  1980. end
  1981.  
  1982. -- Triggers when right clicking on a LinkHandler
  1983. local function OnLinkClicked(rawLink, mouseButton, linkText, color, linkType, lineNumber, chanCode)
  1984.    
  1985.     -- Only executed on LinkType = PCHAT_LINK
  1986.     if linkType == PCHAT_LINK then
  1987.        
  1988.         local chanNumber = tonumber(chanCode)
  1989.         local numLine = tonumber(lineNumber)
  1990.         -- PCHAT_LINK also handle a linkable channel feature for linkable channels
  1991.        
  1992.         -- Context Menu
  1993.         if chanCode and mouseButton == MOUSE_BUTTON_INDEX_LEFT then
  1994.        
  1995.             -- Only Linkable channel - TODO use .channelLinkable
  1996.             if chanNumber == CHAT_CHANNEL_SAY
  1997.             or chanNumber == CHAT_CHANNEL_YELL
  1998.             or chanNumber == CHAT_CHANNEL_PARTY
  1999.             or chanNumber == CHAT_CHANNEL_ZONE
  2000.             or chanNumber == CHAT_CHANNEL_ZONE_LANGUAGE_1
  2001.             or chanNumber == CHAT_CHANNEL_ZONE_LANGUAGE_2
  2002.             or chanNumber == CHAT_CHANNEL_ZONE_LANGUAGE_3
  2003.             or chanNumber == CHAT_CHANNEL_ZONE_LANGUAGE_4
  2004.             or (chanNumber >= CHAT_CHANNEL_GUILD_1 and chanNumber <= CHAT_CHANNEL_GUILD_5)
  2005.             or (chanNumber >= CHAT_CHANNEL_OFFICER_1 and chanNumber <= CHAT_CHANNEL_OFFICER_5) then
  2006.                 IgnoreMouseDownEditFocusLoss()
  2007.                 CHAT_SYSTEM:StartTextEntry(nil, chanNumber)
  2008.             elseif chanNumber == CHAT_CHANNEL_WHISPER then
  2009.                 local target = zo_strformat(SI_UNIT_NAME, db.LineStrings[numLine].rawFrom)
  2010.                 IgnoreMouseDownEditFocusLoss()
  2011.                 CHAT_SYSTEM:StartTextEntry(nil, chanNumber, target)
  2012.             elseif chanNumber == PCHAT_URL_CHAN then
  2013.                 RequestOpenUnsafeURL(linkText)
  2014.             end
  2015.            
  2016.         elseif mouseButton == MOUSE_BUTTON_INDEX_RIGHT then
  2017.             -- Right clic, copy System
  2018.             ShowContextMenuOnHandlers(numLine, chanNumber)
  2019.         end
  2020.        
  2021.         -- Don't execute LinkHandler code
  2022.         return true
  2023.    
  2024.     end
  2025.    
  2026. end
  2027.  
  2028. local function CopyToTextEntryText()
  2029.     if db.enablecopy then
  2030.         LINK_HANDLER:RegisterCallback(LINK_HANDLER.LINK_CLICKED_EVENT, OnLinkClicked)
  2031.         LINK_HANDLER:RegisterCallback(LINK_HANDLER.LINK_MOUSE_UP_EVENT, OnLinkClicked)
  2032.     end
  2033. end
  2034.  
  2035. -- Called by XML
  2036. function pChat_ShowCopyDialogNext()
  2037.    
  2038.     pChatData.messageTableId = pChatData.messageTableId + 1
  2039.    
  2040.     -- Security
  2041.     if pChatData.messageTable[pChatData.messageTableId] then
  2042.        
  2043.         -- Build button
  2044.         pChatCopyDialogTLCNoteNext:SetText(GetString(PCHAT_COPYXMLNEXT) .. " ( " .. pChatData.messageTableId .. " / " .. #pChatData.messageTable .. " )")
  2045.         pChatCopyDialogTLCNoteEdit:SetText(pChatData.messageTable[pChatData.messageTableId])
  2046.         pChatCopyDialogTLCNoteEdit:SetEditEnabled(false)
  2047.         pChatCopyDialogTLCNoteEdit:SelectAll()
  2048.        
  2049.         -- Don't show next button if its the last
  2050.         if not pChatData.messageTable[pChatData.messageTableId + 1] then
  2051.             pChatCopyDialogTLCNoteNext:SetHidden(true)
  2052.         end
  2053.        
  2054.         pChatCopyDialogTLCNoteEdit:TakeFocus()
  2055.        
  2056.     end
  2057.    
  2058. end
  2059.  
  2060. -- Needed to bind Shift+Tab in SetSwitchToNextBinding
  2061. function KEYBINDING_MANAGER:IsChordingAlwaysEnabled()
  2062.     return true
  2063. end
  2064.  
  2065. local function SetSwitchToNextBinding()
  2066.  
  2067.     ZO_CreateStringId("SI_BINDING_NAME_PCHAT_SWITCH_TAB", GetString(PCHAT_SWITCHTONEXTTABBINDING))
  2068.  
  2069.     -- get SwitchTab Keybind params
  2070.     local layerIndex, categoryIndex, actionIndex = GetActionIndicesFromName("PCHAT_SWITCH_TAB")
  2071.    
  2072.     --If exists
  2073.     if layerIndex and categoryIndex and actionIndex then
  2074.        
  2075.         local key = GetActionBindingInfo(layerIndex, categoryIndex, actionIndex, 1)
  2076.        
  2077.         if key == KEY_INVALID then
  2078.             -- Unbind it
  2079.             if IsProtectedFunction("UnbindAllKeysFromAction") then
  2080.                 CallSecureProtected("UnbindAllKeysFromAction", layerIndex, categoryIndex, actionIndex)
  2081.             else
  2082.                 UnbindAllKeysFromAction(layerIndex, categoryIndex, actionIndex)
  2083.             end
  2084.            
  2085.             -- Set it to its default value
  2086.             if IsProtectedFunction("BindKeyToAction") then
  2087.                 CallSecureProtected("BindKeyToAction", layerIndex, categoryIndex, actionIndex, 1, KEY_TAB, 0, 0, KEY_SHIFT, 0)
  2088.             else
  2089.                 BindKeyToAction(layerIndex, categoryIndex, actionIndex, 1, KEY_TAB , 0, 0, KEY_SHIFT, 0)
  2090.             end
  2091.         end
  2092.        
  2093.     end
  2094.    
  2095. end
  2096.  
  2097. -- Can be called by Bindings
  2098. function pChat_SwitchToNextTab()
  2099.    
  2100.     local hasSwitched
  2101.    
  2102.     local PRESSED = 1
  2103.     local UNPRESSED = 2
  2104.     local numTabs = #CHAT_SYSTEM.primaryContainer.windows
  2105.    
  2106.     if numTabs > 1 then
  2107.         for numTab, container in ipairs (CHAT_SYSTEM.primaryContainer.windows) do
  2108.            
  2109.             if (not hasSwitched) then
  2110.                 if pChatData.activeTab + 1 == numTab then
  2111.                     CHAT_SYSTEM.primaryContainer:HandleTabClick(container.tab)
  2112.                    
  2113.                     local tabText = GetControl("ZO_ChatWindowTabTemplate" .. numTab .. "Text")
  2114.                     tabText:SetColor(GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_SELECTED))
  2115.                     tabText:GetParent().state = PRESSED
  2116.                     local oldTabText = GetControl("ZO_ChatWindowTabTemplate" .. numTab - 1 .. "Text")
  2117.                     oldTabText:SetColor(GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_CONTRAST))
  2118.                     oldTabText:GetParent().state = UNPRESSED
  2119.                    
  2120.                     hasSwitched = true
  2121.                 end
  2122.             end
  2123.            
  2124.         end
  2125.        
  2126.         if (not hasSwitched) then
  2127.             CHAT_SYSTEM.primaryContainer:HandleTabClick(CHAT_SYSTEM.primaryContainer.windows[1].tab)
  2128.             local tabText = GetControl("ZO_ChatWindowTabTemplate1Text")
  2129.             tabText:SetColor(GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_SELECTED))
  2130.             tabText:GetParent().state = PRESSED
  2131.             local oldTabText = GetControl("ZO_ChatWindowTabTemplate" .. #CHAT_SYSTEM.primaryContainer.windows .. "Text")
  2132.             oldTabText:SetColor(GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_CONTRAST))
  2133.             oldTabText:GetParent().state = UNPRESSED
  2134.         end
  2135.     end
  2136.    
  2137. end
  2138. --**** Issue
  2139. local function SetDefaultTab(tabToSet)
  2140.    
  2141.     -- Search in all tabs the good name
  2142.     for numTab in ipairs(CHAT_SYSTEM.primaryContainer.windows) do
  2143.         -- Not this one, try the next one, if tab is not found (newly added, removed), pChat_SwitchToNextTab() will go back to tab 1
  2144.         if tonumber(tabToSet) ~= numTab then
  2145.             pChat_SwitchToNextTab()
  2146.         else
  2147.             -- Found it, stop
  2148.             return
  2149.         end
  2150.     end
  2151. end
  2152.  
  2153. function pChat_ChangeTab(tabToSet)
  2154.     if type(tabToSet)~="number" then return end
  2155.     local container=CHAT_SYSTEM.primaryContainer if not container then return end
  2156.     if tabToSet<1 or tabToSet>#container.windows then return end
  2157.     if container.windows[tabToSet].tab==nil then return end
  2158.     container.tabGroup:SetClickedButton(container.windows[tabToSet].tab)
  2159.     if CHAT_SYSTEM:IsMinimized() then CHAT_SYSTEM:Maximize() end
  2160.     local container=CHAT_SYSTEM.primaryContainer
  2161.     if not container then return end
  2162.     local tabToSet=container.currentBuffer:GetParent().tab.tabToSet
  2163.  
  2164. end
  2165.  
  2166. local function StripLinesFromLineStrings(typeOfExit)
  2167.  
  2168.     local k = 1
  2169.     -- First loop is time based. If message is older than our limit, it will be stripped.
  2170.     while k <= #db.LineStrings do
  2171.    
  2172.         if db.LineStrings[k] then
  2173.             local channel = db.LineStrings[k].channel
  2174.             if channel == CHAT_CHANNEL_SYSTEM and (not db.restoreSystem) then
  2175.                 table.remove(db.LineStrings, k)
  2176.                 db.lineNumber = db.lineNumber - 1
  2177.                 k = k-1
  2178.             elseif channel ~= CHAT_CHANNEL_SYSTEM and db.restoreSystemOnly then
  2179.                 table.remove(db.LineStrings, k)
  2180.                 db.lineNumber = db.lineNumber - 1
  2181.                 k = k-1
  2182.             elseif (channel == CHAT_CHANNEL_WHISPER or channel == CHAT_CHANNEL_WHISPER_SENT) and (not db.restoreWhisps) then
  2183.                 table.remove(db.LineStrings, k)
  2184.                 db.lineNumber = db.lineNumber - 1
  2185.                 k = k-1
  2186.             elseif typeOfExit ~= 1 then
  2187.                 if db.LineStrings[k].rawTimestamp then
  2188.                     if (GetTimeStamp() - db.LineStrings[k].rawTimestamp) > (db.timeBeforeRestore * 60 * 60) then
  2189.                         table.remove(db.LineStrings, k)
  2190.                         db.lineNumber = db.lineNumber - 1
  2191.                         k = k-1
  2192.                     elseif db.LineStrings[k].rawTimestamp > GetTimeStamp() then -- System clock of users computer badly set and msg received meanwhile.
  2193.                         table.remove(db.LineStrings, k)
  2194.                         db.lineNumber = db.lineNumber - 1
  2195.                         k = k-1
  2196.                     end
  2197.                 end
  2198.             end
  2199.         end
  2200.        
  2201.         k = k + 1
  2202.    
  2203.     end
  2204.    
  2205.     -- 2nd loop is size based. If dump is too big, just delete old ones
  2206.     if k > 5000 then
  2207.         local linesToDelete = k - 5000
  2208.         for l=1, linesToDelete do
  2209.        
  2210.             if db.LineStrings[l] then
  2211.                 table.remove(db.LineStrings, l)
  2212.                 db.lineNumber = db.lineNumber - 1
  2213.             end
  2214.        
  2215.         end
  2216.     end
  2217.    
  2218. end
  2219.  
  2220. local function SaveChatHistory(typeOf)
  2221.    
  2222.     db.history = {}
  2223.    
  2224.     if (db.restoreOnReloadUI == true and typeOf == 1) or (db.restoreOnLogOut == true and typeOf == 2) or (db.restoreOnAFK == true) or (db.restoreOnQuit == true and typeOf == 3) then  
  2225.    
  2226.         if typeOf == 1 then
  2227.            
  2228.             db.lastWasReloadUI = true
  2229.             db.lastWasLogOut = false
  2230.             db.lastWasQuit = false
  2231.             db.lastWasAFK = false
  2232.            
  2233.             --Save actual channel
  2234.             db.history.currentChannel = CHAT_SYSTEM.currentChannel
  2235.             db.history.currentTarget = CHAT_SYSTEM.currentTarget
  2236.            
  2237.         elseif typeOf == 2 then
  2238.             db.lastWasReloadUI = false
  2239.             db.lastWasLogOut = true
  2240.             db.lastWasQuit = false
  2241.             db.lastWasAFK = false
  2242.         elseif typeOf == 3 then
  2243.             db.lastWasReloadUI = false
  2244.             db.lastWasLogOut = false
  2245.             db.lastWasQuit = true
  2246.             db.lastWasAFK = false
  2247.         end
  2248.        
  2249.         db.history.currentTab = pChatData.activeTab
  2250.        
  2251.         -- Save Chat History isn't needed, because it's saved in realtime, but we can strip some lines from the array to avoid big dumps
  2252.         StripLinesFromLineStrings(typeOf)
  2253.        
  2254.         --Save TextEntry history
  2255.         db.history.textEntry = {}
  2256.         if CHAT_SYSTEM.textEntry.commandHistory.entries then
  2257.             db.history.textEntry.entries = CHAT_SYSTEM.textEntry.commandHistory.entries
  2258.             db.history.textEntry.numEntries = CHAT_SYSTEM.textEntry.commandHistory.index
  2259.         else
  2260.             db.history.textEntry.entries = {}
  2261.             db.history.textEntry.numEntries = 0
  2262.         end
  2263.     else
  2264.         db.LineStrings = {}
  2265.         db.lineNumber = 1
  2266.     end
  2267.    
  2268. end
  2269.  
  2270. local function CreateNewChatTabPostHook()
  2271.  
  2272.     for tabIndex, tabObject in ipairs(CHAT_SYSTEM.primaryContainer.windows) do
  2273.         if db.augmentHistoryBuffer then
  2274.             tabObject.buffer:SetMaxHistoryLines(1000) -- 1000 = max of control
  2275.         end
  2276.         if db.alwaysShowChat then
  2277.             tabObject.buffer:SetLineFade(3600, 2)
  2278.         end
  2279.     end
  2280.    
  2281. end
  2282.  
  2283. local function ShowFadedLines()
  2284.  
  2285.     local origChatSystemCreateNewChatTab = CHAT_SYSTEM.CreateNewChatTab
  2286.     CHAT_SYSTEM.CreateNewChatTab = function(self, ...)
  2287.         origChatSystemCreateNewChatTab(self, ...)
  2288.         CreateNewChatTabPostHook()
  2289.     end
  2290.    
  2291. end
  2292.  
  2293. local function OnReticleTargetChanged()
  2294.     if IsUnitPlayer("reticleover") then
  2295.         targetToWhisp = GetUnitName("reticleover")
  2296.     end
  2297. end
  2298.  
  2299. local function BuildNicknames(lamCall)
  2300.    
  2301.     local function Explode(div, str)
  2302.         if (div=='') then return false end
  2303.         local pos,arr = 0,{}
  2304.         for st,sp in function() return string.find(str,div,pos,true) end do
  2305.             table.insert(arr,string.sub(str,pos,st-1))
  2306.             pos = sp + 1
  2307.         end
  2308.         table.insert(arr,string.sub(str,pos))
  2309.         return arr
  2310.     end
  2311.    
  2312.     pChatData.nicknames = {}
  2313.    
  2314.     if db.nicknames ~= "" then
  2315.         local lines = Explode("\n", db.nicknames)
  2316.        
  2317.         for lineIndex=#lines, 1, -1 do
  2318.             local oldName, newName = string.match(lines[lineIndex], "(@?[%w_-]+) ?= ?([%w- ]+)")
  2319.             if not (oldName and newName) then
  2320.                 table.remove(lines, lineIndex)
  2321.             else
  2322.                 pChatData.nicknames[oldName] = newName
  2323.             end
  2324.         end
  2325.        
  2326.         db.nicknames = table.concat(lines, "\n")
  2327.        
  2328.         if lamCall then
  2329.             CALLBACK_MANAGER:FireCallbacks("LAM-RefreshPanel", pChatData.LAMPanel)
  2330.         end
  2331.        
  2332.     end
  2333.    
  2334. end
  2335.  
  2336. -- Change font of chat
  2337. local function ChangeChatFont(change)
  2338.  
  2339.     local fontSize = GetChatFontSize()
  2340.    
  2341.     if db.fonts == "ESO Standard Font" or db.fonts == "Univers 57" then
  2342.         return
  2343.     else
  2344.    
  2345.         local fontPath = LMP:Fetch("font", db.fonts)
  2346.        
  2347.         -- Entry Box
  2348.         ZoFontEditChat:SetFont(fontPath .. "|".. fontSize .. "|shadow")
  2349.        
  2350.         -- Chat window
  2351.         ZoFontChat:SetFont(fontPath .. "|" .. fontSize .. "|soft-shadow-thin")
  2352.    
  2353.     end
  2354.  
  2355. end
  2356.  
  2357. -- Rewrite of a core function
  2358. function CHAT_SYSTEM:UpdateTextEntryChannel()
  2359.    
  2360.     local channelData = self.channelData[self.currentChannel]
  2361.    
  2362.     if channelData then
  2363.         self.textEntry:SetChannel(channelData, self.currentTarget)
  2364.        
  2365.         if isAddonLoaded then
  2366.             if not db.useESOcolors then
  2367.                
  2368.                 local pChatcolor
  2369.                 if db.allGuildsSameColour and (self.currentChannel >= CHAT_CHANNEL_GUILD_1 and self.currentChannel <= CHAT_CHANNEL_GUILD_5) then
  2370.                     pChatcolor = db.colours[2*CHAT_CHANNEL_GUILD_1]
  2371.                 elseif db.allGuildsSameColour and (self.currentChannel >= CHAT_CHANNEL_OFFICER_1 and self.currentChannel <= CHAT_CHANNEL_OFFICER_5) then
  2372.                     pChatcolor = db.colours[2*CHAT_CHANNEL_OFFICER_1]
  2373.                 elseif db.allZonesSameColour and (self.currentChannel >= CHAT_CHANNEL_ZONE_LANGUAGE_1 and self.currentChannel <= CHAT_CHANNEL_ZONE_LANGUAGE_4) then
  2374.                     pChatcolor = db.colours[2*CHAT_CHANNEL_ZONE]
  2375.                 else
  2376.                     pChatcolor = db.colours[2*self.currentChannel]
  2377.                 end
  2378.                
  2379.                 if not pChatcolor then
  2380.                     self.textEntry:SetColor(1, 1, 1, 1)
  2381.                 else
  2382.                     local r, g, b, a = ConvertHexToRGBA(pChatcolor)
  2383.                     self.textEntry:SetColor(r, g, b, a)
  2384.                 end
  2385.                
  2386.             else
  2387.                 self.textEntry:SetColor(self:GetCategoryColorFromChannel(self.currentChannel))
  2388.             end
  2389.         else
  2390.             self.textEntry:SetColor(self:GetCategoryColorFromChannel(self.currentChannel))
  2391.         end
  2392.        
  2393.     end
  2394.  
  2395. end
  2396.  
  2397. -- Change guild channel names in entry box
  2398. local function UpdateCharCorrespondanceTableChannelNames()
  2399.    
  2400.     -- Each guild
  2401.     for i = 1, GetNumGuilds() do
  2402.         if db.showTagInEntry then
  2403.            
  2404.             -- Get saved string
  2405.             local tag = db.guildTags[GetGuildName(GetGuildId(i))]
  2406.            
  2407.             -- No SavedVar
  2408.             if not tag then
  2409.                 tag = GetGuildName(GetGuildId(i))
  2410.             -- SavedVar, but no tag
  2411.             elseif tag == "" then
  2412.                 tag = GetGuildName(GetGuildId(i))
  2413.             end
  2414.            
  2415.             -- Get saved string
  2416.             local officertag = db.officertag[GetGuildName(GetGuildId(i))]
  2417.            
  2418.             -- No SavedVar
  2419.             if not officertag then
  2420.                 officertag = tag
  2421.             -- SavedVar, but no tag
  2422.             elseif officertag == "" then
  2423.                 officertag = tag
  2424.             end        
  2425.            
  2426.             -- /g1 is 12 /g5 is 16, /o1=17, etc
  2427.             ChanInfoArray[CHAT_CHANNEL_GUILD_1 - 1 + i].name = tag
  2428.             ChanInfoArray[CHAT_CHANNEL_OFFICER_1 - 1 + i].name = officertag
  2429.             --Activating
  2430.             ChanInfoArray[CHAT_CHANNEL_GUILD_1 - 1 + i].dynamicName = false
  2431.             ChanInfoArray[CHAT_CHANNEL_OFFICER_1 - 1 + i].dynamicName = false
  2432.            
  2433.         else
  2434.             -- /g1 is 12 /g5 is 16, /o1=17, etc
  2435.             ChanInfoArray[CHAT_CHANNEL_GUILD_1 - 1 + i].name = GetGuildName(GetGuildId(i))
  2436.             ChanInfoArray[CHAT_CHANNEL_OFFICER_1 - 1 + i].name = GetGuildName(GetGuildId(i))
  2437.             --Deactivating
  2438.             ChanInfoArray[CHAT_CHANNEL_GUILD_1 - 1 + i].dynamicName = true
  2439.             ChanInfoArray[CHAT_CHANNEL_OFFICER_1 - 1 + i].dynamicName = true
  2440.         end
  2441.     end
  2442.    
  2443.     return
  2444.    
  2445. end
  2446.  
  2447. -- Split text with blocs of 100 chars (106 is max for LinkHandle) and add LinkHandle to them
  2448. -- WARNING : See FormatSysMessage()
  2449. local function SplitTextForLinkHandler(text, numLine, chanCode)
  2450.  
  2451.     local newText = ""
  2452.     local textLen = string.len(text)
  2453.     local MAX_LEN = 100
  2454.    
  2455.     -- LinkHandle does not handle text > 106 chars, so we need to split
  2456.     if textLen > MAX_LEN then
  2457.    
  2458.         local splittedStart = 1
  2459.         local splits = 1
  2460.         local needToSplit = true
  2461.        
  2462.         while needToSplit do
  2463.            
  2464.             local splittedString = ""
  2465.             local UTFAditionalBytes = 0
  2466.                
  2467.             if textLen > (splits * MAX_LEN) then
  2468.                
  2469.                 local splittedEnd = splittedStart + MAX_LEN
  2470.                 splittedString = text:sub(splittedStart, splittedEnd) -- We can "cut" characters by doing this
  2471.                
  2472.                 local lastByte = string.byte(splittedString, -1)
  2473.                 local beforeLastByte = string.byte(splittedString, -2, -2)
  2474.                
  2475.                 -- Characters can be into 1, 2 or 3 bytes. Lua don't support UTF natively. We only handle 3 bytes chars.
  2476.                 -- http://www.utf8-chartable.de/unicode-utf8-table.pl
  2477.                
  2478.                 if (lastByte < 128) then -- any ansi character (ex : a  97  LATIN SMALL LETTER A) (cut was well made)
  2479.                     --
  2480.                 elseif lastByte >= 128 and lastByte < 192 then -- any non ansi character ends with last byte = 128-191  (cut was well made) or 2nd byte of a 3 Byte character. We take 1 byte more.  (cut was incorrect)
  2481.                    
  2482.                     if beforeLastByte >= 192 and beforeLastByte < 224 then -- "2 latin characters" ex: 195 169  LATIN SMALL LETTER E WITH ACUTE ; е    208 181 CYRILLIC SMALL LETTER IE
  2483.                         --
  2484.                     elseif beforeLastByte >= 128 and beforeLastByte < 192 then -- "3 Bytes Cyrillic & Japaneese" ex U+3057  し 227 129 151 HIRAGANA LETTER SI
  2485.                         --
  2486.                     elseif beforeLastByte >= 224 and beforeLastByte < 240 then -- 2nd byte of a 3 Byte character. We take 1 byte more.  (cut was incorrect)
  2487.                         UTFAditionalBytes = 1
  2488.                     end
  2489.                    
  2490.                     splittedEnd = splittedEnd + UTFAditionalBytes
  2491.                     splittedString = text:sub(splittedStart, splittedEnd)
  2492.                    
  2493.                 elseif lastByte >= 192 and lastByte < 224 then -- last byte = 1st byte of a 2 Byte character. We take 1 byte more.  (cut was incorrect)
  2494.                     UTFAditionalBytes = 1
  2495.                     splittedEnd = splittedEnd + UTFAditionalBytes
  2496.                     splittedString = text:sub(splittedStart, splittedEnd)
  2497.                 elseif lastByte >= 224 and lastByte < 240 then -- last byte = 1st byte of a 3 Byte character. We take 2 byte more.  (cut was incorrect)
  2498.                     UTFAditionalBytes = 2
  2499.                     splittedEnd = splittedEnd + UTFAditionalBytes
  2500.                     splittedString = text:sub(splittedStart, splittedEnd)
  2501.                 end
  2502.                
  2503.                 splittedStart = splittedEnd + 1
  2504.                 newText = newText .. string.format("|H1:%s:%s:%s|h%s|h", PCHAT_LINK, numLine, chanCode, splittedString)
  2505.                 splits = splits + 1
  2506.                
  2507.             else
  2508.                 splittedString = text:sub(splittedStart)
  2509.                 local textSplittedlen = splittedString:len()
  2510.                 if textSplittedlen > 0 then
  2511.                     newText = newText .. string.format("|H1:%s:%s:%s|h%s|h", PCHAT_LINK, numLine, chanCode, splittedString)
  2512.                 end
  2513.                 needToSplit = false
  2514.             end
  2515.            
  2516.         end
  2517.     else
  2518.         -- When dumping back, the "from" section is sent here. It will add handler to spaces. prevent it to avoid an uneeded increase of the message.
  2519.         if not (text == " " or text == ": ") then
  2520.             newText = string.format("|H1:%s:%s:%s|h%s|h", PCHAT_LINK, numLine, chanCode, text)
  2521.         else
  2522.             newText = text
  2523.         end
  2524.     end
  2525.    
  2526.     return newText
  2527.    
  2528. end
  2529.  
  2530. -- Sub function of addLinkHandlerToString
  2531. -- WARNING : See FormatSysMessage()
  2532. -- Get a string without |cXXXXXX and without |t as parameter
  2533. local function AddLinkHandlerToStringWithoutDDS(textToCheck, numLine, chanCode)
  2534.  
  2535.     local stillToParse = true
  2536.     local noColorlen = textToCheck:len()
  2537.    
  2538.     -- this var seems to get some rework
  2539.     local startNoColor = 1
  2540.     local textLinked = ""
  2541.     local preventLoopsCol = 0
  2542.     local handledText = ""
  2543.    
  2544.     while stillToParse do
  2545.    
  2546.         -- Prevent infinit loops while its still in beta
  2547.         if preventLoopsCol > 10 then
  2548.             stillToParse = false
  2549.             CHAT_SYSTEM:Zo_AddMessage("pChat triggered an infinite LinkHandling loop in its copy system with last message : " .. textToCheck .. " -- pChat")
  2550.         else
  2551.             preventLoopsCol = preventLoopsCol + 1
  2552.         end
  2553.        
  2554.         noColorlen = textToCheck:len()
  2555.        
  2556.         local startpos, endpos = string.find(textToCheck, "|H(.-):(.-)|h(.-)|h", 1)
  2557.         -- LinkHandler not found
  2558.         if not startpos then
  2559.             -- If nil, then we won't have new link after startposition = startNoColor , so add ours util the end
  2560.            
  2561.             -- Some addons use table.insert() and chat convert to a CRLF
  2562.             -- First, drop the final CRLF if we are at the end of the text
  2563.             if string.sub(textToCheck, -2) == "\r\n" then
  2564.                 textToCheck = string.sub(textToCheck, 1, -2)
  2565.             end
  2566.             -- MultiCRLF is handled in .addLinkHandler()
  2567.            
  2568.             textLinked = SplitTextForLinkHandler(textToCheck, numLine, chanCode)
  2569.             handledText = handledText .. textLinked
  2570.            
  2571.             -- No need to parse after
  2572.             stillToParse = false
  2573.            
  2574.         else
  2575.            
  2576.             -- Link is at the begginning of the string
  2577.             if startpos == 1 then
  2578.                 -- New text is (only handler because its at the pos 1)
  2579.                 handledText = handledText .. textToCheck:sub(startpos, endpos)
  2580.                
  2581.                 -- Do we need to continue ?
  2582.                 if endpos == noColorlen then
  2583.                     -- We're at the end
  2584.                     stillToParse = false
  2585.                 else
  2586.                     -- Still to parse
  2587.                     startNoColor = endpos
  2588.                     -- textToCheck is next to our string
  2589.                     textToCheck = textToCheck:sub(startNoColor + 1)
  2590.                 end
  2591.                
  2592.             else
  2593.            
  2594.                 -- We Handle string from startNoColor of the message up to the Handle link
  2595.                 local textToHandle = textToCheck:sub(1, startpos - 1)
  2596.                
  2597.                 -- Add ours
  2598.                 -- Maybe we need a split due to 106 chars restriction
  2599.                 textLinked = SplitTextForLinkHandler(textToHandle, numLine, chanCode)
  2600.                
  2601.                 -- New text is handledText + (textLinked .. LinkHandler)
  2602.                 handledText = handledText .. textLinked
  2603.                 handledText = handledText .. textToCheck:sub(startpos, endpos)
  2604.                
  2605.                 -- Do we need to continue ?
  2606.                 if endpos == noColorlen then
  2607.                     -- We're at the end
  2608.                     stillToParse = false
  2609.                 else
  2610.                     -- Still to parse
  2611.                     startNoColor = endpos
  2612.                     -- textToCheck is next to our string
  2613.                     textToCheck = textToCheck:sub(startNoColor + 1)
  2614.                 end
  2615.                
  2616.             end
  2617.         end
  2618.     end
  2619.  
  2620.     return handledText
  2621.  
  2622. end
  2623.  
  2624. -- Sub function of addLinkHandlerToLine, Handling DDS textures in chat
  2625. -- WARNING : See FormatSysMessage()
  2626. -- Get a string without |cXXXXXX as parameter
  2627. local function AddLinkHandlerToString(textToCheck, numLine, chanCode)
  2628.  
  2629.     local stillToParseDDS = true
  2630.     local noDDSlen = textToCheck:len()
  2631.    
  2632.     -- this var seems to get some rework
  2633.     local startNoDDS = 1
  2634.     local textLinked = ""
  2635.     local preventLoopsDDS = 0
  2636.     local textTReformated = ""
  2637.    
  2638.     -- Seems the "|" are truncated from the link when send to chatsystem (they're needed for build link, but the output does not include them)
  2639.  
  2640.     while stillToParseDDS do
  2641.    
  2642.         -- Prevent infinit loops while its still in beta
  2643.         if preventLoopsDDS > 20 then
  2644.             stillToParseDDS = false
  2645.             CHAT_SYSTEM:Zo_AddMessage("pChat triggered an infinite DDS loop in its copy system with last message : " .. textToCheck .. " -- pChat")
  2646.         else
  2647.             preventLoopsDDS = preventLoopsDDS + 1
  2648.         end
  2649.    
  2650.         noDDSlen = textToCheck:len()
  2651.        
  2652.         local startpos, endpos = string.find(textToCheck, "|t%-?%d+%%?:%-?%d+%%?:.-|t", 1)
  2653.         -- DDS not found
  2654.         if startpos == nil then
  2655.             -- If nil, then we won't have new link after startposition = startNoDDS , so add ours until the end
  2656.            
  2657.             textLinked = AddLinkHandlerToStringWithoutDDS(textToCheck, numLine, chanCode)
  2658.            
  2659.             textTReformated = textTReformated .. textLinked
  2660.            
  2661.             -- No need to parse after
  2662.             stillToParseDDS = false
  2663.            
  2664.         else
  2665.            
  2666.             -- DDS is at the begginning of the string
  2667.             if startpos == 1 then
  2668.                 -- New text is (only DDS because its at the pos 1)
  2669.                 textTReformated = textTReformated .. textToCheck:sub(startpos, endpos)
  2670.                
  2671.                 -- Do we need to continue ?
  2672.                 if endpos == noDDSlen then
  2673.                     -- We're at the end
  2674.                     stillToParseDDS = false
  2675.                 else
  2676.                     -- Still to parse
  2677.                     startNoDDS = endpos
  2678.                     -- textToCheck is next to our string
  2679.                     textToCheck = textToCheck:sub(startNoDDS + 1)
  2680.                 end
  2681.                
  2682.             else
  2683.            
  2684.                 -- We Handle string from startNoDDS of the message up to the Handle link
  2685.                 local textToHandle = textToCheck:sub(1, startpos - 1)
  2686.                
  2687.                 -- Add ours
  2688.                 textLinked = AddLinkHandlerToStringWithoutDDS(textToHandle, numLine, chanCode)
  2689.                
  2690.                 -- New text is formattedText + (textLinked .. DDS)
  2691.                 textTReformated = textTReformated .. textLinked
  2692.                
  2693.                 textTReformated = textTReformated .. textToCheck:sub(startpos, endpos)
  2694.                
  2695.                 -- Do we need to continue ?
  2696.                 if endpos == noDDSlen then
  2697.                     -- We're at the end
  2698.                     stillToParseDDS = false
  2699.                 else
  2700.                     -- Still to parse
  2701.                     startNoDDS = endpos
  2702.                     -- textToCheck is next to our string
  2703.                     textToCheck = textToCheck:sub(startNoDDS + 1)
  2704.                 end
  2705.                
  2706.             end
  2707.         end
  2708.     end
  2709.  
  2710.     return textTReformated
  2711.  
  2712. end
  2713.  
  2714. -- Reformat Colored Sysmessages to the correct format
  2715. -- Bad format = |[cC]XXXXXXblablabla[,|[cC]XXXXXXblablabla,(...)] with facultative |r
  2716. -- Good format : "|c%x%x%x%x%x%x(.-)|r"
  2717. -- WARNING : See FormatSysMessage()
  2718. -- TODO : Handle LinkHandler + Malformatted strings , such as : "|c87B7CC|c87B7CCUpdated: |H0:achievement:68:5252:0|h|h (Artisanat)."
  2719. local function ReformatSysMessages(text)
  2720.  
  2721.     local rawSys = text
  2722.     local startColSys, endColSys
  2723.     local lastTag, lastR, findC, findR
  2724.     local count, countR
  2725.     local firstIsR
  2726.    
  2727.     rawSys = rawSys:gsub("||([Cc])", "%1") -- | is the escape char for |, so if an user type |c it will be sent as ||c by the game which will lead to an infinite loading screen because xxxxx||xxxxxx is a lua pattern operator and few gsub will broke the code
  2728.    
  2729.     --Search for Color tags
  2730.     startColSys, endColSys = string.find(rawSys, "|[cC]%x%x%x%x%x%x", 1)
  2731.     _, count = string.gsub(rawSys, "|[cC]%x%x%x%x%x%x", "")
  2732.    
  2733.     -- No color tags in the SysMessage
  2734.     if startColSys then
  2735.         -- Found X tag
  2736.         -- Searching for |r after tag color
  2737.        
  2738.         -- First destroy tags with nothing inside
  2739.         rawSys = string.gsub(rawSys, "|[cC]%x%x%x%x%x%x|[rR]", "")
  2740.        
  2741.         _, countR = string.gsub(rawSys, "|[cC]%x%x%x%x%x%x(.-)|[rR]", "")
  2742.        
  2743.         -- Start tag found but end tag ~= start tag
  2744.         if count ~= countR then
  2745.            
  2746.             -- Add |r before the tag
  2747.             rawSys = string.gsub(rawSys, "|[cC]%x%x%x%x%x%x", "|r%1")
  2748.             firstIsR = string.find(rawSys, "|[cC]%x%x%x%x%x%x")
  2749.            
  2750.             --No |r tag at the start of the text if start was |cXXXXXX
  2751.             if firstIsR == 3 then
  2752.                 rawSys = string.sub(rawSys, 3)
  2753.             end
  2754.            
  2755.             -- Replace
  2756.             rawSys = string.gsub(rawSys, "|r|r", "|r")
  2757.             rawSys = string.gsub(rawSys, "|r |r", " |r")
  2758.            
  2759.             lastTag = string.match(rawSys, "^.*()|[cC]%x%x%x%x%x%x")
  2760.             lastR = string.match(rawSys, "^.*()|r")
  2761.            
  2762.             -- |r tag found
  2763.             if lastR ~= nil then
  2764.                 if lastTag > lastR then
  2765.                     rawSys = rawSys .. "|r"
  2766.                 end
  2767.             else
  2768.                 -- Got X tags and 0 |r, happens only when we got 1 tag and 0 |r because we added |r to every tags just before
  2769.                 -- So add last "|r"
  2770.                 rawSys = rawSys .. "|r"
  2771.             end
  2772.            
  2773.             -- Double check
  2774.            
  2775.             findC = string.find(rawSys, "|[cC]%x%x%x%x%x%x")
  2776.             findR = string.find(rawSys, "|[rR]")
  2777.            
  2778.             while findR ~= nil and findC ~= nil do
  2779.                
  2780.                 if findC then
  2781.                     if findR < findC then
  2782.                         rawSys = string.sub(rawSys, 1, findR - 1) .. string.sub(rawSys, findR + 2)
  2783.                     end
  2784.                    
  2785.                     findC = string.find(rawSys, "|[cC]%x%x%x%x%x%x", findR)
  2786.                     findR = string.find(rawSys, "|[rR]", findR + 2)
  2787.                 end
  2788.                
  2789.             end
  2790.            
  2791.         end
  2792.        
  2793.     end
  2794.     -- Added |u search
  2795.     startColSys, endColSys = string.find(rawSys, "|u%-?%d+%%?:%-?%d+%%?:.-:|u",1)
  2796.     -- if found
  2797.     if startColSys then
  2798.         rawSys = string.gsub(rawSys,"|u%-?%d+%%?:%-?%d+%%?:.-:|u","")
  2799.     end
  2800.    
  2801.     return rawSys
  2802.    
  2803. end
  2804.  
  2805. -- Add pChatLinks Handlers on the whole text except LinkHandlers already here
  2806. -- WARNING : See FormatSysMessage()
  2807. local function AddLinkHandlerToLine(text, chanCode, numLine)
  2808.    
  2809.     local rawText = ReformatSysMessages(text) -- FUCK YOU
  2810.    
  2811.     local start = 1
  2812.     local rawTextlen = string.len(rawText)
  2813.     local stillToParseCol = true
  2814.     local formattedText
  2815.     local noColorText = ""
  2816.     local textToCheck = ""
  2817.    
  2818.     local startColortag = ""
  2819.    
  2820.     local preventLoops = 0
  2821.     local colorizedText = true
  2822.     local newText = ""
  2823.    
  2824.     while stillToParseCol do
  2825.        
  2826.         -- Prevent infinite loops while its still in beta
  2827.         if preventLoops > 10 then
  2828.             stillToParseCol = false
  2829.         else
  2830.             preventLoops = preventLoops + 1
  2831.         end
  2832.        
  2833.         -- Handling Colors, search for color tag
  2834.         local startcol, endcol = string.find(rawText, "|[cC]%x%x%x%x%x%x(.-)|r", start)
  2835.        
  2836.         -- Not Found
  2837.         if startcol == nil then
  2838.             startColortag = ""
  2839.             textToCheck = string.sub(rawText, start)
  2840.             stillToParseCol = false
  2841.             newText = newText .. AddLinkHandlerToString(textToCheck, numLine, chanCode)
  2842.         else
  2843.             startColortag = string.sub(rawText, startcol, startcol + 7)
  2844.             -- pChat format all strings
  2845.             if start == startcol then
  2846.                 -- textToCheck is only (.-)
  2847.                 textToCheck = string.sub(rawText, (startcol + 8), (endcol - 2))
  2848.                 -- Change our start -> pos of (.-)
  2849.                 start = endcol + 1
  2850.                 newText = newText .. startColortag .. AddLinkHandlerToString(textToCheck, numLine, chanCode) .. "|r"
  2851.                
  2852.                 -- Do we need to continue ?
  2853.                 if endcol == rawTextlen then
  2854.                     -- We're at the end
  2855.                     stillToParseCol = false
  2856.                 end
  2857.                
  2858.             else
  2859.                 -- We will check colorized text at next loop
  2860.                 textToCheck = string.sub(rawText, start, startcol-1)
  2861.                 start = startcol
  2862.                 -- Tag color found but need to check some strings before
  2863.                 newText = newText .. AddLinkHandlerToString(textToCheck, numLine, chanCode)
  2864.             end
  2865.         end
  2866.        
  2867.     end
  2868.    
  2869.     return newText
  2870.    
  2871. end
  2872.  
  2873. -- Split lines using CRLF for function addLinkHandlerToLine
  2874. -- WARNING : See FormatSysMessage()
  2875. local function AddLinkHandler(text, chanCode, numLine)
  2876.    
  2877.     -- Some Addons output multiple lines into a message
  2878.     -- Split the entire string with CRLF, cause LinkHandler don't support CRLF
  2879.    
  2880.     -- Init
  2881.     local formattedText = ""
  2882.    
  2883.     -- Recheck setting if copy is disabled for chat dump
  2884.     if db.enablecopy then
  2885.    
  2886.         -- No CRLF
  2887.         -- ESO LUA Messages output \n instead of \r\n
  2888.         local crtext = string.gsub(text, "\r\n", "\n")
  2889.         -- Find multilines
  2890.         local cr = string.find(crtext, "\n")
  2891.        
  2892.         if not cr then
  2893.             formattedText = AddLinkHandlerToLine(text, chanCode, numLine)
  2894.         else
  2895.             local lines = {zo_strsplit("\n", crtext)}
  2896.             local first = true
  2897.             local strippedLine
  2898.             local nunmRows = 0
  2899.            
  2900.             for _, line in pairs(lines) do
  2901.                
  2902.                 -- Only if there something to display
  2903.                 if string.len(line) > 0 then
  2904.                
  2905.                     if first then
  2906.                         formattedText = AddLinkHandlerToLine(line, chanCode, numLine)
  2907.                         first = false
  2908.                     else
  2909.                         formattedText = formattedText .. "\n" .. AddLinkHandlerToLine(line, chanCode, numLine)
  2910.                     end
  2911.                    
  2912.                     if nunmRows > 10 then
  2913.                         CHAT_SYSTEM:Zo_AddMessage("pChat has triggered 10 packed lines with text=" .. text .. "-- pChat - Message truncated")
  2914.                         return
  2915.                     else
  2916.                         nunmRows = nunmRows + 1
  2917.                     end
  2918.                    
  2919.                 end
  2920.            
  2921.             end
  2922.        
  2923.         end
  2924.     else
  2925.         formattedText = text
  2926.     end
  2927.    
  2928.     return formattedText
  2929.  
  2930. end
  2931.  
  2932. -- Can cause infinite loads (why?)
  2933. local function RestoreChatMessagesFromHistory(wasReloadUI)
  2934.  
  2935.     -- Restore Chat
  2936.     local lastInsertionWas = 0
  2937.  
  2938.     if db.LineStrings then
  2939.        
  2940.         local historyIndex = 1
  2941.         local categories = ZO_ChatSystem_GetEventCategoryMappings()
  2942.        
  2943.         while historyIndex <= #db.LineStrings do
  2944.             if db.LineStrings[historyIndex] then
  2945.                 local channelToRestore = db.LineStrings[historyIndex].channel
  2946.                 if channelToRestore == PCHAT_CHANNEL_SAY then channelToRestore = 0 end
  2947.                
  2948.                 if channelToRestore == CHAT_CHANNEL_SYSTEM and not db.restoreSystem then
  2949.                     table.remove(db.LineStrings, historyIndex)
  2950.                     db.lineNumber = db.lineNumber - 1
  2951.                     historyIndex = historyIndex - 1
  2952.                 elseif channelToRestore ~= CHAT_CHANNEL_SYSTEM and db.restoreSystemOnly then
  2953.                     table.remove(db.LineStrings, historyIndex)
  2954.                     db.lineNumber = db.lineNumber - 1
  2955.                     historyIndex = historyIndex - 1
  2956.                 elseif (channelToRestore == CHAT_CHANNEL_WHISPER or channelToRestore == CHAT_CHANNEL_WHISPER_SENT) and not db.restoreWhisps then
  2957.                     table.remove(db.LineStrings, historyIndex)
  2958.                     db.lineNumber = db.lineNumber - 1
  2959.                     historyIndex = historyIndex - 1
  2960.                 else
  2961.                    
  2962.                     local category = categories[EVENT_CHAT_MESSAGE_CHANNEL][channelToRestore]
  2963.                    
  2964.                     if GetTimeStamp() - db.LineStrings[historyIndex].rawTimestamp < db.timeBeforeRestore * 60 * 60 and db.LineStrings[historyIndex].rawTimestamp < GetTimeStamp() then
  2965.                         lastInsertionWas = math.max(lastInsertionWas, db.LineStrings[historyIndex].rawTimestamp)
  2966.                         for containerIndex=1, #CHAT_SYSTEM.containers do
  2967.                        
  2968.                             for tabIndex=1, #CHAT_SYSTEM.containers[containerIndex].windows do
  2969.                                 if IsChatContainerTabCategoryEnabled(CHAT_SYSTEM.containers[containerIndex].id, tabIndex, category) then
  2970.                                     if not db.chatConfSync[pChatData.localPlayer].tabs[tabIndex].notBefore or db.LineStrings[historyIndex].rawTimestamp > db.chatConfSync[pChatData.localPlayer].tabs[tabIndex].notBefore then
  2971.                                         CHAT_SYSTEM.containers[containerIndex]:AddEventMessageToWindow(CHAT_SYSTEM.containers[containerIndex].windows[tabIndex], AddLinkHandler(db.LineStrings[historyIndex].rawValue, channelToRestore, historyIndex), category)
  2972.                                     end
  2973.                                 end
  2974.                             end
  2975.                            
  2976.                         end
  2977.                     else
  2978.                         table.remove(db.LineStrings, historyIndex)
  2979.                         db.lineNumber = db.lineNumber - 1
  2980.                         historyIndex = historyIndex - 1
  2981.                     end
  2982.                    
  2983.                 end
  2984.             end
  2985.             historyIndex = historyIndex + 1
  2986.         end
  2987.        
  2988.        
  2989.         local PRESSED = 1
  2990.         local UNPRESSED = 2
  2991.         local numTabs = #CHAT_SYSTEM.primaryContainer.windows
  2992.        
  2993.         for numTab, container in ipairs (CHAT_SYSTEM.primaryContainer.windows) do
  2994.             if numTab > 1 then
  2995.                 CHAT_SYSTEM.primaryContainer:HandleTabClick(container.tab)
  2996.                 local tabText = GetControl("ZO_ChatWindowTabTemplate" .. numTab .. "Text")
  2997.                 tabText:SetColor(GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_SELECTED))
  2998.                 tabText:GetParent().state = PRESSED
  2999.                 local oldTabText = GetControl("ZO_ChatWindowTabTemplate" .. numTab - 1 .. "Text")
  3000.                 oldTabText:SetColor(GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_CONTRAST))
  3001.                 oldTabText:GetParent().state = UNPRESSED
  3002.             end
  3003.         end
  3004.        
  3005.         if numTabs > 1 then
  3006.             CHAT_SYSTEM.primaryContainer:HandleTabClick(CHAT_SYSTEM.primaryContainer.windows[1].tab)
  3007.             local tabText = GetControl("ZO_ChatWindowTabTemplate1Text")
  3008.             tabText:SetColor(GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_SELECTED))
  3009.             tabText:GetParent().state = PRESSED
  3010.             local oldTabText = GetControl("ZO_ChatWindowTabTemplate" .. #CHAT_SYSTEM.primaryContainer.windows .. "Text")
  3011.             oldTabText:SetColor(GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_CONTRAST))
  3012.             oldTabText:GetParent().state = UNPRESSED
  3013.         end
  3014.        
  3015.     end
  3016.    
  3017.     -- Restore TextEntry History
  3018.     if (wasReloadUI or db.restoreTextEntryHistoryAtLogOutQuit) and db.history.textEntry then
  3019.    
  3020.         if db.history.textEntry.entries then
  3021.            
  3022.             if lastInsertionWas and ((GetTimeStamp() - lastInsertionWas) < (db.timeBeforeRestore * 60 * 60)) then
  3023.                 for _, text in ipairs(db.history.textEntry.entries) do
  3024.                     CHAT_SYSTEM.textEntry:AddCommandHistory(text)
  3025.                 end
  3026.                
  3027.                 CHAT_SYSTEM.textEntry.commandHistory.index = db.history.textEntry.numEntries
  3028.             end
  3029.        
  3030.         end
  3031.        
  3032.     end
  3033.    
  3034. end
  3035.  
  3036. -- Restore History from SavedVars
  3037. local function RestoreChatHistory()
  3038.  
  3039.     -- Set default tab at login
  3040.     SetDefaultTab(db.defaultTab)
  3041.     -- Restore History
  3042.     if db.history then
  3043.        
  3044.         if db.lastWasReloadUI and db.restoreOnReloadUI then
  3045.            
  3046.             -- RestoreChannel
  3047.             if db.defaultchannel ~= PCHAT_CHANNEL_NONE then
  3048.                 CHAT_SYSTEM:SetChannel(db.history.currentChannel, db.history.currentTarget)
  3049.             end
  3050.            
  3051.             -- restore TextEntry and Chat
  3052.             RestoreChatMessagesFromHistory(true)
  3053.            
  3054.             -- Restore tab when ReloadUI
  3055.             --** blocking for now
  3056.             --SetDefaultTab(db.history.currentTab)
  3057.            
  3058.         elseif (db.lastWasLogOut and db.restoreOnLogOut) or (db.lastWasAFK and db.restoreOnAFK) or (db.lastWasQuit and db.restoreOnQuit) then
  3059.             -- restore TextEntry and Chat
  3060.             RestoreChatMessagesFromHistory(false)
  3061.         end
  3062.        
  3063.         pChatData.messagesHaveBeenRestorated = true
  3064.        
  3065.         local indexMessages = #pChatData.cachedMessages
  3066.         if indexMessages > 0 then
  3067.             for index=1, indexMessages do
  3068.                 CHAT_SYSTEM:AddMessage(pChatData.cachedMessages[index])
  3069.             end
  3070.         end
  3071.        
  3072.         db.lastWasReloadUI = false
  3073.         db.lastWasLogOut = false
  3074.         db.lastWasQuit = false
  3075.         db.lastWasAFK = true
  3076.     else
  3077.         pChatData.messagesHaveBeenRestorated = true
  3078.     end
  3079.  
  3080. end
  3081.  
  3082. -- Store line number
  3083. -- Create an array for the copy functions, spam functions and revert history functions
  3084. -- WARNING : See FormatSysMessage()
  3085. local function StorelineNumber(rawTimestamp, rawFrom, text, chanCode, originalFrom)
  3086.    
  3087.     -- Strip DDS tag from Copy text
  3088.     local function StripDDStags(text)
  3089.         return text:gsub("|t(.-)|t", "")
  3090.     end
  3091.    
  3092.     local formattedMessage = ""
  3093.     local rawText = text
  3094.    
  3095.     -- SysMessages does not have a from
  3096.     if chanCode ~= CHAT_CHANNEL_SYSTEM then
  3097.    
  3098.         -- Timestamp cannot be nil anymore with SpamFilter, so use the option itself
  3099.         if db.showTimestamp then
  3100.             -- Format for Copy
  3101.             formattedMessage = "[" .. CreateTimestamp(GetTimeString()) .. "] "
  3102.         end
  3103.        
  3104.         -- Strip DDS tags for GM
  3105.         rawFrom = StripDDStags(rawFrom)
  3106.        
  3107.         -- Needed for SpamFilter
  3108.         db.LineStrings[db.lineNumber].rawFrom = originalFrom
  3109.        
  3110.         -- formattedMessage is only rawFrom for now
  3111.         formattedMessage = formattedMessage .. rawFrom
  3112.        
  3113.         if db.carriageReturn then
  3114.             formattedMessage = formattedMessage .. "\n"
  3115.         end
  3116.        
  3117.     end
  3118.    
  3119.     -- Needed for SpamFilter & Restoration, UNIX timestamp
  3120.     db.LineStrings[db.lineNumber].rawTimestamp = rawTimestamp
  3121.    
  3122.     -- Store CopyMessage / Used for SpamFiltering. Due to lua 0 == nil in arrays, set value to 98
  3123.     if chanCode == CHAT_CHANNEL_SAY then
  3124.         db.LineStrings[db.lineNumber].channel = PCHAT_CHANNEL_SAY
  3125.     else
  3126.         db.LineStrings[db.lineNumber].channel = chanCode
  3127.     end
  3128.    
  3129.     -- Store CopyMessage
  3130.     db.LineStrings[db.lineNumber].rawText = rawText
  3131.    
  3132.     -- Store CopyMessage
  3133.     --db.LineStrings[db.lineNumber].rawValue = text
  3134.    
  3135.     -- Strip DDS tags
  3136.     rawText = StripDDStags(rawText)
  3137.    
  3138.     -- Used to translate LinkHandlers
  3139.     rawText = FormatRawText(rawText)
  3140.    
  3141.     -- Store CopyMessage
  3142.     db.LineStrings[db.lineNumber].rawMessage = rawText
  3143.    
  3144.     -- Store CopyLine
  3145.     db.LineStrings[db.lineNumber].rawLine = formattedMessage .. rawText
  3146.    
  3147.     --Increment at each message handled
  3148.     db.lineNumber = db.lineNumber + 1
  3149.    
  3150. end
  3151.  
  3152. -- WARNING : Since AddMessage is bypassed, this function and all its subfunctions DOES NOT MUST CALL d() / Emitmessage() / AddMessage() or it will result an infinite loop and crash the game
  3153. -- Debug must call CHAT_SYSTEM:Zo_AddMessage() wich is backuped copy of CHAT_SYSTEM.AddMessage
  3154. local function FormatSysMessage(statusMessage)
  3155.  
  3156.     -- Display Timestamp if needed
  3157.     local function ShowTimestamp()
  3158.  
  3159.         local timestamp = ""
  3160.  
  3161.         -- Add timestamp
  3162.         if db.showTimestamp then
  3163.        
  3164.             -- Timestamp formatted
  3165.             timestamp = CreateTimestamp(GetTimeString())
  3166.            
  3167.             local timecol
  3168.             -- Timestamp color is chanCode so no coloring
  3169.             if db.timestampcolorislcol then
  3170.                  -- Show Message
  3171.                 timestamp = string.format("[%s] ", timestamp)
  3172.             else
  3173.                 -- Timestamp color is our setting color
  3174.                 timecol = db.colours.timestamp
  3175.                
  3176.                 -- Show Message
  3177.                 timestamp = string.format("%s[%s] |r", timecol, timestamp)
  3178.             end
  3179.         else
  3180.             timestamp = ""
  3181.         end
  3182.        
  3183.         return timestamp
  3184.        
  3185.     end
  3186.  
  3187.     -- Only if statusMessage is set
  3188.     if statusMessage then
  3189.        
  3190.         -- Only if there something to display
  3191.         if string.len(statusMessage) > 0 then
  3192.        
  3193.             local sysMessage
  3194.            
  3195.             -- Some addons are quicker than pChat
  3196.             if db then
  3197.            
  3198.                 -- Show Message
  3199.                 statusMessage = ShowTimestamp() .. statusMessage
  3200.                
  3201.                 if not db.lineNumber then
  3202.                     db.lineNumber = 1
  3203.                 end
  3204.                
  3205.                 --  for CopySystem
  3206.                 db.LineStrings[db.lineNumber] = {}
  3207.                
  3208.                 -- Make it Linkable
  3209.                
  3210.                 if db.enablecopy then
  3211.                     sysMessage = AddLinkHandler(statusMessage, CHAT_CHANNEL_SYSTEM, db.lineNumber)
  3212.                 else
  3213.                     sysMessage = statusMessage
  3214.                 end
  3215.                
  3216.                 if not db.LineStrings[db.lineNumber].rawFrom then db.LineStrings[db.lineNumber].rawFrom = "" end
  3217.                 if not db.LineStrings[db.lineNumber].rawMessage then db.LineStrings[db.lineNumber].rawMessage = "" end
  3218.                 if not db.LineStrings[db.lineNumber].rawLine then db.LineStrings[db.lineNumber].rawLine = "" end
  3219.                 if not db.LineStrings[db.lineNumber].rawValue then db.LineStrings[db.lineNumber].rawValue = statusMessage end
  3220.                 if not db.LineStrings[db.lineNumber].rawDisplayed then db.LineStrings[db.lineNumber].rawDisplayed = sysMessage end
  3221.                
  3222.                 -- No From, rawTimestamp is in statusMessage, sent as arg for SpamFiltering even if SysMessages are not filtered
  3223.                 StorelineNumber(GetTimeStamp(), nil, statusMessage, CHAT_CHANNEL_SYSTEM, nil)
  3224.                
  3225.             end
  3226.            
  3227.             -- Show Message
  3228.             return sysMessage
  3229.        
  3230.         end
  3231.        
  3232.     end
  3233.  
  3234. end
  3235.  
  3236. -- Add a pChat handler for URL's
  3237. local function AddURLHandling(text)
  3238.  
  3239.     -- handle complex URLs and do
  3240.     for pos, url, prot, subd, tld, colon, port, slash, path in text:gmatch("()(([%w_.~!*:@&+$/?%%#-]-)(%w[-.%w]*%.)(%w+)(:?)(%d*)(/?)([%w_.~!*:@&+$/?%#=-]*))") do
  3241.         if pChatData.protocols[prot:lower()] == (1 - #slash) * #path
  3242.         and (colon == "" or port ~= "" and port + 0 < 65536)
  3243.         and (pChatData.tlds[tld:lower()] or tld:find("^%d+$") and subd:find("^%d+%.%d+%.%d+%.$") and math.max(tld, subd:match("^(%d+)%.(%d+)%.(%d+)%.$")) < 256)
  3244.         and not subd:find("%W%W")
  3245.         then
  3246.             local urlHandled = string.format("|H1:%s:%s:%s|h%s|h", PCHAT_LINK, db.lineNumber, PCHAT_URL_CHAN, url)
  3247.             url = url:gsub("([?+-])", "%%%1") -- don't understand why, 1st arg of gsub must be escaped and 2nd not.
  3248.             text, count = text:gsub(url, urlHandled)
  3249.         end
  3250.     end
  3251.    
  3252.     return text
  3253.  
  3254. end
  3255.  
  3256. local function InitializeURLHandling()
  3257.  
  3258.     -- cTLD + most used (> 0.1%)
  3259.     local domains =
  3260.     [[
  3261.     .ac.ad.ae.af.ag.ai.al.am.an.ao.aq.ar.as.at.au.aw.ax.az.ba.bb.bd.be.bf.bg.bh.bi.bj.bl.bm.bn.bo.bq
  3262.     .br.bs.bt.bv.bw.by.bz.ca.cc.cd.cf.cg.ch.ci.ck.cl.cm.cn.co.cr.cu.cv.cw.cx.cy.cz.de.dj.dk.dm.do.dz
  3263.     .ec.ee.eg.eh.er.es.et.eu.fi.fj.fk.fm.fo.fr.ga.gb.gd.ge.gf.gg.gh.gi.gl.gm.gn.gp.gq.gr.gs.gt.gu.gw
  3264.     .gy.hk.hm.hn.hr.ht.hu.id.ie.il.im.in.io.iq.ir.is.it.je.jm.jo.jp.ke.kg.kh.ki.km.kn.kp.kr.kw.ky.kz
  3265.     .la.lb.lc.li.lk.lr.ls.lt.lu.lv.ly.ma.mc.md.me.mf.mg.mh.mk.ml.mm.mn.mo.mp.mq.mr.ms.mt.mu.mv.mw.mx
  3266.     .my.mz.na.nc.ne.nf.ng.ni.nl.no.np.nr.nu.nz.om.pa.pe.pf.pg.ph.pk.pl.pm.pn.pr.ps.pt.pw.py.qa.re.ro
  3267.     .rs.ru.rw.sa.sb.sc.sd.se.sg.sh.si.sj.sk.sl.sm.sn.so.sr.ss.st.su.sv.sx.sy.sz.tc.td.tf.tg.th.tj.tk
  3268.     .tl.tm.tn.to.tp.tr.tt.tv.tw.tz.ua.ug.uk.um.us.uy.uz.va.vc.ve.vg.vi.vn.vu.wf.ws.ye.yt.za.zm.zw
  3269.     .biz.com.coop.edu.gov.int.mil.mobi.info.net.org.xyz.top.club.pro.asia
  3270.     ]]
  3271.    
  3272.     -- wxx.yy.zz
  3273.     pChatData.tlds = {}
  3274.     for tld in domains:gmatch('%w+') do
  3275.         pChatData.tlds[tld] = true
  3276.     end
  3277.    
  3278.     -- protos : only http/https
  3279.     pChatData.protocols = {['http://'] = 0, ['https://'] = 0}
  3280.    
  3281. end
  3282.  
  3283. local function GetChannelColors(channel, from)
  3284.  
  3285.      -- Substract XX to a color (darker)
  3286.     local function FirstColorFromESOSettings(r, g, b)
  3287.         -- Scale is from 0-100 so divide per 300 will maximise difference at 0.33 (*2)
  3288.         r = math.max(r - (db.diffforESOcolors / 300 ),0)
  3289.         g = math.max(g - (db.diffforESOcolors / 300 ),0)
  3290.         b = math.max(b - (db.diffforESOcolors / 300 ),0)
  3291.         return r,g,b
  3292.     end
  3293.    
  3294.      -- Add XX to a color (brighter)
  3295.     local function SecondColorFromESOSettings(r, g, b)
  3296.         r = math.min(r + (db.diffforESOcolors / 300 ),1)
  3297.         g = math.min(g + (db.diffforESOcolors / 300 ),1)
  3298.         b = math.min(b + (db.diffforESOcolors / 300 ),1)
  3299.         return r,g,b
  3300.     end
  3301.  
  3302.     if db.useESOcolors then
  3303.        
  3304.         -- ESO actual color, return r,g,b
  3305.         local rESO, gESO, bESO
  3306.         -- Handle the same-colour options.
  3307.         if db.allNPCSameColour and (channel == CHAT_CHANNEL_MONSTER_SAY or channel == CHAT_CHANNEL_MONSTER_YELL or channel == CHAT_CHANNEL_MONSTER_WHISPER) then
  3308.             rESO, gESO, bESO = CHAT_SYSTEM:GetCategoryColorFromChannel(CHAT_CHANNEL_MONSTER_SAY)
  3309.         elseif db.allGuildsSameColour and (channel >= CHAT_CHANNEL_GUILD_1 and channel <= CHAT_CHANNEL_GUILD_5) then
  3310.             rESO, gESO, bESO = CHAT_SYSTEM:GetCategoryColorFromChannel(CHAT_CHANNEL_GUILD_1)
  3311.         elseif db.allGuildsSameColour and (channel >= CHAT_CHANNEL_OFFICER_1 and channel <= CHAT_CHANNEL_OFFICER_5) then
  3312.             rESO, gESO, bESO = CHAT_SYSTEM:GetCategoryColorFromChannel(CHAT_CHANNEL_OFFICER_1)
  3313.         elseif db.allZonesSameColour and (channel >= CHAT_CHANNEL_ZONE_LANGUAGE_1 and channel <= CHAT_CHANNEL_ZONE_LANGUAGE_4) then
  3314.             rESO, gESO, bESO = CHAT_SYSTEM:GetCategoryColorFromChannel(CHAT_CHANNEL_ZONE_LANGUAGE_1)
  3315.         elseif channel == CHAT_CHANNEL_PARTY and from and db.groupLeader and zo_strformat(SI_UNIT_NAME, from) == GetUnitName(GetGroupLeaderUnitTag()) then
  3316.             rESO, gESO, bESO = ConvertHexToRGBA(db.colours["groupleader"])
  3317.         else
  3318.             rESO, gESO, bESO = CHAT_SYSTEM:GetCategoryColorFromChannel(channel)
  3319.         end
  3320.        
  3321.         -- Set right colour to left colour - cause ESO colors are rewrited, if onecolor, no rewriting
  3322.         if db.oneColour then
  3323.             lcol = ConvertRGBToHex(rESO, gESO, bESO)
  3324.             rcol = lcol
  3325.         else
  3326.             lcol = ConvertRGBToHex(FirstColorFromESOSettings(rESO,gESO,bESO))
  3327.             rcol = ConvertRGBToHex(SecondColorFromESOSettings(rESO,gESO,bESO))
  3328.         end
  3329.  
  3330.     else
  3331.         -- pChat Colors
  3332.         -- Handle the same-colour options.
  3333.         if db.allNPCSameColour and (channel == CHAT_CHANNEL_MONSTER_SAY or channel == CHAT_CHANNEL_MONSTER_YELL or channel == CHAT_CHANNEL_MONSTER_WHISPER) then
  3334.             lcol = db.colours[2*CHAT_CHANNEL_MONSTER_SAY]
  3335.             rcol = db.colours[2*CHAT_CHANNEL_MONSTER_SAY + 1]
  3336.         elseif db.allGuildsSameColour and (channel >= CHAT_CHANNEL_GUILD_1 and channel <= CHAT_CHANNEL_GUILD_5) then
  3337.             lcol = db.colours[2*CHAT_CHANNEL_GUILD_1]
  3338.             rcol = db.colours[2*CHAT_CHANNEL_GUILD_1 + 1]
  3339.         elseif db.allGuildsSameColour and (channel >= CHAT_CHANNEL_OFFICER_1 and channel <= CHAT_CHANNEL_OFFICER_5) then
  3340.             lcol = db.colours[2*CHAT_CHANNEL_OFFICER_1]
  3341.             rcol = db.colours[2*CHAT_CHANNEL_OFFICER_1 + 1]
  3342.         elseif db.allZonesSameColour and (channel >= CHAT_CHANNEL_ZONE_LANGUAGE_1 and channel <= CHAT_CHANNEL_ZONE_LANGUAGE_4) then
  3343.             lcol = db.colours[2*CHAT_CHANNEL_ZONE]
  3344.             rcol = db.colours[2*CHAT_CHANNEL_ZONE + 1]
  3345.         elseif channel == CHAT_CHANNEL_PARTY and from and db.groupLeader and zo_strformat(SI_UNIT_NAME, from) == GetUnitName(GetGroupLeaderUnitTag()) then
  3346.             lcol = db.colours["groupleader"]
  3347.             rcol = db.colours["groupleader1"]
  3348.         else
  3349.             lcol = db.colours[2*channel]
  3350.             rcol = db.colours[2*channel + 1]
  3351.         end
  3352.        
  3353.         -- Set right colour to left colour
  3354.         if db.oneColour then
  3355.             rcol = lcol
  3356.         end
  3357.        
  3358.     end
  3359.    
  3360.     return lcol, rcol
  3361.  
  3362. end
  3363.  
  3364. -- Executed when EVENT_CHAT_MESSAGE_CHANNEL triggers
  3365. -- Formats the message
  3366. local function FormatMessage(chanCode, from, text, isCS, fromDisplayName, originalFrom, originalText, DDSBeforeAll, TextBeforeAll, DDSBeforeSender, TextBeforeSender, TextAfterSender, DDSAfterSender, DDSBeforeText, TextBeforeText, TextAfterText, DDSAfterText)
  3367.  
  3368.     -- Will calculate if this message is a spam
  3369.     local isSpam = SpamFilter(chanCode, from, text, isCS)
  3370.    
  3371.     -- A spam, drop everything
  3372.     if isSpam then return end
  3373.    
  3374.     local info = ChanInfoArray[chanCode]
  3375.    
  3376.     -- Init message with other addons stuff
  3377.     local message = DDSBeforeAll .. TextBeforeAll
  3378.    
  3379.     -- Init text with other addons stuff. Note : text can also be modified by other addons. Only originalText is the string the game has receive
  3380.     text = DDSBeforeText .. TextBeforeText .. text .. TextAfterText .. DDSAfterText
  3381.    
  3382.     if db.disableBrackets then
  3383.         chatStrings["copystandard"] = "%s: " -- standard format: say, yell, group, npc, npc yell, npc whisper, zone
  3384.         chatStrings["copyesostandard"] = "%s %s: " -- standard format: say, yell, group, npc, npc yell, npc whisper, zone with tag (except for monsters)
  3385.         chatStrings["copyesoparty"] = "[%s]%s: " -- standard format: party
  3386.         chatStrings["copytellIn"] = "%s: " -- tell in
  3387.         chatStrings["copytellOut"] = "-> %s: " -- tell out
  3388.         chatStrings["copyguild"] = "[%s] %s: " -- guild
  3389.         chatStrings["copylanguage"] = "[%s] %s: " -- language zones
  3390.     else
  3391.         chatStrings["copystandard"] = "[%s]: " -- standard format: say, yell, group, npc, npc yell, npc whisper, zone
  3392.         chatStrings["copyesostandard"] = "[%s] %s: " -- standard format: say, yell, group, npc, npc yell, npc whisper, zone with tag (except for monsters)
  3393.         chatStrings["copyesoparty"] = "[%s]%s: " -- standard format: party
  3394.         chatStrings["copytellIn"] = "[%s]: " -- tell in
  3395.         chatStrings["copytellOut"] = "-> [%s]: " -- tell out
  3396.         chatStrings["copyguild"] = "[%s] [%s]: " -- guild
  3397.         chatStrings["copylanguage"] = "[%s] %s: " -- language zones
  3398.     end
  3399.    
  3400.     --  for CopySystem
  3401.     db.LineStrings[db.lineNumber] = {}
  3402.     if not db.LineStrings[db.lineNumber].rawFrom then db.LineStrings[db.lineNumber].rawFrom = from end
  3403.     if not db.LineStrings[db.lineNumber].rawValue then db.LineStrings[db.lineNumber].rawValue = text end
  3404.     if not db.LineStrings[db.lineNumber].rawMessage then db.LineStrings[db.lineNumber].rawMessage = text end
  3405.     if not db.LineStrings[db.lineNumber].rawLine then db.LineStrings[db.lineNumber].rawLine = text end
  3406.     if not db.LineStrings[db.lineNumber].rawDisplayed then db.LineStrings[db.lineNumber].rawDisplayed = text end
  3407.    
  3408.     local new_from = ConvertName(chanCode, from, isCS, fromDisplayName)
  3409.     local displayedFrom = db.LineStrings[db.lineNumber].rawFrom
  3410.    
  3411.     -- Add other addons stuff related to sender
  3412.     new_from = DDSBeforeSender .. TextBeforeSender .. new_from .. TextAfterSender .. DDSAfterSender
  3413.    
  3414.     -- Guild tag
  3415.     local tag
  3416.     if (chanCode >= CHAT_CHANNEL_GUILD_1 and chanCode <= CHAT_CHANNEL_GUILD_5) then
  3417.         local guild_number = chanCode - CHAT_CHANNEL_GUILD_1 + 1
  3418.         local guild_name = GetGuildName(GetGuildId(guild_number))
  3419.         tag = db.guildTags[guild_name]
  3420.         if tag and tag ~= "" then
  3421.             tag = tag
  3422.         else
  3423.             tag = guild_name
  3424.         end
  3425.     elseif (chanCode >= CHAT_CHANNEL_OFFICER_1 and chanCode <= CHAT_CHANNEL_OFFICER_5) then
  3426.         local guild_number = chanCode - CHAT_CHANNEL_OFFICER_1 + 1
  3427.         local guild_name = GetGuildName(GetGuildId(guild_number))
  3428.         local officertag = db.officertag[guild_name]
  3429.         if officertag and officertag ~= "" then
  3430.             tag = officertag
  3431.         else
  3432.             tag = guild_name
  3433.         end
  3434.     end
  3435.    
  3436.     -- Initialise colours
  3437.     local lcol, rcol = GetChannelColors(chanCode, from)
  3438.    
  3439.     -- Add timestamp
  3440.     if db.showTimestamp then
  3441.    
  3442.         -- Initialise timstamp color
  3443.         local timecol = db.colours.timestamp
  3444.        
  3445.         -- Timestamp color is lcol
  3446.         if db.timestampcolorislcol then
  3447.             timecol = lcol
  3448.         else
  3449.             -- Timestamp color is timestamp color
  3450.             timecol = db.colours.timestamp
  3451.         end
  3452.        
  3453.         -- Message is timestamp for now
  3454.         -- Add PCHAT_HANDLER for display
  3455.         local timestamp = ZO_LinkHandler_CreateLink(CreateTimestamp(GetTimeString()), nil, PCHAT_LINK, db.lineNumber .. ":" .. chanCode) .. " "
  3456.        
  3457.         -- Timestamp color
  3458.         message = message .. string.format("%s%s|r", timecol, timestamp)
  3459.         db.LineStrings[db.lineNumber].rawValue = string.format("%s[%s] |r", timecol, CreateTimestamp(GetTimeString()))
  3460.        
  3461.     end
  3462.    
  3463.     local linkedText = text
  3464.    
  3465.     -- Add URL Handling
  3466.     if db.urlHandling then
  3467.         text = AddURLHandling(text)
  3468.     end
  3469.    
  3470.     if db.enablecopy then
  3471.         linkedText = AddLinkHandler(text, chanCode, db.lineNumber)
  3472.     end
  3473.    
  3474.     local carriageReturn = ""
  3475.     if db.carriageReturn then
  3476.         carriageReturn = "\n"
  3477.     end
  3478.    
  3479.     -- Standard format
  3480.     if chanCode == CHAT_CHANNEL_SAY or chanCode == CHAT_CHANNEL_YELL or chanCode == CHAT_CHANNEL_PARTY or chanCode == CHAT_CHANNEL_ZONE then
  3481.         -- Remove zone tags
  3482.         if db.delzonetags then
  3483.        
  3484.             -- Used for Copy
  3485.             db.LineStrings[db.lineNumber].rawFrom = string.format(chatStrings.copystandard, db.LineStrings[db.lineNumber].rawFrom)
  3486.        
  3487.             message = message .. string.format(chatStrings.standard, lcol, new_from, carriageReturn, rcol, linkedText)
  3488.             db.LineStrings[db.lineNumber].rawValue = db.LineStrings[db.lineNumber].rawValue .. string.format(chatStrings.standard, lcol, new_from, carriageReturn, rcol, text)
  3489.         -- Keep them
  3490.         else
  3491.             -- Init zonetag to keep the channel tag
  3492.             local zonetag
  3493.             -- Pattern for party is [Party]
  3494.             if chanCode == CHAT_CHANNEL_PARTY then
  3495.                 zonetag = GetString(PCHAT_ZONETAGPARTY)
  3496.                
  3497.                 -- Used for Copy
  3498.                 db.LineStrings[db.lineNumber].rawFrom = string.format(chatStrings.copyesoparty, zonetag, db.LineStrings[db.lineNumber].rawFrom)
  3499.                
  3500.                 -- PartyHandler
  3501.                 zonetag = ZO_LinkHandler_CreateLink(zonetag, nil, CHANNEL_LINK_TYPE, chanCode)
  3502.                
  3503.                 message = message .. string.format(chatStrings.esoparty, lcol, zonetag, new_from, carriageReturn, rcol, linkedText)
  3504.                 db.LineStrings[db.lineNumber].rawValue = db.LineStrings[db.lineNumber].rawValue .. string.format(chatStrings.esoparty, lcol, zonetag, new_from, carriageReturn, rcol, text)
  3505.             else
  3506.                 -- Pattern for say/yell/zone is "player says:" ..
  3507.                 if chanCode == CHAT_CHANNEL_SAY then zonetag = GetString(PCHAT_ZONETAGSAY)
  3508.                 elseif chanCode == CHAT_CHANNEL_YELL then zonetag = GetString(PCHAT_ZONETAGYELL)
  3509.                 elseif chanCode == CHAT_CHANNEL_ZONE then zonetag = GetString(PCHAT_ZONETAGZONE)
  3510.                 end
  3511.                
  3512.                 -- Used for Copy
  3513.                 db.LineStrings[db.lineNumber].rawFrom = string.format(chatStrings.copyesostandard, db.LineStrings[db.lineNumber].rawFrom, zonetag)
  3514.                
  3515.                 -- pChat Handler
  3516.                 zonetag = string.format("|H1:p:%s|h%s|h", chanCode, zonetag)
  3517.                
  3518.                 message = message .. string.format(chatStrings.esostandart, lcol, new_from, zonetag, carriageReturn, rcol, linkedText)
  3519.                 db.LineStrings[db.lineNumber].rawValue = db.LineStrings[db.lineNumber].rawValue .. string.format(chatStrings.esostandart, lcol, new_from, zonetag, carriageReturn, rcol, text)
  3520.             end
  3521.         end
  3522.    
  3523.     -- NPC speech
  3524.     elseif chanCode == CHAT_CHANNEL_MONSTER_SAY or chanCode == CHAT_CHANNEL_MONSTER_YELL or chanCode == CHAT_CHANNEL_MONSTER_WHISPER then
  3525.        
  3526.         -- Used for Copy
  3527.         db.LineStrings[db.lineNumber].rawFrom = string.format(chatStrings.copynpc, db.LineStrings[db.lineNumber].rawFrom)
  3528.        
  3529.         message = message .. string.format(chatStrings.standard, lcol, new_from, carriageReturn, rcol, linkedText)
  3530.         db.LineStrings[db.lineNumber].rawValue = db.LineStrings[db.lineNumber].rawValue .. string.format(chatStrings.standard, lcol, new_from, carriageReturn, rcol, text)
  3531.    
  3532.     -- Incoming whispers
  3533.     elseif chanCode == CHAT_CHANNEL_WHISPER then
  3534.    
  3535.         --PlaySound
  3536.         if db.soundforincwhisps then
  3537.             PlaySound(db.soundforincwhisps)
  3538.         end
  3539.    
  3540.         -- Used for Copy
  3541.         db.LineStrings[db.lineNumber].rawFrom = string.format(chatStrings.copytellIn, db.LineStrings[db.lineNumber].rawFrom)
  3542.    
  3543.         message = message .. string.format(chatStrings.tellIn, lcol, new_from, carriageReturn, rcol, linkedText)
  3544.         db.LineStrings[db.lineNumber].rawValue = db.LineStrings[db.lineNumber].rawValue .. string.format(chatStrings.tellIn, lcol, new_from, carriageReturn, rcol, text)
  3545.    
  3546.     -- Outgoing whispers
  3547.     elseif chanCode == CHAT_CHANNEL_WHISPER_SENT then
  3548.        
  3549.         -- Used for Copy
  3550.         db.LineStrings[db.lineNumber].rawFrom = string.format(chatStrings.copytellOut, db.LineStrings[db.lineNumber].rawFrom)
  3551.        
  3552.         message = message .. string.format(chatStrings.tellOut, lcol, new_from, carriageReturn, rcol, linkedText)
  3553.         db.LineStrings[db.lineNumber].rawValue = db.LineStrings[db.lineNumber].rawValue .. string.format(chatStrings.tellOut, lcol, new_from, carriageReturn, rcol, text)
  3554.    
  3555.     -- Guild chat
  3556.     elseif chanCode >= CHAT_CHANNEL_GUILD_1 and chanCode <= CHAT_CHANNEL_GUILD_5 then
  3557.        
  3558.         local gtag = tag
  3559.         if db.showGuildNumbers then
  3560.             gtag = (chanCode - CHAT_CHANNEL_GUILD_1 + 1) .. "-" .. tag
  3561.            
  3562.             -- Used for Copy
  3563.             db.LineStrings[db.lineNumber].rawFrom = string.format(chatStrings.copyguild, gtag, db.LineStrings[db.lineNumber].rawFrom)
  3564.            
  3565.             -- GuildHandler
  3566.             gtag = ZO_LinkHandler_CreateLink(gtag, nil, CHANNEL_LINK_TYPE, chanCode)
  3567.             message = message .. string.format(chatStrings.guild, lcol, gtag, new_from, carriageReturn, rcol, linkedText)
  3568.             db.LineStrings[db.lineNumber].rawValue = db.LineStrings[db.lineNumber].rawValue .. string.format(chatStrings.guild, lcol, gtag, new_from, carriageReturn, rcol, text)
  3569.            
  3570.         else
  3571.            
  3572.             -- Used for Copy
  3573.             db.LineStrings[db.lineNumber].rawFrom = string.format(chatStrings.copyguild, gtag, db.LineStrings[db.lineNumber].rawFrom)
  3574.            
  3575.             -- GuildHandler
  3576.             gtag = ZO_LinkHandler_CreateLink(gtag, nil, CHANNEL_LINK_TYPE, chanCode)
  3577.            
  3578.             message = message .. string.format(chatStrings.guild, lcol, gtag, new_from, carriageReturn, rcol, linkedText)
  3579.             db.LineStrings[db.lineNumber].rawValue = db.LineStrings[db.lineNumber].rawValue .. string.format(chatStrings.guild, lcol, gtag, new_from, carriageReturn, rcol, text)
  3580.            
  3581.         end
  3582.    
  3583.     -- Officer chat
  3584.     elseif chanCode >= CHAT_CHANNEL_OFFICER_1 and chanCode <= CHAT_CHANNEL_OFFICER_5 then
  3585.        
  3586.         local gtag = tag
  3587.         if db.showGuildNumbers then
  3588.             gtag = (chanCode - CHAT_CHANNEL_OFFICER_1 + 1) .. "-" .. tag
  3589.            
  3590.             -- Used for Copy
  3591.             db.LineStrings[db.lineNumber].rawFrom = string.format(chatStrings.copyguild, gtag, db.LineStrings[db.lineNumber].rawFrom)
  3592.            
  3593.             -- GuildHandler
  3594.             gtag = ZO_LinkHandler_CreateLink(gtag, nil, CHANNEL_LINK_TYPE, chanCode)
  3595.            
  3596.             message = message .. string.format(chatStrings.guild, lcol, gtag, new_from, carriageReturn, rcol, linkedText)
  3597.             db.LineStrings[db.lineNumber].rawValue = db.LineStrings[db.lineNumber].rawValue .. string.format(chatStrings.guild, lcol, gtag, new_from, carriageReturn, rcol, text)
  3598.         else
  3599.            
  3600.             -- Used for Copy
  3601.             db.LineStrings[db.lineNumber].rawFrom = string.format(chatStrings.copyguild, gtag, db.LineStrings[db.lineNumber].rawFrom)
  3602.            
  3603.             -- GuildHandler
  3604.             gtag = ZO_LinkHandler_CreateLink(gtag, nil, CHANNEL_LINK_TYPE, chanCode)
  3605.            
  3606.             message = message .. string.format(chatStrings.guild, lcol, gtag, new_from, carriageReturn, rcol, linkedText)
  3607.             db.LineStrings[db.lineNumber].rawValue = db.LineStrings[db.lineNumber].rawValue .. string.format(chatStrings.guild, lcol, gtag, new_from, carriageReturn, rcol, text)
  3608.         end
  3609.    
  3610.     -- Player emotes
  3611.     elseif chanCode == CHAT_CHANNEL_EMOTE then
  3612.    
  3613.         db.LineStrings[db.lineNumber].rawFrom = string.format(chatStrings.copyemote, db.LineStrings[db.lineNumber].rawFrom)
  3614.         message = message .. string.format(chatStrings.emote, lcol, new_from, rcol, linkedText)
  3615.         db.LineStrings[db.lineNumber].rawValue = db.LineStrings[db.lineNumber].rawValue .. string.format(chatStrings.emote, lcol, new_from, rcol, text)
  3616.    
  3617.     -- NPC emotes
  3618.     elseif chanCode == CHAT_CHANNEL_MONSTER_EMOTE then
  3619.    
  3620.         -- Used for Copy
  3621.         db.LineStrings[db.lineNumber].rawFrom = string.format(chatStrings.copyemote, db.LineStrings[db.lineNumber].rawFrom)
  3622.    
  3623.         message = message .. string.format(chatStrings.emote, lcol, new_from, rcol, linkedText)
  3624.         db.LineStrings[db.lineNumber].rawValue = db.LineStrings[db.lineNumber].rawValue .. string.format(chatStrings.emote, lcol, new_from, rcol, text)
  3625.    
  3626.     -- Language zones
  3627.     elseif chanCode >= CHAT_CHANNEL_ZONE_LANGUAGE_1 and chanCode <= CHAT_CHANNEL_ZONE_LANGUAGE_4 then
  3628.         local lang
  3629.         if chanCode == CHAT_CHANNEL_ZONE_LANGUAGE_1 then lang = "EN"
  3630.         elseif chanCode == CHAT_CHANNEL_ZONE_LANGUAGE_2 then lang = "FR"
  3631.         elseif chanCode == CHAT_CHANNEL_ZONE_LANGUAGE_3 then lang = "DE"
  3632.         elseif chanCode == CHAT_CHANNEL_ZONE_LANGUAGE_4 then lang = "JP"
  3633.         end
  3634.        
  3635.         -- Used for Copy
  3636.         db.LineStrings[db.lineNumber].rawFrom = string.format(chatStrings.copylanguage, lang, db.LineStrings[db.lineNumber].rawFrom)
  3637.        
  3638.         message = message .. string.format(chatStrings.language, lcol, lang, new_from, carriageReturn, rcol, linkedText)
  3639.         db.LineStrings[db.lineNumber].rawValue = db.LineStrings[db.lineNumber].rawValue .. string.format(chatStrings.language, lcol, lang, new_from, carriageReturn, rcol, text)
  3640.    
  3641.     -- Unknown messages - just pass it through, no changes.
  3642.     else
  3643.         local notHandled = true
  3644.         message = text
  3645.     end
  3646.    
  3647.     db.LineStrings[db.lineNumber].rawDisplayed = message
  3648.    
  3649.     -- Only if handled by pChat
  3650.    
  3651.     if not notHandled then
  3652.         -- Store message and params into an array for copy system and SpamFiltering
  3653.         StorelineNumber(GetTimeStamp(), db.LineStrings[db.lineNumber].rawFrom, text, chanCode, originalFrom)
  3654.     end
  3655.    
  3656.     -- Needs to be after .storelineNumber()
  3657.     if chanCode == CHAT_CHANNEL_WHISPER then
  3658.         OnIMReceived(displayedFrom, db.lineNumber - 1)
  3659.     end
  3660.    
  3661.     return message
  3662.    
  3663. end
  3664.  
  3665. -- Rewrite of core function
  3666. function CHAT_SYSTEM:Zo_AddMessage(text)
  3667.  
  3668.     -- Overwrite CHAT_SYSTEM:AddMessage(text) function to format it
  3669.     -- Overwrite SharedChatContainer:AddDebugMessage(formattedEventText) in order to display system message in specific tabs
  3670.     -- CHAT_SYSTEM:Zo_AddMessage() can be used in order to use old function
  3671.     -- Store the message in pChatData.cachedMessages if this one is sent before CHAT_SYSTEM.primaryContainer goes up (before 1st EVENT_PLAYER_ACTIVATED)
  3672.    
  3673.     if CHAT_SYSTEM.primaryContainer and pChatData.messagesHaveBeenRestorated then
  3674.         for k in ipairs(CHAT_SYSTEM.containers) do
  3675.             CHAT_SYSTEM.containers[k]:OnChatEvent(nil, FormatSysMessage(text), CHAT_CATEGORY_SYSTEM)
  3676.         end
  3677.     else
  3678.         table.insert(pChatData.cachedMessages, text)
  3679.     end
  3680.    
  3681. end
  3682.  
  3683. local function EmitMessage(text)
  3684.     if CHAT_SYSTEM and CHAT_SYSTEM.primaryContainer and pChatData.messagesHaveBeenRestorated then
  3685.         if text == "" then
  3686.             text = "[Empty String]"
  3687.         end
  3688.         CHAT_SYSTEM:Zo_AddMessage(text)
  3689.     else
  3690.         table.insert(pChatData.cachedMessages, text)
  3691.     end
  3692. end
  3693.  
  3694. local function EmitTable(t, indent, tableHistory)
  3695.     indent        = indent or "."
  3696.     tableHistory    = tableHistory or {}
  3697.    
  3698.     for k, v in pairs(t)
  3699.     do
  3700.         local vType = type(v)
  3701.  
  3702.         EmitMessage(indent.."("..vType.."): "..tostring(k).." = "..tostring(v))
  3703.        
  3704.         if(vType == "table")
  3705.         then
  3706.             if(tableHistory[v])
  3707.             then
  3708.                 EmitMessage(indent.."Avoiding cycle on table...")
  3709.             else
  3710.                 tableHistory[v] = true
  3711.                 EmitTable(v, indent.."  ", tableHistory)
  3712.             end
  3713.         end
  3714.     end
  3715. end
  3716.  
  3717. -- Rewrite of a core function
  3718. function d(...)
  3719.     for i = 1, select("#", ...) do
  3720.         local value = select(i, ...)
  3721.         if(type(value) == "table")
  3722.         then
  3723.             EmitTable(value)
  3724.         else
  3725.             EmitMessage(tostring (value))
  3726.         end
  3727.     end
  3728. end
  3729.  
  3730. -- Rewrite of core function
  3731. function ZO_TabButton_Text_SetTextColor(self, color)
  3732.    
  3733.     local r, g, b, a = ConvertHexToRGBA(db.colours.tabwarning)
  3734.    
  3735.     if(self.allowLabelColorChanges) then
  3736.         local label = GetControl(self, "Text")
  3737.         label:SetColor(r, g, b, a)
  3738.     end
  3739.    
  3740. end
  3741.  
  3742. -- Rewrite of (local) core function
  3743. local function GetOfficerChannelErrorFunction(guildIndex)
  3744.     return function()
  3745.         if(GetNumGuilds() < guildIndex) then
  3746.             return zo_strformat(SI_CANT_GUILD_CHAT_NOT_IN_GUILD, guildIndex)
  3747.         else
  3748.             return zo_strformat(SI_CANT_OFFICER_CHAT_NO_PERMISSION, GetGuildName(guildIndex))
  3749.         end
  3750.     end
  3751. end
  3752.  
  3753. -- Rewrite of (local) core function
  3754. local function GetGuildChannelErrorFunction(guildIndex)
  3755.     return function()
  3756.         if(GetNumGuilds() < guildIndex) then
  3757.             return zo_strformat(SI_CANT_GUILD_CHAT_NOT_IN_GUILD, guildIndex)
  3758.         else
  3759.             return zo_strformat(SI_CANT_GUILD_CHAT_NO_PERMISSION, GetGuildName(guildIndex))
  3760.         end
  3761.     end
  3762. end
  3763.  
  3764. local FILTERS_PER_ROW = 2
  3765.  
  3766. -- defines the ordering of the filter categories
  3767. local CHANNEL_ORDERING_WEIGHT = {
  3768.     [CHAT_CATEGORY_SAY] = 10,
  3769.     [CHAT_CATEGORY_YELL] = 20,
  3770.  
  3771.     [CHAT_CATEGORY_WHISPER_INCOMING] = 30,
  3772.     [CHAT_CATEGORY_PARTY] = 40,
  3773.  
  3774.     [CHAT_CATEGORY_EMOTE] = 50,
  3775.     [CHAT_CATEGORY_MONSTER_SAY] = 60,
  3776.  
  3777.     [CHAT_CATEGORY_ZONE] = 80,
  3778.     [CHAT_CATEGORY_ZONE_ENGLISH] = 90,
  3779.  
  3780.     [CHAT_CATEGORY_ZONE_FRENCH] = 100,
  3781.     [CHAT_CATEGORY_ZONE_GERMAN] = 110,
  3782.  
  3783.     [CHAT_CATEGORY_ZONE_JAPANESE] = 120,
  3784.     [CHAT_CATEGORY_SYSTEM] = 130,
  3785. }
  3786.  
  3787. local function FilterComparator(left, right)
  3788.     local leftPrimaryCategory = left.channels[1]
  3789.     local rightPrimaryCategory = right.channels[1]
  3790.  
  3791.     local leftWeight = CHANNEL_ORDERING_WEIGHT[leftPrimaryCategory]
  3792.     local rightWeight = CHANNEL_ORDERING_WEIGHT[rightPrimaryCategory]
  3793.  
  3794.     if leftWeight and rightWeight then
  3795.         return leftWeight < rightWeight
  3796.     elseif not leftWeight and not rightWeight then
  3797.         return false
  3798.     elseif leftWeight then
  3799.         return true
  3800.     end
  3801.  
  3802.     return false
  3803. end
  3804.  
  3805. local SKIP_CHANNELS = {
  3806.     -- [CHAT_CATEGORY_SYSTEM] = true,
  3807.     [CHAT_CATEGORY_GUILD_1] = true,
  3808.     [CHAT_CATEGORY_GUILD_2] = true,
  3809.     [CHAT_CATEGORY_GUILD_3] = true,
  3810.     [CHAT_CATEGORY_GUILD_4] = true,
  3811.     [CHAT_CATEGORY_GUILD_5] = true,
  3812.     [CHAT_CATEGORY_OFFICER_1] = true,
  3813.     [CHAT_CATEGORY_OFFICER_2] = true,
  3814.     [CHAT_CATEGORY_OFFICER_3] = true,
  3815.     [CHAT_CATEGORY_OFFICER_4] = true,
  3816.     [CHAT_CATEGORY_OFFICER_5] = true,
  3817. }
  3818.  
  3819. local FILTER_PAD_X = 90
  3820. local FILTER_PAD_Y = 0
  3821. local FILTER_WIDTH = 150
  3822. local FILTER_HEIGHT = 27
  3823. local INITIAL_XOFFS = 0
  3824. local INITIAL_YOFFS = 0
  3825.  
  3826. -- Rewrite of a core data
  3827. local COMBINED_CHANNELS = {
  3828.     [CHAT_CATEGORY_WHISPER_INCOMING] = {parentChannel = CHAT_CATEGORY_WHISPER_INCOMING, name = SI_CHAT_CHANNEL_NAME_WHISPER},
  3829.     [CHAT_CATEGORY_WHISPER_OUTGOING] = {parentChannel = CHAT_CATEGORY_WHISPER_INCOMING, name = SI_CHAT_CHANNEL_NAME_WHISPER},
  3830.  
  3831.     [CHAT_CATEGORY_MONSTER_SAY] = {parentChannel = CHAT_CATEGORY_MONSTER_SAY, name = SI_CHAT_CHANNEL_NAME_NPC},
  3832.     [CHAT_CATEGORY_MONSTER_YELL] = {parentChannel = CHAT_CATEGORY_MONSTER_SAY, name = SI_CHAT_CHANNEL_NAME_NPC},
  3833.     [CHAT_CATEGORY_MONSTER_WHISPER] = {parentChannel = CHAT_CATEGORY_MONSTER_SAY, name = SI_CHAT_CHANNEL_NAME_NPC},
  3834.     [CHAT_CATEGORY_MONSTER_EMOTE] = {parentChannel = CHAT_CATEGORY_MONSTER_SAY, name = SI_CHAT_CHANNEL_NAME_NPC},
  3835. }
  3836.  
  3837. -- Rewrite of a core function. Nothing is changed except in SKIP_CHANNELS set a above
  3838. function CHAT_OPTIONS:InitializeFilterButtons(dialogControl)
  3839.     --generate a table of entry data from the chat category header information
  3840.     local entryData = {}
  3841.     local lastEntry = CHAT_CATEGORY_HEADER_COMBAT - 1
  3842.  
  3843.     for i = CHAT_CATEGORY_HEADER_CHANNELS, lastEntry do
  3844.         if(SKIP_CHANNELS[i] == nil and GetString("SI_CHATCHANNELCATEGORIES", i) ~= "") then
  3845.  
  3846.             if(COMBINED_CHANNELS[i] == nil) then
  3847.                 entryData[i] =
  3848.                 {
  3849.                     channels = { i },
  3850.                     name = GetString("SI_CHATCHANNELCATEGORIES", i),
  3851.                 }
  3852.             else
  3853.                 --create the entry for those with combined channels just once
  3854.                 local parentChannel = COMBINED_CHANNELS[i].parentChannel
  3855.  
  3856.                 if(not entryData[parentChannel]) then
  3857.                     entryData[parentChannel] =
  3858.                     {
  3859.                         channels = { },
  3860.                         name = GetString(COMBINED_CHANNELS[i].name),
  3861.                     }
  3862.                 end
  3863.  
  3864.                 table.insert(entryData[parentChannel].channels, i)
  3865.             end
  3866.         end
  3867.     end
  3868.  
  3869.     --now generate and anchor buttons
  3870.     local filterAnchor = ZO_Anchor:New(TOPLEFT, self.filterSection, TOPLEFT, 0, 0)
  3871.     local count = 0
  3872.  
  3873.     local sortedEntries = {}
  3874.     for _, entry in pairs(entryData) do
  3875.         sortedEntries[#sortedEntries + 1] = entry
  3876.     end
  3877.  
  3878.     table.sort(sortedEntries, FilterComparator)
  3879.  
  3880.     for _, entry in ipairs(sortedEntries) do
  3881.         local filter, key = self.filterPool:AcquireObject()
  3882.         filter.key = key
  3883.  
  3884.         local button = filter:GetNamedChild("Check")
  3885.         button.channels = entry.channels
  3886.         table.insert(self.filterButtons, button)
  3887.  
  3888.         local label = filter:GetNamedChild("Label")
  3889.         label:SetText(entry.name)
  3890.  
  3891.         ZO_Anchor_BoxLayout(filterAnchor, filter, count, FILTERS_PER_ROW, FILTER_PAD_X, FILTER_PAD_Y, FILTER_WIDTH, FILTER_HEIGHT, INITIAL_XOFFS, INITIAL_YOFFS)
  3892.         count = count + 1
  3893.     end
  3894. end
  3895.  
  3896. -- Rewrite of core data
  3897. -- Added "id" key with raw values here because of partial overwriting
  3898. local channelInfo =
  3899. {
  3900.     [CHAT_CHANNEL_SAY] = {
  3901.         format = SI_CHAT_MESSAGE_SAY,
  3902.         name = GetString(SI_CHAT_CHANNEL_NAME_SAY),
  3903.         playerLinkable = true,
  3904.         channelLinkable = true,
  3905.         switches = GetString(SI_CHANNEL_SWITCH_SAY),
  3906.         id = CHAT_CHANNEL_SAY
  3907.     },
  3908.     [CHAT_CHANNEL_YELL] =
  3909.     {
  3910.         format = SI_CHAT_MESSAGE_YELL,
  3911.         name = GetString(SI_CHAT_CHANNEL_NAME_YELL),
  3912.         playerLinkable = true,
  3913.         channelLinkable = true, -- Modified
  3914.         switches = GetString(SI_CHANNEL_SWITCH_YELL),
  3915.         id = CHAT_CHANNEL_YELL
  3916.     },
  3917.     [CHAT_CHANNEL_ZONE] =
  3918.     {
  3919.         format = SI_CHAT_MESSAGE_ZONE,
  3920.         name = GetString(SI_CHAT_CHANNEL_NAME_ZONE),
  3921.         playerLinkable = true,
  3922.         channelLinkable = true, -- Modified
  3923.         switches = GetString(SI_CHANNEL_SWITCH_ZONE),
  3924.         id = CHAT_CHANNEL_ZONE
  3925.     },
  3926.     [CHAT_CHANNEL_ZONE_LANGUAGE_1] =
  3927.     {
  3928.         format = SI_CHAT_MESSAGE_ZONE_ENGLISH,
  3929.         name = GetString(SI_CHAT_CHANNEL_NAME_ZONE_ENGLISH),
  3930.         playerLinkable = true,
  3931.         channelLinkable = true, -- Modified
  3932.         switches = GetString(SI_CHANNEL_SWITCH_ZONE_ENGLISH),
  3933.         id = CHAT_CHANNEL_ZONE_LANGUAGE_1
  3934.     },
  3935.     [CHAT_CHANNEL_ZONE_LANGUAGE_2] =
  3936.     {
  3937.         format = SI_CHAT_MESSAGE_ZONE_FRENCH,
  3938.         name = GetString(SI_CHAT_CHANNEL_NAME_ZONE_FRENCH),
  3939.         playerLinkable = true,
  3940.         channelLinkable = true, -- Modified
  3941.         switches = GetString(SI_CHANNEL_SWITCH_ZONE_FRENCH),
  3942.         id = CHAT_CHANNEL_ZONE_LANGUAGE_2
  3943.     },
  3944.     [CHAT_CHANNEL_ZONE_LANGUAGE_3] =
  3945.     {
  3946.         format = SI_CHAT_MESSAGE_ZONE_GERMAN,
  3947.         name = GetString(SI_CHAT_CHANNEL_NAME_ZONE_GERMAN),
  3948.         playerLinkable = true,
  3949.         channelLinkable = true, -- Modified
  3950.         switches = GetString(SI_CHANNEL_SWITCH_ZONE_GERMAN),
  3951.         id = CHAT_CHANNEL_ZONE_LANGUAGE_3
  3952.     },
  3953.     [CHAT_CHANNEL_ZONE_LANGUAGE_4] =
  3954.     {
  3955.         format = SI_CHAT_MESSAGE_ZONE_JAPANESE,
  3956.         name = GetString(SI_CHAT_CHANNEL_NAME_ZONE_JAPANESE),
  3957.         playerLinkable = true,
  3958.         channelLinkable = true, -- Modified
  3959.         switches = GetString(SI_CHANNEL_SWITCH_ZONE_JAPANESE),
  3960.         id = CHAT_CHANNEL_ZONE_LANGUAGE_4
  3961.     },
  3962.     [CHAT_CHANNEL_PARTY] =
  3963.     {
  3964.         format = SI_CHAT_MESSAGE_PARTY,
  3965.         name = GetString(SI_CHAT_CHANNEL_NAME_PARTY),
  3966.         playerLinkable = true,
  3967.         channelLinkable = true,
  3968.         switches = GetString(SI_CHANNEL_SWITCH_PARTY),
  3969.         requires = function()
  3970.             return IsUnitGrouped("player")
  3971.         end,
  3972.         deferRequirement = true,
  3973.         requirementErrorMessage = GetString(SI_GROUP_NOTIFICATION_YOU_ARE_NOT_IN_A_GROUP),
  3974.         id = CHAT_CHANNEL_PARTY
  3975.     },
  3976.     [CHAT_CHANNEL_WHISPER] =
  3977.     {
  3978.         format = SI_CHAT_MESSAGE_WHISPER,
  3979.         name = GetString(SI_CHAT_CHANNEL_NAME_WHISPER),
  3980.         playerLinkable = true,
  3981.         channelLinkable = false,
  3982.         switches = GetString(SI_CHANNEL_SWITCH_WHISPER),
  3983.         target = true,
  3984.         saveTarget = CHAT_CHANNEL_WHISPER,
  3985.         targetSwitches = GetString(SI_CHANNEL_SWITCH_WHISPER_REPLY),
  3986.         id = CHAT_CHANNEL_WHISPER
  3987.     },
  3988.     [CHAT_CHANNEL_WHISPER_SENT] =
  3989.     {
  3990.         format = SI_CHAT_MESSAGE_WHISPER_SENT,
  3991.         playerLinkable = true,
  3992.         channelLinkable = false,
  3993.         id = CHAT_CHANNEL_WHISPER_SENT
  3994.     },
  3995.     [CHAT_CHANNEL_EMOTE] =
  3996.     {
  3997.         format = SI_CHAT_EMOTE,
  3998.         name = GetString(SI_CHAT_CHANNEL_NAME_EMOTE),
  3999.         playerLinkable = true,
  4000.         channelLinkable = false,
  4001.         switches = GetString(SI_CHANNEL_SWITCH_EMOTE),
  4002.         id = CHAT_CHANNEL_EMOTE
  4003.     },
  4004.     [CHAT_CHANNEL_MONSTER_SAY] =
  4005.     {
  4006.         format = SI_CHAT_MONSTER_MESSAGE_SAY,
  4007.         playerLinkable = false,
  4008.         channelLinkable = false,
  4009.         id = CHAT_CHANNEL_MONSTER_SAY
  4010.     },
  4011.     [CHAT_CHANNEL_MONSTER_YELL] =
  4012.     {
  4013.         format = SI_CHAT_MONSTER_MESSAGE_YELL,
  4014.         playerLinkable = false,
  4015.         channelLinkable = false,
  4016.         id = CHAT_CHANNEL_MONSTER_YELL
  4017.     },
  4018.     [CHAT_CHANNEL_MONSTER_WHISPER] =
  4019.     {
  4020.         format = SI_CHAT_MONSTER_MESSAGE_WHISPER,
  4021.         playerLinkable = false,
  4022.         channelLinkable = false,
  4023.         id = CHAT_CHANNEL_MONSTER_WHISPER
  4024.     },
  4025.     [CHAT_CHANNEL_MONSTER_EMOTE] =
  4026.     {
  4027.         format = SI_CHAT_MONSTER_EMOTE,
  4028.         playerLinkable = false,
  4029.         channelLinkable = false,
  4030.         id = CHAT_CHANNEL_MONSTER_EMOTE
  4031.     },
  4032.     [CHAT_CHANNEL_SYSTEM] =
  4033.     {
  4034.         format = SI_CHAT_MESSAGE_SYSTEM,
  4035.         playerLinkable = false,
  4036.         channelLinkable = false,
  4037.         id = CHAT_CHANNEL_SYSTEM
  4038.     },
  4039.     [CHAT_CHANNEL_GUILD_1] =
  4040.     {
  4041.         format = SI_CHAT_MESSAGE_GUILD,
  4042.         dynamicName = true,
  4043.         playerLinkable = true,
  4044.         channelLinkable = true,
  4045.         switches = GetString(SI_CHANNEL_SWITCH_GUILD_1),
  4046.         requires = CanWriteGuildChannel,
  4047.         requirementErrorMessage = GetGuildChannelErrorFunction(1),
  4048.         deferRequirement = true,
  4049.         id = CHAT_CHANNEL_GUILD_1
  4050.     },
  4051.     [CHAT_CHANNEL_GUILD_2] =
  4052.     {
  4053.         format = SI_CHAT_MESSAGE_GUILD,
  4054.         dynamicName = true,
  4055.         playerLinkable = true,
  4056.         channelLinkable = true,
  4057.         switches = GetString(SI_CHANNEL_SWITCH_GUILD_2),
  4058.         requires = CanWriteGuildChannel,
  4059.         requirementErrorMessage = GetGuildChannelErrorFunction(2),
  4060.         deferRequirement = true,
  4061.         id = CHAT_CHANNEL_GUILD_2
  4062.     },
  4063.     [CHAT_CHANNEL_GUILD_3] =
  4064.     {
  4065.         format = SI_CHAT_MESSAGE_GUILD,
  4066.         dynamicName = true,
  4067.         playerLinkable = true,
  4068.         channelLinkable = true,
  4069.         switches = GetString(SI_CHANNEL_SWITCH_GUILD_3),
  4070.         requires = CanWriteGuildChannel,
  4071.         requirementErrorMessage = GetGuildChannelErrorFunction(3),
  4072.         deferRequirement = true,
  4073.         id = CHAT_CHANNEL_GUILD_3
  4074.     },
  4075.     [CHAT_CHANNEL_GUILD_4] =
  4076.     {
  4077.         format = SI_CHAT_MESSAGE_GUILD,
  4078.         dynamicName = true,
  4079.         playerLinkable = true,
  4080.         channelLinkable = true,
  4081.         switches = GetString(SI_CHANNEL_SWITCH_GUILD_4),
  4082.         requires = CanWriteGuildChannel,
  4083.         requirementErrorMessage = GetGuildChannelErrorFunction(4),
  4084.         deferRequirement = true,
  4085.         id = CHAT_CHANNEL_GUILD_4
  4086.     },
  4087.     [CHAT_CHANNEL_GUILD_5] =
  4088.     {
  4089.         format = SI_CHAT_MESSAGE_GUILD,
  4090.         dynamicName = true,
  4091.         playerLinkable = true,
  4092.         channelLinkable = true,
  4093.         switches = GetString(SI_CHANNEL_SWITCH_GUILD_5),
  4094.         requires = CanWriteGuildChannel,
  4095.         requirementErrorMessage = GetGuildChannelErrorFunction(5),
  4096.         deferRequirement = true,
  4097.         id = CHAT_CHANNEL_GUILD_5
  4098.     },
  4099.     [CHAT_CHANNEL_OFFICER_1] =
  4100.     {
  4101.         format = SI_CHAT_MESSAGE_GUILD,
  4102.         dynamicName = true,
  4103.         playerLinkable = true,
  4104.         channelLinkable = true,
  4105.         switches = GetString(SI_CHANNEL_SWITCH_OFFICER_1),
  4106.         requires = CanWriteGuildChannel,
  4107.         requirementErrorMessage = GetOfficerChannelErrorFunction(1),
  4108.         deferRequirement = true,
  4109.         id = CHAT_CHANNEL_OFFICER_1
  4110.     },
  4111.     [CHAT_CHANNEL_OFFICER_2] =
  4112.     {
  4113.         format = SI_CHAT_MESSAGE_GUILD,
  4114.         dynamicName = true,
  4115.         playerLinkable = true,
  4116.         channelLinkable = true,
  4117.         switches = GetString(SI_CHANNEL_SWITCH_OFFICER_2),
  4118.         requires = CanWriteGuildChannel,
  4119.         requirementErrorMessage = GetOfficerChannelErrorFunction(2),
  4120.         deferRequirement = true,
  4121.         id = CHAT_CHANNEL_OFFICER_2
  4122.     },
  4123.     [CHAT_CHANNEL_OFFICER_3] =
  4124.     {
  4125.         format = SI_CHAT_MESSAGE_GUILD,
  4126.         dynamicName = true,
  4127.         playerLinkable = true,
  4128.         channelLinkable = true,
  4129.         switches = GetString(SI_CHANNEL_SWITCH_OFFICER_3),
  4130.         requires = CanWriteGuildChannel,
  4131.         requirementErrorMessage = GetOfficerChannelErrorFunction(3),
  4132.         deferRequirement = true,
  4133.         id = CHAT_CHANNEL_OFFICER_3
  4134.     },
  4135.     [CHAT_CHANNEL_OFFICER_4] =
  4136.     {
  4137.         format = SI_CHAT_MESSAGE_GUILD,
  4138.         dynamicName = true,
  4139.         playerLinkable = true,
  4140.         channelLinkable = true,
  4141.         switches = GetString(SI_CHANNEL_SWITCH_OFFICER_4),
  4142.         requires = CanWriteGuildChannel,
  4143.         requirementErrorMessage = GetOfficerChannelErrorFunction(4),
  4144.         deferRequirement = true,
  4145.         id = CHAT_CHANNEL_OFFICER_4
  4146.     },
  4147.     [CHAT_CHANNEL_OFFICER_5] =
  4148.     {
  4149.         format = SI_CHAT_MESSAGE_GUILD,
  4150.         dynamicName = true,
  4151.         playerLinkable = true,
  4152.         channelLinkable = true,
  4153.         switches = GetString(SI_CHANNEL_SWITCH_OFFICER_5),
  4154.         requires = CanWriteGuildChannel,
  4155.         requirementErrorMessage = GetOfficerChannelErrorFunction(5),
  4156.         deferRequirement = true,
  4157.         id = CHAT_CHANNEL_OFFICER_5
  4158.     },
  4159. }
  4160.  
  4161. -- Rewrite of core function
  4162. function ZO_ChatSystem_GetChannelInfo()
  4163.     return channelInfo
  4164. end
  4165.  
  4166. -- Save chat configuration
  4167. local function SaveChatConfig()
  4168.    
  4169.     if not pChatData.tabNotBefore then
  4170.         pChatData.tabNotBefore = {} -- Init here or in SyncChatConfig depending if the "Clear Tab" has been used
  4171.     end
  4172.    
  4173.     if isAddonLoaded and CHAT_SYSTEM and CHAT_SYSTEM.primaryContainer then -- Some addons calls SetCVar before
  4174.    
  4175.         -- Rewrite the whole char tab
  4176.         db.chatConfSync[pChatData.localPlayer] = {}
  4177.        
  4178.         -- Save Chat positions
  4179.         db.chatConfSync[pChatData.localPlayer].relPoint = CHAT_SYSTEM.primaryContainer.settings.relPoint
  4180.         db.chatConfSync[pChatData.localPlayer].x = CHAT_SYSTEM.primaryContainer.settings.x
  4181.         db.chatConfSync[pChatData.localPlayer].y = CHAT_SYSTEM.primaryContainer.settings.y
  4182.         db.chatConfSync[pChatData.localPlayer].height = CHAT_SYSTEM.primaryContainer.settings.height
  4183.         db.chatConfSync[pChatData.localPlayer].width = CHAT_SYSTEM.primaryContainer.settings.width
  4184.         db.chatConfSync[pChatData.localPlayer].point = CHAT_SYSTEM.primaryContainer.settings.point
  4185.        
  4186.         --db.chatConfSync[pChatData.localPlayer].textEntryDocked = true
  4187.        
  4188.         -- Don't overflow screen, remove 10px.
  4189.         if CHAT_SYSTEM.primaryContainer.settings.height >= ( CHAT_SYSTEM.maxContainerHeight - 15 ) then
  4190.             db.chatConfSync[pChatData.localPlayer].height = ( CHAT_SYSTEM.maxContainerHeight - 15 )
  4191.         else
  4192.             db.chatConfSync[pChatData.localPlayer].height = CHAT_SYSTEM.primaryContainer.settings.height
  4193.         end
  4194.        
  4195.         -- Same
  4196.         if CHAT_SYSTEM.primaryContainer.settings.width >= ( CHAT_SYSTEM.maxContainerWidth - 15 ) then
  4197.             db.chatConfSync[pChatData.localPlayer].width = ( CHAT_SYSTEM.maxContainerWidth - 15 )
  4198.         else
  4199.             db.chatConfSync[pChatData.localPlayer].width = CHAT_SYSTEM.primaryContainer.settings.width
  4200.         end
  4201.        
  4202.         -- Save Colors
  4203.         db.chatConfSync[pChatData.localPlayer].colors = {}
  4204.        
  4205.         for _, category in ipairs (pChatData.chatCategories) do
  4206.             local r, g, b = GetChatCategoryColor(category)
  4207.             db.chatConfSync[pChatData.localPlayer].colors[category] = { red = r, green = g, blue = b }
  4208.         end
  4209.        
  4210.         -- Save Font Size
  4211.         db.chatConfSync[pChatData.localPlayer].fontSize = GetChatFontSize()
  4212.        
  4213.         -- Save Tabs
  4214.         db.chatConfSync[pChatData.localPlayer].tabs = {}
  4215.        
  4216.         -- GetNumChatContainerTabs(1) don't refresh its number before a ReloadUI
  4217.         -- for numTab = 1, GetNumChatContainerTabs(1) do
  4218.         for numTab in ipairs (CHAT_SYSTEM.primaryContainer.windows) do
  4219.            
  4220.             db.chatConfSync[pChatData.localPlayer].tabs[numTab] = {}
  4221.            
  4222.             -- Save "Clear Tab" flag
  4223.             if pChatData.tabNotBefore[numTab] then
  4224.                 db.chatConfSync[pChatData.localPlayer].tabs[numTab].notBefore = pChatData.tabNotBefore[numTab]
  4225.             end
  4226.            
  4227.             -- No.. need a ReloadUI     local name, isLocked, isInteractable, isCombatLog, areTimestampsEnabled = GetChatContainerTabInfo(1, numTab)
  4228.             -- IsLocked
  4229.             if CHAT_SYSTEM.primaryContainer:IsLocked(numTab) then
  4230.                 db.chatConfSync[pChatData.localPlayer].tabs[numTab].isLocked = true
  4231.             else
  4232.                 db.chatConfSync[pChatData.localPlayer].tabs[numTab].isLocked = false
  4233.             end
  4234.            
  4235.             -- IsInteractive
  4236.             if CHAT_SYSTEM.primaryContainer:IsInteractive(numTab) then
  4237.                 db.chatConfSync[pChatData.localPlayer].tabs[numTab].isInteractable = true
  4238.             else
  4239.                 db.chatConfSync[pChatData.localPlayer].tabs[numTab].isInteractable = false
  4240.             end
  4241.            
  4242.             -- IsCombatLog
  4243.             if CHAT_SYSTEM.primaryContainer:IsCombatLog(numTab) then
  4244.                 db.chatConfSync[pChatData.localPlayer].tabs[numTab].isCombatLog = true
  4245.                 -- AreTimestampsEnabled
  4246.                 if CHAT_SYSTEM.primaryContainer:AreTimestampsEnabled(numTab) then
  4247.                     db.chatConfSync[pChatData.localPlayer].tabs[numTab].areTimestampsEnabled = true
  4248.                 else
  4249.                     db.chatConfSync[pChatData.localPlayer].tabs[numTab].areTimestampsEnabled = false
  4250.                 end
  4251.             else
  4252.                 db.chatConfSync[pChatData.localPlayer].tabs[numTab].isCombatLog = false
  4253.                 db.chatConfSync[pChatData.localPlayer].tabs[numTab].areTimestampsEnabled = false
  4254.             end
  4255.            
  4256.             -- GetTabName
  4257.             db.chatConfSync[pChatData.localPlayer].tabs[numTab].name = CHAT_SYSTEM.primaryContainer:GetTabName(numTab)
  4258.            
  4259.             -- Enabled categories
  4260.             db.chatConfSync[pChatData.localPlayer].tabs[numTab].enabledCategories = {}
  4261.            
  4262.             for _, category in ipairs (pChatData.chatCategories) do
  4263.                 local isEnabled = IsChatContainerTabCategoryEnabled(1, numTab, category)
  4264.                 db.chatConfSync[pChatData.localPlayer].tabs[numTab].enabledCategories[category] = isEnabled
  4265.             end
  4266.            
  4267.         end
  4268.        
  4269.         db.chatConfSync.lastChar = db.chatConfSync[pChatData.localPlayer]
  4270.        
  4271.     end
  4272.    
  4273. end
  4274.  
  4275. -- Save Chat Tabs config when user changes it
  4276. local function SaveTabsCategories()
  4277.    
  4278.     for numTab in ipairs (CHAT_SYSTEM.primaryContainer.windows) do
  4279.        
  4280.         for _, category in ipairs (pChatData.guildCategories) do
  4281.             local isEnabled = IsChatContainerTabCategoryEnabled(1, numTab, category)
  4282.             if db.chatConfSync[pChatData.localPlayer].tabs[numTab] then
  4283.                 db.chatConfSync[pChatData.localPlayer].tabs[numTab].enabledCategories[category] = isEnabled
  4284.             else
  4285.                 SaveChatConfig()
  4286.             end
  4287.         end
  4288.        
  4289.     end
  4290.    
  4291. end
  4292.  
  4293. -- Function for Minimizing chat at launch
  4294. local function MinimizeChatAtLaunch()
  4295.     if db.chatMinimizedAtLaunch then
  4296.         CHAT_SYSTEM:Minimize()
  4297.     end
  4298. end
  4299.  
  4300. local function MinimizeChatInMenus()
  4301.  
  4302.     -- RegisterCallback for Maximize/Minimize chat when entering/leaving scenes
  4303.     -- "hud" is base scene (with "hudui")
  4304.     local hudScene = SCENE_MANAGER:GetScene("hud")
  4305.     hudScene:RegisterCallback("StateChange", function(oldState, newState)
  4306.  
  4307.         if db.chatMinimizedInMenus then
  4308.             if newState == SCENE_HIDDEN and SCENE_MANAGER:GetNextScene():GetName() ~= "hudui" then
  4309.                 CHAT_SYSTEM:Minimize()
  4310.             end
  4311.         end
  4312.        
  4313.         if db.chatMaximizedAfterMenus then
  4314.             if newState == SCENE_SHOWING then
  4315.                 CHAT_SYSTEM:Maximize()
  4316.             end
  4317.         end
  4318.        
  4319.     end)
  4320.    
  4321. end
  4322.  
  4323. -- Set the chat config from pChat settings
  4324. local function SyncChatConfig(shouldSync, whichChar)
  4325.  
  4326.     if shouldSync then
  4327.         if db.chatConfSync then
  4328.             if db.chatConfSync[whichChar] then
  4329.                
  4330.                 -- Position and width/height
  4331.                 CHAT_SYSTEM.control:SetAnchor(db.chatConfSync[whichChar].point, GuiRoot, db.chatConfSync[whichChar].relPoint, db.chatConfSync[whichChar].x, db.chatConfSync[whichChar].y)
  4332.                 -- Height / Width
  4333.                 CHAT_SYSTEM.control:SetDimensions(db.chatConfSync[whichChar].width, db.chatConfSync[whichChar].height)
  4334.                
  4335.                 -- Save settings immediatly (to check, maybe call function which do this)
  4336.                 CHAT_SYSTEM.primaryContainer.settings.height = db.chatConfSync[whichChar].height
  4337.                 CHAT_SYSTEM.primaryContainer.settings.point = db.chatConfSync[whichChar].point
  4338.                 CHAT_SYSTEM.primaryContainer.settings.relPoint = db.chatConfSync[whichChar].relPoint
  4339.                 CHAT_SYSTEM.primaryContainer.settings.width = db.chatConfSync[whichChar].width
  4340.                 CHAT_SYSTEM.primaryContainer.settings.x = db.chatConfSync[whichChar].x
  4341.                 CHAT_SYSTEM.primaryContainer.settings.y = db.chatConfSync[whichChar].y
  4342.                
  4343.                 --[[
  4344.                 -- Don't overflow screen, remove 15px.
  4345.                 if db.chatConfSync[whichChar].height >= (CHAT_SYSTEM.maxContainerHeight - 15 ) then
  4346.                     CHAT_SYSTEM.control:SetHeight((CHAT_SYSTEM.maxContainerHeight - 15 ))
  4347.                     d("Overflow height " .. db.chatConfSync[whichChar].height .. " -+- " .. (CHAT_SYSTEM.maxContainerHeight - 15))
  4348.                     d(CHAT_SYSTEM.control:GetHeight())
  4349.                 else
  4350.                     -- Don't set good values ?! SetHeight(674) = GetHeight(524) ? same with Width and resizing is buggy
  4351.                     --CHAT_SYSTEM.control:SetHeight(db.chatConfSync[whichChar].height)
  4352.                     CHAT_SYSTEM.control:SetDimensions(settings.width, settings.height)
  4353.                     d("height " .. db.chatConfSync[whichChar].height .. " -+- " .. CHAT_SYSTEM.control:GetHeight())
  4354.                 end
  4355.                
  4356.                 -- Same
  4357.                 if db.chatConfSync[whichChar].width >= (CHAT_SYSTEM.maxContainerWidth - 15 ) then
  4358.                     CHAT_SYSTEM.control:SetWidth((CHAT_SYSTEM.maxContainerWidth - 15 ))
  4359.                     d("Overflow width " .. db.chatConfSync[whichChar].width .. " -+- " .. (CHAT_SYSTEM.maxContainerWidth - 15))
  4360.                     d(CHAT_SYSTEM.control:GetWidth())
  4361.                 else
  4362.                     CHAT_SYSTEM.control:SetHeight(db.chatConfSync[whichChar].width)
  4363.                     d("width " .. db.chatConfSync[whichChar].width .. " -+- " .. CHAT_SYSTEM.control:GetWidth())
  4364.                 end
  4365.                 ]]--
  4366.                
  4367.                 -- Colors
  4368.                 for _, category in ipairs (pChatData.chatCategories) do
  4369.                     if not db.chatConfSync[whichChar].colors[category] then
  4370.                         local r, g, b = GetChatCategoryColor(category)
  4371.                         db.chatConfSync[whichChar].colors[category] = { red = r, green = g, blue = b }
  4372.                     end
  4373.                     SetChatCategoryColor(category, db.chatConfSync[whichChar].colors[category].red, db.chatConfSync[whichChar].colors[category].green, db.chatConfSync[whichChar].colors[category].blue)
  4374.                 end
  4375.                
  4376.                 -- Font Size
  4377.                 -- Not in Realtime SetChatFontSize(db.chatConfSync[whichChar].fontSize), need to add CHAT_SYSTEM:SetFontSize for Realtimed
  4378.                
  4379.                 -- ?!? Need to go by a local?..
  4380.                 local fontSize = db.chatConfSync[whichChar].fontSize
  4381.                 CHAT_SYSTEM:SetFontSize(fontSize)
  4382.                 SetChatFontSize(db.chatConfSync[whichChar].fontSize)
  4383.                 local chatSyncNumTab = 1
  4384.                
  4385.                 for numTab in ipairs(db.chatConfSync[whichChar].tabs) do
  4386.                    
  4387.                     --Create a Tab if nessesary
  4388.                     if (GetNumChatContainerTabs(1) < numTab) then
  4389.                         -- AddChatContainerTab(1, , db.chatConfSync[whichChar].tabs[numTab].isCombatLog) No ! Require a ReloadUI
  4390.                         CHAT_SYSTEM.primaryContainer:AddWindow(db.chatConfSync[whichChar].tabs[numTab].name)
  4391.                     end
  4392.                    
  4393.                     if db.chatConfSync[whichChar].tabs[numTab] and db.chatConfSync[whichChar].tabs[numTab].notBefore then
  4394.                    
  4395.                         if not pChatData.tabNotBefore then
  4396.                             pChatData.tabNotBefore = {} -- Used for tab restoration, init here.
  4397.                         end
  4398.                        
  4399.                         pChatData.tabNotBefore[numTab] = db.chatConfSync[whichChar].tabs[numTab].notBefore
  4400.                        
  4401.                     end
  4402.                    
  4403.                     -- Set Tab options
  4404.                     -- Not in realtime : SetChatContainerTabInfo(1, numTab, db.chatConfSync[whichChar].tabs[numTab].name, db.chatConfSync[whichChar].tabs[numTab].isLocked, db.chatConfSync[whichChar].tabs[numTab].isInteractable, db.chatConfSync[whichChar].tabs[numTab].areTimestampsEnabled)
  4405.                    
  4406.                     CHAT_SYSTEM.primaryContainer:SetTabName(numTab, db.chatConfSync[whichChar].tabs[numTab].name)
  4407.                     CHAT_SYSTEM.primaryContainer:SetLocked(numTab, db.chatConfSync[whichChar].tabs[numTab].isLocked)
  4408.                     CHAT_SYSTEM.primaryContainer:SetInteractivity(numTab, db.chatConfSync[whichChar].tabs[numTab].isInteractable)
  4409.                     CHAT_SYSTEM.primaryContainer:SetTimestampsEnabled(numTab, db.chatConfSync[whichChar].tabs[numTab].areTimestampsEnabled)
  4410.                    
  4411.                     -- Set Channel per tab configuration
  4412.                     for _, category in ipairs (pChatData.chatCategories) do
  4413.                         if db.chatConfSync[whichChar].tabs[numTab].enabledCategories[category] == nil then -- Cal be false
  4414.                             db.chatConfSync[whichChar].tabs[numTab].enabledCategories[category] = IsChatContainerTabCategoryEnabled(1, numTab, category)
  4415.                         end
  4416.                         SetChatContainerTabCategoryEnabled(1, numTab, category, db.chatConfSync[whichChar].tabs[numTab].enabledCategories[category])
  4417.                     end
  4418.                    
  4419.                     chatSyncNumTab = numTab
  4420.                    
  4421.                 end
  4422.                
  4423.                 -- If they're was too many tabs before, drop them
  4424.                 local removeTabs = true
  4425.                 while removeTabs do
  4426.                     -- Too many tabs, deleting one
  4427.                     if GetNumChatContainerTabs(1) > chatSyncNumTab then
  4428.                         -- Not in realtime : RemoveChatContainerTab(1, chatSyncNumTab + 1)
  4429.                         CHAT_SYSTEM.primaryContainer:RemoveWindow(chatSyncNumTab + 1, nil)
  4430.                     else
  4431.                         removeTabs = false
  4432.                     end
  4433.                 end
  4434.            
  4435.             end
  4436.         end
  4437.     end
  4438.  
  4439. end
  4440.  
  4441. -- When creating a char, try to import settings
  4442. local function AutoSyncSettingsForNewPlayer()
  4443.    
  4444.     -- New chars get automaticaly last char config
  4445.     if GetIsNewCharacter() then
  4446.         SyncChatConfig(true, "lastChar")
  4447.     end
  4448.    
  4449. end
  4450.  
  4451. -- Set channel to the default one
  4452. local function SetToDefaultChannel()
  4453.     if db.defaultchannel ~= PCHAT_CHANNEL_NONE then
  4454.         CHAT_SYSTEM:SetChannel(db.defaultchannel)
  4455.     end
  4456. end
  4457.  
  4458. local function SaveGuildIndexes()
  4459.  
  4460.     pChatData.guildIndexes = {}
  4461.    
  4462.     for guild = 1, GetNumGuilds() do
  4463.        
  4464.         -- Guildname
  4465.         local guildId = GetGuildId(guild)
  4466.         local guildName = GetGuildName(guildId)
  4467.        
  4468.         -- Occurs sometimes
  4469.         if(not guildName or (guildName):len() < 1) then
  4470.             guildName = "Guild " .. guildId
  4471.         end
  4472.        
  4473.         pChatData.guildIndexes[guildName] = guild
  4474.    
  4475.     end
  4476.    
  4477. end
  4478.  
  4479. -- Executed when EVENT_IGNORE_ADDED triggers
  4480. local function OnIgnoreAdded(displayName)
  4481.    
  4482.     -- DisplayName is linkable
  4483.     local displayNameLink = ZO_LinkHandler_CreateDisplayNameLink(displayName)
  4484.     local statusMessage = zo_strformat(SI_FRIENDS_LIST_IGNORE_ADDED, displayNameLink)
  4485.    
  4486.     -- Only if statusMessage is set
  4487.     if statusMessage then
  4488.         return FormatSysMessage(statusMessage)
  4489.     end
  4490.    
  4491. end
  4492.  
  4493. -- Executed when EVENT_IGNORE_REMOVED triggers
  4494. local function OnIgnoreRemoved(displayName)
  4495.    
  4496.     -- DisplayName is linkable
  4497.     local displayNameLink = ZO_LinkHandler_CreateDisplayNameLink(displayName)
  4498.     local statusMessage = zo_strformat(SI_FRIENDS_LIST_IGNORE_REMOVED, displayNameLink)
  4499.    
  4500.     -- Only if statusMessage is set
  4501.     if statusMessage then
  4502.         return FormatSysMessage(statusMessage)
  4503.     end
  4504.    
  4505. end
  4506.  
  4507. -- triggers when EVENT_FRIEND_PLAYER_STATUS_CHANGED
  4508. local function OnFriendPlayerStatusChanged(displayName, characterName, oldStatus, newStatus)
  4509.  
  4510.     local statusMessage
  4511.    
  4512.     -- DisplayName is linkable
  4513.     local displayNameLink = ZO_LinkHandler_CreateDisplayNameLink(displayName)
  4514.     -- CharacterName is linkable
  4515.     local characterNameLink = ZO_LinkHandler_CreateCharacterLink(characterName)
  4516.    
  4517.     local wasOnline = oldStatus ~= PLAYER_STATUS_OFFLINE
  4518.     local isOnline = newStatus ~= PLAYER_STATUS_OFFLINE
  4519.    
  4520.     -- Not connected before and Connected now (no messages for Away/Busy)
  4521.     if not wasOnline and isOnline then
  4522.         -- Return
  4523.         statusMessage = zo_strformat(SI_FRIENDS_LIST_FRIEND_CHARACTER_LOGGED_ON, displayNameLink, characterNameLink)
  4524.     -- Connected before and Offline now
  4525.     elseif wasOnline and not isOnline then
  4526.         statusMessage = zo_strformat(SI_FRIENDS_LIST_FRIEND_CHARACTER_LOGGED_OFF, displayNameLink, characterNameLink)
  4527.     end
  4528.    
  4529.     -- Only if statusMessage is set
  4530.     if statusMessage then
  4531.         return FormatSysMessage(statusMessage)
  4532.     end
  4533.  
  4534. end
  4535.  
  4536. -- Executed when EVENT_GROUP_TYPE_CHANGED triggers
  4537. local function OnGroupTypeChanged(largeGroup)
  4538.    
  4539.     if largeGroup then
  4540.         return FormatSysMessage(GetString(SI_CHAT_ANNOUNCEMENT_IN_LARGE_GROUP))
  4541.     else
  4542.         return FormatSysMessage(GetString(SI_CHAT_ANNOUNCEMENT_IN_SMALL_GROUP))
  4543.     end
  4544.  
  4545. end
  4546.  
  4547. local function OnGroupMemberLeft(_, reason, isLocalPlayer, _, _, actionRequiredVote)
  4548.     if reason == GROUP_LEAVE_REASON_KICKED and isLocalPlayer and actionRequiredVote then
  4549.         return GetString(SI_GROUP_ELECTION_KICK_PLAYER_PASSED)
  4550.     end
  4551. end
  4552.  
  4553. local function UpdateCharCorrespondanceTableSwitchs()
  4554.  
  4555.     -- Each guild
  4556.     for i = 1, GetNumGuilds() do
  4557.            
  4558.         -- Get saved string
  4559.         local switch = db.switchFor[GetGuildName(GetGuildId(i))]
  4560.        
  4561.         if switch and switch ~= "" then
  4562.             switch = GetString(SI_CHANNEL_SWITCH_GUILD_1 - 1 + i) .. " " .. switch
  4563.         else
  4564.             switch = GetString(SI_CHANNEL_SWITCH_GUILD_1 - 1 + i)
  4565.         end
  4566.        
  4567.         ChanInfoArray[CHAT_CHANNEL_GUILD_1 - 1 + i].switches = switch
  4568.        
  4569.         -- Get saved string
  4570.         local officerSwitch = db.officerSwitchFor[GetGuildName(GetGuildId(i))]
  4571.        
  4572.         -- No SavedVar
  4573.         if officerSwitch and officerSwitch ~= "" then
  4574.             officerSwitch = GetString(SI_CHANNEL_SWITCH_OFFICER_1 - 1 + i)  .. " " .. officerSwitch
  4575.         else
  4576.             officerSwitch = GetString(SI_CHANNEL_SWITCH_OFFICER_1 - 1 + i)
  4577.         end
  4578.        
  4579.         ChanInfoArray[CHAT_CHANNEL_OFFICER_1 - 1 + i].switches = officerSwitch
  4580.        
  4581.     end
  4582.  
  4583. end
  4584.  
  4585. -- *********************************************************************************
  4586. -- * Section: LAM Outside Functions : used in LAM but function resides outside LAM
  4587. -- *********************************************************************************
  4588. -- Character Sync
  4589. local function SyncCharacterSelectChoices()
  4590.     -- Sync Character Select
  4591.     pChatData.chatConfSyncChoices = {}
  4592.     if db.chatConfSync then
  4593.         for names, tagada in pairs (db.chatConfSync) do
  4594.             if names ~= "lastChar" then
  4595.                 table.insert(pChatData.chatConfSyncChoices, names)
  4596.             end
  4597.         end
  4598.     else
  4599.         table.insert(pChatData.chatConfSyncChoices, pChatData.localPlayer)
  4600.     end
  4601. end
  4602.  
  4603.  
  4604. -- *********************************************************************************
  4605. -- * Section: Settings Start (LAM)
  4606. -- *********************************************************************************
  4607. -- Build LAM Option Table, used when AddonLoads or when a player join/leave a guild
  4608. local function BuildLAMPanel()
  4609.  
  4610.     -- Used to reset colors to default value, lam need a formatted array
  4611.     -- LAM Message Settings
  4612.    
  4613.     local fontsDefined = LMP:List('font')
  4614.    
  4615.     local function ConvertHexToRGBAPacked(colourString)
  4616.         local r, g, b, a = ConvertHexToRGBA(colourString)
  4617.         return {r = r, g = g, b = b, a = a}
  4618.     end
  4619.         -- Sync Character Select
  4620.     pChatData.chatConfSyncChoices = {}
  4621.     if db.chatConfSync then
  4622.         for names, tagada in pairs (db.chatConfSync) do
  4623.             if names ~= "lastChar" then
  4624.                 table.insert(pChatData.chatConfSyncChoices, names)
  4625.             end
  4626.         end
  4627.     else
  4628.         table.insert(pChatData.chatConfSyncChoices, pChatData.localPlayer)
  4629.     end
  4630.    
  4631.     -- CHAT_SYSTEM.primaryContainer.windows doesn't exists yet at OnAddonLoaded. So using the pChat reference.
  4632.     local arrayTab = {}
  4633.     if db.chatConfSync and db.chatConfSync[pChatData.localPlayer] and db.chatConfSync[pChatData.localPlayer].tabs then
  4634.         for numTab, data in pairs (db.chatConfSync[pChatData.localPlayer].tabs) do
  4635.             table.insert(arrayTab, numTab)
  4636.         end
  4637.     else
  4638.         table.insert(arrayTab, 1)
  4639.     end
  4640.    
  4641.     getTabNames()
  4642.  
  4643.     local optionsData = {}
  4644.  
  4645.     -- Messages Settings
  4646.     optionsData[#optionsData + 1] = {
  4647.         type = "submenu",
  4648.         name = GetString(PCHAT_OPTIONSH),
  4649.         controls = {
  4650.             {-- LAM Option Show Guild Numbers
  4651.                 type = "checkbox",
  4652.                 name = GetString(PCHAT_GUILDNUMBERS),
  4653.                 tooltip = GetString(PCHAT_GUILDNUMBERSTT),
  4654.                 getFunc = function() return db.showGuildNumbers end,
  4655.                 setFunc = function(newValue)
  4656.                     db.showGuildNumbers = newValue
  4657.                 end,
  4658.                 width = "full",
  4659.                 default = defaults.showGuildNumbers,
  4660.             },
  4661.             {-- LAM Option Use Same Color for all Guilds
  4662.                 type = "checkbox",
  4663.                 name = GetString(PCHAT_ALLGUILDSSAMECOLOUR),
  4664.                 tooltip = GetString(PCHAT_ALLGUILDSSAMECOLOURTT),
  4665.                 getFunc = function() return db.allGuildsSameColour end,
  4666.                 setFunc = function(newValue) db.allGuildsSameColour = newValue end,
  4667.                 width = "full",
  4668.                 default = defaults.allGuildsSameColour,
  4669.             },
  4670.             {-- LAM Option Use same color for all zone chats
  4671.                 type = "checkbox",
  4672.                 name = GetString(PCHAT_ALLZONESSAMECOLOUR),
  4673.                 tooltip = GetString(PCHAT_ALLZONESSAMECOLOURTT),
  4674.                 getFunc = function() return db.allZonesSameColour end,
  4675.                 setFunc = function(newValue) db.allZonesSameColour = newValue end,
  4676.                 width = "full",
  4677.                 default = defaults.allZonesSameColour,
  4678.             },
  4679.             {-- LAM Option Use same color for all NPC
  4680.                 type = "checkbox",
  4681.                 name = GetString(PCHAT_ALLNPCSAMECOLOUR),
  4682.                 tooltip = GetString(PCHAT_ALLNPCSAMECOLOURTT),
  4683.                 getFunc = function() return db.allNPCSameColour end,
  4684.                 setFunc = function(newValue) db.allNPCSameColour = newValue end,
  4685.                 width = "full",
  4686.                 default = defaults.allNPCSameColour,
  4687.             },
  4688.             {-- LAM Option Remove Zone Tags
  4689.                 type = "checkbox",
  4690.                 name = GetString(PCHAT_DELZONETAGS),
  4691.                 tooltip = GetString(PCHAT_DELZONETAGSTT),
  4692.                 getFunc = function() return db.delzonetags end,
  4693.                 setFunc = function(newValue) db.delzonetags = newValue end,
  4694.                 width = "full",
  4695.                 default = defaults.delzonetags,
  4696.             },
  4697.             {-- LAM Option Newline between name and message
  4698.                 type = "checkbox",
  4699.                 name = GetString(PCHAT_CARRIAGERETURN),
  4700.                 tooltip = GetString(PCHAT_CARRIAGERETURNTT),
  4701.                 getFunc = function() return db.carriageReturn end,
  4702.                 setFunc = function(newValue) db.carriageReturn = newValue end,
  4703.                 width = "full",
  4704.                 default = defaults.carriageReturn,
  4705.             },
  4706.             {-- LAM Option Use ESO Colors
  4707.                 type = "checkbox",
  4708.                 name = GetString(PCHAT_USEESOCOLORS),
  4709.                 tooltip = GetString(PCHAT_USEESOCOLORSTT),
  4710.                 getFunc = function() return db.useESOcolors end,
  4711.                 setFunc = function(newValue) db.useESOcolors = newValue end,
  4712.                 width = "full",
  4713.                 default = defaults.useESOcolors,
  4714.             },
  4715.             {-- LAM Option Difference Between ESO Colors
  4716.                 type = "slider",
  4717.                 name = GetString(PCHAT_DIFFFORESOCOLORS),
  4718.                 tooltip = GetString(PCHAT_DIFFFORESOCOLORSTT),
  4719.                 min = 0,
  4720.                 max = 100,
  4721.                 step = 1,
  4722.                 getFunc = function() return db.diffforESOcolors end,
  4723.                 setFunc = function(newValue) db.diffforESOcolors = newValue end,
  4724.                 width = "full",
  4725.                 default = defaults.diffforESOcolors,
  4726.                 disabled = function() return not db.useESOcolors end,
  4727.             },
  4728.             {-- LAM Option Prevent Chat Fading
  4729.                 type = "checkbox",
  4730.                 name = GetString(PCHAT_PREVENTCHATTEXTFADING),
  4731.                 tooltip = GetString(PCHAT_PREVENTCHATTEXTFADINGTT),
  4732.                 getFunc = function() return db.alwaysShowChat end,
  4733.                 setFunc = function(newValue) db.alwaysShowChat = newValue end,
  4734.                 width = "full",
  4735.                 default = defaults.alwaysShowChat,
  4736.             },
  4737.             {-- Augment lines of chat
  4738.                 type = "checkbox",
  4739.                 name = GetString(PCHAT_AUGMENTHISTORYBUFFER),
  4740.                 tooltip = GetString(PCHAT_AUGMENTHISTORYBUFFERTT),
  4741.                 getFunc = function() return db.augmentHistoryBuffer end,
  4742.                 setFunc = function(newValue) db.augmentHistoryBuffer = newValue end,
  4743.                 width = "full",
  4744.                 default = defaults.augmentHistoryBuffer,
  4745.             },
  4746.             {-- LAM Option Use one color for lines
  4747.                 type = "checkbox",
  4748.                 name = GetString(PCHAT_USEONECOLORFORLINES),
  4749.                 tooltip = GetString(PCHAT_USEONECOLORFORLINESTT),
  4750.                 getFunc = function() return db.oneColour end,
  4751.                 setFunc = function(newValue) db.oneColour = newValue end,
  4752.                 width = "full",
  4753.                 default = defaults.oneColour,
  4754.             },
  4755.             {-- LAM Option Guild Tags next to entry box
  4756.                 type = "checkbox",
  4757.                 name = GetString(PCHAT_GUILDTAGSNEXTTOENTRYBOX),
  4758.                 tooltip = GetString(PCHAT_GUILDTAGSNEXTTOENTRYBOXTT),
  4759.                 width = "full",
  4760.                 default = defaults.showTagInEntry,
  4761.                 getFunc = function() return db.showTagInEntry end,
  4762.                 setFunc = function(newValue)
  4763.                             db.showTagInEntry = newValue
  4764.                             UpdateCharCorrespondanceTableChannelNames()
  4765.                         end
  4766.             },
  4767.             {-- LAM Option Names Format
  4768.                 type = "dropdown",
  4769.                 name = GetString(PCHAT_GEOCHANNELSFORMAT),
  4770.                 tooltip = GetString(PCHAT_GEOCHANNELSFORMATTT),
  4771.                 choices = {GetString("PCHAT_GROUPNAMESCHOICE", 1), GetString("PCHAT_GROUPNAMESCHOICE", 2), GetString("PCHAT_GROUPNAMESCHOICE", 3)}, -- Same as group.
  4772.                 width = "full",
  4773.                 default = defaults.geoChannelsFormat,
  4774.                 getFunc = function() return GetString("PCHAT_GROUPNAMESCHOICE", db.geoChannelsFormat) end,
  4775.                 setFunc = function(choice)
  4776.                     if choice == GetString("PCHAT_GROUPNAMESCHOICE", 1) then
  4777.                         db.geoChannelsFormat = 1
  4778.                     elseif choice == GetString("PCHAT_GROUPNAMESCHOICE", 2) then
  4779.                         db.geoChannelsFormat = 2
  4780.                     elseif choice == GetString("PCHAT_GROUPNAMESCHOICE", 3) then
  4781.                         db.geoChannelsFormat = 3
  4782.                     else
  4783.                         -- When clicking on LAM default button
  4784.                         db.geoChannelsFormat = defaults.geoChannelsFormat
  4785.                     end
  4786.                    
  4787.                 end,
  4788.             },
  4789.             {-- Disable Brackets
  4790.                 type = "checkbox",
  4791.                 name = GetString(PCHAT_DISABLEBRACKETS),
  4792.                 tooltip = GetString(PCHAT_DISABLEBRACKETSTT),
  4793.                 getFunc = function() return db.disableBrackets end,
  4794.                 setFunc = function(newValue) db.disableBrackets = newValue end,
  4795.                 width = "full",
  4796.                 default = defaults.disableBrackets,
  4797.             },
  4798.             {--Traget Histoyr
  4799.                 type = "checkbox",
  4800.                 name = GetString(PCHAT_ADDCHANNELANDTARGETTOHISTORY),
  4801.                 tooltip = GetString(PCHAT_ADDCHANNELANDTARGETTOHISTORYTT),
  4802.                 getFunc = function() return db.addChannelAndTargetToHistory end,
  4803.                 setFunc = function(newValue) db.addChannelAndTargetToHistory = newValue end,
  4804.                 width = "full",
  4805.                 default = defaults.addChannelAndTargetToHistory,
  4806.             },
  4807.             {-- URL is clickable       
  4808.                 type = "checkbox",
  4809.                 name = GetString(PCHAT_URLHANDLING),
  4810.                 tooltip = GetString(PCHAT_URLHANDLINGTT),
  4811.                 getFunc = function() return db.urlHandling end,
  4812.                 setFunc = function(newValue) db.urlHandling = newValue end,
  4813.                 width = "full",
  4814.                 default = defaults.urlHandling,
  4815.             },
  4816.             {-- Copy Chat
  4817.                 type = "checkbox",
  4818.                 name = GetString(PCHAT_ENABLECOPY),
  4819.                 tooltip = GetString(PCHAT_ENABLECOPYTT),
  4820.                 getFunc = function() return db.enablecopy end,
  4821.                 setFunc = function(newValue) db.enablecopy = newValue end,
  4822.                 width = "full",
  4823.                 default = defaults.enablecopy,
  4824.             },--
  4825.         },
  4826.     } -- Chat Tabs
  4827.     optionsData[#optionsData + 1] = {
  4828.         type = "submenu",
  4829.         name = GetString(PCHAT_CHATTABH),
  4830.         controls = {
  4831.             {-- Enable chat channel memory
  4832.                 type = "checkbox",
  4833.                 name = GetString(PCHAT_enableChatTabChannel),
  4834.                 tooltip = GetString(PCHAT_enableChatTabChannelT),
  4835.                 getFunc = function() return db.enableChatTabChannel end,
  4836.                 setFunc = function(newValue) db.enableChatTabChannel = newValue end,
  4837.                 width = "full",
  4838.                 default = defaults.enableChatTabChannel,
  4839.             },
  4840.             {-- TODO : optimize
  4841.                 type = "dropdown",
  4842.                 name = GetString(PCHAT_DEFAULTCHANNEL),
  4843.                 tooltip = GetString(PCHAT_DEFAULTCHANNELTT),
  4844.                 --choices = chatTabNames,
  4845.                 choices = {
  4846.                     GetString("PCHAT_DEFAULTCHANNELCHOICE", PCHAT_CHANNEL_NONE),
  4847.                     GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_ZONE),
  4848.                     GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_SAY),
  4849.                     GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_GUILD_1),
  4850.                     GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_GUILD_2),
  4851.                     GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_GUILD_3),
  4852.                     GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_GUILD_4),
  4853.                     GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_GUILD_5),
  4854.                     GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_OFFICER_1),
  4855.                     GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_OFFICER_2),
  4856.                     GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_OFFICER_3),
  4857.                     GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_OFFICER_4),
  4858.                     GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_OFFICER_5)
  4859.                 },
  4860.                 width = "full",
  4861.                 default = defaults.defaultchannel,
  4862.                 getFunc = function() return GetString("PCHAT_DEFAULTCHANNELCHOICE", db.defaultchannel) end,
  4863.                 setFunc = function(choice)
  4864.                     if choice == GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_ZONE) then
  4865.                         db.defaultchannel = CHAT_CHANNEL_ZONE
  4866.                     elseif choice == GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_SAY) then
  4867.                         db.defaultchannel = CHAT_CHANNEL_SAY
  4868.                     elseif choice == GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_GUILD_1) then
  4869.                         db.defaultchannel = CHAT_CHANNEL_GUILD_1
  4870.                     elseif choice == GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_GUILD_2) then
  4871.                         db.defaultchannel = CHAT_CHANNEL_GUILD_2
  4872.                     elseif choice == GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_GUILD_3) then
  4873.                         db.defaultchannel = CHAT_CHANNEL_GUILD_3
  4874.                     elseif choice == GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_GUILD_4) then
  4875.                         db.defaultchannel = CHAT_CHANNEL_GUILD_4
  4876.                     elseif choice == GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_GUILD_5) then
  4877.                         db.defaultchannel = CHAT_CHANNEL_GUILD_5
  4878.                     elseif choice == GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_OFFICER_1) then
  4879.                         db.defaultchannel = CHAT_CHANNEL_OFFICER_1
  4880.                     elseif choice == GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_OFFICER_2) then
  4881.                         db.defaultchannel = CHAT_CHANNEL_OFFICER_2
  4882.                     elseif choice == GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_OFFICER_3) then
  4883.                         db.defaultchannel = CHAT_CHANNEL_OFFICER_3
  4884.                     elseif choice == GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_OFFICER_4) then
  4885.                         db.defaultchannel = CHAT_CHANNEL_OFFICER_4
  4886.                     elseif choice == GetString("PCHAT_DEFAULTCHANNELCHOICE", CHAT_CHANNEL_OFFICER_5) then
  4887.                         db.defaultchannel = CHAT_CHANNEL_OFFICER_5
  4888.                     elseif choice == GetString("PCHAT_DEFAULTCHANNELCHOICE", PCHAT_CHANNEL_NONE) then
  4889.                         db.defaultchannel = PCHAT_CHANNEL_NONE
  4890.                     else
  4891.                         -- When user click on LAM reinit button
  4892.                         db.defaultchannel = defaults.defaultchannel
  4893.                     end
  4894.                    
  4895.                 end,
  4896.             },
  4897.             {-- CHAT_SYSTEM.primaryContainer.windows doesn't exists yet at OnAddonLoaded. So using the pChat reference.
  4898.                 type = "dropdown",
  4899.                 name = GetString(PCHAT_DEFAULTTAB),
  4900.                 tooltip = GetString(PCHAT_DEFAULTTABTT),
  4901.                 choices = tabNames,
  4902.                 width = "full",
  4903.                 getFunc = function() return db.defaultTabName end,
  4904.                 setFunc =   function(choice)
  4905.                                 db.defaultTabName = choice
  4906.                                 --d(choice)
  4907.                                 --d(db.defaultTabName)
  4908.                                 db.defaultTab = getTabIdx(choice)
  4909.                                 --d(db.defaultTab)
  4910.                             end,
  4911.             },
  4912.             --[[{-- CHAT_SYSTEM.primaryContainer.windows doesn't exists yet at OnAddonLoaded. So using the pChat reference.
  4913.                 type = "dropdown",
  4914.                 name = GetString(PCHAT_DEFAULTTAB),
  4915.                 tooltip = GetString(PCHAT_DEFAULTTABTT),
  4916.                 choices = arrayTab,
  4917.                 width = "full",
  4918.                 default = defaults.defaultTab,
  4919.                 getFunc = function() return db.defaultTab end,
  4920.                 setFunc = function(choice) db.defaultTab = choice end,
  4921.             },]]--
  4922.             --[[{-- Enable whisper redirect
  4923.                 type = "checkbox",
  4924.                 name = GetString(PCHAT_enableWhisperTab),
  4925.                 tooltip = GetString(PCHAT_enableWhisperTabT),
  4926.                 getFunc = function() return db.enableWhisperTab end,
  4927.                 setFunc = function(newValue) db.enableWhisperTab = newValue end,
  4928.                 width = "full",
  4929.                 default = defaults.enableWhisperTab,
  4930.             },]]--
  4931.             -- !!!!!need code for specific tab here
  4932.         },
  4933.     } -- Group Submenu
  4934.     optionsData[#optionsData + 1] = {
  4935.         type = "submenu",
  4936.         name = GetString(PCHAT_GROUPH),
  4937.         controls = {
  4938.             {-- Enable Party Switch
  4939.                 type = "checkbox",
  4940.                 name = GetString(PCHAT_ENABLEPARTYSWITCH),
  4941.                 tooltip = GetString(PCHAT_ENABLEPARTYSWITCHTT),
  4942.                 getFunc = function() return db.enablepartyswitch end,
  4943.                 setFunc = function(newValue) db.enablepartyswitch = newValue end,
  4944.                 width = "full",
  4945.                 default = defaults.enablepartyswitch,
  4946.             },
  4947.             {-- Group Leader
  4948.                 type = "checkbox",
  4949.                 name = GetString(PCHAT_GROUPLEADER),
  4950.                 tooltip = GetString(PCHAT_GROUPLEADERTT),
  4951.                 getFunc = function() return db.groupLeader end,
  4952.                 setFunc = function(newValue) db.groupLeader = newValue end,
  4953.                 width = "full",
  4954.                 default = defaults.groupLeader,
  4955.             },
  4956.             {-- Group Leader Color
  4957.                 type = "colorpicker",
  4958.                 name = GetString(PCHAT_GROUPLEADERCOLOR),
  4959.                 tooltip = GetString(PCHAT_GROUPLEADERCOLORTT),
  4960.                 getFunc = function() return ConvertHexToRGBA(db.colours["groupleader"]) end,
  4961.                 setFunc = function(r, g, b) db.colours["groupleader"] = ConvertRGBToHex(r, g, b) end,
  4962.                 default = ConvertHexToRGBAPacked(defaults.colours["groupleader"]),
  4963.                 disabled = function() return not db.groupLeader end,
  4964.             },
  4965.             {-- Group Leader Coor 2
  4966.                 type = "colorpicker",
  4967.                 name = GetString(PCHAT_GROUPLEADERCOLOR1),
  4968.                 tooltip = GetString(PCHAT_GROUPLEADERCOLOR1TT),
  4969.                 getFunc = function() return ConvertHexToRGBA(db.colours["groupleader1"]) end,
  4970.                 setFunc = function(r, g, b) db.colours["groupleader1"] = ConvertRGBToHex(r, g, b) end,
  4971.                 default = ConvertHexToRGBAPacked(defaults.colours["groupleader1"]),
  4972.                 disabled = function()
  4973.                         if not db.groupLeader then
  4974.                             return true
  4975.                         elseif db.useESOcolors then
  4976.                             return true
  4977.                         else
  4978.                             return false
  4979.                         end
  4980.                     end,
  4981.             },
  4982.             {-- Group Names
  4983.                 type = "dropdown",
  4984.                 name = GetString(PCHAT_GROUPNAMES),
  4985.                 tooltip = GetString(PCHAT_GROUPNAMESTT),
  4986.                 choices = {GetString("PCHAT_GROUPNAMESCHOICE", 1), GetString("PCHAT_GROUPNAMESCHOICE", 2), GetString("PCHAT_GROUPNAMESCHOICE", 3)},
  4987.                 width = "full",
  4988.                 default = defaults.groupNames,
  4989.                 getFunc = function() return GetString("PCHAT_GROUPNAMESCHOICE", db.groupNames) end,
  4990.                 setFunc = function(choice)
  4991.                     if choice == GetString("PCHAT_GROUPNAMESCHOICE", 1) then
  4992.                         db.groupNames = 1
  4993.                     elseif choice == GetString("PCHAT_GROUPNAMESCHOICE", 2) then
  4994.                         db.groupNames = 2
  4995.                     elseif choice == GetString("PCHAT_GROUPNAMESCHOICE", 3) then
  4996.                         db.groupNames = 3
  4997.                     else
  4998.                         -- When clicking on LAM default button
  4999.                         db.groupNames = defaults.groupNames
  5000.                     end
  5001.                    
  5002.                 end,
  5003.             },
  5004.         },
  5005.     } -- Sync Settings Header
  5006.     optionsData[#optionsData + 1] = {
  5007.         type = "submenu",
  5008.         name = GetString(PCHAT_SYNCH),
  5009.         controls = {
  5010.             {-- Sync ON
  5011.                 type = "checkbox",
  5012.                 name = GetString(PCHAT_CHATSYNCCONFIG),
  5013.                 tooltip = GetString(PCHAT_CHATSYNCCONFIGTT),
  5014.                 getFunc = function() return db.chatSyncConfig end,
  5015.                 setFunc = function(newValue) db.chatSyncConfig = newValue end,
  5016.                 width = "full",
  5017.                 default = defaults.chatSyncConfig,
  5018.             },
  5019.             {-- Config Import From
  5020.                 type = "dropdown",
  5021.                 name = GetString(PCHAT_CHATSYNCCONFIGIMPORTFROM),
  5022.                 tooltip = GetString(PCHAT_CHATSYNCCONFIGIMPORTFROMTT),
  5023.                 choices = pChatData.chatConfSyncChoices,
  5024.                 width = "full",
  5025.                 getFunc = function() return pChatData.localPlayer end,
  5026.                 setFunc = function(choice)
  5027.                     SyncChatConfig(true, choice)
  5028.                 end,
  5029.             },
  5030.         },
  5031.     } -- Mouse
  5032.     optionsData[#optionsData + 1] = {
  5033.         type = "submenu",
  5034.         name = GetString(PCHAT_APPARENCEMH),
  5035.         controls = {
  5036.             {-- New Message Color
  5037.                 type = "colorpicker",
  5038.                 name = GetString(PCHAT_TABWARNING),
  5039.                 tooltip = GetString(PCHAT_TABWARNINGTT),
  5040.                 getFunc = function() return ConvertHexToRGBA(db.colours["tabwarning"]) end,
  5041.                 setFunc = function(r, g, b) db.colours["tabwarning"] = ConvertRGBToHex(r, g, b) end,
  5042.                 default = ConvertHexToRGBAPacked(defaults.colours["tabwarning"]),
  5043.             },
  5044.             {-- Chat Window Transparency
  5045.                 type = "slider",
  5046.                 name = GetString(PCHAT_WINDOWDARKNESS),
  5047.                 tooltip = GetString(PCHAT_WINDOWDARKNESSTT),
  5048.                 min = 0,
  5049.                 max = 11,
  5050.                 step = 1,
  5051.                 getFunc = function() return db.windowDarkness end,
  5052.                 setFunc = function(newValue)
  5053.                     db.windowDarkness = newValue
  5054.                     ChangeChatWindowDarkness(true)
  5055.                     CHAT_SYSTEM:Maximize()
  5056.                 end,
  5057.                 width = "full",
  5058.                 default = defaults.windowDarkness,
  5059.             },
  5060.             {-- Minimize at luanch
  5061.                 type = "checkbox",
  5062.                 name = GetString(PCHAT_CHATMINIMIZEDATLAUNCH),
  5063.                 tooltip = GetString(PCHAT_CHATMINIMIZEDATLAUNCHTT),
  5064.                 getFunc = function() return db.chatMinimizedAtLaunch end,
  5065.                 setFunc = function(newValue) db.chatMinimizedAtLaunch = newValue end,
  5066.                 width = "full",
  5067.                 default = defaults.chatMinimizedAtLaunch,
  5068.             },
  5069.             {-- Minimize Menues
  5070.                 type = "checkbox",
  5071.                 name = GetString(PCHAT_CHATMINIMIZEDINMENUS),
  5072.                 tooltip = GetString(PCHAT_CHATMINIMIZEDINMENUSTT),
  5073.                 getFunc = function() return db.chatMinimizedInMenus end,
  5074.                 setFunc = function(newValue) db.chatMinimizedInMenus = newValue end,
  5075.                 width = "full",
  5076.                 default = defaults.chatMinimizedInMenus,
  5077.             },
  5078.             { -- Mximize After Menus
  5079.                 type = "checkbox",
  5080.                 name = GetString(PCHAT_CHATMAXIMIZEDAFTERMENUS),
  5081.                 tooltip = GetString(PCHAT_CHATMAXIMIZEDAFTERMENUSTT),
  5082.                 getFunc = function() return db.chatMaximizedAfterMenus end,
  5083.                 setFunc = function(newValue) db.chatMaximizedAfterMenus = newValue end,
  5084.                 width = "full",
  5085.                 default = defaults.chatMaximizedAfterMenus,
  5086.             },
  5087.             { -- Fonts
  5088.                 type = "dropdown",
  5089.                 name = GetString(PCHAT_FONTCHANGE),
  5090.                 tooltip = GetString(PCHAT_FONTCHANGETT),
  5091.                 choices = fontsDefined,
  5092.                 width = "full",
  5093.                 getFunc = function() return db.fonts end,
  5094.                 setFunc = function(choice)
  5095.                     db.fonts = choice
  5096.                     ChangeChatFont(true)
  5097.                     ReloadUI()
  5098.                 end,
  5099.                 default = defaults.fontChange,
  5100.                 warning = "ReloadUI"
  5101.             },
  5102.         },
  5103.     } -- LAM Menu Whispers
  5104.     optionsData[#optionsData + 1] = {
  5105.         type = "submenu",
  5106.         name = GetString(PCHAT_IMH),
  5107.         controls = {
  5108.             {-- LAM Option Whispers: Sound
  5109.                 type = "dropdown",
  5110.                 name = GetString(PCHAT_SOUNDFORINCWHISPS),
  5111.                 tooltip = GetString(PCHAT_SOUNDFORINCWHISPSTT),
  5112.                 choices = {
  5113.                     GetString("PCHAT_SOUNDFORINCWHISPSCHOICE", 1),
  5114.                     GetString("PCHAT_SOUNDFORINCWHISPSCHOICE", 2),
  5115.                     GetString("PCHAT_SOUNDFORINCWHISPSCHOICE", 3),
  5116.                     GetString("PCHAT_SOUNDFORINCWHISPSCHOICE", 4),
  5117.                     },
  5118.                 width = "full",
  5119.                 default = defaults.soundforincwhisps, --> SOUNDS.NEW_NOTIFICATION
  5120.                 getFunc = function()
  5121.                     if db.soundforincwhisps == SOUNDS.NONE then
  5122.                         return GetString("PCHAT_SOUNDFORINCWHISPSCHOICE", 1)
  5123.                     elseif db.soundforincwhisps == SOUNDS.NEW_NOTIFICATION then
  5124.                         return GetString("PCHAT_SOUNDFORINCWHISPSCHOICE", 2)
  5125.                     elseif db.soundforincwhisps == SOUNDS.DEFAULT_CLICK then
  5126.                         return GetString("PCHAT_SOUNDFORINCWHISPSCHOICE", 3)
  5127.                     elseif db.soundforincwhisps == SOUNDS.EDIT_CLICK then
  5128.                         return GetString("PCHAT_SOUNDFORINCWHISPSCHOICE", 4)
  5129.                     else
  5130.                         return GetString("PCHAT_SOUNDFORINCWHISPSCHOICE", 2)
  5131.                     end
  5132.                 end,
  5133.                 setFunc = function(choice)
  5134.                     if choice == GetString("PCHAT_SOUNDFORINCWHISPSCHOICE", 1) then
  5135.                         db.soundforincwhisps = SOUNDS.NONE
  5136.                         PlaySound(SOUNDS.NONE)
  5137.                     elseif choice == GetString("PCHAT_SOUNDFORINCWHISPSCHOICE", 2) then
  5138.                         db.soundforincwhisps = SOUNDS.NEW_NOTIFICATION
  5139.                         PlaySound(SOUNDS.NEW_NOTIFICATION)
  5140.                     elseif choice == GetString("PCHAT_SOUNDFORINCWHISPSCHOICE", 3) then
  5141.                         db.soundforincwhisps = SOUNDS.DEFAULT_CLICK
  5142.                         PlaySound(SOUNDS.DEFAULT_CLICK)
  5143.                     elseif choice == GetString("PCHAT_SOUNDFORINCWHISPSCHOICE", 4) then
  5144.                         db.soundforincwhisps = SOUNDS.EDIT_CLICK
  5145.                         PlaySound(SOUNDS.EDIT_CLICK)
  5146.                     else
  5147.                         -- When clicking on LAM default button
  5148.                         db.soundforincwhisps = defaults.soundforincwhisps
  5149.                     end
  5150.                    
  5151.                 end,
  5152.             },
  5153.             {-- -- LAM Option Whisper: Visual Notification
  5154.                 type = "checkbox",
  5155.                 name = GetString(PCHAT_NOTIFYIM),
  5156.                 tooltip = GetString(PCHAT_NOTIFYIMTT),
  5157.                 getFunc = function() return db.notifyIM end,
  5158.                 setFunc = function(newValue) db.notifyIM = newValue end,
  5159.                 width = "full",
  5160.                 default = defaults.notifyIM,
  5161.             },
  5162.         },
  5163.     }-- LAM Menu Restore Chat
  5164.     optionsData[#optionsData + 1] = {
  5165.         type = "submenu",
  5166.         name = GetString(PCHAT_RESTORECHATH),
  5167.         controls = {
  5168.             {-- LAM Option Restore: After ReloadUI
  5169.                 type = "checkbox",
  5170.                 name = GetString(PCHAT_RESTOREONRELOADUI),
  5171.                 tooltip = GetString(PCHAT_RESTOREONRELOADUITT),
  5172.                 getFunc = function() return db.restoreOnReloadUI end,
  5173.                 setFunc = function(newValue) db.restoreOnReloadUI = newValue end,
  5174.                 width = "full",
  5175.                 default = defaults.restoreOnReloadUI,
  5176.             },
  5177.             {-- LAM Option Restore: Logout
  5178.                 type = "checkbox",
  5179.                 name = GetString(PCHAT_RESTOREONLOGOUT),
  5180.                 tooltip = GetString(PCHAT_RESTOREONLOGOUTTT),
  5181.                 getFunc = function() return db.restoreOnLogOut end,
  5182.                 setFunc = function(newValue) db.restoreOnLogOut = newValue end,
  5183.                 width = "full",
  5184.                 default = defaults.restoreOnLogOut,
  5185.             },
  5186.             {-- LAM Option Restore: Kicked
  5187.                 type = "checkbox",
  5188.                 name = GetString(PCHAT_RESTOREONAFK),
  5189.                 tooltip = GetString(PCHAT_RESTOREONAFKTT),
  5190.                 getFunc = function() return db.restoreOnAFK end,
  5191.                 setFunc = function(newValue) db.restoreOnAFK = newValue end,
  5192.                 width = "full",
  5193.                 default = defaults.restoreOnAFK,
  5194.             },
  5195.             {-- LAM Option Restore: Hours  
  5196.                 type = "slider",
  5197.                 name = GetString(PCHAT_TIMEBEFORERESTORE),
  5198.                 tooltip = GetString(PCHAT_TIMEBEFORERESTORETT),
  5199.                 min = 1,
  5200.                 max = 24,
  5201.                 step = 1,
  5202.                 getFunc = function() return db.timeBeforeRestore end,
  5203.                 setFunc = function(newValue) db.timeBeforeRestore = newValue end,
  5204.                 width = "full",
  5205.                 default = defaults.timeBeforeRestore,
  5206.             },
  5207.             {-- LAM Option Restore: Leave
  5208.                 type = "checkbox",
  5209.                 name = GetString(PCHAT_RESTOREONQUIT),
  5210.                 tooltip = GetString(PCHAT_RESTOREONQUITTT),
  5211.                 getFunc = function() return db.restoreOnQuit end,
  5212.                 setFunc = function(newValue) db.restoreOnQuit = newValue end,
  5213.                 width = "full",
  5214.                 default = defaults.restoreOnQuit,
  5215.             },
  5216.             {-- LAM Option Restore: System Messages
  5217.                 type = "checkbox",
  5218.                 name = GetString(PCHAT_RESTORESYSTEM),
  5219.                 tooltip = GetString(PCHAT_RESTORESYSTEMTT),
  5220.                 getFunc = function() return db.restoreSystem end,
  5221.                 setFunc = function(newValue) db.restoreSystem = newValue end,
  5222.                 width = "full",
  5223.                 default = defaults.restoreSystem,
  5224.             },
  5225.             {-- LAM Option Restore: System Only Messages
  5226.                 type = "checkbox",
  5227.                 name = GetString(PCHAT_RESTORESYSTEMONLY),
  5228.                 tooltip = GetString(PCHAT_RESTORESYSTEMONLYTT),
  5229.                 getFunc = function() return db.restoreSystemOnly end,
  5230.                 setFunc = function(newValue) db.restoreSystemOnly = newValue end,
  5231.                 width = "full",
  5232.                 default = defaults.restoreSystemOnly,
  5233.             },
  5234.             {-- LAM Option Restore: Whispers
  5235.                 type = "checkbox",
  5236.                 name = GetString(PCHAT_RESTOREWHISPS),
  5237.                 tooltip = GetString(PCHAT_RESTOREWHISPSTT),
  5238.                 getFunc = function() return db.restoreWhisps end,
  5239.                 setFunc = function(newValue) db.restoreWhisps = newValue end,
  5240.                 width = "full",
  5241.                 default = defaults.restoreWhisps,
  5242.             },
  5243.             {-- LAM Option Restore: Text entry history
  5244.                 type = "checkbox",
  5245.                 name = GetString(PCHAT_RESTORETEXTENTRYHISTORYATLOGOUTQUIT),
  5246.                 tooltip = GetString(PCHAT_RESTORETEXTENTRYHISTORYATLOGOUTQUITTT),
  5247.                 getFunc = function() return db.restoreTextEntryHistoryAtLogOutQuit end,
  5248.                 setFunc = function(newValue) db.restoreTextEntryHistoryAtLogOutQuit = newValue end,
  5249.                 width = "full",
  5250.                 default = defaults.restoreTextEntryHistoryAtLogOutQuit,
  5251.             },
  5252.         },
  5253.     } -- Anti-Spam   Timestamp options
  5254.     optionsData[#optionsData + 1] = {
  5255.         type = "submenu",
  5256.         name = GetString(PCHAT_ANTISPAMH),
  5257.         controls = {
  5258.             {-- flood protect
  5259.                 type = "checkbox",
  5260.                 name = GetString(PCHAT_FLOODPROTECT),
  5261.                 tooltip = GetString(PCHAT_FLOODPROTECTTT),
  5262.                 getFunc = function() return db.floodProtect end,
  5263.                 setFunc = function(newValue) db.floodProtect = newValue end,
  5264.                 width = "full",
  5265.                 default = defaults.floodProtect,
  5266.             }, --Anti spam  grace period
  5267.             {
  5268.                 type = "slider",
  5269.                 name = GetString(PCHAT_FLOODGRACEPERIOD),
  5270.                 tooltip = GetString(PCHAT_FLOODGRACEPERIODTT),
  5271.                 min = 0,
  5272.                 max = 180,
  5273.                 step = 1,
  5274.                 getFunc = function() return db.floodGracePeriod end,
  5275.                 setFunc = function(newValue) db.floodGracePeriod = newValue end,
  5276.                 width = "full",
  5277.                 default = defaults.floodGracePeriod,
  5278.                 disabled = function() return not db.floodProtect end,
  5279.             },
  5280.             {
  5281.                 type = "checkbox",
  5282.                 name = GetString(PCHAT_LOOKINGFORPROTECT),
  5283.                 tooltip = GetString(PCHAT_LOOKINGFORPROTECTTT),
  5284.                 getFunc = function() return db.lookingForProtect end,
  5285.                 setFunc = function(newValue) db.lookingForProtect = newValue end,
  5286.                 width = "full",
  5287.                 default = defaults.lookingForProtect,
  5288.             },
  5289.             {
  5290.             type = "checkbox",
  5291.                 name = GetString(PCHAT_WANTTOPROTECT),
  5292.                 tooltip = GetString(PCHAT_WANTTOPROTECTTT),
  5293.                 getFunc = function() return db.wantToProtect end,
  5294.                 setFunc = function(newValue) db.wantToProtect = newValue end,
  5295.                 width = "full",
  5296.                 default = defaults.wantToProtect,
  5297.             },
  5298.             {  
  5299.                 type = "slider",
  5300.                 name = GetString(PCHAT_SPAMGRACEPERIOD),
  5301.                 tooltip = GetString(PCHAT_SPAMGRACEPERIODTT),
  5302.                 min = 0,
  5303.                 max = 10,
  5304.                 step = 1,
  5305.                 getFunc = function() return db.spamGracePeriod end,
  5306.                 setFunc = function(newValue) db.spamGracePeriod = newValue end,
  5307.                 width = "full",
  5308.                 default = defaults.spamGracePeriod,
  5309.             },
  5310.             {
  5311.                 type = "editbox",
  5312.                 name = GetString(PCHAT_NICKNAMES),
  5313.                 tooltip = GetString(PCHAT_NICKNAMESTT),
  5314.                 isMultiline = true,
  5315.                 isExtraWide = true,
  5316.                 getFunc = function() return db.nicknames end,
  5317.                 setFunc = function(newValue)
  5318.                     db.nicknames = newValue
  5319.                     BuildNicknames(true) -- Rebuild the control if data is invalid
  5320.                 end,
  5321.                 width = "full",
  5322.                 default = defaults.nicknames,
  5323.             },
  5324.         },
  5325.     } -- Timestamp options
  5326.     optionsData[#optionsData + 1] = {
  5327.         type = "submenu",
  5328.         name = GetString(PCHAT_TIMESTAMPH),
  5329.         controls = {
  5330.             {
  5331.                 type = "checkbox",
  5332.                 name = GetString(PCHAT_ENABLETIMESTAMP),
  5333.                 tooltip = GetString(PCHAT_ENABLETIMESTAMPTT),
  5334.                 getFunc = function() return db.showTimestamp end,
  5335.                 setFunc = function(newValue) db.showTimestamp = newValue end,
  5336.                 width = "full",
  5337.                 default = defaults.showTimestamp,
  5338.             },
  5339.             {
  5340.                 type = "checkbox",
  5341.                 name = GetString(PCHAT_TIMESTAMPCOLORISLCOL),
  5342.                 tooltip = GetString(PCHAT_TIMESTAMPCOLORISLCOLTT),
  5343.                 getFunc = function() return db.timestampcolorislcol end,
  5344.                 setFunc = function(newValue) db.timestampcolorislcol = newValue end,
  5345.                 width = "full",
  5346.                 default = defaults.timestampcolorislcol,
  5347.                 disabled = function() return not db.showTimestamp end,
  5348.             },
  5349.             {  
  5350.                 type = "editbox",
  5351.                 name = GetString(PCHAT_TIMESTAMPFORMAT),
  5352.                 tooltip = GetString(PCHAT_TIMESTAMPFORMATTT),
  5353.                 getFunc = function() return db.timestampFormat end,
  5354.                 setFunc = function(newValue) db.timestampFormat = newValue end,
  5355.                 width = "full",
  5356.                 default = defaults.timestampFormat,
  5357.                 disabled = function() return not db.showTimestamp end,
  5358.             },
  5359.             {
  5360.                 type = "colorpicker",
  5361.                 name = GetString(PCHAT_TIMESTAMP),
  5362.                 tooltip = GetString(PCHAT_TIMESTAMPTT),
  5363.                 getFunc = function() return ConvertHexToRGBA(db.colours.timestamp) end,
  5364.                 setFunc = function(r, g, b) db.colours.timestamp = ConvertRGBToHex(r, g, b) end,
  5365.                 default = ConvertHexToRGBAPacked(defaults.colours.timestamp),
  5366.                 disabled = function() return not db.showTimestamp end,
  5367.             },
  5368.         },
  5369.     }
  5370.         -- Addon Menu Other Colors
  5371.     optionsData[#optionsData + 1] = {
  5372.     &nbs