Forked from
tome / Tales of MajEyal
3508 commits behind the upstream repository.
UserChat.lua 26.88 KiB
-- TE4 - T-Engine 4
-- Copyright (C) 2009 - 2017 Nicolas Casalini
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
require "engine.class"
local Shader = require "engine.Shader"
local KeyBind = require "engine.KeyBind"
local Mouse = require "engine.Mouse"
local Dialog = require "engine.ui.Dialog"
local Slider = require "engine.ui.Slider"
local Base = require "engine.ui.Base"
--- Module that handles multiplayer chats
-- @classmod engine.UserChat
module(..., package.seeall, class.inherit(Base))
local channel_colors = {
}
--- Creates the log zone
function _M:init()
self.changed = true
self.channels_changed = true
self.cur_channel = "global"
self.channels = {}
self.channel_codes = {}
self.channel_codes_rev = {}
self.max = 500
self.do_display_chans = true
self.on_event = {}
self.full_log = {}
self.last_whispers = {}
self.friends = {}
end
--- Hook up in the current running game
function _M:setupOnGame()
KeyBind:load("chat")
_G.game.key:bindKeys() -- Make sure it updates
_G.game.key:addBinds{
USERCHAT_TALK = function()
self:talkBox()
end,
USERCHAT_SWITCH_CHANNEL = function()
if not self.display_chans then return end
for i = 1, #self.display_chans do
if self.display_chans[i].name == self.cur_channel then
self:selectChannel(self.display_chans[util.boundWrap(i + 1, 1, #self.display_chans)].name)
if game.logChat then game.logChat("Talking in channel: %s", self.cur_channel) end
break
end
end
end,
}
local ok, UC = pcall(require, "mod.class.UserChatExtension")
if ok and UC then self.uc_ext = UC.new(self) end
end
function _M:getChannelCode(chan)
if not game then return "0" end
if not self.channel_codes[chan] then
local chans = table.keys(self.channels)
table.sort(chans, function(a, b)
if a == game.__mod_info.short_name then return 1
elseif b == game.__mod_info.short_name then return nil
elseif a == game.__mod_info.short_name.."-spoiler" then return 1
elseif b == game.__mod_info.short_name.."-spoiler" then return nil
else return a < b
end
end)
self.channel_codes_rev = chans
self.channel_codes = table.keys_to_values(chans)
end
return tostring(self.channel_codes[chan])
end
--- Filter messages
function _M:filterMessage(item)
if config.settings.chat.filter[item.kind] then return true end
if config.settings.chat.ignores[item.login] then return true end
end
function _M:ignoreUser(login)
config.settings.chat.ignores[login] = true
if game.log then game.log("Ignoring all new messages from %s.", login) end
self:saveIgnores()
end
function _M:saveIgnores()
local l = {}
for k, v in pairs(config.settings.chat.ignores) do
if v then l[#l+1] = "chat.ignores["..("%q"):format(k).."]=true" end
end
game:saveSettings("chat.ignores", table.concat(l, "\n"))
end
local urlfind = (lpeg.P"http://" + lpeg.P"https://") * (1-lpeg.P" ")^0
local urlmatch = lpeg.anywhere(lpeg.C(urlfind))
function _M:addMessage(kind, channel, login, name, msg, extra_data, no_change)
if not self.channels[channel] then return end
local color_name = colors.WHITE
if type(name) == "table" then name, color_name = name[1], name[2] end
local url = urlmatch:match(msg)
if url then
pcall(function() msg = msg:lpegSub(urlfind, "#LIGHT_BLUE##{italic}#"..url.."#{normal}##LAST#") end)
end
local item = {channel=channel, kind=kind, login=login, name=name, color_name=color_name, msg=msg, url=url, extra_data=extra_data, timestamp=core.game.getTime()}
if self:filterMessage(item) then return end
if self.uc_ext and self.uc_ext.filterMessage then
if self.uc_ext:filterMessage(item) then return end
end
local log = self.channels[channel].log
table.insert(log, 1, item)
while #log > self.max do table.remove(log) end
self.changed = true
if not no_change and channel ~= self.cur_channel then self.channels[channel].changed = true self.channels_changed = true end
local log = self.full_log
table.insert(log, 1, item)
while #log > self.max do table.remove(log) end
if kind == "whisper" then
local found = nil
for i, l in ipairs(self.last_whispers) do if l == login then found = i break end end
if not found then
table.insert(self.last_whispers, 1, login)
else
table.remove(self.last_whispers, found)
table.insert(self.last_whispers, 1, login)
end
end
end
--- Register to receive events
function _M:registerTalkEvents(fct)
self.on_event[fct] = true
end
--- Register to not receive events
function _M:unregisterTalkEvents(fct)
self.on_event[fct] = nil
end
function _M:getUserColor(e)
local name = e.name
local color = colors.WHITE
if e.status == 'dev' then color = colors.CRIMSON name = "{dev} "..name
elseif e.status == 'mod' then color = colors.GOLD name = "[mod] "..name
elseif e.donator == "oneshot" then color = colors.LIGHT_GREEN
elseif e.donator == "recurring" then color = colors.ROYAL_BLUE end
e.color_name = color
return color, name
end
function _M:event(e)
if not profile.auth then return end
if e.se == "Talk" then
e.msg = e.msg:removeColorCodes():gsub("#", "##")
local color, uname = self:getUserColor(e)
self.channels[e.channel] = self.channels[e.channel] or {users={}, log={}}
if profile and profile.auth and profile.auth.name then
local ni, nj = e.msg:lower():find(profile.auth.name:lower(), 1, true)
if ni and nj then
e.msg = e.msg:sub(1, ni - 1).."#YELLOW##{underline}#"..profile.auth.name.."#{normal}##LAST#"..e.msg:sub(nj + 1)
end
end
self:addMessage("talk", e.channel, e.login, {uname, color}, e.msg)
if type(game) == "table" and game.logChat and self.cur_channel == e.channel then
game.logChat("#YELLOW#<%s> %s", e.name, e.msg)
end
elseif e.se == "Whisper" then
e.msg = e.msg:removeColorCodes()
local color, uname = self:getUserColor(e)
self.channels[self.cur_channel] = self.channels[self.cur_channel] or {users={}, log={}}
self:addMessage("whisper", self.cur_channel, e.login, uname, e.msg)
if type(game) == "table" and game.logChat then
game.logChat("#GOLD#<Whisper from %s> %s", e.name, e.msg)
end
e.channel = self.cur_channel
elseif e.se == "Achievement" then
e.msg = e.msg:removeColorCodes()
local color, uname = self:getUserColor(e)
local kind = "achievement_other"
if e.first then kind = "achievement_first"
elseif e.huge then kind = "achievement_huge"
end
local acolor = "LIGHT_BLUE"
if e.huge then acolor = "GOLD" end
local first = ""
if e.first then first = " for the #FIREBRICK#first time!" end
self.channels[e.channel] = self.channels[e.channel] or {users={}, log={}}
self:addMessage(kind, e.channel, e.login, {uname, color}, "#{italic}##"..acolor.."#has earned the achievement <"..e.msg..">"..first.."#{normal}#", nil, true)
if type(game) == "table" and game.logChat and self.cur_channel == e.channel then
game.logChat("#"..acolor.."#%s has earned the achievement <%s>%s", e.name, e.msg, first)
end
elseif e.se == "SerialData" then
local color, uname = self:getUserColor(e)
local data = zlib.decompress(e.msg)
if not data then return end
data = data:unserialize()
if not data then return end
self.channels[e.channel] = self.channels[e.channel] or {users={}, log={}}
if data.kind == "donator-update" and data.donated > 0 then
if world then pcall(function()
if data.donated <= 5 then world:gainAchievement({id="BRONZE_DONATOR", no_difficulties=true}, game:getPlayer(true))
elseif data.donated <= 15 then world:gainAchievement({id="SILVER_DONATOR", no_difficulties=true}, game:getPlayer(true))
elseif data.donated <= 30 then world:gainAchievement({id="GOLD_DONATOR", no_difficulties=true}, game:getPlayer(true))
elseif data.donated <= 60 then world:gainAchievement({id="STRALITE_DONATOR", no_difficulties=true}, game:getPlayer(true))
else world:gainAchievement({id="VORATUN_DONATOR", no_difficulties=true}, game:getPlayer(true))
end
end) end
local text = ([[#{bold}#Thank you#{normal}# for you donation, your support means a lot for the continued survival of this game.
Your current donation total is #LIGHT_GREEN#%0.2f euro#WHITE# which equals to #ROYAL_BLUE#%d voratun coins#WHITE# to use on te4.org.
Your Item Vault has #TEAL#%d slots#WHITE#.
Again, thank you, and enjoy Eyal!
#{italic}#Your malevolent local god of darkness, #GOLD#DarkGod#{normal}#]]):format(data.donated, data.donated * 10, data.items_vault_slots)
Dialog:simpleLongPopup("Thank you!", text, 600)
elseif self.uc_ext then
self.uc_ext:event(e)
end
elseif e.se == "SelfJoin" then
self:addMessage("join", e.channel, profile.auth.login, e.channel, "#{italic}#Joined channel#{normal}#", nil, true)
self:updateChanList()
self:saveChannels()
elseif e.se == "SelfPart" then
self:addMessage("join", e.channel, profile.auth.login, e.channel, "#{italic}#Left channel#{normal}#", nil, true)
self.channels[e.channel] = nil
self:updateChanList()
self:saveChannels()
elseif e.se == "Join" then
local color, uname = self:getUserColor(e)
self.channels[e.channel] = self.channels[e.channel] or {users={}, log={}}
self.channels[e.channel].users[e.login] = {name=e.name, donator=e.donator, status=e.status, login=e.login}
self.channels_changed = true
self:addMessage("join", e.channel, e.login, {uname, color}, "#{italic}##FIREBRICK#has joined the channel#{normal}#", nil, true)
if type(game) == "table" and game.logChat and e.channel == self.cur_channel then
game.logChat("#{italic}##FIREBRICK#%s has joined channel %s (press space to talk).#{normal}#", e.login, e.channel)
end
self:updateChanList()
elseif e.se == "Part" then
local color, uname = self:getUserColor(e)
self.channels[e.channel] = self.channels[e.channel] or {users={}, log={}}
self.channels[e.channel].users[e.login] = nil
self.channels_changed = true
self:addMessage("join", e.channel, e.login, {uname, color}, "#{italic}##FIREBRICK#has left the channel#{normal}#", nil, true)
if type(game) == "table" and game.logChat and e.channel == self.cur_channel then
game.logChat("#{italic}##FIREBRICK#%s has left channel %s.#{normal}#", e.login, e.channel)
end
self:updateChanList()
elseif e.se == "FriendJoin" then
local color, uname = self:getUserColor(e)
self.friends[e.login] = {name=e.name, donator=e.donator, status=e.status, login=e.login}
if not e.silent then
self:addMessage("friendjoin", self.cur_channel, e.login, {uname, color}, "#{italic}##YELLOW_GREEN#has logged in#{normal}#", nil, true)
if type(game) == "table" and game.logChat then
game.logChat("#{italic}##YELLOW_GREEN#%s has logged in (press space to talk).#{normal}#", e.login, e.channel)
end
end
elseif e.se == "FriendPart" then
local color, uname = self:getUserColor(e)
self.friends[e.login] = nil
self:addMessage("friendpart", self.cur_channel, e.login, {uname, color}, "#{italic}##CRIMSON#has logged off#{normal}#", nil, true)
if type(game) == "table" and game.logChat then
game.logChat("#{italic}##CRIMSON#%s has logged off.#{normal}#", e.login, e.channel)
end
elseif e.se == "UserInfo" then
local info = e.data:unserialize()
if not info then return end
elseif e.se == "ChannelList" then
local info = zlib.decompress(e.data):unserialize()
if not info then return end
if not e.channel or not self.channels[e.channel] then return end
self.channels[e.channel].users = {}
for _, user in ipairs(info.users) do
self.channels[e.channel].users[user.login] = {
id=user.id,
login=user.login,
name=user.name,
donator=user.donator,
status=user.status,
current_char_data=user.current_char,
current_char=user.current_char and user.current_char.title or "unknown",
module=user.current_char and user.current_char.module or "unknown",
valid=user.current_char and user.current_char.valid and "validate" or "not validated",
}
end
self.channels_changed = true
end
for fct, _ in pairs(self.on_event) do
fct(e)
end
end
function _M:isFriend(login)
return self.friends[login]
end
function _M:addFriend(login, id)
core.profile.pushOrder(table.serialize{o="AddFriend", id=id})
end
function _M:removeFriend(login, id)
self.friends[login] = nil
core.profile.pushOrder(table.serialize{o="RemoveFriend", id=id})
end
function _M:join(channel)
if not profile.auth then return end
core.profile.pushOrder(string.format("o='ChatJoin' channel=%q", channel))
self.cur_channel = channel
self.channels[channel] = self.channels[channel] or {users={}, log={}}
self.channels_changed = true
self.changed = true
self:updateChanList(true)
end
function _M:part(channel)
if not profile.auth then return end
core.profile.pushOrder(string.format("o='ChatPart' channel=%q", channel))
self:setCurrentTarget(true, game.__mod_info.short_name)
end
function _M:saveChannels()
local l = {
"chat.channels = chat.channels or {}",
"chat.channels["..string.format("%q", game.__mod_info.short_name).."]={}"
}
for k, v in pairs(profile.chat.channels) do
if v then l[#l+1] = "chat.channels["..string.format("%q", game.__mod_info.short_name).."]["..string.format("%q", k).."]=true" end
end
game:saveSettings("chat.channels."..game.__mod_info.short_name, table.concat(l, "\n"))
end
function _M:selectChannel(channel, no_recurs)
if not self.channels[channel] then return end
self.channels[channel].changed = false
self.cur_channel = channel
self.channels_changed = true
self.changed = true
self.scroll = 0
self:updateChanList(true)
if not no_recurs then self:setCurrentTarget(true, channel, true) end
end
function _M:talk(msg)
if not profile.auth then return end
if not msg or msg == "" then return end
msg = msg:removeColorCodes()
core.profile.pushOrder(string.format("o='ChatTalk' channel=%q msg=%q", self.cur_channel, msg))
end
function _M:whisper(to, msg)
if not profile.auth then return end
if not to or not msg or msg == "" then return end
msg = msg:removeColorCodes()
core.profile.pushOrder(string.format("o='ChatWhisper' target=%q msg=%q", to, msg))
self:addMessage("whisper", self.cur_channel, to, ":"..to, msg)
if type(game) == "table" and game.logChat then game.logChat("%s", msg) end
for fct, _ in pairs(self.on_event) do fct{channel=self.cur_channel, se="JustUpdate"} end
end
function _M:reportUser(to, msg)
if not profile.auth then return end
if not to or not msg or msg == "" then return end
msg = msg:removeColorCodes()
core.profile.pushOrder(string.format("o='ReportUser' target=%q msg=%q", to, msg))
self:addMessage("report", self.cur_channel, to, to, "#VIOLET#Report for "..to.." sent.#LAST#")
if type(game) == "table" and game.logChat then game.logChat("#VIOLET#Report for %s sent.#LAST#", to) end
end
function _M:achievement(name, huge, first)
if not profile.auth then return end
core.profile.pushOrder(string.format("o='ChatAchievement' channel=%q msg=%q huge=%s first=%s", self.cur_channel, name, tostring(huge), tostring(first)))
end
--- Request a line to send
function _M:talkBox(on_end, only_friends)
if not profile.auth then return end
local Talkbox = require "engine.dialogs.Talkbox"
local d = Talkbox.new(self, on_end, only_friends)
if not d.nobody then game:registerDialog(d)
else game.log("#CRIMSON#You are not subscribed to any channel, you can change that in the game options.#LAST#") end
self:updateChanList()
end
--- Sets the current talk target, channel or whisper
function _M:setCurrentTarget(channel, name, no_switch)
if channel and not self.channels[name] then return end
self.cur_target = {channel and "channel" or "whisper", name}
if channel and not no_switch then self:selectChannel(name) end
end
--- Gets the current talk target, channel or whisper
function _M:getCurrentTarget()
if not self.cur_target then return "channel", self.cur_channel end
return self.cur_target[1], self.cur_target[2]
end
function _M:findChannel(name)
for cname, data in pairs(self.channels) do
if cname:lower() == name:lower() then return cname end
end
end
function _M:findUser(name)
for login, data in pairs(self.friends) do
print("===friendtest", data.name:lower(), name:lower())
if data.name:lower() == name:lower() then return data.name end
end
if not self.channels[self.cur_channel] then return end
for login, data in pairs(self.channels[self.cur_channel].users) do
if data.name:lower() == name:lower() then return data.name end
end
end
function _M:updateChanList(force)
local time = os.time()
if force or not self.last_chan_update or self.last_chan_update + 60 < time then
self.last_chan_update = time
core.profile.pushOrder(string.format("o='ChatChannelList' channel=%q", self.cur_channel))
end
end
--- Display user infos
function _M:showUserInfo(login)
if not profile.auth then return end
local popup = Dialog:simpleWaiter("Requesting...", "Requesting user info...")
core.display.forceRedraw()
core.profile.pushOrder(string.format("o='ChatUserInfo' login=%q", login))
local data = nil
profile:waitEvent("UserInfo", function(e) data=e.data end, 5000)
popup:done()
if not data then
Dialog:simplePopup("Error", "The server does not know about this player.")
return
end
data = zlib.decompress(data):unserialize()
local UserInfo = require "engine.dialogs.UserInfo"
game:registerDialog(UserInfo.new(data))
end
--- Get user infos
function _M:getUserInfo(login)
if not profile.auth then return end
local popup = Dialog:simpleWaiter("Requesting...", "Requesting user info...")
core.display.forceRedraw()
core.profile.pushOrder(string.format("o='ChatUserInfo' login=%q", login))
local data = nil
profile:waitEvent("UserInfo", function(e) data=e.data end, 5000)
popup:done()
if not data then
return
end
data = zlib.decompress(data):unserialize()
return data
end
----------------------------------------------------------------
-- UI Section
----------------------------------------------------------------
--- Returns the full log
function _M:getLog(channel, extra, timestamp)
channel = channel or self.cur_channel
local log = {}
if self.channels[channel] then
for _, i in ipairs(self.channels[channel].log) do
if timestamp and i.timestamp <= timestamp then break end
local tstr
if i.kind == "whisper" then
tstr = tstring{{"color", 0xcb,0x87,0xd2}, "<", i.name, "> "}
elseif i.kind == "join" then
tstr = tstring{{"color", "FIREBRICK"}, "[", i.name, "]" }
else
tstr = tstring{"<", {"color",unpack(colors.simple(i.color_name or colors.WHITE))}, i.name, {"color", "LAST"}, "> "}
end
tstr:merge(i.msg:toTString())
if extra then
log[#log+1] = {str=tstr:toString(), src=i}
else
log[#log+1] = tstr:toString()
end
end
end
return log
end
function _M:getLogLast(channel)
channel = channel or self.cur_channel
if self.channels[channel] then
if not self.channels[channel].log[1] then return 0 end
return self.channels[channel].log[1].timestamp
else
return 0
end
end
--- Make a dialog popup with the full log
function _M:showLogDialog(title, shadow)
local log = self:getLog(self.cur_channel)
local d = require_first("mod.dialogs.ShowLog", "engine.dialogs.ShowLog").new(title or "Chat Log", shadow, {log=log})
game:registerDialog(d)
end
--- Resize the display area
function _M:resize(x, y, w, h, fontname, fontsize, color, bgcolor)
self.color = color or {255,255,255}
if type(bgcolor) ~= "string" then
self.bgcolor = bgcolor or {0,0,0}
else
self.bgcolor = {0,0,0}
self.bg_image = bgcolor
end
self.font = core.display.newFont(fontname or "/data/font/DroidSans.ttf", fontsize or 12)
self.font_h = self.font:lineSkip()
self.scroll = 0
self.changed = true
self.frame_sel = self:makeFrame("ui/selector-sel", 1, 5 + self.font_h)
self.frame = self:makeFrame("ui/selector", 1, 5 + self.font_h)
self.display_x, self.display_y = math.floor(x), math.floor(y)
self.w, self.h = math.floor(w), math.floor(h)
self.fw, self.fh = self.w - 4, self.font:lineSkip()
self.max_display = math.floor(self.h / self.fh)
if self.bg_image then
local fill = core.display.loadImage(self.bg_image)
local fw, fh = fill:getSize()
self.bg_surface = core.display.newSurface(w, h)
self.bg_surface:erase(0, 0, 0)
for i = 0, w, fw do for j = 0, h, fh do
self.bg_surface:merge(fill, i, j)
end end
self.bg_texture, self.bg_texture_w, self.bg_texture_h = self.bg_surface:glTexture()
end
self.scrollbar = Slider.new{size=self.h - 20, max=1, inverse=true}
self.mouse = Mouse.new()
self.mouse.delegate_offset_x = self.display_x
self.mouse.delegate_offset_y = self.display_y
self.mouse:registerZone(0, 0, self.w, self.h, function(button, x, y, xrel, yrel, bx, by, event) self:mouseEvent(button, x, y, xrel, yrel, bx, by, event) end)
self.last_chan_update = 0
end
function _M:mouseEvent(button, x, y, xrel, yrel, bx, by, event)
if button == "wheelup" then self:scrollUp(1)
elseif button == "wheeldown" then self:scrollUp(-1)
elseif event == "button" and button == "left" and y <= self.frame.h and self.do_display_chans then
local w = 0
local last_ok = nil
for i = 1, #self.display_chans do
local item = self.display_chans[i]
last_ok = item
w = w + item.w + 4
if w > x then break end
end
if last_ok then
local old = self.cur_channel
self:selectChannel(last_ok.name)
if old == self.cur_channel then self:showLogDialog(nil, self.shadow) end
end
else
if not self.on_mouse or not self.dlist then return end
local citem = nil
local ci
for i = 1, #self.dlist do
local item = self.dlist[i]
if item.dh and by >= item.dh - self.mouse.delegate_offset_y then citem = self.dlist[i].src ci=i break end
end
if citem and citem.url and button == "left" and event == "button" and (citem.fade or 1) > 0 then
util.browserOpenUrl(citem.url, {is_external=true})
else
self.on_mouse(citem and citem.login and self.channels[self.cur_channel] and self.channels[self.cur_channel].users and self.channels[self.cur_channel].users[citem.login], citem and citem.login and citem, button, event, x, y, xrel, yrel, bx, by)
end
end
end
function _M:enableShadow(v)
self.shadow = v
end
function _M:enableFading(v)
self.fading = v
end
function _M:enableDisplayChans(v)
self.do_display_chans = v
end
function _M:onMouse(fct)
self.on_mouse = fct
end
function _M:display()
-- Changed channels list
if self.channels_changed then
self.display_chans = {}
local list = {}
for name, data in pairs(self.channels) do list[#list+1] = name end
table.sort(list, function(a,b) if a == "global" then return 1 elseif b == "global" then return nil else return a < b end end)
for i, name in ipairs(list) do
local oname = name
local nb_users = 0
for _, _ in pairs(self.channels[name].users) do nb_users = nb_users + 1 end
name = "["..name:capitalize().." ("..nb_users..")]"
local tex = self:drawFontLine(self.font_mono, name)
table.update(tex, {name = oname, sel = oname == self.cur_channel})
self.display_chans[#self.display_chans+1] = tex
end
self.channels_changed = false
end
-- If nothing changed, return the same surface as before
if not self.changed then return end
self.changed = false
-- Erase and the display
self.dlist = {}
local h = 0
local log = {}
if self.full_log then log = self.full_log end
local old_style = self.font:getStyle()
for z = 1 + self.scroll, #log do
local stop = false
local tstr
if log[z].kind == "whisper" then
tstr = tstring{{"color", 0xcb,0x87,0xd2}, "<", log[z].name, ">" }
elseif log[z].kind == "join" then
tstr = tstring{{"color", "FIREBRICK"}, "[", self:getChannelCode(log[z].channel), "-", log[z].name, "]" }
else
tstr = tstring{"[", self:getChannelCode(log[z].channel), "-", log[z].channel, "] <", {"color",unpack(colors.simple(log[z].color_name))}, log[z].name, {"color", "LAST"}, "> "}
end
tstr:merge(log[z].msg:toTString())
--local gen = tstring.makeLineTextures(tstr, self.w, self.font_mono)
local gen = self.font_mono:draw(tstr:toString(), self.w, 255, 255, 255)
for i = #gen, 1, -1 do
gen[i].login = log[z].login
gen[i].extra_data = log[z].extra_data
self.dlist[#self.dlist+1] = {item=gen[i], date=log[z].reset_fade or log[z].timestamp, src=log[z]}
h = h + self.fh
if h > self.h - self.fh - (self.do_display_chans and self.fh or 0) then stop=true break end
end
if stop then break end
end
self.font:setStyle(old_style)
return
end
function _M:toScreen()
self:display()
local shader = Shader.default.textoutline and Shader.default.textoutline.shad
if self.bg_texture then self.bg_texture:toScreenFull(self.display_x, self.display_y, self.w, self.h, self.bg_texture_w, self.bg_texture_h) end
local h = self.display_y + self.h - self.fh
local now = core.game.getTime()
for i = 1, #self.dlist do
local item = self.dlist[i].item
local fade = 1
if self.fading and self.fading > 0 then
fade = now - self.dlist[i].date
if fade < self.fading * 1000 then fade = 1
elseif fade < self.fading * 2000 then fade = (self.fading * 2000 - fade) / (self.fading * 1000)
else fade = 0 end
self.dlist[i].src.faded = fade
end
self.dlist[i].dh = h
if self.shadow then
if shader then
shader:use(true)
shader:uniOutlineSize(0.7, 0.7)
shader:uniTextSize(item._tex_w, item._tex_h)
else
item._tex:toScreenFull(self.display_x+2, h+2, item.w, item.h, item._tex_w, item._tex_h, 0,0,0, self.shadow * fade)
end
end
item._tex:toScreenFull(self.display_x, h, item.w, item.h, item._tex_w, item._tex_h, 1, 1, 1, fade)
if self.shadow and shader then shader:use(false) end
h = h - self.fh
end
if self.do_display_chans then
local w = 0
for i = 1, #self.display_chans do
local item = self.display_chans[i]
local f = item.sel and self.frame_sel or self.frame
f.w = item.w
Base:drawFrame(f, self.display_x + w, self.display_y)
if self.channels[item.name].changed then
local glow = (1+math.sin(core.game.getTime() / 500)) / 2 * 100 + 120
Base:drawFrame(f, self.display_x + w, self.display_y, 139/255, 210/255, 77/255, glow / 255)
end
self:textureToScreen(item, self.display_x + w + self.frame.b4.w, self.display_y + (self.frame.h - self.font_h)/2)
w = w + item.w + 4
end
end
if not self.fading then
self.scrollbar.pos = self.scroll
self.scrollbar.max = self.max - self.max_display + 1
self.scrollbar:display(self.display_x + self.w - self.scrollbar.w, self.display_y)
end
end
--- Scroll the zone
-- @param i number representing how many lines to scroll
function _M:scrollUp(i)
local log = {}
if self.channels[self.cur_channel] then log = self.channels[self.cur_channel].log end
self.scroll = self.scroll + i
if self.scroll > #log - 1 then self.scroll = #log - 1 end
if self.scroll < 0 then self.scroll = 0 end
self.changed = true
self:resetFade()
end
function _M:resetFade()
local log = {}
if self.channels[self.cur_channel] then log = self.channels[self.cur_channel].log end
-- Reset fade
for i = 1,#log do
log[i].reset_fade = core.game.getTime()
end
end