-- OT-MMO: escenas de mazmorra estilo FFXIV. Solo pinta: el servidor (server/scripts/combate/cine.lua) -- decide qué y cuándo. Opcode extendido 107: -- «B|1»/«B|0» franjas · «T|titulo|sub|ms» cartel · «D|nombre|lookType|texto|ms» diálogo con retrato -- «K|x|y|z|ms» cámara a una casilla · «K|0» cámara al jugador · «O|titulo|t^0~t^1» objetivos · «V|texto|ms» local OPCODE = 107 local FRANJA = 64 -- alto de las franjas de cine local PASO = 30 cineController = Controller:new() local capa = nil local anim = {} -- [clave] = evento local function mapa() return modules.game_interface.getMapPanel() end local function parar(clave) removeEvent(anim[clave]) anim[clave] = nil end -- Interpola de 0 a 1 en `ms` y llama a f(t) en cada paso. local function animar(clave, ms, f, alFinal) parar(clave) local inicio = g_clock.millis() local function paso() local t = math.min(1, (g_clock.millis() - inicio) / math.max(1, ms)) f(t) if t < 1 then anim[clave] = scheduleEvent(paso, PASO) else anim[clave] = nil if alFinal then alFinal() end end end paso() end local function suave(t) return t * t * (3 - 2 * t) end local function franjas(si) local a, b = capa.franjaArriba, capa.franjaAbajo local desde = a:getHeight() local hasta = si and FRANJA or 0 animar('franjas', 500, function(t) local h = math.floor(desde + (hasta - desde) * suave(t)) a:setHeight(h) b:setHeight(h) end) end -- Aparece, se queda y se desvanece. local function mostrarPorTiempo(clave, w, ms) w:setOpacity(0) w:show() local entrada, salida = 450, 600 animar(clave, ms, function(t) local e = t * ms local o if e < entrada then o = e / entrada elseif e > ms - salida then o = math.max(0, (ms - e) / salida) else o = 1 end w:setOpacity(o) end, function() w:hide() end) end local function titulo(nombre, sub, ms) capa.titulo.nombre:setText(nombre) capa.titulo.sub:setText(sub or '') mostrarPorTiempo('titulo', capa.titulo, ms) end local function dialogo(nombre, look, texto, ms) local d = capa.dialogo d.nombre:setText(nombre) d.marco:setVisible(look > 0) if look > 0 then d.marco.retrato:setOutfit({ type = look }) end d.texto:setText('') mostrarPorTiempo('dialogo', d, ms) -- el texto se escribe letra a letra en el primer tercio local largo = #texto animar('letras', math.min(ms / 3, largo * 28), function(t) d.texto:setText(texto:sub(1, math.floor(largo * t))) end) end -- La cámara viaja casilla a casilla hasta el destino (solo casillas que el cliente ya conoce). local function camara(destino, ms) local m = mapa() local jugador = g_game.getLocalPlayer() if not m or not jugador then return end if not destino then parar('camara') m:followCreature(jugador) return end local o = jugador:getPosition() animar('camara', ms, function(t) local k = suave(t) m:setCameraPosition({ x = math.floor(o.x + (destino.x - o.x) * k + 0.5), y = math.floor(o.y + (destino.y - o.y) * k + 0.5), z = destino.z }) end) end local function objetivos(tituloTxt, lista) local o = capa.objetivos for _, w in ipairs(o:getChildren()) do if w:getId() ~= 'cabecera' then w:destroy() end end if not tituloTxt or tituloTxt == '' then o:hide() return end o.cabecera:setText(tituloTxt) for _, f in ipairs(lista) do local fila = g_ui.createWidget('FilaObjetivo', o) fila.texto:setText(f.texto) if f.hecho then fila.marca:setBackgroundColor(OtmmoTheme.color.ok) fila.marca:setBorderColor(OtmmoTheme.color.ok) fila.texto:setColor(OtmmoTheme.color.text2) end end o:show() end local function victoria(texto, ms) local v = capa.victoria v:setText(texto) mostrarPorTiempo('victoria', v, ms) end local function onOpcode(protocol, opcode, buffer) if not capa then return end local p = buffer:split('|') local c = p[1] if c == 'B' then franjas(p[2] == '1') elseif c == 'T' then titulo(p[2] or '', p[3] or '', tonumber(p[4]) or 4000) elseif c == 'D' then dialogo(p[2] or '', tonumber(p[3]) or 0, p[4] or '', tonumber(p[5]) or 3500) elseif c == 'K' then if p[2] == '0' then camara(nil) else camara({ x = tonumber(p[2]), y = tonumber(p[3]), z = tonumber(p[4]) }, tonumber(p[5]) or 900) end elseif c == 'O' then local lista = {} for _, f in ipairs((p[3] or ''):split('~')) do local t, h = f:match('^(.*)%^(%d)$') if t then lista[#lista + 1] = { texto = t, hecho = h == '1' } end end objetivos(p[2], lista) elseif c == 'V' then victoria(p[2] or '', tonumber(p[3]) or 5000) end end local function limpiar() for k in pairs(anim) do parar(k) end if capa then capa.franjaArriba:setHeight(0) capa.franjaAbajo:setHeight(0) capa.titulo:hide() capa.dialogo:hide() capa.victoria:hide() objetivos(nil) end end function cineController:onInit() ProtocolGame.registerExtendedOpcode(OPCODE, onOpcode) end function cineController:onTerminate() ProtocolGame.unregisterExtendedOpcode(OPCODE) limpiar() if capa then capa:destroy() capa = nil end end function cineController:onGameStart() if not capa then g_ui.importStyle('cine') capa = g_ui.createWidget('CineCapa', mapa()) capa:fill('parent') end end function cineController:onGameEnd() limpiar() local m = mapa() local j = g_game.getLocalPlayer() if m and j then m:followCreature(j) end end