-- =========================================================================== -- NpcMaluco - loja do NPC Maluco em Lua (cliente). Pedido do dono em 20/10/2026. -- Servidor (quem decide tudo): Data/Script/Scripts/NpcMaluco. -- -- O cliente e do jogador: este arquivo so DESENHA e PEDE. -- botao do NPC -> NM_Abrir o servidor confere evento e distancia e responde NM_Loja -- botao Comprar -> NM_Comprar (k) o servidor confere zen e espaco, cobra e entrega -- fechar -> NM_Sair -- NM_Evento (do servidor): evento aberto ou fechado, e onde o NPC esta -- NM_Fechar (do servidor): o evento acabou, fecha a janela -- -- Botao "NPC Maluco": com o evento aberto e o personagem perto do NPC, aparece um botao no pe da tela que abre a -- loja. O clique no proprio NPC nao serve: o 592 nasce pelo MonsterCreate e o clique nele nao chega ao servidor, -- e o gancho ObjectRenderName do cliente tambem nao avisou nada (DEV, 20/10/2026). -- Janela no mesmo desenho do banco de joias (Scripts/BancoJoias), com texturas do tamanho exato. -- =========================================================================== local CONFIG = require("Scripts\\NpcMaluco\\Config") local NM = { Aberto = false, Loja = {}, Botoes = {}, Evento = nil, Chamar = nil } NpcMaluco = NM local IMG_BOTAO = 31326 -- botao vazio do jogo (108 x 87, 3 estados de 29) local IMG_FECHAR = 40020 -- Custom\Interface\button_close.tga (21 x 84, 4 estados) local IMG_CABECALHO = 40021 -- Custom\Interface\BancoJoias_Cabecalho.tga (138 x 48) local MARGEM, ALT_BOTAO, ESPACO = 26, 22, 6 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 Pot2(n) local p = 1 while p < n do p = p * 2 end return p end -- RenderImage nao estica; RenderImage2 com a fracao da textura, guardada em potencia de 2 (ver BancoJoias) 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 Enviar(nome, valores) CreatePacket(nome, 1) for _, v in ipairs(valores or {}) do SetDwordPacket(nome, v) end SendPacket(nome) ClearPacket(nome) end local function LerDword(nome) local ok, v = pcall(GetDwordPacket, nome) if ok and tonumber(v) then return tonumber(v) end ok, v = pcall(GetDwordPacket, nome, -1) return ok and tonumber(v) or nil end local function NomeDoItem(item, nivel) for _, it in ipairs(CONFIG.Loja) do if it.Item == item and it.Nivel == nivel then return it.Nome end end return tostring(item) end function NM.Fechar(avisar) if NM.Aberto and avisar then Enviar("NM_Sair", {}) end NM.Aberto = false end function NM.Protocolo(head, nome) if nome == "NM_Loja" then local n = LerDword(nome) or 0 local loja = {} for _ = 1, math.min(n, 16) do local item, nivel, preco = LerDword(nome), LerDword(nome), LerDword(nome) if item then loja[#loja + 1] = { Item = item, Nivel = nivel or 0, Preco = preco or 0, Nome = NomeDoItem(item, nivel or 0) } end end pcall(ClearPacket, nome) NM.Loja = loja NM.Aberto = true if BancoJoias and BancoJoias.Fechar then pcall(BancoJoias.Fechar) end -- as duas janelas ocupam o mesmo lugar if CheckWindowOpen(UIInventory) ~= 1 then OpenWindow(UIInventory) end return true end if nome == "NM_Evento" then local aberto, mapa, x, y, dist = LerDword(nome), LerDword(nome), LerDword(nome), LerDword(nome), LerDword(nome) pcall(ClearPacket, nome) NM.Evento = (aberto == 1) and { mapa = mapa, x = x, y = y, dist = dist } or nil if not NM.Evento then NM.Fechar(false) end return true end if nome == "NM_Fechar" then pcall(ClearPacket, nome) NM.Fechar(false) return true end return nil end ProtocolFunctions.ClientProtocol(NM.Protocolo) ------------------------------------------------------------------------------- -- Perto do NPC (so para mostrar o botao: quem confere de verdade e o servidor) ------------------------------------------------------------------------------- local function Perto() local e = NM.Evento if not e then return false end local ok1, mapa = pcall(UserGetMap) local ok2, x = pcall(UserPositionX) local ok3, y = pcall(UserPositionY) if not (ok1 and ok2 and ok3) or tonumber(mapa) ~= e.mapa then return false end return math.abs(tonumber(x) - e.x) <= e.dist and math.abs(tonumber(y) - e.y) <= e.dist end ------------------------------------------------------------------------------- -- Janela ------------------------------------------------------------------------------- local function Botao(id, x, y, w, h, texto) NM.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 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) NM.Botoes.fechar = { x = px + 190 - 24, y = 6, w = 16, h = 16 } Esticar(IMG_FECHAR, px + 190 - 24, 6, 16, 16, 21, 84, 21) end local function Icone(item, nivel, x, y, w, h) pcall(CreateItem, x, y, w, h, item, nivel, 0, 0, 0) end -- Botao no pe da tela, centralizado, acima da barra do jogo local BOTAO_W, BOTAO_H = 120, 24 local function BotaoChamar() local x = math.floor(GetWideX() / 2 - BOTAO_W / 2) local y = 372 NM.Chamar = { x = x, y = y, w = BOTAO_W, h = BOTAO_H } local mx, my = MousePosX(), MousePosY() local em = mx >= x and mx <= x + BOTAO_W and my >= y and my <= y + BOTAO_H Esticar(IMG_BOTAO, x, y, BOTAO_W, BOTAO_H, 108, 87, 29, em and 29 or 0) SetFontType(1); SetTextBg(0, 0, 0, 0); SetTextColor(255, 189, 25, 255) RenderText5(x, y + math.floor(BOTAO_H / 2) - 5, T("Chamar"), BOTAO_W, 3) end function NpcMaluco_Interface() NM.Chamar = nil if not NM.Aberto then if CONFIG.Ativo == 1 and Perto() then BotaoChamar() end return end if CheckWindowOpen(UIInventory) ~= 1 then NM.Fechar(true); return end NM.Botoes = {} local px = GetWideX() - 275 local larg = 190 - 2 * MARGEM Moldura(px) SetFontType(1); SetTextBg(0, 0, 0, 0) SetTextColor(255, 255, 255, 255); RenderText5(px, 12, T("Titulo"), 190, 3) local y = 36 for k, it in ipairs(NM.Loja) do Esticar(IMG_CABECALHO, px + MARGEM, y, larg, 48, 138, 48) Icone(it.Item, it.Nivel, px + MARGEM + 4, y + 4, 40, 40) SetTextColor(255, 255, 255, 255); RenderText5(px + MARGEM + 50, y + 10, it.Nome, larg - 54, 1) SetTextColor(255, 189, 25, 255); RenderText5(px + MARGEM + 50, y + 26, string.format(T("Preco"), it.Preco), larg - 54, 1) Botao("comprar" .. k, px + MARGEM, y + 48 + ESPACO, larg, ALT_BOTAO, T("Comprar")) y = y + 48 + ESPACO + ALT_BOTAO + 12 end Botao("sair", px + MARGEM, 336 + ALT_BOTAO + ESPACO, larg, ALT_BOTAO, T("Fechar")) 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 function NpcMaluco_Mouse() if CONFIG.Ativo ~= 1 then return end if CheckReleasedKey(Keys.LButton) ~= 1 then return end local x, y = MousePosX(), MousePosY() if NM.Aberto then for id, b in pairs(NM.Botoes) do if Dentro(b, x, y) then if id == "fechar" or id == "sair" then NM.Fechar(true); return end local k = tonumber(string.match(id, "^comprar(%d+)$")) if k then Enviar("NM_Comprar", { k }) end return end end end if Dentro(NM.Chamar, x, y) then Enviar("NM_Abrir", {}) end end function NpcMaluco_Teclas() if NM.Chamar and Dentro(NM.Chamar, MousePosX(), MousePosY()) then DisableClickClient() end if not NM.Aberto then return end if CheckWindowOpen(UIChatWindow) == 1 then return end local px = GetWideX() - 275 if MousePosX() >= px and MousePosX() <= px + 190 and MousePosY() <= 428 then DisableClickClient() end if CheckPressedKey(Keys.Escape) == 1 then NM.Fechar(true) end end function NpcMaluco_Carregar() pcall(LoadBitmap, "Custom\\Interface\\button_close.tga", IMG_FECHAR) pcall(LoadBitmap, "Custom\\Interface\\BancoJoias_Cabecalho.tga", IMG_CABECALHO) end BridgeFunctionAttach('MainLoader', 'NpcMaluco_Carregar') BridgeFunctionAttach('MainInterfaceProcThread', 'NpcMaluco_Interface') BridgeFunctionAttach('UpdateMouseEvent', 'NpcMaluco_Mouse') BridgeFunctionAttach('UpdateKeyEvent', 'NpcMaluco_Teclas')