-- OT-MMO: tablón de contratos (estilo Monster Hunter) y seguimiento del contrato activo. -- Muestra la presa (criatura o material) y los objetos de recompensa con su icono. Solo muestra: el servidor (server/scripts/expedicion/tablon.lua) decide todo. Opcode extendido 102: -- «L|activo|progreso|estado|desmayos|c1~c2~...» lista del tablón (contrato: id^nombre^rango^objetivo^cantidad^recompensa^descripcion) -- «P|id|progreso|cantidad|estado|desmayos|texto|nombre|rango» seguimiento (id 0 = sin contrato) -- El cliente envía «A|id» (aceptar), «X» (abandonar) y «E» (entregar). local OPCODE = 102 local DIFICULTAD = { 'Normal', 'Peligrosa', 'Mortal' } -- Nombres en español de los objetos de recompensa (los de Tibia vienen en inglés). local NOMBRES = { [3031] = 'monedas de oro', [5901] = 'madera', [5880] = 'mineral de hierro', [3736] = 'hierba estrella' } tablonController = Controller:new() local ventana = nil local seguimiento = nil local contratos = {} local activo = { id = 0, progreso = 0, estado = 0, desmayos = 0 } local elegido = nil local function enviar(texto) local protocol = g_game.getProtocolGame() if protocol then protocol:sendExtendedOpcode(OPCODE, texto) end end local function pintarEstrellas(panel, rango) panel:destroyChildren() for i = 1, 3 do local e = g_ui.createWidget('Estrella', panel) e:setOn(i <= rango) end end -- «c:lookType[:cabeza:cuerpo:piernas:pies]» (criatura) o «i:itemId» (objeto) local function pintarRetrato(retrato, imagen) local partes = (imagen or ''):split(':') local tipo = partes[1] retrato.criatura:setVisible(tipo == 'c') retrato.objeto:setVisible(tipo == 'i') if tipo == 'c' then retrato.criatura:setOutfit({ type = tonumber(partes[2]) or 0, head = tonumber(partes[3]) or 0, body = tonumber(partes[4]) or 0, legs = tonumber(partes[5]) or 0, feet = tonumber(partes[6]) or 0 }) elseif tipo == 'i' then retrato.objeto:setItemId(tonumber(partes[2]) or 0) end end local function mostrarDetalle(c) if not ventana or not c then return end elegido = c local d = ventana.detalle d.nombre:setText(c.nombre) pintarEstrellas(d.estrellas, c.rango) pintarRetrato(d.retrato, c.imagen) d.recompensas:destroyChildren() for _, par in ipairs((c.objetos or ''):split(',')) do local id, cantidad = par:match('(%d+):(%d+)') if id then local item = g_ui.createWidget('Recompensa', d.recompensas) item:setItemId(tonumber(id)) item:setItemCount(tonumber(cantidad)) local tipo = g_things.getThingType(tonumber(id), ThingCategoryItem) local nombre = NOMBRES[tonumber(id)] or (tipo and tipo:getName() ~= '' and tipo:getName()) or 'objeto' item:setTooltip(cantidad .. ' ' .. nombre) end end d.experiencia:setText('+' .. (c.exp or 0) .. ' de experiencia') d.objetivo:setText(string.format('Objetivo: %d %s', c.cantidad, c.objetivo)) d.dificultad:setText('Dificultad de la expedición: ' .. (DIFICULTAD[c.rango] or '?')) d.descripcion:setText(c.descripcion) local mio = activo.id == c.id if mio and activo.estado == 2 then d.estado:setText('Objetivo cumplido. Entrega el contrato para cobrar.') elseif mio then d.estado:setText(string.format('En curso: %d/%d. Desmayos: %d/3.', activo.progreso, c.cantidad, activo.desmayos)) elseif activo.id > 0 then d.estado:setText('Ya tienes otro contrato en curso.') else d.estado:setText('') end ventana.botones.aceptar:setEnabled(activo.id == 0) ventana.botones.entregar:setEnabled(mio and activo.estado == 2) ventana.botones.abandonar:setEnabled(mio) end local function abrirLista(partes) activo = { id = tonumber(partes[2]) or 0, progreso = tonumber(partes[3]) or 0, estado = tonumber(partes[4]) or 0, desmayos = tonumber(partes[5]) or 0 } contratos = {} for _, fila in ipairs((partes[6] or ''):split('~')) do local f = fila:split('^') if #f >= 7 then contratos[#contratos + 1] = { id = tonumber(f[1]), nombre = f[2], rango = tonumber(f[3]), objetivo = f[4], cantidad = tonumber(f[5]), recompensa = f[6], descripcion = f[7], imagen = f[8], objetos = f[9], exp = tonumber(f[10]) } end end if not ventana then ventana = g_ui.displayUI('tablon') OtmmoVentanas.recordar(ventana, 'tablon') -- posición entre sesiones (fase 4) end ventana:show() ventana:raise() ventana:focus() local lista = ventana.lista lista:destroyChildren() local foco = nil for _, c in ipairs(contratos) do local fila = g_ui.createWidget('FilaContrato', lista) fila.nombre:setText(c.nombre) pintarEstrellas(fila.estrellas, c.rango) pintarRetrato(fila.retrato, c.imagen) fila.retrato.criatura:setSize({ width = 32, height = 32 }) if c.id == activo.id then fila.marca:setText(activo.estado == 2 and 'Cumplido' or 'En curso') foco = fila end fila.onFocusChange = function(w, focused) if focused then mostrarDetalle(c) end end if (elegido and elegido.id == c.id) then foco = foco or fila end end local primero = foco or lista:getFirstChild() if primero then lista:focusChild(primero, KeyboardFocusReason) for _, c in ipairs(contratos) do if primero.nombre:getText() == c.nombre then mostrarDetalle(c) end end end end local function actualizarSeguimiento(partes) if not seguimiento then return end local id = tonumber(partes[2]) or 0 if id == 0 then seguimiento:hide() activo = { id = 0, progreso = 0, estado = 0, desmayos = 0 } return end activo = { id = id, progreso = tonumber(partes[3]) or 0, estado = tonumber(partes[5]) or 0, desmayos = tonumber(partes[6]) or 0 } local cantidad = tonumber(partes[4]) or 0 seguimiento.nombre:setText(partes[8] or '') if activo.estado == 2 then seguimiento.progreso:setText('Cumplido: vuelve al tablón') seguimiento.progreso:setColor(OtmmoTheme.color.ok) else seguimiento.progreso:setText(string.format('%s %d/%d Desmayos %d/3', partes[7] or '', activo.progreso, cantidad, activo.desmayos)) seguimiento.progreso:setColor(OtmmoTheme.color.text2) end seguimiento:show() end local function onOpcode(protocol, opcode, buffer) local partes = buffer:split('|') if partes[1] == 'L' then abrirLista(partes) elseif partes[1] == 'P' then actualizarSeguimiento(partes) end end function aceptar() if elegido then enviar('A|' .. elegido.id) end end function entregar() enviar('E') end function abandonar() enviar('X') end function cerrar() if ventana then ventana:hide() end end function tablonController:onInit() ProtocolGame.registerExtendedOpcode(OPCODE, onOpcode) end function tablonController:onTerminate() ProtocolGame.unregisterExtendedOpcode(OPCODE) if ventana then ventana:destroy() ventana = nil end if seguimiento then seguimiento:destroy() seguimiento = nil end end function tablonController:onGameStart() if not seguimiento then seguimiento = g_ui.loadUI('seguimiento', modules.game_interface.getRootPanel()) seguimiento:addAnchor(AnchorTop, 'parent', AnchorTop) seguimiento:addAnchor(AnchorHorizontalCenter, 'parent', AnchorHorizontalCenter) seguimiento:setMarginTop(40) end end function tablonController:onGameEnd() cerrar() if seguimiento then seguimiento:hide() end end