-- OT-MMO: el Gunslinger (vocación 13, clientid 8). Recámara en el HUD y RECARGA ACTIVA (como Gears of War). -- Solo dibuja y pide: el servidor (server/scripts/clases/gunslinger.lua) lleva las balas, la recarga y el tiempo, y -- juzga la segunda pulsación (zona dorada, blanca o fallo) con margen de latencia. Opcode 138: -- cliente «R» empieza a recargar · «C|ms» segunda pulsación (ms desde que empezó la barra, reloj del cliente) · «P» estado -- servidor «S|familia|balas|capacidad|dorada|estado» (estado L listo · R recargando · E encasquillada · - sin arma) -- «Z|familia|dur|blancaIni|blancaFin|doradaIni|doradaFin» · «E|D|ms» dorada · «E|B|ms» blanca · «E|F|ms» fallo -- · «E|N|0» acabó sola · «E|X|0» cancelada · «N|motivo» no se puede -- Tecla R (sin chat) o Alt+R (con chat). Para las demás clases no hace nada. local OPCODE = 138 local VOC = 8 -- clientid de vocations.xml local C = OtmmoTheme.color local ORO, BRASA, HUESO = '#C7A86B', '#D99A45', '#D6D0C2' local TAM = { pistolas = { 7, 16, 2 }, rifle = { 9, 22, 3 }, metralleta = { 5, 11, 1 } } -- ancho, alto, separación local NOMBRE = { pistolas = 'Pistolas', rifle = 'Rifle', metralleta = 'Metralleta' } local ANCHO_PISTA = 220 gunslingerController = Controller:new() local hud, barra = nil, nil local estado = { fam = nil, balas = 0, cap = 0, dorada = false, st = '-' } local recarga = nil -- { t0, dur, ba, bb, da, db, enviado } local ciclo, ocultarBarra = nil, nil local function enviar(texto) local protocol = g_game.getProtocolGame() if protocol then protocol:sendExtendedOpcode(OPCODE, texto) end end local function partir(s) local t = {} for x in ((s or '') .. '|'):gmatch('(.-)|') do t[#t + 1] = x end return t end local function esGunslinger() local p = g_game.getLocalPlayer() return p and p:getVocation() == VOC end local function ahora() return g_clock.millis() end -- Recámara -------------------------------------------------------------------------------------------------------- local function pintarBalas() local fam = estado.fam local caja = hud.balas caja:destroyChildren() if not fam then return end local w, h, sep = unpack(TAM[fam]) local porFila = fam == 'metralleta' and math.ceil(estado.cap / 2) or estado.cap caja:getLayout():setCellSize({ width = w, height = h }) caja:getLayout():setCellSpacing(sep) caja:getLayout():setNumColumns(porFila) caja:setWidth(porFila * (w + sep) - sep) caja:setHeight(fam == 'metralleta' and (2 * h + sep) or h) for i = 1, estado.cap do local b = g_ui.createWidget('GunslingerBala', caja) b:setImageSource('/modules/game_gunslinger/images/bala-' .. fam) local clip = i <= estado.balas and (estado.dorada and 2 or 0) or 1 b:setImageClip({ x = clip * w, y = 0, width = w, height = h }) b:setSize({ width = w, height = h }) end end local function pintarHud() if not hud then return end if not estado.fam or not esGunslinger() then hud:hide() return end hud:show() hud.arma:setImageSource('/modules/game_gunslinger/images/arma-' .. estado.fam) hud.cuenta:setText(string.format('%d/%d', estado.balas, estado.cap)) pintarBalas() local placa, texto, color = 'placa', NOMBRE[estado.fam], C.text2 if estado.st == 'E' then placa, texto, color = 'placa-atasco', 'Encasquillada', C.errorText elseif estado.st == 'R' then texto, color = 'Recargando...', C.text2 elseif estado.balas <= 0 then placa, texto, color = 'placa-vacia', 'Vacía: recarga', BRASA elseif estado.dorada then texto, color = 'Recámara dorada +20 %', ORO end hud:setImageSource('/modules/game_gunslinger/images/' .. placa) hud.estado:setText(texto) hud.estado:setColor(color) hud.cuenta:setColor(estado.balas <= 0 and BRASA or (estado.dorada and ORO or C.text)) hud.tecla:setVisible(estado.balas < estado.cap and estado.st == 'L') end -- Barra de recarga activa ---------------------------------------------------------------------------------------- local function px(ms) return math.floor(ANCHO_PISTA * ms / recarga.dur + 0.5) end local function mostrarBarra(dur, ba, bb, da, db) recarga = { t0 = ahora(), dur = dur, ba = ba, bb = bb, da = da, db = db } ocultarBarra = nil barra:setOpacity(1) barra.pista.rayado:hide() barra.pista.blanca:setMarginLeft(px(ba)) barra.pista.blanca:setWidth(math.max(4, px(bb) - px(ba))) barra.pista.dorada:setMarginLeft(px(da) - 1) barra.pista.dorada:setWidth(math.max(4, px(db) - px(da) + 2)) barra.aguja:setMarginLeft(0) barra.resultado:setText('Pulsa R en la franja') barra.resultado:setColor(C.text2) barra:show() barra:raise() end local function resultado(tipo, ms) local textos = { D = { '¡Recarga dorada!', ORO }, B = { 'Recarga rápida', HUESO }, F = { '¡Encasquillada! 2 s', C.errorText }, N = { 'Recargada', C.text2 }, X = { 'Recarga cancelada', C.text2 }, } local t = textos[tipo] or textos.N barra.resultado:setText(t[1]) barra.resultado:setColor(t[2]) if recarga and ms and tonumber(ms) and tonumber(ms) > 0 then barra.aguja:setMarginLeft(math.min(ANCHO_PISTA, px(tonumber(ms))) - 3) end if tipo == 'F' then barra.pista.rayado:show() end if tipo == 'D' then barra.destello:show() local paso = 0 local function animar() if not barra or paso > 3 then if barra then barra.destello:hide() end return end barra.destello:setImageClip({ x = 0, y = paso * 16, width = 16, height = 16 }) paso = paso + 1 scheduleEvent(animar, 70) end animar() end recarga = nil ocultarBarra = ahora() + (tipo == 'F' and 2000 or 900) end local function pulsarR() if not esGunslinger() or g_keyboard.escribiendo() then return end if recarga and not recarga.enviado then recarga.enviado = true enviar('C|' .. (ahora() - recarga.t0)) elseif not recarga then enviar('R') end end local function tic() if not barra then return end if recarga then local t = math.min(recarga.dur, ahora() - recarga.t0) if not recarga.enviado then barra.aguja:setMarginLeft(px(t) - 3) end elseif ocultarBarra and ahora() >= ocultarBarra then local resto = 1 - math.min(1, (ahora() - ocultarBarra) / 250) barra:setOpacity(resto) if resto <= 0 then ocultarBarra = nil barra:hide() end end end local function onOpcode(protocol, opcode, buffer) local m = partir(buffer) local tipo = m[1] if tipo == 'S' then estado.fam = m[2] ~= '-' and m[2] or nil estado.balas, estado.cap = tonumber(m[3]) or 0, tonumber(m[4]) or 0 estado.dorada, estado.st = m[5] == '1', m[6] or 'L' pintarHud() elseif tipo == 'Z' then mostrarBarra(tonumber(m[3]), tonumber(m[4]), tonumber(m[5]), tonumber(m[6]), tonumber(m[7])) elseif tipo == 'E' then if barra:isVisible() or m[2] ~= 'N' then if not barra:isVisible() then barra:show() barra:setOpacity(1) end resultado(m[2], m[3]) end elseif tipo == 'N' then if modules.game_textmessage then modules.game_textmessage.displayFailureMessage(m[2] or '') end end end -- Para las capturas sin servidor (fixture): modules.game_gunslinger.probar(138, 'S|...') function probar(op, texto) if op == OPCODE then onOpcode(nil, op, texto) end end -- Congela la aguja en «ms» (capturas). function congelar(ms) if recarga then recarga.enviado = true barra.aguja:setMarginLeft(px(ms) - 3) end end function gunslingerController:onInit() local raiz = modules.game_interface.getRootPanel() g_ui.importStyle('gunslinger') hud = g_ui.createWidget('GunslingerHud', raiz) hud:addAnchor(AnchorBottom, 'parent', AnchorBottom) hud:addAnchor(AnchorHorizontalCenter, 'parent', AnchorHorizontalCenter) hud:setMarginBottom(232) -- 27-oct: a 108 quedaba TAPADA por las barras de hechizos (y 612-660); ahora sobre la barra de nivel/vida hud:hide() barra = g_ui.createWidget('GunslingerRecarga', raiz) barra:addAnchor(AnchorBottom, hud:getId(), AnchorTop) barra:addAnchor(AnchorHorizontalCenter, 'parent', AnchorHorizontalCenter) barra:setMarginBottom(6) barra:hide() ProtocolGame.registerExtendedOpcode(OPCODE, onOpcode) Keybind.new('Combate', 'Recargar (Gunslinger)', { [CHAT_MODE.ON] = 'Alt+R', [CHAT_MODE.OFF] = 'R' }, '') Keybind.bind('Combate', 'Recargar (Gunslinger)', { { type = KEY_DOWN, callback = pulsarR } }) if g_game.isOnline() then self:onGameStart() end end function gunslingerController:onGameStart() if ciclo then removeEvent(ciclo) end ciclo = cycleEvent(tic, 16) if esGunslinger() then enviar('P') end end function gunslingerController:onGameEnd() if ciclo then removeEvent(ciclo) ciclo = nil end recarga, ocultarBarra = nil, nil estado = { fam = nil, balas = 0, cap = 0, dorada = false, st = '-' } if hud then hud:hide() end if barra then barra:hide() end end function gunslingerController:onTerminate() self:onGameEnd() ProtocolGame.unregisterExtendedOpcode(OPCODE) Keybind.delete('Combate', 'Recargar (Gunslinger)') if barra then barra:destroy() end if hud then hud:destroy() end hud, barra = nil, nil end