-- OT-MMO: marco de objetivo (estilo WoW). Arriba, a la izquierda del centro: retrato, nombre, qué es (enemigo, -- jefe, jugador y su clase, invocación), vida en % y la barra de carga cuando el objetivo canaliza. -- Solo escucha. Las cargas de jugadores llegan por el opcode 101 (server/scripts/lanzamiento.lua, que pinta -- game_vitals sobre la cabeza) y las habilidades de jefe por el 106 (server/scripts/combate/avisos.lua, que pinta -- game_avisos). Para leerlas sin quitarles el opcode se envuelve ProtocolGame.onExtendedOpcode. local CLASES = { [1] = 'Guardi\225n', [11] = 'Guardi\225n', [2] = 'Cazador', [12] = 'Cazador', [3] = 'Hechicero', [13] = 'Hechicero', [4] = 'Druida', [14] = 'Druida', [6] = 'Brujo' } -- clientid de vocations.xml -- ponytail: el servidor no marca a los jefes; lista a mano hasta que lo haga (p. ej. con un icono de criatura). -- En minúsculas: el cliente recibe «Centinela De Prueba». local RANGOS = { ['varg el tuerto'] = 'Jefe', ['rey hueco'] = 'Jefe', ['centinela de prueba'] = 'Jefe', ['coloso de ceniza'] = '\201lite' } local NOMBRES = { Regeneracion = 'Regeneraci\243n' } -- el servidor envía los nombres de carga sin acentos local C = OtmmoTheme.color -- tokens (data/styles/0-vars.otui) local ORO, BRASA, SANGRE, APAGADO = C.accent, C.warn, C.hp, C.text2 objetivoController = Controller:new() local panel, objetivo, carga, avisoId = nil, nil, nil, nil local original = nil local function pintarVida() if not objetivo or not panel then return end local pct = objetivo:getHealthPercent() local barra = panel.marco.vida barra.fill:setWidth(math.max(1, math.floor((barra:getWidth() - 2) * pct / 100))) barra.pct:setText(pct .. ' %') end local function quitarCarga() if carga then removeEvent(carga.evento) carga = nil end panel.carga:hide() end local function mostrarCarga(nombre, ms, interrumpible) quitarCarga() local b = panel.carga local c = { inicio = g_clock.millis(), ms = math.max(1, ms) } b.nombre:setText(nombre) b.fill:setBackgroundColor(interrumpible and BRASA or ORO) b.fill:setOpacity(0.35) b:setBorderColor(interrumpible and BRASA or C.border) b.aviso:setVisible(interrumpible) b:show() local function paso() local hecho = g_clock.millis() - c.inicio local frac = math.min(1, hecho / c.ms) b.fill:setWidth(math.max(1, math.floor((b:getWidth() - 2) * frac))) b.tiempo:setText((string.format('%.1f s', math.max(0, c.ms - hecho) / 1000):gsub('%.', ','))) if frac >= 1 then quitarCarga() end end c.evento = cycleEvent(paso, 30) carga = c paso() end -- cortada antes de terminar: un momento en rojo, como la barra sobre la cabeza local function cortarCarga() if not carga then return end removeEvent(carga.evento) carga = nil local b = panel.carga b.fill:setBackgroundColor(SANGRE) b.fill:setOpacity(0.6) b.fill:setWidth(b:getWidth() - 2) b.nombre:setText('Interrumpido') b.aviso:hide() b.tiempo:setText('') scheduleEvent(function() if panel and not carga then panel.carga:hide() end end, 600) end local function queEs(c) if c:isPlayer() then local clase = CLASES[c:getVocation()] return clase and ('Jugador \183 ' .. clase) or 'Jugador', APAGADO elseif c:isNpc() then return 'Habitante', APAGADO end local t = c:getType() if t == CreatureTypeSummonOwn then return 'Tu invocaci\243n', APAGADO elseif t == CreatureTypeSummonOther then return 'Invocaci\243n', APAGADO end local rango = RANGOS[c:getName():lower()] if rango then return rango, ORO end return 'Enemigo', APAGADO end local function actualizar() if not panel then return end local c = g_game.getAttackingCreature() or g_game.getFollowingCreature() if c == objetivo then return end objetivo, avisoId = c, nil quitarCarga() if not c then return panel:hide() end local m = panel.marco m.retratoMarco.retrato:setCreature(c) m.nombre:setText(c:getName()) local texto, color = queEs(c) m.tipo:setText(texto) m.tipo:setColor(color) -- jefe/élite: borde de acento; el resto, borde normal m:setBorderColor(color == ORO and ORO or C.border) m.retratoMarco:setBorderColor(color == ORO and ORO or C.border) pintarVida() panel:show() end -- Amenaza (server/scripts/amenaza.lua, «A|pct|lleva»): ok lejos, xp (amarillo) desde el 80 %, warn desde el 100 % -- (a punto de quitárselo al tanque), rojo cuando el monstruo va a por ti. local function pintarAmenaza(buffer) local w = panel and panel.amenaza if not w then return end local pct, lleva = buffer:match('^A|(%d+)|(%d)$') if not pct then return w:hide() end pct = tonumber(pct) w:setText((lleva == '1' and 'Te ataca ' or 'Amenaza ') .. pct .. ' %') w:setColor(lleva == '1' and C.errorText or pct >= 100 and C.warn or pct >= 80 and C.xp or C.ok) w:show() end local function escuchar(opcode, buffer) if opcode == 119 then return pintarAmenaza(buffer) end if not objetivo or (opcode ~= 101 and opcode ~= 106) then return end local p = buffer:split('|') if opcode == 101 then if tonumber(p[2]) ~= objetivo:getId() then return end if p[1] == 'C' then mostrarCarga(NOMBRES[p[3]] or p[3], tonumber(p[4]) or 1000, false) elseif p[1] == 'X' then if p[3] == 'ok' then quitarCarga() else cortarCarga() end end elseif objetivo:isMonster() then -- ponytail: el aviso no dice quién lo lanza; se atribuye al monstruo que tienes de objetivo if p[1] == 'A' then avisoId = p[2] mostrarCarga(p[4], tonumber(p[5]) or 1000, p[3] == 'INTERRUMPE') elseif p[1] == 'C' and p[2] == avisoId then avisoId = nil cortarCarga() -- solo hay carga si se quitó antes de acabar end end end local function onSalud(c) if c == objetivo then pintarVida() end end function objetivoController:onInit() original = ProtocolGame.onExtendedOpcode ProtocolGame.onExtendedOpcode = function(self, opcode, buffer) original(self, opcode, buffer) local ok, err = pcall(escuchar, opcode, buffer) if not ok then g_logger.error('[objetivo] ' .. tostring(err)) end end connect(g_game, { onAttackingCreatureChange = actualizar, onFollowingCreatureChange = actualizar }) connect(Creature, { onHealthPercentChange = onSalud }) if g_game.isOnline() then -- recargado a mitad de partida self:onGameStart() end end function objetivoController:onGameStart() if panel then return end -- arriba, a la izquierda de la columna central (contrato y avisos de jefe van en el centro) panel = g_ui.loadUI('objetivo', modules.game_interface.getRootPanel()) panel:addAnchor(AnchorTop, 'parent', AnchorTop) panel:addAnchor(AnchorRight, 'parent', AnchorHorizontalCenter) panel:setMarginTop(12) panel:setMarginRight(216) panel:hide() actualizar() end function objetivoController:onGameEnd() objetivo = nil if panel then quitarCarga() panel:hide() end end function objetivoController:onTerminate() if original then ProtocolGame.onExtendedOpcode = original original = nil end disconnect(g_game, { onAttackingCreatureChange = actualizar, onFollowingCreatureChange = actualizar }) disconnect(Creature, { onHealthPercentChange = onSalud }) if panel then quitarCarga() panel:destroy() panel = nil end objetivo = nil end