-- OT-MMO: intercambio entre jugadores, como el de Eldervale. El servidor (server/scripts/intercambio.lua, opcode 117) -- decide todo; aquí solo se pinta y se pide. Clic derecho sobre un jugador -> «Intercambiar con ...». local OPCODE = 117 local ORO, APAGADO, VERDE = OtmmoTheme.color.accent, OtmmoTheme.color.text2, OtmmoTheme.color.ok -- tokens intercambioController = Controller:new() local ventana, invitacion = nil, nil local estado = nil local cuentaAtras = nil local escribiendoOro = false local firmaMochila, filas = nil, {} 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(math.floor(n or 0)) while true do local r, k = s:gsub('^(%d+)(%d%d%d)', '%1.%2') s = r if k == 0 then break end end return s end function invitar(nombre) enviar('I|' .. nombre) end function responder(si) enviar(si and 'S' or 'N') if cuentaAtras then removeEvent(cuentaAtras) cuentaAtras = nil end if invitacion then invitacion:hide() end end function aceptar() enviar('A') end function cancelar() enviar('X') end local function retrato(widget, id) local c = id and g_map.getCreatureById(id) if c then widget:setOutfit(c:getOutfit()) end end local function pintarOferta(col, oferta, vacioTexto) col.lista:destroyChildren() local hay = false for _, it in ipairs(oferta.items or {}) do local w = g_ui.createWidget('InOferta', col.lista) w.marco.item:setItemId(it.id) w.nombre:setText(it.nombre) w.cuenta:setText('x' .. it.n) hay = true end if (oferta.oro or 0) > 0 then local w = g_ui.createWidget('InOferta', col.lista) w.marco.item:setItemId(3031) w.nombre:setText(miles(oferta.oro) .. ' de oro') w.nombre:setColor(OtmmoTheme.color.xp) w.cuenta:setText('') hay = true end col.vacio:setText(hay and '' or vacioTexto) end local function pintar() local d = estado ventana.yo.nombre:setText('T\250') ventana.yo.estado:setText(d.acepte and 'Aceptaste' or 'Sin aceptar') ventana.yo.estado:setColor(d.acepte and VERDE or APAGADO) ventana.ellos.nombre:setText(d.ellos) ventana.ellos.estado:setText(d.acepto and 'Acept\243' or 'Sin aceptar') ventana.ellos.estado:setColor(d.acepto and VERDE or APAGADO) local yo = g_game.getLocalPlayer() if yo then retrato(ventana.yo.marco.retrato, yo:getId()) end retrato(ventana.ellos.marco.retrato, d.ellosId) ventana.ofreces.titulo:setText('Ofreces') ventana.ofreces.estado:setText(d.acepte and 'Aceptado' or '') ventana.ofreces:setBorderColor(d.acepte and ORO or OtmmoTheme.color.border) pintarOferta(ventana.ofreces, d.mia, 'No ofreces nada a\250n.') ventana.recibes.titulo:setText('Recibes') ventana.recibes.estado:setText(d.acepto and 'Aceptado' or '') ventana.recibes:setBorderColor(d.acepto and ORO or OtmmoTheme.color.border) pintarOferta(ventana.recibes, d.suya, 'A\250n no ofrece nada.') -- Filas de la mochila: se rehacen solo si cambia la lista; si no, se actualizan en su sitio (rehacerlas con cada -- respuesta del servidor se comía el segundo clic rápido en «+»). La cantidad pedida se lleva aquí y se manda entera. local claves = {} for _, e in ipairs(d.inv) do claves[#claves + 1] = e.k .. ':' .. e.id .. ':' .. e.n end claves = table.concat(claves, ',') if claves ~= firmaMochila then firmaMochila = claves filas = {} ventana.mochila:destroyChildren() if #d.inv == 0 then local t = g_ui.createWidget('GameTextMuted', ventana.mochila) t:setText('No tienes objetos para ofrecer.') end for _, e in ipairs(d.inv) do local f = g_ui.createWidget('InFila', ventana.mochila) f.e = e f.marco.item:setItemId(e.id) if e.n > 1 then f.marco.item:setItemCount(e.n) end f.nombre:setText(e.nombre) local function pedir(n) f.pedido = math.max(0, math.min(f.e.n, n)) enviar('O|' .. f.e.k .. '|' .. f.pedido) end f.menos.onClick = function() pedir((f.pedido or f.e.ofrecido) - 1) end f.mas.onClick = function() pedir((f.pedido or f.e.ofrecido) + 1) end f.todo.onClick = function() pedir((f.pedido or f.e.ofrecido) == f.e.n and 0 or f.e.n) end filas[e.k] = f end end for _, e in ipairs(d.inv) do local f = filas[e.k] if f then f.e = e if f.pedido == e.ofrecido then f.pedido = nil end local n = f.pedido or e.ofrecido f.cuenta:setText(n .. ' / ' .. e.n) f.cuenta:setColor(n > 0 and OtmmoTheme.color.text or APAGADO) f.todo:setText(n == e.n and 'Nada' or 'Todo') end end if not escribiendoOro then ventana.oroFila.oro:setText(d.mia.oro > 0 and tostring(d.mia.oro) or '') end ventana.oroFila.oroMax:setText('de ' .. miles(d.dinero) .. ' (mochila y banco)') ventana.pie.estado:setText(d.acepte and ('Esperando a ' .. d.ellos .. '...') or d.acepto and (d.ellos .. ' ya acept\243') or 'Los dos deben aceptar') ventana.pie.aceptar:setEnabled(not d.acepte) ventana.pie.aceptar:setText(d.acepte and 'Aceptado' or 'Aceptar') end local function onOpcode(protocol, opcode, buffer) local tipo, resto = buffer:sub(1, 1), buffer:sub(3) if tipo == 'M' then return modules.game_textmessage.displayStatusMessage(resto) elseif tipo == 'F' then estado = nil firmaMochila, filas = nil, {} ventana:hide() if resto == 'ok' then modules.game_textmessage.displayStatusMessage('Intercambio hecho.') elseif resto ~= '' then modules.game_textmessage.displayStatusMessage(resto) end return end local ok, d = pcall(json.decode, resto) if not ok or type(d) ~= 'table' then return end if tipo == 'V' then invitacion.texto:setText(d.de .. ' quiere intercambiar contigo.') retrato(invitacion.marco.retrato, d.deId) invitacion:show() invitacion:raise() local fin = g_clock.millis() + (d.segundos or 30) * 1000 if cuentaAtras then removeEvent(cuentaAtras) end cuentaAtras = cycleEvent(function() local s = math.ceil((fin - g_clock.millis()) / 1000) if s <= 0 then invitacion:hide() removeEvent(cuentaAtras) cuentaAtras = nil return end invitacion.tiempo:setText('Caduca en ' .. s .. ' s') end, 250) elseif tipo == 'T' then if invitacion:isVisible() then invitacion:hide() end estado = d if not ventana:isVisible() then ventana:show() ventana:raise() end pintar() end end -- Para probar sin servidor: modules.game_intercambio.probar('T|') o probar('V|{"de":"Kit","segundos":30}') function probar(texto) onOpcode(nil, OPCODE, texto) end function intercambioController:onInit() ventana = g_ui.displayUI('intercambio') OtmmoVentanas.recordar(ventana, 'intercambio') -- posición entre sesiones (fase 4) ventana:hide() invitacion = g_ui.displayUI('invitacion') invitacion:hide() invitacion:breakAnchors() invitacion:addAnchor(AnchorTop, 'parent', AnchorTop) invitacion:addAnchor(AnchorHorizontalCenter, 'parent', AnchorHorizontalCenter) invitacion:setMarginTop(120) local oro = ventana.oroFila.oro -- foco de verdad al pulsar el campo (toda la cadena de padres): si no, los números lanzaban los atajos 1-0 oro.onMousePress = function(w) local hijo, padre = w, w:getParent() while padre do padre:focusChild(hijo, MouseFocusReason) hijo, padre = padre, padre:getParent() end end oro.onFocusChange = function(w, foco) escribiendoOro = foco if not foco and estado then enviar('G|' .. (tonumber(w:getText()) or 0)) end end oro.onTextChange = function(w, texto) local limpio = texto:gsub('%D', '') if limpio ~= texto then w:setText(limpio) return end end g_keyboard.bindKeyDown('Enter', function() if estado then enviar('G|' .. (tonumber(oro:getText()) or 0)) end end, oro) ProtocolGame.registerExtendedOpcode(OPCODE, onOpcode) end function intercambioController:onGameEnd() estado = nil if ventana then ventana:hide() end if invitacion then invitacion:hide() end end function intercambioController:onTerminate() ProtocolGame.unregisterExtendedOpcode(OPCODE) if cuentaAtras then removeEvent(cuentaAtras) end if ventana then ventana:destroy() end if invitacion then invitacion:destroy() end ventana, invitacion = nil, nil end