-- OT-MMO: ventana del maestro de clase (Corvo), estilo entrenador de WoW. Solo dibuja y pide: el servidor -- (server/scripts/clases/maestro.lua) manda la lista de tu clase con su estado y valida cada compra. Opcode 125: -- «A|clase|oro|nivel|fila~fila...» abre o actualiza (fila = nombre^nivel^precio^estado^reemplaza^mision; -- estado S sabido · D disponible · N nivel insuficiente · O sin oro) · «R|1|texto» o «R|0|texto» resultado · «X» cierra. -- El cliente envía «C|nombre» (aprender). Íconos y datos: SpellInfo (gamelib/spells.lua) y LIBRO_DATOS (game_libro). local OPCODE = 125 local C = OtmmoTheme.color local CLASES = { guardian = 'Guardián', cazador = 'Cazador', druida = 'Druida', brujo = 'Brujo', hechicero = 'Hechicero' } local ESTADOS = { -- texto de la fila y color S = { 'Aprendido', C.ok }, D = { 'Disponible', C.accent }, N = { 'Nivel insuficiente', C.errorText }, O = { 'Sin oro', C.warn }, } maestroController = Controller:new() local ventana = nil local datos = nil -- { clase, oro, nivel, filas = { {nombre, nivel, precio, estado, reemplaza, mision} } } local elegido = nil -- nombre del hechizo elegido local soloDisponibles = false local esperando = false -- compra enviada, sin respuesta todavía local ultimo, ultimoOk = nil, false -- resultado («R») que se pinta con la lista que llega detrás local function enviar(texto) local protocol = g_game.getProtocolGame() if protocol then protocol:sendExtendedOpcode(OPCODE, texto) end end local function miles(n) local s = tostring(n) while true do local r, k = s:gsub('^(%d+)(%d%d%d)', '%1,%2') s = r if k == 0 then return s end end end local function segundos(ms) local s = ms / 1000 return (s == math.floor(s) and tostring(math.floor(s)) or (string.format('%.1f', s):gsub('%.', ','))) .. ' s' end -- string.split de OTClient se salta los campos vacíos (reemplaza puede venir vacío): este los conserva local function campos(t, sep) local r = {} for x in (t .. sep):gmatch('(.-)%' .. sep) do table.insert(r, x) end return r end local function info(nombre) return SpellInfo and SpellInfo.Default[nombre] end local function icono(widget, nombre) local s = info(nombre) widget:setImageSource(SpelllistSettings.Default.iconFile) widget:setImageClip(Spells.getImageClip(s and s.clientId or 0, 'Default')) end local function fila(nombre) for _, f in ipairs(datos and datos.filas or {}) do if f.nombre == nombre then return f end end end local function dato(clave, valor, color) local w = g_ui.createWidget('MaestroDato', ventana.ficha.datos) w.clave:setText(clave) w.valor:setText(valor) if color then w.valor:setColor(color) end end local function mostrarFicha(mensaje, ok) local fi = ventana.ficha local f = fila(elegido) fi:setVisible(f ~= nil) if not f then return end local s = info(f.nombre) or {} local libro = modules.game_libro and modules.game_libro.LIBRO_DATOS -- game_libro/datos.lua (módulo aislado) local d = libro and libro[f.nombre] or {} icono(fi.marco.icono, f.nombre) fi.marco.icono:setOpacity((f.estado == 'D' or f.estado == 'S') and 1 or 0.5) fi.marco:setBorderColor(f.estado == 'D' and C.accent or C.border) fi.nombre:setText(f.nombre) fi.tipo:setText((d.tipo or 'Habilidad') .. ' \183 ' .. (d.carga and ('carga de ' .. segundos(d.carga)) or 'instantánea')) fi.datos:destroyChildren() if s.mana then dato('Maná', tostring(s.mana)) end if s.exhaustion then dato('Espera', segundos(s.exhaustion)) end dato('Alcance', s.range and s.range > 0 and (s.range == 1 and 'cuerpo a cuerpo' or (s.range .. ' casillas')) or (d.area and 'a tu alrededor' or 'en ti')) dato('Nivel', tostring(f.nivel), f.estado == 'N' and C.errorText or nil) dato('Precio', miles(f.precio) .. ' de oro', f.estado == 'O' and C.warn or nil) fi.texto:setText(d.texto or '') local notas = {} if f.reemplaza ~= '' then table.insert(notas, 'Sustituye a ' .. f.reemplaza .. ': el mismo hueco de tu barra lanzará la nueva.') end if f.mision then table.insert(notas, 'Algún día se ganará con una prueba de tu clase; de momento, basta con el oro.') end fi.nota:setText(table.concat(notas, ' ')) local puede = f.estado == 'D' and not esperando fi.aprender:setEnabled(puede) fi.aprender:setText(f.estado == 'S' and 'Aprendido' or 'Aprender') if mensaje then fi.estado:setText(mensaje) fi.estado:setColor(ok and C.ok or C.errorText) elseif f.estado == 'D' then fi.estado:setText('') elseif f.estado == 'O' then fi.estado:setText(string.format('Te faltan %s de oro.', miles(f.precio - datos.oro))) fi.estado:setColor(C.warn) elseif f.estado == 'N' then fi.estado:setText(string.format('Vuelve a nivel %d (eres %d).', f.nivel, datos.nivel)) fi.estado:setColor(C.errorText) else fi.estado:setText('Ya está en tu libro de hechizos (P).') fi.estado:setColor(C.text2) end end local function visibles() local l = {} for _, f in ipairs(datos.filas) do if not soloDisponibles or f.estado == 'D' then table.insert(l, f) end end return l end local function pintarLista() ventana.lista:destroyChildren() local l = visibles() if not fila(elegido) or (soloDisponibles and fila(elegido).estado ~= 'D') then elegido = nil for _, f in ipairs(l) do -- la primera disponible; si no hay, la primera if not elegido and f.estado == 'D' then elegido = f.nombre end end elegido = elegido or (l[1] and l[1].nombre) end for _, f in ipairs(l) do local w = g_ui.createWidget('MaestroFila', ventana.lista) local est = ESTADOS[f.estado] or ESTADOS.N icono(w.marco.icono, f.nombre) w.nombre:setText(f.nombre) w.nivel:setText('Nivel ' .. f.nivel) w.precio:setText(f.estado == 'S' and '' or miles(f.precio)) w.precio:setColor(f.estado == 'O' and C.warn or C.text) w.estado:setText(est[1]) w.estado:setColor(est[2]) if f.estado == 'N' or f.estado == 'O' then w.marco.icono:setOpacity(0.5) w.nombre:setColor(C.text2) end w:setChecked(f.nombre == elegido) w.onClick = function() elegido = f.nombre for _, c in ipairs(ventana.lista:getChildren()) do c:setChecked(c == w) end mostrarFicha() end end ventana.vacia:setVisible(#l == 0) ventana.vacia:setText(soloDisponibles and 'Ahora no puedes aprender nada más. Quita el filtro para ver toda la lista.' or 'Corvo no tiene nada que enseñarle a tu clase.') end local function pintar(mensaje, ok) ventana:setText('Corvo, maestro de clase') local sabidos = 0 for _, f in ipairs(datos.filas) do if f.estado == 'S' then sabidos = sabidos + 1 end end ventana.meta:setText(string.format('%s \183 %d de %d aprendidos', CLASES[datos.clase] or 'Tu clase', sabidos, #datos.filas)) ventana.oro:setText(miles(datos.oro)) pintarLista() mostrarFicha(mensaje, ok) end local function leer(partes) local d = { clase = partes[2], oro = tonumber(partes[3]) or 0, nivel = tonumber(partes[4]) or 1, filas = {} } for _, t in ipairs((partes[5] or ''):split('~')) do local c = campos(t, '^') if #c >= 4 then table.insert(d.filas, { nombre = c[1], nivel = tonumber(c[2]) or 1, precio = tonumber(c[3]) or 0, estado = c[4], reemplaza = c[5] or '', mision = c[6] == '1' }) end end table.sort(d.filas, function(a, b) return a.nivel == b.nivel and a.nombre < b.nombre or a.nivel < b.nivel end) return d end local function crearVentana() if not ventana then ventana = g_ui.displayUI('maestro') OtmmoVentanas.recordar(ventana, 'maestro') -- posición entre sesiones ventana.soloDisponibles:setChecked(soloDisponibles) end end -- Recibe un mensaje del servidor (también lo usa la vista previa sin servidor). function recibir(buffer) local partes = buffer:split('|') local tipo = partes[1] if tipo == 'A' then local abierta = ventana and ventana:isVisible() datos = leer(partes) esperando = false crearVentana() pintar(ultimo, ultimoOk) -- el resultado de una compra llega justo antes («R») ultimo = nil if not abierta then ventana:show() ventana:raise() ventana:focus() end elseif tipo == 'R' and ventana then esperando = false ultimo, ultimoOk = table.concat(partes, '|', 3), partes[2] == '1' elseif tipo == 'X' then cerrar() end end local function onOpcode(protocol, opcode, buffer) recibir(buffer) end function aprender() local f = fila(elegido) if not f or f.estado ~= 'D' or esperando then return end esperando = true ventana.ficha.aprender:setEnabled(false) enviar('C|' .. f.nombre) end function alFiltrar(marcado) soloDisponibles = marcado if datos and ventana then pintar() end end function cerrar() ultimo = nil if ventana then ventana:hide() end end function maestroController:onInit() ProtocolGame.registerExtendedOpcode(OPCODE, onOpcode) end function maestroController:onGameEnd() cerrar() datos, elegido, esperando = nil, nil, false end function maestroController:onTerminate() ProtocolGame.unregisterExtendedOpcode(OPCODE) if ventana then ventana:destroy() ventana = nil end end