-- OT-MMO: logros con títulos (Y), aviso al conseguir uno y perfil del jugador al mirarlo. Solo pinta: el servidor -- (server/scripts/logros/logros.lua, opcode 118) decide qué se consigue y qué enseña cada perfil. -- «L|{lista,puntos,titulo,titulos,equipo}» estado · «G|{nombre,puntos,titulo}» conseguido · «F|{…}» perfil -- cliente: «P» pedir · «T|id» título · «E|0/1» mostrar equipo · «V|nombre» ver perfil de alguien local OPCODE = 118 local CATEGORIAS = { 'Todos', 'Progreso', 'Misiones', 'Jefes', 'Grupo', 'Exploración', 'Oficios', 'Monturas' } -- Colores de calidad: los mismos que en todo el cliente (gamelib/items.lua). local function colorCalidad(c) return ItemsDatabase and ItemsDatabase.calidadColor and ItemsDatabase.calidadColor[tostring(c)] end logrosController = Controller:new() local ventana, aviso, perfil = nil, nil, nil local estado = { lista = {}, puntos = 0, titulos = {}, titulo = '', equipo = false } local categoria = 'Todos' local colaAvisos, avisando = {}, false local pintando = false -- evita reenviar al servidor lo que acaba de llegar local function enviar(texto) local protocol = g_game.getProtocolGame() if protocol then protocol:sendExtendedOpcode(OPCODE, texto) end end local function escribiendo() local w = rootWidget while w:getFocusedChild() do w = w:getFocusedChild() end return w:getClassName() == 'UITextEdit' and w:isVisible() end local function fecha(t) return os.date('%d/%m/%Y', t) end -- Ventana de logros ---------------------------------------------------------------------------------------------------- local function pintarLista() if not ventana then return end ventana.lista:destroyChildren() for _, l in ipairs(estado.lista) do if categoria == 'Todos' or l.cat == categoria then local f = g_ui.createWidget('LgFila', ventana.lista) local hecho = l.fecha > 0 f.medalla:setText(tostring(l.puntos)) f.nombre:setText(l.nombre .. (l.titulo ~= '' and (' \183 título «' .. l.titulo .. '»') or '')) f.texto:setText(l.texto) if hecho then f.medalla:setColor(OtmmoTheme.color.accent) f.medalla:setBorderColor(OtmmoTheme.color.accent) f.nombre:setColor(OtmmoTheme.color.text) f.texto:setColor(OtmmoTheme.color.text2) f.derecha:setText(fecha(l.fecha)) elseif l.meta then f.derecha:setText(l.progreso .. ' / ' .. l.meta) f.barra:show() f.barra.fill:setWidth(math.max(1, math.floor(108 * l.progreso / l.meta))) end end end end local function pintarVentana() if not ventana then return end pintando = true local n = 0 for _, l in ipairs(estado.lista) do if l.fecha > 0 then n = n + 1 end end ventana.puntos:setText(estado.puntos .. ' puntos') ventana.resumen:setText(n .. ' de ' .. #estado.lista .. ' logros') ventana.titulo:clearOptions() ventana.titulo:addOption('Sin título', '') local elegido = 'Sin título' for _, t in ipairs(estado.titulos) do ventana.titulo:addOption(t.titulo, t.id) if t.id == estado.titulo then elegido = t.titulo end end ventana.titulo:setCurrentOption(elegido, true) ventana.mostrarEquipo:setChecked(estado.equipo) pintarLista() pintando = false end local function crearPestanas() ventana.pestanas:destroyChildren() for _, c in ipairs(CATEGORIAS) do local p = g_ui.createWidget('GameTab', ventana.pestanas) p:setText(c) p:setChecked(c == categoria) p.onClick = function() categoria = c for _, o in ipairs(ventana.pestanas:getChildren()) do o:setChecked(o:getText() == c) end pintarLista() end end end function toggle() if not ventana then return end if ventana:isVisible() then return ventana:hide() end pintarVentana() ventana:show() ventana:raise() ventana:focus() enviar('P') end -- Aviso: uno detrás de otro, 5 s cada uno ---------------------------------------------------------------------------- local function siguienteAviso() if avisando or #colaAvisos == 0 or not aviso then return end avisando = true local d = table.remove(colaAvisos, 1) aviso.medalla:setText(tostring(d.puntos)) aviso.nombre:setText(d.nombre) aviso.titulo:setText(d.titulo ~= '' and ('Título nuevo: «' .. d.titulo .. '»') or d.texto) aviso:setOpacity(0) aviso:show() aviso:raise() local t0 = g_clock.millis() local ev ev = cycleEvent(function() local dt = g_clock.millis() - t0 local a = dt < 300 and dt / 300 or dt > 4600 and math.max(0, 1 - (dt - 4600) / 400) or 1 aviso:setOpacity(a) if dt >= 5000 then removeEvent(ev) aviso:hide() avisando = false siguienteAviso() end end, 30) end -- Perfil ------------------------------------------------------------------------------------------------------------- function cerrarPerfil() if perfil then perfil:hide() end end local function abrirPerfil(d) if not perfil then return end perfil:setText(d.propio and 'Tu perfil' or 'Perfil') perfil.nombre:setText(d.nombre) perfil.titulo:setText(d.titulo ~= '' and ('«' .. d.titulo .. '»') or '') perfil.linea:setText('Nivel ' .. d.nivel .. ' \183 ' .. d.clase .. (d.rama ~= '' and (' \183 ' .. d.rama) or '') .. (d.puntuacion and (' \183 Equipo ' .. d.puntuacion) or '')) -- puntuación: solo si muestra su equipo (opcode 141) perfil.puntos:setText(d.puntos .. ' puntos de logro \183 ' .. d.total .. ' de ' .. d.de) local a = d.atuendo or {} perfil.retrato.criatura:setCreatureSize(88) perfil.retrato.criatura:setOutfit({ type = a[1], head = a[2], body = a[3], legs = a[4], feet = a[5], addons = a[6], mount = 0 }) perfil.logros:destroyChildren() if #d.logros == 0 then local t = g_ui.createWidget('GameTextMuted', perfil.logros) t:setText('Todavía ninguno.') end for _, l in ipairs(d.logros) do local t = g_ui.createWidget('GameText', perfil.logros) t:setText(fecha(l.fecha) .. ' ' .. l.nombre .. ' (' .. l.puntos .. ')') t:setTooltip(l.texto) end perfil.equipo:destroyChildren() perfil.equipo:setVisible(d.mostrar) perfil.oculto:setVisible(not d.mostrar) for _, it in ipairs(d.equipo or {}) do local c = g_ui.createWidget('LgCasilla', perfil.equipo) c.item:setItemId(it.id) c:setTooltip(it.nombre) if colorCalidad(it.calidad) then c:setBorderColor(colorCalidad(it.calidad)) end end perfil:show() perfil:raise() end function verPerfil(nombre) enviar('V|' .. (nombre or '')) end local function onOpcode(protocol, opcode, buffer) local tipo, resto = buffer:sub(1, 1), buffer:sub(3) local ok, d = pcall(json.decode, resto) if not ok or type(d) ~= 'table' then return end if tipo == 'L' then estado = d if ventana and ventana:isVisible() then pintarVentana() end elseif tipo == 'G' then table.insert(colaAvisos, d) siguienteAviso() elseif tipo == 'F' then abrirPerfil(d) end end -- Para probar sin servidor: modules.game_logros.probar('L', '') function probar(tipo, texto) onOpcode(nil, OPCODE, tipo .. '|' .. texto) end function logrosController:onInit() g_ui.importStyle('estilos') ventana = g_ui.displayUI('logros') OtmmoVentanas.recordar(ventana, 'logros') -- posición entre sesiones (fase 4) ventana:hide() crearPestanas() ventana.titulo.onOptionChange = function(widget, texto, valor) if not pintando then enviar('T|' .. (valor or '')) end end ventana.mostrarEquipo.onCheckChange = function(widget, checked) if not pintando then enviar('E|' .. (checked and '1' or '0')) end end aviso = g_ui.loadUI('aviso', modules.game_interface.getRootPanel()) aviso:addAnchor(AnchorTop, 'parent', AnchorTop) aviso:addAnchor(AnchorHorizontalCenter, 'parent', AnchorHorizontalCenter) aviso:setMarginTop(70) aviso:hide() perfil = g_ui.displayUI('perfil') OtmmoVentanas.recordar(perfil, 'perfil') -- posición entre sesiones (fase 4) perfil:hide() ProtocolGame.registerExtendedOpcode(OPCODE, onOpcode) Keybind.new('Ventanas', 'Logros', { [CHAT_MODE.ON] = '', [CHAT_MODE.OFF] = 'Y' }, '') Keybind.bind('Ventanas', 'Logros', { { type = KEY_DOWN, callback = function() if not escribiendo() then toggle() end end } }) if modules.game_interface.addMenuHook then modules.game_interface.addMenuHook('logros', 'Ver perfil', function(menuPosition, lookThing, useThing, creatureThing) verPerfil(creatureThing:getName()) end, function(menuPosition, lookThing, useThing, creatureThing) return creatureThing and creatureThing:isPlayer() end) end end function logrosController:onGameStart() enviar('P') end function logrosController:onGameEnd() if ventana then ventana:hide() end if perfil then perfil:hide() end colaAvisos = {} end function logrosController:onTerminate() ProtocolGame.unregisterExtendedOpcode(OPCODE) Keybind.delete('Ventanas', 'Logros') if modules.game_interface.removeMenuHook then modules.game_interface.removeMenuHook('logros', 'Ver perfil') end for _, w in ipairs({ ventana, aviso, perfil }) do if w then w:destroy() end end ventana, aviso, perfil = nil, nil, nil end