SpeakTypesSettings = { none = {}, say = { speakType = MessageModes.Say, color = '#FFFF00' }, whisper = { speakType = MessageModes.Whisper, color = '#FFFF00' }, yell = { speakType = MessageModes.Yell, color = '#FFFF00' }, broadcast = { speakType = MessageModes.GamemasterBroadcast, color = '#F55E5E' }, private = { speakType = MessageModes.PrivateTo, color = '#5FF7F7', private = true }, privateRed = { speakType = MessageModes.GamemasterTo, color = '#F55E5E', private = true }, privatePlayerToPlayer = { speakType = MessageModes.PrivateTo, color = '#9F9DFD', private = true }, privatePlayerToNpc = { speakType = MessageModes.NpcTo, color = '#9F9DFD', private = true, npcChat = true }, privateNpcToPlayer = { speakType = MessageModes.NpcFrom, color = '#5FF7F7', private = true, npcChat = true }, channelYellow = { speakType = MessageModes.Channel, color = '#FFFF00' }, channelWhite = { speakType = MessageModes.ChannelManagement, color = '#FFFFFF' }, channelRed = { speakType = MessageModes.GamemasterChannel, color = '#F55E5E' }, channelOrange = { speakType = MessageModes.ChannelHighlight, color = '#FE6500' }, monsterSay = { speakType = MessageModes.MonsterSay, color = '#FE6500', hideInConsole = true}, monsterYell = { speakType = MessageModes.MonsterYell, color = '#FE6500', hideInConsole = true}, rvrAnswerFrom = { speakType = MessageModes.RVRAnswer, color = '#FE6500' }, rvrAnswerTo = { speakType = MessageModes.RVRAnswer, color = '#FE6500' }, rvrContinue = { speakType = MessageModes.RVRContinue, color = '#FFFF00' }, } SpeakTypes = { [MessageModes.Say] = SpeakTypesSettings.say, [MessageModes.Whisper] = SpeakTypesSettings.whisper, [MessageModes.Yell] = SpeakTypesSettings.yell, [MessageModes.GamemasterBroadcast] = SpeakTypesSettings.broadcast, [MessageModes.PrivateFrom] = SpeakTypesSettings.private, [MessageModes.GamemasterPrivateFrom] = SpeakTypesSettings.privateRed, [MessageModes.NpcTo] = SpeakTypesSettings.privatePlayerToNpc, [MessageModes.NpcFrom] = SpeakTypesSettings.privateNpcToPlayer, [MessageModes.Channel] = SpeakTypesSettings.channelYellow, [MessageModes.ChannelManagement] = SpeakTypesSettings.channelWhite, [MessageModes.GamemasterChannel] = SpeakTypesSettings.channelRed, [MessageModes.ChannelHighlight] = SpeakTypesSettings.channelOrange, [MessageModes.MonsterSay] = SpeakTypesSettings.monsterSay, [MessageModes.MonsterYell] = SpeakTypesSettings.monsterYell, [MessageModes.RVRChannel] = SpeakTypesSettings.channelWhite, [MessageModes.RVRContinue] = SpeakTypesSettings.rvrContinue, [MessageModes.RVRAnswer] = SpeakTypesSettings.rvrAnswerFrom, [MessageModes.NpcFromStartBlock] = SpeakTypesSettings.privateNpcToPlayer, -- ignored types [MessageModes.Spell] = SpeakTypesSettings.none, [MessageModes.BarkLow] = SpeakTypesSettings.none, [MessageModes.BarkLoud] = SpeakTypesSettings.none, } SayModes = { [1] = { speakTypeDesc = 'whisper', icon = '/images/game/console/whisper' }, [2] = { speakTypeDesc = 'say', icon = '/images/game/console/say' }, [3] = { speakTypeDesc = 'yell', icon = '/images/game/console/yell' } } MAX_HISTORY = 500 MAX_LINES = 100 HELP_CHANNEL = 9 consolePanel = nil consoleContentPanel = nil consoleTabBar = nil consoleTextEdit = nil channels = nil channelsWindow = nil communicationWindow = nil ownPrivateName = nil messageHistory = {} currentMessageIndex = 0 ignoreNpcMessages = false defaultTab = nil serverTab = nil violationsChannelId = nil violationWindow = nil violationReportTab = nil ignoredChannels = {} filters = {} local communicationSettings = { useIgnoreList = true, useWhiteList = true, privateMessages = false, yelling = false, allowVIPs = false, ignoredPlayers = {}, whitelistedPlayers = {} } function init() connect(g_game, { onTalk = onTalk, onChannelList = onChannelList, onOpenChannel = onOpenChannel, onOpenPrivateChannel = onOpenPrivateChannel, onOpenOwnPrivateChannel = onOpenOwnPrivateChannel, onCloseChannel = onCloseChannel, onRuleViolationChannel = onRuleViolationChannel, onRuleViolationRemove = onRuleViolationRemove, onRuleViolationCancel = onRuleViolationCancel, onRuleViolationLock = onRuleViolationLock, onGameStart = online, onGameEnd = offline }) consolePanel = g_ui.loadUI('console', modules.game_interface.getBottomPanel()) consoleTextEdit = consolePanel:getChildById('consoleTextEdit') consoleContentPanel = consolePanel:getChildById('consoleContentPanel') consoleTabBar = consolePanel:getChildById('consoleTabBar') consoleTabBar:setContentWidget(consoleContentPanel) channels = {} consolePanel.onKeyPress = function(self, keyCode, keyboardModifiers) if not (keyboardModifiers == KeyboardCtrlModifier and keyCode == KeyC) then return false end local tab = consoleTabBar:getCurrentTab() if not tab then return false end local selection = tab.tabPanel:getChildById('consoleBuffer').selectionText if not selection then return false end g_window.setClipboardText(selection) return true end g_keyboard.bindKeyPress('Shift+Up', function() navigateMessageHistory(1) end, consolePanel) g_keyboard.bindKeyPress('Shift+Down', function() navigateMessageHistory(-1) end, consolePanel) g_keyboard.bindKeyPress('Tab', function() consoleTabBar:selectNextTab() end, consolePanel) g_keyboard.bindKeyPress('Shift+Tab', function() consoleTabBar:selectPrevTab() end, consolePanel) g_keyboard.bindKeyDown('Enter', sendCurrentMessage, consolePanel) g_keyboard.bindKeyPress('Ctrl+A', function() consoleTextEdit:clearText() end, consolePanel) -- apply buttom functions after loaded consoleTabBar:setNavigation(consolePanel:getChildById('prevChannelButton'), consolePanel:getChildById('nextChannelButton')) consoleTabBar.onTabChange = onTabChange -- tibia like hotkeys g_keyboard.bindKeyDown('Ctrl+O', g_game.requestChannels) g_keyboard.bindKeyDown('Ctrl+E', removeCurrentTab) g_keyboard.bindKeyDown('Ctrl+H', openHelp) consoleToggleChat = consolePanel:getChildById('toggleChat') load() if g_game.isOnline() then online() end end function clearSelection(consoleBuffer) for _,label in pairs(consoleBuffer:getChildren()) do label:clearSelection() end consoleBuffer.selectionText = nil consoleBuffer.selection = nil end function selectAll(consoleBuffer) clearSelection(consoleBuffer) if consoleBuffer:getChildCount() > 0 then local text = {} for _,label in pairs(consoleBuffer:getChildren()) do label:selectAll() table.insert(text, label:getSelection()) end consoleBuffer.selectionText = table.concat(text, '\n') consoleBuffer.selection = { first = consoleBuffer:getChildIndex(consoleBuffer:getFirstChild()), last = consoleBuffer:getChildIndex(consoleBuffer:getLastChild()) } end end function toggleChat() if consoleToggleChat:isChecked() then disableChat() else enableChat() end end function enableChat() local gameInterface = modules.game_interface consoleTextEdit:setVisible(true) consoleTextEdit:setText("") g_keyboard.unbindKeyUp("Space") g_keyboard.unbindKeyUp("Enter") g_keyboard.unbindKeyUp("Escape") gameInterface.unbindWalkKey("W") gameInterface.unbindWalkKey("D") gameInterface.unbindWalkKey("S") gameInterface.unbindWalkKey("A") gameInterface.unbindWalkKey("E") gameInterface.unbindWalkKey("Q") gameInterface.unbindWalkKey("C") gameInterface.unbindWalkKey("Z") consoleToggleChat:setTooltip(tr("Disable chat mode, allow to walk using ASDW")) end function disableChat() local gameInterface = modules.game_interface consoleTextEdit:setVisible(false) consoleTextEdit:setText("") local quickFunc = function() if consoleToggleChat:isChecked() then consoleToggleChat:setChecked(false) end enableChat() end g_keyboard.bindKeyUp("Space", quickFunc) g_keyboard.bindKeyUp("Enter", quickFunc) g_keyboard.bindKeyUp("Escape", quickFunc) gameInterface.bindWalkKey("W", North) gameInterface.bindWalkKey("D", East) gameInterface.bindWalkKey("S", South) gameInterface.bindWalkKey("A", West) gameInterface.bindWalkKey("E", NorthEast) gameInterface.bindWalkKey("Q", NorthWest) gameInterface.bindWalkKey("C", SouthEast) gameInterface.bindWalkKey("Z", SouthWest) consoleToggleChat:setTooltip(tr("Enable chat mode")) end function terminate() save() disconnect(g_game, { onTalk = onTalk, onChannelList = onChannelList, onOpenChannel = onOpenChannel, onOpenPrivateChannel = onOpenPrivateChannel, onOpenOwnPrivateChannel = onOpenPrivateChannel, onCloseChannel = onCloseChannel, onRuleViolationChannel = onRuleViolationChannel, onRuleViolationRemove = onRuleViolationRemove, onRuleViolationCancel = onRuleViolationCancel, onRuleViolationLock = onRuleViolationLock, onGameStart = online, onGameEnd = offline }) if g_game.isOnline() then clear() end g_keyboard.unbindKeyDown('Ctrl+O') g_keyboard.unbindKeyDown('Ctrl+E') g_keyboard.unbindKeyDown('Ctrl+H') saveCommunicationSettings() if channelsWindow then channelsWindow:destroy() end if communicationWindow then communicationWindow:destroy() end if violationWindow then violationWindow:destroy() end consoleTabBar = nil consoleContentPanel = nil consoleToggleChat = nil consoleTextEdit = nil consolePanel:destroy() consolePanel = nil ownPrivateName = nil Console = nil end function save() local settings = {} settings.messageHistory = messageHistory g_settings.setNode('game_console', settings) end function load() local settings = g_settings.getNode('game_console') if settings then messageHistory = settings.messageHistory or {} end loadCommunicationSettings() end function onTabChange(tabBar, tab) if tab == defaultTab or tab == serverTab then consolePanel:getChildById('closeChannelButton'):disable() else consolePanel:getChildById('closeChannelButton'):enable() end end function clear() -- save last open channels local lastChannelsOpen = g_settings.getNode('lastChannelsOpen') or {} local char = g_game.getCharacterName() local savedChannels = {} local set = false for channelId, channelName in pairs(channels) do if type(channelId) == 'number' then savedChannels[channelName] = channelId set = true end end if set then lastChannelsOpen[char] = savedChannels else lastChannelsOpen[char] = nil end g_settings.setNode('lastChannelsOpen', lastChannelsOpen) -- close channels for _, channelName in pairs(channels) do local tab = consoleTabBar:getTab(channelName) consoleTabBar:removeTab(tab) end channels = {} consoleTabBar:removeTab(defaultTab) defaultTab = nil consoleTabBar:removeTab(serverTab) serverTab = nil local npcTab = consoleTabBar:getTab('NPCs') if npcTab then consoleTabBar:removeTab(npcTab) npcTab = nil end if violationReportTab then consoleTabBar:removeTab(violationReportTab) violationReportTab = nil end consoleTextEdit:clearText() if violationWindow then violationWindow:destroy() violationWindow = nil end if channelsWindow then channelsWindow:destroy() channelsWindow = nil end end function clearChannel(consoleTabBar) consoleTabBar:getCurrentTab().tabPanel:getChildById('consoleBuffer'):destroyChildren() end function setTextEditText(text) consoleTextEdit:setText(text) consoleTextEdit:setCursorPos(-1) end function openHelp() local helpChannel = 9 if g_game.getClientVersion() <= 810 then helpChannel = 8 end g_game.joinChannel(helpChannel) end function openPlayerReportRuleViolationWindow() if violationWindow or violationReportTab then return end violationWindow = g_ui.loadUI('violationwindow', rootWidget) violationWindow.onEscape = function() violationWindow:destroy() violationWindow = nil end violationWindow.onEnter = function() local text = violationWindow:getChildById('text'):getText() g_game.talkChannel(MessageModes.RVRChannel, 0, text) violationReportTab = addTab(tr('Report Rule') .. '...', true) addTabText(tr('Please wait patiently for a gamemaster to reply') .. '.', SpeakTypesSettings.privateRed, violationReportTab) addTabText(applyMessagePrefixies(g_game.getCharacterName(), 0, text), SpeakTypesSettings.say, violationReportTab, g_game.getCharacterName()) violationReportTab.locked = true violationWindow:destroy() violationWindow = nil end end function addTab(name, focus) local tab = getTab(name) if tab then -- is channel already open if not focus then focus = true end else tab = consoleTabBar:addTab(name, nil, processChannelTabMenu) end if focus then consoleTabBar:selectTab(tab) end return tab end function removeTab(tab) if type(tab) == 'string' then tab = consoleTabBar:getTab(tab) end if tab == defaultTab or tab == serverTab then return end if tab == violationReportTab then g_game.cancelRuleViolation() violationReportTab = nil elseif tab.violationChatName then g_game.closeRuleViolation(tab.violationChatName) elseif tab.channelId then -- notificate the server that we are leaving the channel for k, v in pairs(channels) do if (k == tab.channelId) then channels[k] = nil end end g_game.leaveChannel(tab.channelId) elseif tab:getText() == "NPCs" then g_game.closeNpcChannel() end consoleTabBar:removeTab(tab) end function removeCurrentTab() removeTab(consoleTabBar:getCurrentTab()) end function getTab(name) return consoleTabBar:getTab(name) end function getChannelTab(channelId) local channel = channels[channelId] if channel then return getTab(channel) end return nil end function getRuleViolationsTab() if violationsChannelId then return getChannelTab(violationsChannelId) end return nil end function getCurrentTab() return consoleTabBar:getCurrentTab() end function addChannel(name, id) channels[id] = name local focus = not table.find(ignoredChannels, id) local tab = addTab(name, focus) tab.channelId = id return tab end function addPrivateChannel(receiver) channels[receiver] = receiver return addTab(receiver, false) end function addPrivateText(text, speaktype, name, isPrivateCommand, creatureName) local focus = false if speaktype.npcChat then name = 'NPCs' focus = true end local privateTab = getTab(name) if privateTab == nil then if (modules.client_options.getOption('showPrivateMessagesInConsole') and not focus) or (isPrivateCommand and not privateTab) then privateTab = defaultTab else privateTab = addTab(name, focus) channels[name] = name end privateTab.npcChat = speaktype.npcChat elseif focus then consoleTabBar:selectTab(privateTab) end addTabText(text, speaktype, privateTab, creatureName) end function addText(text, speaktype, tabName, creatureName) local tab = getTab(tabName) if tab ~= nil then addTabText(text, speaktype, tab, creatureName) end end -- Contains letter width for font "verdana-11px-antialised" as console is based on it local letterWidth = { -- New line (10) and Space (32) have width 1 because they are printed and not replaced with spacer [10] = 1, [32] = 1, [33] = 3, [34] = 6, [35] = 8, [36] = 7, [37] = 13, [38] = 9, [39] = 3, [40] = 5, [41] = 5, [42] = 6, [43] = 8, [44] = 4, [45] = 5, [46] = 3, [47] = 8, [48] = 7, [49] = 6, [50] = 7, [51] = 7, [52] = 7, [53] = 7, [54] = 7, [55] = 7, [56] = 7, [57] = 7, [58] = 3, [59] = 4, [60] = 8, [61] = 8, [62] = 8, [63] = 6, [64] = 10, [65] = 9, [66] = 7, [67] = 7, [68] = 8, [69] = 7, [70] = 7, [71] = 8, [72] = 8, [73] = 5, [74] = 5, [75] = 7, [76] = 7, [77] = 9, [78] = 8, [79] = 8, [80] = 7, [81] = 8, [82] = 8, [83] = 7, [84] = 8, [85] = 8, [86] = 8, [87] = 12, [88] = 8, [89] = 8, [90] = 7, [91] = 5, [92] = 8, [93] = 5, [94] = 9, [95] = 8, [96] = 5, [97] = 7, [98] = 7, [99] = 6, [100] = 7, [101] = 7, [102] = 5, [103] = 7, [104] = 7, [105] = 3, [106] = 4, [107] = 7, [108] = 3, [109] = 11, [110] = 7, [111] = 7, [112] = 7, [113] = 7, [114] = 6, [115] = 6, [116] = 5, [117] = 7, [118] = 8, [119] = 10, [120] = 8, [121] = 8, [122] = 6, [123] = 7, [124] = 4, [125] = 7, [126] = 8, [127] = 1, [128] = 7, [129] = 6, [130] = 3, [131] = 7, [132] = 6, [133] = 11, [134] = 7, [135] = 7, [136] = 7, [137] = 13, [138] = 7, [139] = 4, [140] = 11, [141] = 6, [142] = 6, [143] = 6, [144] = 6, [145] = 4, [146] = 3, [147] = 7, [148] = 6, [149] = 6, [150] = 7, [151] = 10, [152] = 7, [153] = 10, [154] = 6, [155] = 5, [156] = 11, [157] = 6, [158] = 6, [159] = 8, [160] = 4, [161] = 3, [162] = 7, [163] = 7, [164] = 7, [165] = 8, [166] = 4, [167] = 7, [168] = 6, [169] = 10, [170] = 6, [171] = 8, [172] = 8, [173] = 16, [174] = 10, [175] = 8, [176] = 5, [177] = 8, [178] = 5, [179] = 5, [180] = 6, [181] = 7, [182] = 7, [183] = 3, [184] = 5, [185] = 6, [186] = 6, [187] = 8, [188] = 12, [189] = 12, [190] = 12, [191] = 6, [192] = 9, [193] = 9, [194] = 9, [195] = 9, [196] = 9, [197] = 9, [198] = 11, [199] = 7, [200] = 7, [201] = 7, [202] = 7, [203] = 7, [204] = 5, [205] = 5, [206] = 6, [207] = 5, [208] = 8, [209] = 8, [210] = 8, [211] = 8, [212] = 8, [213] = 8, [214] = 8, [215] = 8, [216] = 8, [217] = 8, [218] = 8, [219] = 8, [220] = 8, [221] = 8, [222] = 7, [223] = 7, [224] = 7, [225] = 7, [226] = 7, [227] = 7, [228] = 7, [229] = 7, [230] = 11, [231] = 6, [232] = 7, [233] = 7, [234] = 7, [235] = 7, [236] = 3, [237] = 4, [238] = 4, [239] = 4, [240] = 7, [241] = 7, [242] = 7, [243] = 7, [244] = 7, [245] = 7, [246] = 7, [247] = 9, [248] = 7, [249] = 7, [250] = 7, [251] = 7, [252] = 7, [253] = 8, [254] = 7, [255] = 8 } -- Return information about start, end in the string and the highlighted words function getHighlightedText(text) local tmpData = {} repeat local tmp = {string.find(text, "{([^}]+)}", tmpData[#tmpData-1])} for _, v in pairs(tmp) do table.insert(tmpData, v) end until not(string.find(text, "{([^}]+)}", tmpData[#tmpData-1])) return tmpData end function addTabText(text, speaktype, tab, creatureName) if not tab or tab.locked or not text or #text == 0 then return end if modules.client_options.getOption('showTimestampsInConsole') then text = os.date('%H:%M') .. ' ' .. text end local panel = consoleTabBar:getTabPanel(tab) local consoleBuffer = panel:getChildById('consoleBuffer') local label = g_ui.createWidget('ConsoleLabel', consoleBuffer) label:setId('consoleLabel' .. consoleBuffer:getChildCount()) label:setText(text) label:setColor(speaktype.color) consoleTabBar:blinkTab(tab) -- Overlay for consoleBuffer which shows highlighted words only if speaktype.npcChat and (g_game.getCharacterName() ~= creatureName or g_game.getCharacterName() == 'Account Manager') then local highlightData = getHighlightedText(text) if #highlightData > 0 then local labelHighlight = g_ui.createWidget('ConsolePhantomLabel', label) labelHighlight:fill('parent') labelHighlight:setId('consoleLabelHighlight' .. consoleBuffer:getChildCount()) labelHighlight:setColor("#1f9ffe") -- Remove the curly braces for i = 1, #highlightData / 3 do local dataBlock = { _start = highlightData[(i-1)*3+1], _end = highlightData[(i-1)*3+2], words = highlightData[(i-1)*3+3] } text = text:gsub("%{(.-)%}", dataBlock.words, 1) -- Recalculate positions as braces are removed highlightData[(i-1)*3+1] = dataBlock._start - ((i-1) * 2) highlightData[(i-1)*3+2] = dataBlock._end - (1 + (i-1) * 2) end label:setText(text) -- Calculate the positions of the highlighted text and fill with string.char(127) [Width: 1] local drawText = label:getDrawText() local tmpText = "" for i = 1, #highlightData / 3 do local dataBlock = { _start = highlightData[(i-1)*3+1], _end = highlightData[(i-1)*3+2], words = highlightData[(i-1)*3+3] } local lastBlockEnd = (highlightData[(i-2)*3+2] or 1) for letter = lastBlockEnd, dataBlock._start-1 do local tmpChar = string.byte(drawText:sub(letter, letter)) local fillChar = (tmpChar == 10 or tmpChar == 32) and string.char(tmpChar) or string.char(127) tmpText = tmpText .. string.rep(fillChar, letterWidth[tmpChar]) end tmpText = tmpText .. dataBlock.words end -- Fill the highlight label to the same size as default label local finalBlockEnd = (highlightData[(#highlightData/3-1)*3+2] or 1) for letter = finalBlockEnd, drawText:len() do local tmpChar = string.byte(drawText:sub(letter, letter)) local fillChar = (tmpChar == 10 or tmpChar == 32) and string.char(tmpChar) or string.char(127) tmpText = tmpText .. string.rep(fillChar, letterWidth[tmpChar]) end labelHighlight:setText(tmpText) end end label.name = creatureName consoleBuffer.onMouseRelease = function(self, mousePos, mouseButton) processMessageMenu(mousePos, mouseButton, nil, nil, nil, tab) end label.onMouseRelease = function(self, mousePos, mouseButton) processMessageMenu(mousePos, mouseButton, creatureName, text, self, tab) end label.onMousePress = function(self, mousePos, button) if button == MouseLeftButton then clearSelection(consoleBuffer) end end label.onDragEnter = function(self, mousePos) clearSelection(consoleBuffer) return true end label.onDragLeave = function(self, droppedWidget, mousePos) local text = {} for selectionChild = consoleBuffer.selection.first, consoleBuffer.selection.last do local label = self:getParent():getChildByIndex(selectionChild) table.insert(text, label:getSelection()) end consoleBuffer.selectionText = table.concat(text, '\n') return true end label.onDragMove = function(self, mousePos, mouseMoved) local parent = self:getParent() local parentRect = parent:getPaddingRect() local selfIndex = parent:getChildIndex(self) local child = parent:getChildByPos(mousePos) -- find bonding children if not child then if mousePos.y < self:getY() then for index = selfIndex - 1, 1, -1 do local label = parent:getChildByIndex(index) if label:getY() + label:getHeight() > parentRect.y then if (mousePos.y >= label:getY() and mousePos.y <= label:getY() + label:getHeight()) or index == 1 then child = label break end else child = parent:getChildByIndex(index + 1) break end end elseif mousePos.y > self:getY() + self:getHeight() then for index = selfIndex + 1, parent:getChildCount(), 1 do local label = parent:getChildByIndex(index) if label:getY() < parentRect.y + parentRect.height then if (mousePos.y >= label:getY() and mousePos.y <= label:getY() + label:getHeight()) or index == parent:getChildCount() then child = label break end else child = parent:getChildByIndex(index - 1) break end end else child = self end end if not child then return false end local childIndex = parent:getChildIndex(child) -- remove old selection clearSelection(consoleBuffer) -- update self selection local textBegin = self:getTextPos(self:getLastClickPosition()) local textPos = self:getTextPos(mousePos) self:setSelection(textBegin, textPos) consoleBuffer.selection = { first = math.min(selfIndex, childIndex), last = math.max(selfIndex, childIndex) } -- update siblings selection if child ~= self then for selectionChild = consoleBuffer.selection.first + 1, consoleBuffer.selection.last - 1 do parent:getChildByIndex(selectionChild):selectAll() end local textPos = child:getTextPos(mousePos) if childIndex > selfIndex then child:setSelection(0, textPos) else child:setSelection(string.len(child:getText()), textPos) end end return true end if consoleBuffer:getChildCount() > MAX_LINES then local child = consoleBuffer:getFirstChild() clearSelection(consoleBuffer) child:destroy() end end function removeTabLabelByName(tab, name) local panel = consoleTabBar:getTabPanel(tab) local consoleBuffer = panel:getChildById('consoleBuffer') for _,label in pairs(consoleBuffer:getChildren()) do if label.name == name then label:destroy() end end end function processChannelTabMenu(tab, mousePos, mouseButton) local menu = g_ui.createWidget('PopupMenu') menu:setGameMenu(true) local worldName = g_game.getWorldName() local characterName = g_game.getCharacterName() channelName = tab:getText() if tab ~= defaultTab and tab ~= serverTab then menu:addOption(tr('Close'), function() removeTab(channelName) end) --menu:addOption(tr('Show Server Messages'), function() --[[TODO]] end) menu:addSeparator() end if consoleTabBar:getCurrentTab() == tab then menu:addOption(tr('Clear Messages'), function() clearChannel(consoleTabBar) end) menu:addOption(tr('Save Messages'), function() local panel = consoleTabBar:getTabPanel(tab) local consoleBuffer = panel:getChildById('consoleBuffer') local lines = {} for _,label in pairs(consoleBuffer:getChildren()) do table.insert(lines, label:getText()) end local filename = worldName .. ' - ' .. characterName .. ' - ' .. channelName .. '.txt' local filepath = '/' .. filename -- extra information at the beginning table.insert(lines, 1, os.date('\nChannel saved at %a %b %d %H:%M:%S %Y')) if g_resources.fileExists(filepath) then table.insert(lines, 1, protectedcall(g_resources.readFileContents, filepath) or '') end g_resources.writeFileContents(filepath, table.concat(lines, '\n')) modules.game_textmessage.displayStatusMessage(tr('Channel appended to %s', filename)) end) end menu:display(mousePos) end function processMessageMenu(mousePos, mouseButton, creatureName, text, label, tab) if mouseButton == MouseRightButton then local menu = g_ui.createWidget('PopupMenu') menu:setGameMenu(true) if creatureName and #creatureName > 0 then if creatureName ~= g_game.getCharacterName() then menu:addOption(tr('Message to ' .. creatureName), function () g_game.openPrivateChannel(creatureName) end) if not g_game.getLocalPlayer():hasVip(creatureName) then menu:addOption(tr('Add to VIP list'), function () g_game.addVip(creatureName) end) end if modules.game_console.getOwnPrivateTab() then menu:addSeparator() menu:addOption(tr('Invite to private chat'), function() g_game.inviteToOwnChannel(creatureName) end) menu:addOption(tr('Exclude from private chat'), function() g_game.excludeFromOwnChannel(creatureName) end) end if isIgnored(creatureName) then menu:addOption(tr('Unignore') .. ' ' .. creatureName, function() removeIgnoredPlayer(creatureName) end) else menu:addOption(tr('Ignore') .. ' ' .. creatureName, function() addIgnoredPlayer(creatureName) end) end menu:addSeparator() end if modules.game_ruleviolation.hasWindowAccess() then menu:addOption(tr('Rule Violation'), function() modules.game_ruleviolation.show(creatureName, text:match('.+%:%s(.+)')) end) menu:addSeparator() end menu:addOption(tr('Copy name'), function () g_window.setClipboardText(creatureName) end) end local selection = tab.tabPanel:getChildById('consoleBuffer').selectionText if selection and #selection > 0 then menu:addOption(tr('Copy'), function() g_window.setClipboardText(selection) end, '(Ctrl+C)') end if text then menu:addOption(tr('Copy message'), function() g_window.setClipboardText(text) end) end menu:addOption(tr('Select all'), function() selectAll(tab.tabPanel:getChildById('consoleBuffer')) end) if tab.violations and creatureName then menu:addSeparator() menu:addOption(tr('Process') .. ' ' .. creatureName, function() processViolation(creatureName, text) end) menu:addOption(tr('Remove') .. ' ' .. creatureName, function() g_game.closeRuleViolation(creatureName) end) end menu:display(mousePos) end end function sendCurrentMessage() local message = consoleTextEdit:getText() if #message == 0 then return end consoleTextEdit:clearText() -- send message sendMessage(message) end function addFilter(filter) table.insert(filters, filter) end function removeFilter(filter) table.removevalue(filters, filter) end function sendMessage(message, tab) local tab = tab or getCurrentTab() if not tab then return end for k,func in pairs(filters) do if func(message) then return true end end -- when talking on server log, the message goes to default channel local name = tab:getText() if tab == serverTab or tab == getRuleViolationsTab() then tab = defaultTab name = defaultTab:getText() end -- handling chat commands local channel = tab.channelId local originalMessage = message local chatCommandSayMode local chatCommandPrivate local chatCommandPrivateReady local chatCommandMessage -- player used yell command chatCommandMessage = message:match("^%#[y|Y] (.*)") if chatCommandMessage ~= nil then chatCommandSayMode = 'yell' channel = 0 message = chatCommandMessage end -- player used whisper chatCommandMessage = message:match("^%#[w|W] (.*)") if chatCommandMessage ~= nil then chatCommandSayMode = 'whisper' message = chatCommandMessage channel = 0 end -- player say chatCommandMessage = message:match("^%#[s|S] (.*)") if chatCommandMessage ~= nil then chatCommandSayMode = 'say' message = chatCommandMessage channel = 0 end -- player red talk on channel chatCommandMessage = message:match("^%#[c|C] (.*)") if chatCommandMessage ~= nil then chatCommandSayMode = 'channelRed' message = chatCommandMessage end -- player broadcast chatCommandMessage = message:match("^%#[b|B] (.*)") if chatCommandMessage ~= nil then chatCommandSayMode = 'broadcast' message = chatCommandMessage channel = 0 end local findIni, findEnd, chatCommandInitial, chatCommandPrivate, chatCommandEnd, chatCommandMessage = message:find("([%*%@])(.+)([%*%@])(.*)") if findIni ~= nil and findIni == 1 then -- player used private chat command if chatCommandInitial == chatCommandEnd then chatCommandPrivateRepeat = false if chatCommandInitial == "*" then setTextEditText('*'.. chatCommandPrivate .. '* ') end message = chatCommandMessage:trim() chatCommandPrivateReady = true end end message = message:gsub("^(%s*)(.*)","%2") -- remove space characters from message init if #message == 0 then return end -- add new command to history currentMessageIndex = 0 if #messageHistory == 0 or messageHistory[#messageHistory] ~= originalMessage then table.insert(messageHistory, originalMessage) if #messageHistory > MAX_HISTORY then table.remove(messageHistory, 1) end end local speaktypedesc if (channel or tab == defaultTab) and not chatCommandPrivateReady then if tab == defaultTab then speaktypedesc = chatCommandSayMode or SayModes[consolePanel:getChildById('sayModeButton').sayMode].speakTypeDesc if speaktypedesc ~= 'say' then sayModeChange(2) end -- head back to say mode else speaktypedesc = chatCommandSayMode or 'channelYellow' end g_game.talkChannel(SpeakTypesSettings[speaktypedesc].speakType, channel, message) return else local isPrivateCommand = false local priv = true local tabname = name if chatCommandPrivateReady then speaktypedesc = 'privatePlayerToPlayer' name = chatCommandPrivate isPrivateCommand = true elseif tab.npcChat then speaktypedesc = 'privatePlayerToNpc' elseif tab == violationReportTab then if violationReportTab.locked then modules.game_textmessage.displayFailureMessage('Wait for a gamemaster reply.') return end speaktypedesc = 'rvrContinue' tabname = tr('Report Rule') .. '...' elseif tab.violationChatName then speaktypedesc = 'rvrAnswerTo' name = tab.violationChatName tabname = tab.violationChatName .. '\'...' else speaktypedesc = 'privatePlayerToPlayer' end local speaktype = SpeakTypesSettings[speaktypedesc] local player = g_game.getLocalPlayer() g_game.talkPrivate(speaktype.speakType, name, message) message = applyMessagePrefixies(g_game.getCharacterName(), player:getLevel(), message) addPrivateText(message, speaktype, tabname, isPrivateCommand, g_game.getCharacterName()) end end function sayModeChange(sayMode) local buttom = consolePanel:getChildById('sayModeButton') if sayMode == nil then sayMode = buttom.sayMode + 1 end if sayMode > #SayModes then sayMode = 1 end buttom:setIcon(SayModes[sayMode].icon) buttom.sayMode = sayMode end function getOwnPrivateTab() if not ownPrivateName then return end return getTab(ownPrivateName) end function setIgnoreNpcMessages(ignore) ignoreNpcMessages = ignore end function navigateMessageHistory(step) local numCommands = #messageHistory if numCommands > 0 then currentMessageIndex = math.min(math.max(currentMessageIndex + step, 0), numCommands) if currentMessageIndex > 0 then local command = messageHistory[numCommands - currentMessageIndex + 1] setTextEditText(command) else consoleTextEdit:clearText() end end end function applyMessagePrefixies(name, level, message) if name then if modules.client_options.getOption('showLevelsInConsole') and level > 0 then message = name .. ' [' .. level .. ']: ' .. message else message = name .. ': ' .. message end end return message end function onTalk(name, level, mode, message, channelId, creaturePos) if mode == MessageModes.GamemasterBroadcast then modules.game_textmessage.displayBroadcastMessage(name .. ': ' .. message) return end local isNpcMode = (mode == MessageModes.NpcFromStartBlock or mode == MessageModes.NpcFrom) if ignoreNpcMessages and isNpcMode then return end speaktype = SpeakTypes[mode] if not speaktype then perror('unhandled onTalk message mode ' .. mode .. ': ' .. message) return end local localPlayer = g_game.getLocalPlayer() if name ~= g_game.getCharacterName() and isUsingIgnoreList() and not(isUsingWhiteList()) or (isUsingWhiteList() and not(isWhitelisted(name)) and not(isAllowingVIPs() and localPlayer:hasVip(name))) then if mode == MessageModes.Yell and isIgnoringYelling() then return elseif speaktype.private and isIgnoringPrivate() and not isNpcMode then return elseif isIgnored(name) then return end end if mode == MessageModes.RVRChannel then channelId = violationsChannelId end if (mode == MessageModes.Say or mode == MessageModes.Whisper or mode == MessageModes.Yell or mode == MessageModes.Spell or mode == MessageModes.MonsterSay or mode == MessageModes.MonsterYell or mode == MessageModes.NpcFrom or mode == MessageModes.BarkLow or mode == MessageModes.BarkLoud or mode == MessageModes.NpcFromStartBlock) and creaturePos then local staticText = StaticText.create() -- Remove curly braces from screen message local staticMessage = message if isNpcMode then local highlightData = getHighlightedText(staticMessage) if #highlightData > 0 then for i = 1, #highlightData / 3 do local dataBlock = { _start = highlightData[(i-1)*3+1], _end = highlightData[(i-1)*3+2], words = highlightData[(i-1)*3+3] } staticMessage = staticMessage:gsub("{"..dataBlock.words.."}", dataBlock.words) end end staticText:setColor(speaktype.color) end staticText:addMessage(name, mode, staticMessage) g_map.addThing(staticText, creaturePos, -1) end local defaultMessage = mode <= 3 and true or false if speaktype == SpeakTypesSettings.none then return end if speaktype.hideInConsole then return end local composedMessage = applyMessagePrefixies(name, level, message) if mode == MessageModes.RVRAnswer then violationReportTab.locked = false addTabText(composedMessage, speaktype, violationReportTab, name) elseif mode == MessageModes.RVRContinue then addText(composedMessage, speaktype, name .. '\'...', name) elseif speaktype.private then addPrivateText(composedMessage, speaktype, name, false, name) if modules.client_options.getOption('showPrivateMessagesOnScreen') and speaktype ~= SpeakTypesSettings.privateNpcToPlayer then modules.game_textmessage.displayPrivateMessage(name .. ':\n' .. message) end else local channel = tr('Default') if not defaultMessage then channel = channels[channelId] end if channel then addText(composedMessage, speaktype, channel, name) else -- server sent a message on a channel that is not open pwarning('message in channel id ' .. channelId .. ' which is unknown, this is a server bug, relogin if you want to see messages in this channel') end end end function onOpenChannel(channelId, channelName) addChannel(channelName, channelId) end function onOpenPrivateChannel(receiver) addPrivateChannel(receiver) end function onOpenOwnPrivateChannel(channelId, channelName) local privateTab = getTab(channelName) if privateTab == nil then addChannel(channelName, channelId) end ownPrivateName = channelName end function onCloseChannel(channelId) local channel = channels[channelId] if channel then local tab = getTab(channel) if tab then consoleTabBar:removeTab(tab) end for k, v in pairs(channels) do if (k == tab.channelId) then channels[k] = nil end end end end function processViolation(name, text) local tabname = name .. '\'...' local tab = addTab(tabname, true) channels[tabname] = tabname tab.violationChatName = name g_game.openRuleViolation(name) addTabText(text, SpeakTypesSettings.say, tab, name) end function onRuleViolationChannel(channelId) violationsChannelId = channelId local tab = addChannel(tr('Rule Violations'), channelId) tab.violations = true end function onRuleViolationRemove(name) local tab = getRuleViolationsTab() if not tab then return end removeTabLabelByName(tab, name) end function onRuleViolationCancel(name) local tab = getTab(name .. '\'...') if not tab then return end addTabText(tr('%s has finished the request', name) .. '.', SpeakTypesSettings.privateRed, tab) tab.locked = true end function onRuleViolationLock() if not violationReportTab then return end violationReportTab.locked = false addTabText(tr('Your request has been closed') .. '.', SpeakTypesSettings.privateRed, violationReportTab) violationReportTab.locked = true end function doChannelListSubmit() local channelListPanel = channelsWindow:getChildById('channelList') local openPrivateChannelWith = channelsWindow:getChildById('openPrivateChannelWith'):getText() if openPrivateChannelWith ~= '' then if openPrivateChannelWith:lower() ~= g_game.getCharacterName():lower() then g_game.openPrivateChannel(openPrivateChannelWith) else modules.game_textmessage.displayFailureMessage('You cannot create a private chat channel with yourself.') end else local selectedChannelLabel = channelListPanel:getFocusedChild() if not selectedChannelLabel then return end if selectedChannelLabel.channelId == 0xFFFF then g_game.openOwnChannel() else g_game.joinChannel(selectedChannelLabel.channelId) end end channelsWindow:destroy() end function onChannelList(channelList) if channelsWindow then channelsWindow:destroy() end channelsWindow = g_ui.displayUI('channelswindow') local channelListPanel = channelsWindow:getChildById('channelList') channelsWindow.onEnter = doChannelListSubmit channelsWindow.onDestroy = function() channelsWindow = nil end g_keyboard.bindKeyPress('Down', function() channelListPanel:focusNextChild(KeyboardFocusReason) end, channelsWindow) g_keyboard.bindKeyPress('Up', function() channelListPanel:focusPreviousChild(KeyboardFocusReason) end, channelsWindow) for k,v in pairs(channelList) do local channelId = v[1] local channelName = v[2] if #channelName > 0 then local label = g_ui.createWidget('ChannelListLabel', channelListPanel) label.channelId = channelId label:setText(channelName) label:setPhantom(false) label.onDoubleClick = doChannelListSubmit end end end function loadCommunicationSettings() communicationSettings.whitelistedPlayers = {} communicationSettings.ignoredPlayers = {} local ignoreNode = g_settings.getNode('IgnorePlayers') if ignoreNode then for i = 1, #ignoreNode do table.insert(communicationSettings.ignoredPlayers, ignoreNode[i]) end end local whitelistNode = g_settings.getNode('WhitelistedPlayers') if whitelistNode then for i = 1, #whitelistNode do table.insert(communicationSettings.whitelistedPlayers, whitelistNode[i]) end end communicationSettings.useIgnoreList = g_settings.getBoolean('UseIgnoreList') communicationSettings.useWhiteList = g_settings.getBoolean('UseWhiteList') communicationSettings.privateMessages = g_settings.getBoolean('IgnorePrivateMessages') communicationSettings.yelling = g_settings.getBoolean('IgnoreYelling') communicationSettings.allowVIPs = g_settings.getBoolean('AllowVIPs') end function saveCommunicationSettings() local tmpIgnoreList = {} local ignoredPlayers = getIgnoredPlayers() for i = 1, #ignoredPlayers do table.insert(tmpIgnoreList, ignoredPlayers[i]) end local tmpWhiteList = {} local whitelistedPlayers = getWhitelistedPlayers() for i = 1, #whitelistedPlayers do table.insert(tmpWhiteList, whitelistedPlayers[i]) end g_settings.set('UseIgnoreList', communicationSettings.useIgnoreList) g_settings.set('UseWhiteList', communicationSettings.useWhiteList) g_settings.set('IgnorePrivateMessages', communicationSettings.privateMessages) g_settings.set('IgnoreYelling', communicationSettings.yelling) g_settings.setNode('IgnorePlayers', tmpIgnoreList) g_settings.setNode('WhitelistedPlayers', tmpWhiteList) end function getIgnoredPlayers() return communicationSettings.ignoredPlayers end function getWhitelistedPlayers() return communicationSettings.whitelistedPlayers end function isUsingIgnoreList() return communicationSettings.useIgnoreList end function isUsingWhiteList() return communicationSettings.useWhiteList end function isIgnored(name) return table.find(communicationSettings.ignoredPlayers, name, true) end function addIgnoredPlayer(name) if isIgnored(name) then return end table.insert(communicationSettings.ignoredPlayers, name) end function removeIgnoredPlayer(name) table.removevalue(communicationSettings.ignoredPlayers, name) end function isWhitelisted(name) return table.find(communicationSettings.whitelistedPlayers, name, true) end function addWhitelistedPlayer(name) if isWhitelisted(name) then return end table.insert(communicationSettings.whitelistedPlayers, name) end function removeWhitelistedPlayer(name) table.removevalue(communicationSettings.whitelistedPlayers, name) end function isIgnoringPrivate() return communicationSettings.privateMessages end function isIgnoringYelling() return communicationSettings.yelling end function isAllowingVIPs() return communicationSettings.allowVIPs end function onClickIgnoreButton() if communicationWindow then return end communicationWindow = g_ui.displayUI('communicationwindow') local ignoreListPanel = communicationWindow:getChildById('ignoreList') local whiteListPanel = communicationWindow:getChildById('whiteList') communicationWindow.onDestroy = function() communicationWindow = nil end local useIgnoreListBox = communicationWindow:getChildById('checkboxUseIgnoreList') useIgnoreListBox:setChecked(communicationSettings.useIgnoreList) local useWhiteListBox = communicationWindow:getChildById('checkboxUseWhiteList') useWhiteListBox:setChecked(communicationSettings.useWhiteList) local removeIgnoreButton = communicationWindow:getChildById('buttonIgnoreRemove') removeIgnoreButton:disable() ignoreListPanel.onChildFocusChange = function() removeIgnoreButton:enable() end removeIgnoreButton.onClick = function() local selection = ignoreListPanel:getFocusedChild() if selection then ignoreListPanel:removeChild(selection) selection:destroy() end removeIgnoreButton:disable() end local removeWhitelistButton = communicationWindow:getChildById('buttonWhitelistRemove') removeWhitelistButton:disable() whiteListPanel.onChildFocusChange = function() removeWhitelistButton:enable() end removeWhitelistButton.onClick = function() local selection = whiteListPanel:getFocusedChild() if selection then whiteListPanel:removeChild(selection) selection:destroy() end removeWhitelistButton:disable() end local newlyIgnoredPlayers = {} local addIgnoreName = communicationWindow:getChildById('ignoreNameEdit') local addIgnoreButton = communicationWindow:getChildById('buttonIgnoreAdd') local addIgnoreFunction = function() local newEntry = addIgnoreName:getText() if newEntry == '' then return end if table.find(getIgnoredPlayers(), newEntry) then return end if table.find(newlyIgnoredPlayers, newEntry) then return end local label = g_ui.createWidget('IgnoreListLabel', ignoreListPanel) label:setText(newEntry) table.insert(newlyIgnoredPlayers, newEntry) addIgnoreName:setText('') end addIgnoreButton.onClick = addIgnoreFunction local newlyWhitelistedPlayers = {} local addWhitelistName = communicationWindow:getChildById('whitelistNameEdit') local addWhitelistButton = communicationWindow:getChildById('buttonWhitelistAdd') local addWhitelistFunction = function() local newEntry = addWhitelistName:getText() if newEntry == '' then return end if table.find(getWhitelistedPlayers(), newEntry) then return end if table.find(newlyWhitelistedPlayers, newEntry) then return end local label = g_ui.createWidget('WhiteListLabel', whiteListPanel) label:setText(newEntry) table.insert(newlyWhitelistedPlayers, newEntry) addWhitelistName:setText('') end addWhitelistButton.onClick = addWhitelistFunction communicationWindow.onEnter = function() if addWhitelistName:isFocused() then addWhitelistFunction() elseif addIgnoreName:isFocused() then addIgnoreFunction() end end local ignorePrivateMessageBox = communicationWindow:getChildById('checkboxIgnorePrivateMessages') ignorePrivateMessageBox:setChecked(communicationSettings.privateMessages) local ignoreYellingBox = communicationWindow:getChildById('checkboxIgnoreYelling') ignoreYellingBox:setChecked(communicationSettings.yelling) local allowVIPsBox = communicationWindow:getChildById('checkboxAllowVIPs') allowVIPsBox:setChecked(communicationSettings.allowVIPs) local saveButton = communicationWindow:recursiveGetChildById('buttonSave') saveButton.onClick = function() communicationSettings.ignoredPlayers = {} for i = 1, ignoreListPanel:getChildCount() do addIgnoredPlayer(ignoreListPanel:getChildByIndex(i):getText()) end communicationSettings.whitelistedPlayers = {} for i = 1, whiteListPanel:getChildCount() do addWhitelistedPlayer(whiteListPanel:getChildByIndex(i):getText()) end communicationSettings.useIgnoreList = useIgnoreListBox:isChecked() communicationSettings.useWhiteList = useWhiteListBox:isChecked() communicationSettings.yelling = ignoreYellingBox:isChecked() communicationSettings.privateMessages = ignorePrivateMessageBox:isChecked() communicationSettings.allowVIPs = allowVIPsBox:isChecked() communicationWindow:destroy() end local cancelButton = communicationWindow:recursiveGetChildById('buttonCancel') cancelButton.onClick = function() communicationWindow:destroy() end local ignoredPlayers = getIgnoredPlayers() for i = 1, #ignoredPlayers do local label = g_ui.createWidget('IgnoreListLabel', ignoreListPanel) label:setText(ignoredPlayers[i]) end local whitelistedPlayers = getWhitelistedPlayers() for i = 1, #whitelistedPlayers do local label = g_ui.createWidget('WhiteListLabel', whiteListPanel) label:setText(whitelistedPlayers[i]) end end function online() defaultTab = addTab(tr('Default'), true) serverTab = addTab(tr('Server Log'), false) if g_game.getClientVersion() < 862 then g_keyboard.bindKeyDown('Ctrl+R', openPlayerReportRuleViolationWindow) end -- open last channels local lastChannelsOpen = g_settings.getNode('lastChannelsOpen') if lastChannelsOpen then local savedChannels = lastChannelsOpen[g_game.getCharacterName()] if savedChannels then for channelName, channelId in pairs(savedChannels) do channelId = tonumber(channelId) if channelId ~= -1 and channelId < 100 then if not table.find(channels, channelId) then g_game.joinChannel(channelId) table.insert(ignoredChannels, channelId) end end end end end scheduleEvent(function() ignoredChannels = {} end, 3000) end function offline() if g_game.getClientVersion() < 862 then g_keyboard.unbindKeyDown('Ctrl+R') end clear() end