-- =========================================================================== -- BancoJoias - banco de joias refeito em Lua (cliente) -- Plano: docs/superpowers/plans/2026-10-19-banco-de-joias-em-lua.md (pedido do dono em 19/10/2026) -- -- O cliente e do jogador: este arquivo so DESENHA e PEDE. Quem confere saldo, espaco e da o item e o -- servidor (Data/Script/Scripts/BancoJoias). Nunca decidir nada aqui. -- -- FASE 0: quando o servidor manda BJ_Pong (/bjteste do administrador), responde BJ_Ping com o numero. -- JANELA: so com CONFIG.Ativo = 1 (liga junto com o banco do Louis desligado, CustomJewelBankEnable = 0). -- -- Pacotes: -- cliente -> servidor BJ_Abrir pede a lista -- BJ_Sacar (item, nivel, modo, qtd) modo 0 avulso, 1 pack, 2 max -- BJ_Depositar (casa) Ctrl + clique direito no inventario -- BJ_DepTudo (item, nivel) Deposit All da janela do item -- servidor -> cliente BJ_Lista (n, n x [item, nivel, qtd]) -- -- Funcoes de API sem assinatura documentada (CreateItem, Get*Packet) vao em pcall; a forma que funcionou -- fica guardada e o erro vai ao console uma vez so. Nada pesado por quadro (micro-travada: 6ee747bc). -- =========================================================================== local CONFIG = require("Scripts\\BancoJoias\\Config") local BJ = { Aberto = false, Tela = "lista", Saldo = {}, Rolagem = 0, Sel = nil, Botoes = {}, Clique = nil, Avisado = {} } function MO_BancoAberto() return BJ.Aberto end -- a lixeira (Scripts/UX) some quando o banco esta aberto na tela 4:3 BancoJoias = BJ -- Fundos desenhados no tamanho exato em que aparecem (134 x 34 a linha, 138 x 48 o cabecalho): o Button.tga -- de 40 x 19 esticado saia borrado (print de 20/10/2026). Gerados por script no estilo dele. local IMG_LINHA = 40017 -- Custom\Interface\BancoJoias_Linha.tga (134 x 68: normal e mouse em cima) local IMG_CABECALHO = 40018 -- Custom\Interface\BancoJoias_Cabecalho.tga (138 x 48) local IMG_FECHAR = 40011 -- Interface\mini_map_ui_cancel.tga: o X amarelo do inventario (36 x 58, 2 estados) local IMG_BOTAO = 31326 -- botao vazio do jogo (o mesmo do exemplo NewWindow do Louis) local IMG_TRILHO_C = 40012 -- Interface\newui_scrollbar_up.tga (7 x 3) local IMG_TRILHO_M = 40013 -- Interface\newui_scrollbar_m.tga (7 x 15) local IMG_TRILHO_B = 40014 -- Interface\newui_scrollbar_down.tga (7 x 3) local IMG_CURSOR = 40015 -- Interface\newui_scroll_on.tga (15 x 30) local IMG_SWORD = 40016 -- Custom\Interface\BancoJoias_Sword.tga (64 x 64, icone 2D da Sword of Style) -- RenderImage(id, x, y, w, h) NAO estica: recorta w x h pixels da textura (medido nos prints do DEV de -- 20/10/2026: a linha de 146 x 34 mostrou o Button.tga de 40 x 19 no canto e lixo verde no resto, e o -- indicador de rolagem de 6 px saiu como um pedaco cortado do botao). Para esticar, RenderImage2 com a -- fracao da textura, e a textura e guardada em potencia de 2 (40 x 19 vira 64 x 32): fracao = real / pot2. local function Pot2(n) local p = 1 while p < n do p = p * 2 end return p end -- tw x th = tamanho real da textura; recorte = altura do pedaco usado (estados empilhados), v0 = inicio dele local function Esticar(id, x, y, w, h, tw, th, recorte, v0) local pw, ph = Pot2(tw), Pot2(th) pcall(RenderImage2, id, x, y, w, h, 0, (v0 or 0) / ph, tw / pw, (recorte or th) / ph, 1, 1, 1.0) end local function T(chave) local m = CONFIG.MESSAGES[chave] local ok, l = pcall(GetLanguage) return (m and (m[ok and l or "Eng"] or m.Eng)) or chave end local function Avisar(chave, txt) if BJ.Avisado[chave] then return end BJ.Avisado[chave] = true pcall(Console, 2, "[BancoJoias] " .. txt) end local function ChaveItem(item, nivel) return tostring(item) .. ":" .. tostring(nivel) end ------------------------------------------------------------------------------- -- Pacotes ------------------------------------------------------------------------------- local LerForma = nil local function LerDword(nome) local formas = { function() return GetDwordPacket(nome) end, function() return GetDwordPacket(nome, -1) end, } if LerForma then local ok, v = pcall(formas[LerForma]); if ok then return tonumber(v) end end for i, f in ipairs(formas) do local ok, v = pcall(f) if ok and tonumber(v) then LerForma = i; return tonumber(v) end end Avisar("ler", "GetDwordPacket nao leu o pacote " .. nome) return nil end local function Enviar(nome, valores) CreatePacket(nome, 1) for _, v in ipairs(valores or {}) do SetDwordPacket(nome, v) end SendPacket(nome) ClearPacket(nome) end function BJ.Protocolo(head, nome) -- o ProtocolCore para no primeiro callback que devolver verdadeiro: pacote alheio devolve nil if nome == "BJ_Pong" then local valor = LerDword(nome) pcall(ClearPacket, nome) pcall(Console, 2, "[BancoJoias] BJ_Pong recebido (head " .. tostring(head) .. "): " .. tostring(valor)) Enviar("BJ_Ping", { valor or 0 }) return true end if nome == "BJ_Lista" then local n = LerDword(nome) or 0 local saldo = {} for _ = 1, math.min(n, 64) do local item, nivel, qtd = LerDword(nome), LerDword(nome), LerDword(nome) if item then saldo[ChaveItem(item, nivel)] = qtd or 0 end end pcall(ClearPacket, nome) BJ.Saldo = saldo return true end return nil end ProtocolFunctions.ClientProtocol(BJ.Protocolo) ------------------------------------------------------------------------------- -- Janela ------------------------------------------------------------------------------- local CONFLITO = { "UIFriendList", "UIMoveList", "UIQuest", "UITrade", "UIWarehouse", "UICommandWindow", "UIPetInfo", "UIShop", "UIStore", "UIOptions", "UIHelp", "UIFastDial", "UISkillTree", "UINPC_Titus", "UICashShop", "UIFullMap", "UINPC_Dialog", "UIChaosBox" } local function Conflita() for _, n in ipairs(CONFLITO) do local id = _G[n] if id and CheckWindowOpen(id) == 1 then return true end end return false end function BJ.Abrir() if CONFIG.Ativo ~= 1 or Conflita() then return end BJ.Aberto, BJ.Tela, BJ.Sel, BJ.Rolagem = true, "lista", nil, 0 OpenWindow(UIInventory) Enviar("BJ_Abrir", {}) end function BJ.Fechar() BJ.Aberto, BJ.Tela, BJ.Sel = false, "lista", nil end local function Botao(id, x, y, w, h, texto) BJ.Botoes[id] = { x = x, y = y, w = w, h = h } local mx, my = MousePosX(), MousePosY() local em = mx >= x and mx <= x + w and my >= y and my <= y + h Esticar(IMG_BOTAO, x, y, w, h, 108, 87, 29, em and 29 or 0) SetFontType(1); SetTextColor(255, 255, 255, 255); SetTextBg(0, 0, 0, 0) RenderText5(x, y + math.floor(h / 2) - 5, texto, w, 3) end local IconeForma = nil -- O CreateItem so CENTRALIZA o item na caixa (x + w/2, y + h/2): o tamanho vem do proprio item, e a Sword of -- Style (1 x 4) sai com ~160 px de altura (prints do DEV de 20/10/2026, com caixa de 30 e de 7). Item cujo 3D -- nao cabe tem icone 2D no Config (Img), desenhado do tamanho da caixa. local function Icone(item, nivel, x, y, w, h, img) if img then Esticar(img, x, y, w, h, 64, 64); return end local formas = { function() return CreateItem(x, y, w, h, item, nivel, 0, 0, 0) end, function() return CreateItem(x, y, w, h, item, nivel) end, function() return CreateItemSize(x, y, w, h, item, nivel, 0, 0, 0) end, } if IconeForma == false then return end if IconeForma then pcall(formas[IconeForma]); return end for i, f in ipairs(formas) do local ok, e = pcall(f) if ok then IconeForma = i; return end Avisar("icone" .. i, "icone forma " .. i .. ": " .. tostring(e)) end IconeForma = false end local function Moldura(px) RenderImage(31322, px, 0, 190, 428) RenderImage(31353, px, 0, 190, 64) RenderImage(31355, px, 64, 21, 320) RenderImage(31356, px + 169, 64, 21, 320) RenderImage(31357, px, 384, 190, 45) -- fechar: o mesmo X amarelo do inventario (pedido de 21/10/2026). O circulo ocupa 25 x 25 a partir de (6,2) -- em cada estado de 36 x 29 (medido na textura); o 2o estado, 29 px abaixo, e o de mouse em cima local fx, fy, ft = px + 190 - 27, 5, 19 BJ.Botoes.fechar = { x = fx, y = fy, w = ft, h = ft } local mx, my = MousePosX(), MousePosY() local em = mx >= fx and mx <= fx + ft and my >= fy and my <= fy + ft pcall(RenderImage2, IMG_FECHAR, fx, fy, ft, ft, 6 / 64, ((em and 29 or 0) + 2) / 64, 25 / 64, 25 / 64, 1, 1, 1.0) end local LARG_LINHA = 134 -- deixa 7 px para a barra de rolagem antes da borda direita da moldura local function DesenharLista(px) SetFontType(1); SetTextColor(255, 255, 255, 255); SetTextBg(0, 0, 0, 0) RenderText5(px, 12, T("Titulo"), 190, 3) local total = #CONFIG.Itens local vis = CONFIG.LinhasVisiveis BJ.Rolagem = math.max(0, math.min(BJ.Rolagem, total - vis)) for i = 1, vis do local k = i + BJ.Rolagem local it = CONFIG.Itens[k] if not it then break end local y = 40 + (i - 1) * 40 local id = "linha" .. k BJ.Botoes[id] = { x = px + 22, y = y, w = LARG_LINHA, h = 34, item = k } local em = MousePosX() >= px + 22 and MousePosX() <= px + 22 + LARG_LINHA and MousePosY() >= y and MousePosY() <= y + 34 Esticar(IMG_LINHA, px + 22, y, LARG_LINHA, 34, 134, 68, 34, em and 34 or 0) Icone(it.Item, it.Nivel, px + 26, y + 2, 30, 30, it.Img) SetTextColor(255, 255, 255, 255) RenderText5(px + 60, y + 6, it.Nome, LARG_LINHA - 40, 1) SetTextColor(255, 189, 25, 255) RenderText5(px + 60, y + 19, string.format("x%05d", BJ.Saldo[ChaveItem(it.Item, it.Nivel)] or 0), LARG_LINHA - 40, 1) end -- barra de rolagem do jogo (trilho 7 px e cursor 15 x 30), dentro da moldura, a direita das linhas if total > vis then local tx, ty, alt = px + 161, 40, vis * 40 - 6 Esticar(IMG_TRILHO_C, tx, ty, 7, 3, 7, 3) Esticar(IMG_TRILHO_M, tx, ty + 3, 7, alt - 6, 7, 15) Esticar(IMG_TRILHO_B, tx, ty + alt - 3, 7, 3, 7, 3) local cy = ty + math.floor((alt - 30) * BJ.Rolagem / (total - vis)) Esticar(IMG_CURSOR, tx - 4, cy, 15, 30, 15, 30) end SetTextColor(200, 200, 200, 255) RenderText5(px, 392, T("Dica"), 190, 3) end -- Tela do item, em grade: tudo alinhado nas mesmas bordas (MARGEM a esquerda e a direita), botoes da mesma -- altura e o mesmo espaco entre eles; quantidades em 3 colunas (joia: 10, 20, 30) ou 2 (Sword: 4, 8, 12, 16); -- Deposit All e Cancel presos ao pe da moldura, separados do saque. local MARGEM, ALT_BOTAO, ESPACO = 26, 22, 6 local function Grade(px, y, ids, textos) local larg = 190 - 2 * MARGEM local cols = (#ids % 3 == 0) and 3 or 2 local w = math.floor((larg - (cols - 1) * ESPACO) / cols) for i, id in ipairs(ids) do local col, lin = (i - 1) % cols, math.floor((i - 1) / cols) Botao(id, px + MARGEM + col * (w + ESPACO), y + lin * (ALT_BOTAO + ESPACO), w, ALT_BOTAO, textos[i]) end return y + math.ceil(#ids / cols) * (ALT_BOTAO + ESPACO) end local function Secao(px, y, texto) SetTextColor(170, 170, 170, 255) RenderText5(px + MARGEM, y, texto, 190 - 2 * MARGEM, 1) return y + 14 end local function DesenharItem(px) local it = CONFIG.Itens[BJ.Sel] if not it then BJ.Tela = "lista"; return end local qtd = BJ.Saldo[ChaveItem(it.Item, it.Nivel)] or 0 local larg = 190 - 2 * MARGEM SetFontType(1); SetTextBg(0, 0, 0, 0) SetTextColor(255, 255, 255, 255); RenderText5(px, 12, T("Titulo"), 190, 3) -- cabecalho: icone na caixa, nome e saldo Esticar(IMG_CABECALHO, px + MARGEM, 36, larg, 48, 138, 48) Icone(it.Item, it.Nivel, px + MARGEM + 4, 40, 40, 40, it.Img) SetTextColor(255, 255, 255, 255); RenderText5(px + MARGEM + 50, 46, it.Nome, larg - 54, 1) SetTextColor(255, 189, 25, 255); RenderText5(px + MARGEM + 50, 62, string.format("x%05d", qtd), larg - 54, 1) -- saque local y = Secao(px, 96, T("Sacar"):gsub("%s*%%d", "")) local ids, txt = { "sacar1" }, { "1" } if it.Max then ids[#ids + 1] = "max"; txt[#txt + 1] = "Max" end y = Grade(px, y, ids, txt) + 4 if #(it.Saque or {}) > 0 then ids, txt = {}, {} for i, q in ipairs(it.Saque) do ids[i] = "un" .. i; txt[i] = string.format(T("Unidades"), q) end y = Grade(px, y, ids, txt) + 4 end if it.Pack then y = Secao(px, y, "Pack") ids, txt = {}, {} for i, q in ipairs(it.Saque) do ids[i] = "pk" .. i; txt[i] = tostring(q) end Grade(px, y, ids, txt) end -- pe: deposito e voltar, sempre no mesmo lugar Botao("deptudo", px + MARGEM, 336, larg, ALT_BOTAO, T("DepTudo")) Botao("cancelar", px + MARGEM, 336 + ALT_BOTAO + ESPACO, larg, ALT_BOTAO, T("Cancelar")) end -- Posicao da janela: encostada a esquerda do inventario, como o bau normal (pedido de 21/10/2026). Ate 21/10 era -- GetWideX() - 275 (a mesma conta do exemplo NewWindow do Louis), que no print do dono a 1600 px deixava um vao -- de ~215 pontos ate o inventario. Com o inventario expandido aberto, mais uma largura de janela para a esquerda. -- O 215 foi medido numa resolucao so, e a conta so valia na wide: na tela 4:3 o banco abria fora da tela -- (LordOFWAR, 1024x768, 22/10/2026). Desde entao a janela encosta pela largura medida (Scripts/Comum/Tela.lua): -- inventario em MO_InventarioX(), banco 190 a esquerda dele. Na wide da o mesmo de antes (900). local Medido = false local function PosX() local px = MO_InventarioX and (MO_InventarioX() - 190) or (GetWideX() - 275 + (CONFIG.EncostoX or 215)) if UIExpandInventory and CheckWindowOpen(UIExpandInventory) == 1 then px = px - 190 end return px end local function MedirTela() if Medido or CONFIG.MedirTela ~= 1 then return end Medido = true local function v(f) local ok, r = pcall(f); return ok and tostring(r) or "?" end pcall(SendMessageClient, string.format("[BJ] tela: wide %s, janela %s, add %s, gl %s", v(GetWideX), v(WindowGetWidth), v(GetWindowWidthAdd), v(GetOpenGLWidth))) end function BancoJoias_Interface() if not BJ.Aberto then if MO_EsconderNomes then MO_EsconderNomes("banco", false) end return end MedirTela() if Conflita() or CheckWindowOpen(UIInventory) ~= 1 then BJ.Fechar(); return end BJ.Botoes = {} -- Os nomes de item no chao e de NPC saiam por cima da janela (print de 21/10/2026). Chamar DisableShowName a -- cada quadro fez o nome piscar (teste do dono, 21/10/2026): agora quem chama e o Scripts/Comum/Nomes.lua, -- so quando o estado muda. if MO_EsconderNomes then MO_EsconderNomes("banco", true) end local px = PosX() Moldura(px) if BJ.Tela == "item" then DesenharItem(px) else DesenharLista(px) end end local function Dentro(b, x, y) return b and x >= b.x and x <= b.x + b.w and y >= b.y and y <= b.y + b.h end local function Clicou(id) local it = CONFIG.Itens[BJ.Sel] if id == "fechar" then BJ.Fechar(); return end if string.sub(id, 1, 5) == "linha" then BJ.Sel = BJ.Botoes[id].item; BJ.Tela = "item"; return end if id == "cancelar" then BJ.Tela = "lista"; BJ.Sel = nil; return end if not it then return end if id == "sacar1" then Enviar("BJ_Sacar", { it.Item, it.Nivel, 0, 1 }); return end if id == "max" then Enviar("BJ_Sacar", { it.Item, it.Nivel, 2, 0 }); return end if id == "deptudo" then Enviar("BJ_DepTudo", { it.Item, it.Nivel }); return end local tipo, n = string.match(id, "^(%a%a)(%d+)$") n = tonumber(n) if n and it.Saque[n] then Enviar("BJ_Sacar", { it.Item, it.Nivel, tipo == "pk" and 1 or 0, it.Saque[n] }) end end function BancoJoias_Mouse() if not BJ.Aberto then return end if CheckReleasedKey(Keys.LButton) == 1 then local x, y = MousePosX(), MousePosY() for id, b in pairs(BJ.Botoes) do if Dentro(b, x, y) then Clicou(id); return end end end end function BancoJoias_Teclas() if not BJ.Aberto then return end if CheckWindowOpen(UIChatWindow) == 1 then return end local px = PosX() if MousePosX() >= px and MousePosX() <= px + 190 and MousePosY() <= 428 then DisableClickClient() end if CheckPressedKey(Keys.Escape) == 1 then if BJ.Tela == "item" then BJ.Tela = "lista"; BJ.Sel = nil else BJ.Fechar() end end end function BancoJoias_Tecla(key) if CONFIG.Ativo ~= 1 then return false end if CheckWindowOpen(UIChatWindow) == 1 then return false end if key == CONFIG.Tecla then if BJ.Aberto then BJ.Fechar() else BJ.Abrir() end return true end return false end function BancoJoias_Rolar(valor) if not BJ.Aberto or BJ.Tela ~= "lista" then return end local v = tonumber(valor) or 0 if v > 0 then BJ.Rolagem = BJ.Rolagem - 1 elseif v < 0 then BJ.Rolagem = BJ.Rolagem + 1 end end -- Ctrl + clique direito num item do inventario: deposita (o servidor confere se o item e do banco) function BancoJoias_CliqueDireito() if CONFIG.Ativo ~= 1 then return end if CheckWindowOpen(UIInventory) ~= 1 then return end if CheckPressedKey(Keys.ControlKey) ~= 1 and CheckRepeatKey(Keys.ControlKey) ~= 1 then return end local ok, casa = pcall(GetInventoryMouseItemSlot) if ok and tonumber(casa) and tonumber(casa) >= 0 then Enviar("BJ_Depositar", { tonumber(casa) }) end end function BancoJoias_Carregar() pcall(LoadBitmap, "Custom\\Interface\\BancoJoias_Linha.tga", IMG_LINHA) pcall(LoadBitmap, "Custom\\Interface\\BancoJoias_Cabecalho.tga", IMG_CABECALHO) pcall(LoadBitmap, "Interface\\mini_map_ui_cancel.tga", IMG_FECHAR) pcall(LoadBitmap, "Interface\\newui_scrollbar_up.tga", IMG_TRILHO_C) pcall(LoadBitmap, "Interface\\newui_scrollbar_m.tga", IMG_TRILHO_M) pcall(LoadBitmap, "Interface\\newui_scrollbar_down.tga", IMG_TRILHO_B) pcall(LoadBitmap, "Interface\\newui_scroll_on.tga", IMG_CURSOR) pcall(LoadBitmap, "Custom\\Interface\\BancoJoias_Sword.tga", IMG_SWORD) end BridgeFunctionAttach('MainLoader', 'BancoJoias_Carregar') BridgeFunctionAttach('MainInterfaceProcThread', 'BancoJoias_Interface') BridgeFunctionAttach('UpdateMouseEvent', 'BancoJoias_Mouse') BridgeFunctionAttach('UpdateKeyEvent', 'BancoJoias_Teclas') BridgeFunctionAttach('KeyboardEvent', 'BancoJoias_Tecla') BridgeFunctionAttach('ScrollMouseEvent', 'BancoJoias_Rolar') BridgeFunctionAttach('RightClickEvent', 'BancoJoias_CliqueDireito')