SpeakTypesSettings = { say = { speakType = SpeakSay, color = '#FFFF00' }, whisper = { speakType = SpeakWhisper, color = '#FFFF00' }, yell = { speakType = SpeakYell, color = '#FFFF00' }, broadcast = { speakType = SpeakBroadcast, color = '#F55E5E' }, private = { speakType = SpeakPrivate, color = '#5FF7F7', private = true }, privateRed = { speakType = SpeakPrivateRed, color = '#F55E5E', private = true }, privatePlayerToPlayer = { speakType = SpeakPrivate, color = '#9F9DFD', private = true }, privatePlayerToNpc = { speakType = SpeakPrivatePlayerToNpc, color = '#9F9DFD', private = true, npcChat = true }, privateNpcToPlayer = { speakType = SpeakPrivateNpcToPlayer, color = '#5FF7F7', private = true, npcChat = true }, channelYellow = { speakType = SpeakChannelYellow, color = '#FFFF00' }, channelWhite = { speakType = SpeakChannelWhite, color = '#FFFFFF' }, channelRed = { speakType = SpeakChannelRed, color = '#F55E5E' }, channelOrange = { speakType = SpeakChannelOrange, color = '#FE6500' }, monsterSay = { speakType = SpeakMonsterSay, color = '#FE6500', hideInConsole = true}, monsterYell = { speakType = SpeakMonsterYell, color = '#FE6500', hideInConsole = true}, } SpeakTypes = { [SpeakSay] = SpeakTypesSettings.say, [SpeakWhisper] = SpeakTypesSettings.whisper, [SpeakYell] = SpeakTypesSettings.yell, [SpeakBroadcast] = SpeakTypesSettings.broadcast, [SpeakPrivate] = SpeakTypesSettings.private, [SpeakPrivateRed] = SpeakTypesSettings.privateRed, [SpeakPrivatePlayerToNpc] = SpeakTypesSettings.privatePlayerToNpc, [SpeakPrivateNpcToPlayer] = SpeakTypesSettings.privateNpcToPlayer, [SpeakChannelYellow] = SpeakTypesSettings.channelYellow, [SpeakChannelWhite] = SpeakTypesSettings.channelWhite, [SpeakChannelRed] = SpeakTypesSettings.channelRed, [SpeakChannelOrange] = SpeakTypesSettings.channelOrange, [SpeakMonsterSay] = SpeakTypesSettings.monsterSay, [SpeakMonsterYell] = SpeakTypesSettings.monsterYell, } SayModes = { [1] = { speakTypeDesc = 'whisper', icon = 'icons/whisper.png' }, [2] = { speakTypeDesc = 'say', icon = 'icons/say.png' }, [3] = { speakTypeDesc = 'yell', icon = 'icons/yell.png' } } MAX_HISTORY = 1000 MAX_LINES = 100 HELP_CHANNEL = 9 consolePanel = nil consoleContentPanel = nil consoleTabBar = nil consoleTextEdit = nil channels = nil channelsWindow = nil ownPrivateName = nil messageHistory = {} currentMessageIndex = 0 ignoreNpcMessages = false function init() connect(g_game, { onCreatureSpeak = onCreatureSpeak, onChannelList = onChannelList, onOpenChannel = onOpenChannel, onOpenPrivateChannel = onOpenPrivateChannel, onOpenOwnPrivateChannel = onOpenOwnPrivateChannel, onCloseChannel = onCloseChannel, onGameStart = onGameStart, onGameEnd = clear }) consolePanel = g_ui.loadUI('console.otui', modules.game_interface.getBottomPanel()) consoleTextEdit = consolePanel:getChildById('consoleTextEdit') consoleContentPanel = consolePanel:getChildById('consoleContentPanel') consoleTabBar = consolePanel:getChildById('consoleTabBar') consoleTabBar:setContentWidget(consoleContentPanel) channels = {} addTab(tr('Default'), true) addTab(tr('Server Log'), false) 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 consolePanel:getChildById('nextChannelButton').onClick = function() consoleTabBar:selectNextTab() end consolePanel:getChildById('prevChannelButton').onClick = function() consoleTabBar:selectPrevTab() end 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) end function terminate() disconnect(g_game, { onCreatureSpeak = onCreatureSpeak, onChannelList = onChannelList, onOpenChannel = onOpenChannel, onOpenPrivateChannel = onOpenPrivateChannel, onOpenOwnPrivateChannel = onOpenPrivateChannel, onCloseChannel = onCloseChannel, onGameStart = onGameStart, onGameEnd = clear }) for channelid, channelname in pairs(channels) do if tonumber(channelid) and tonumber(channelid) ~= 0 then g_game.leaveChannel(channelid) end end channels = {} g_keyboard.unbindKeyDown('Ctrl+O') g_keyboard.unbindKeyDown('Ctrl+E') g_keyboard.unbindKeyDown('Ctrl+H') if channelsWindow then channelsWindow:destroy() channelsWindow = nil end consolePanel:destroy() consolePanel = nil consoleTextEdit = nil consoleContentPanel = nil consoleTabBar = nil ownPrivateName = nil Console = nil end function onTabChange(tabBar, tab) if tab:getText() == tr('Default') or tab:getText() == tr('Server Log') then consolePanel:getChildById('closeChannelButton'):disable() else consolePanel:getChildById('closeChannelButton'):enable() end end function clear() local lastChannelsOpen = {} local player = g_game.getLocalPlayer() if(player) then local char = player:getName() lastChannelsOpen[char] = {} for channelId, channelName in pairs(channels) do table.insert(lastChannelsOpen[char], channelId) end end -- save last open channels g_settings.setNode('LastChannelsOpen', lastChannelsOpen) for _, channelName in pairs(channels) do local tab = consoleTabBar:getTab(channelName) consoleTabBar:removeTab(tab) end channels = {} consoleTabBar:getTab(tr('Default')).tabPanel:getChildById('consoleBuffer'):destroyChildren() consoleTabBar:getTab(tr('Server Log')).tabPanel:getChildById('consoleBuffer'):destroyChildren() local npcTab = consoleTabBar:getTab('NPCs') if npcTab then consoleTabBar:removeTab(npcTab) end consoleTextEdit:clearText() if channelsWindow then channelsWindow:destroy() channelsWindow = nil end end function setTextEditText(text) consoleTextEdit:setText(text) end function openHelp() g_game.joinChannel(HELP_CHANNEL) 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) end if focus then consoleTabBar:selectTab(tab) elseif name ~= tr('Server Log') then consoleTabBar:blinkTab(tab) end return tab end function removeCurrentTab() local tab = consoleTabBar:getCurrentTab() if tab:getText() == tr('Default') or tab:getText() == tr('Server Log') then return end -- notificate the server that we are leaving the channel if tab.channelId then 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 getTab(name) return consoleTabBar:getTab(name) end function getCurrentTab() return consoleTabBar:getCurrentTab() end function addChannel(name, id) channels[id] = name local tab = addTab(name, true) tab.channelId = id return tab end function addPrivateChannel(receiver) channels[receiver] = receiver return addTab(receiver, true) end function addPrivateText(text, speaktype, name, isPrivateCommand, creatureName) local focus = false if speaktype.speakType == SpeakPrivateNpcToPlayer then name = 'NPCs' focus = true end local privateTab = getTab(name) if privateTab == nil then if (Options.getOption('showPrivateMessagesInConsole') and not focus) or (isPrivateCommand and not privateTab) then privateTab = getTab(tr('Default')) 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 function addTabText(text, speaktype, tab, creatureName) if 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' .. panel:getChildCount()) label:setText(text) label:setColor(speaktype.color) consoleTabBar:blinkTab(tab) label.onMouseRelease = function (self, mousePos, mouseButton) popupMenu(mousePos, mouseButton, creatureName, text) end if consoleBuffer:getChildCount() > MAX_LINES then consoleBuffer:getFirstChild():destroy() end end function popupMenu(mousePos, mouseButton, creatureName, text) if mouseButton == MouseRightButton then local menu = g_ui.createWidget('PopupMenu') if creatureName then if creatureName ~= g_game.getCharacterName() then menu:addOption(tr('Message to ' .. creatureName), function () g_game.openPrivateChannel(creatureName) end) if (not Player:hasVip(creatureName)) then menu:addOption(tr('Add to VIP list'), function () g_game.addVip(creatureName) end) end -- TODO ignore creatureName menu:addSeparator() end --TODO select all menu:addOption(tr('Copy message'), function () g_window.setClipboardText(text) end) if RuleViolation.hasWindowAccess() then menu:addSeparator() menu:addOption(tr('Rule Violation'), function() RuleViolation.show(creatureName, text:match('.+%:%s(.+)')) end) end menu:addSeparator() menu:addOption(tr('Copy name'), function () g_window.setClipboardText(creatureName) end) else --TODO select all menu:addOption(tr('Copy message'), function () g_window.setClipboardText(text) end) end menu:display(mousePos) end end function sendCurrentMessage() local message = consoleTextEdit:getText() if #message == 0 then return end consoleTextEdit:clearText() -- get current channel local tab = getCurrentTab() -- handling chat commands local originalMessage = message local chatCommandSayMode local chatCommandPrivate local chatCommandPrivateReady local chatCommandMessage = message:match("^%#y (.*)") if chatCommandMessage ~= nil then chatCommandSayMode = 'yell' end -- player used yell command message = chatCommandMessage or message local chatCommandMessage = message:match("^%#w (.*)") if chatCommandMessage ~= nil then chatCommandSayMode = 'whisper' end -- player used whisper message = chatCommandMessage or message 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 consoleTextEdit:setText('*'.. 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 -- when talking on server log, the message goes to default channel local name = tab:getText() if name == tr('Server Log') then tab = getTab(tr('Default')) name = tr('Default') end local speaktypedesc if (tab.channelId or name == tr('Default')) and not chatCommandPrivateReady then if name == tr('Default') then speaktypedesc = chatCommandSayMode or SayModes[consolePanel:getChildById('sayModeButton').sayMode].speakTypeDesc if speaktypedesc ~= 'say' then sayModeChange(2) end -- head back to say mode else speaktypedesc = 'channelYellow' end g_game.talkChannel(SpeakTypesSettings[speaktypedesc].speakType, tab.channelId, message) return else local isPrivateCommand = false if chatCommandPrivateReady then speaktypedesc = 'privatePlayerToPlayer' name = chatCommandPrivate isPrivateCommand = true elseif tab.npcChat then speaktypedesc = 'privatePlayerToNpc' else speaktypedesc = 'privatePlayerToPlayer' end local speaktype = SpeakTypesSettings[speaktypedesc] local player = g_game.getLocalPlayer() g_game.talkPrivate(speaktype.speakType, name, message) message = applyMessagePrefixies(player:getName(), player:getLevel(), message) addPrivateText(message, speaktype, name, 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 ignoreNpcMessages(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] consoleTextEdit:setText(command) else consoleTextEdit:clearText() end end end function applyMessagePrefixies(name, level, message) if name then if Options.getOption('showLevelsInConsole') and level > 0 then message = name .. ' [' .. level .. ']: ' .. message else message = name .. ': ' .. message end end return message end function onCreatureSpeak(name, level, speaktype, message, channelId, creaturePos) if ignoreNpcMessages and speaktype == SpeakPrivateNpcToPlayer then return end local defaultMessage = speaktype < 3 and true or false speaktype = SpeakTypes[speaktype] if speaktype.hideInConsole then return end local composedMessage = applyMessagePrefixies(name, level, message) if speaktype.private then addPrivateText(composedMessage, speaktype, name, false, name) if Options.getOption('showPrivateMessagesOnScreen') then if(speaktype.speakType ~= SpeakPrivateNpcToPlayer) then TextMessage.displayPrivate(name .. ':\n' .. message) end end else local channel = tr('Default') if not defaultMessage then channel = channels[channelId] end if channel then addText(composedMessage, speaktype, channel, name) elseif channelId ~= 0 then -- 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 local function onOpenChannel(channelId, channelName) addChannel(channelName, channelId) end local function onOpenPrivateChannel(receiver) addPrivateChannel(receiver) end local function onOpenOwnPrivateChannel(channelId, channelName) local privateTab = getTab(channelName) if privateTab == nil then addChannel(channelName, channelId) end ownPrivateName = channelName end local 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 local function doChannelListSubmit() local channelListPanel = channelsWindow:getChildById('channelList') local openPrivateChannelWith = channelsWindow:getChildById('openPrivateChannelWith'):getText() if openPrivateChannelWith ~= '' then g_game.openPrivateChannel(openPrivateChannelWith) 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 local function onChannelList(channelList) if channelsWindow then channelsWindow:destroy() end channelsWindow = g_ui.displayUI('channelswindow.otui') 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 local function onGameStart() -- open last channels local player = g_game.getLocalPlayer() if(player) then local char = player:getName() local lastChannelsOpen = g_settings.getNode('LastChannelsOpen') if(not table.empty(lastChannelsOpen) and lastChannelsOpen[char]) then for channelName, channelId in ipairs(lastChannelsOpen[char]) do channelId = tonumber(channelId) if channelId ~= 0 then if not table.find(channels, channelId) then g_game.joinChannel(channelId) end end end end end local tab = getTab(tr('Default')) if tab then --[[ Known Issue: The server is calling to open channels after onGameStart is executed causing it to focus the last tab opened. Fix: Don't save channels to the settings that are opened by the server. ]] addEvent(function() consoleTabBar:selectTab(tab) end, true) end end