Project Zomboid

Project Zomboid

True Music B42
[Fix] True Music – Right-click error depois do update 42.11
[Fix] True Music – Right-click error after update 42.11

Após a atualização 42.11 do Project Zomboid, o mod True Music começou a apresentar este erro ao clicar com o botão direito.

function: TCFillContextMenu -- file: worldContextBoombox.lua line # 46 | MOD: True Music_b42 Callframe at: se.krka.kahlua.integration.expose.MultiLuaJavaInvoker@9e2411ce function: createMenu -- file: ISWorldObjectContextMenu.lua line # 214 | Vanilla function: createMenu -- file: ISMenuContextWorld.lua line # 50 | Vanilla function: createWorldMenu -- file: ISContextManager.lua line # 28 | Vanilla function: doRClick -- file: ISObjectClickHandler.lua line # 63 | Vanilla function: onObjectRightMouseButtonUp -- file: ISObjectClickHandler.lua line # 428 | Vanilla ERROR: General f:2886, t:1754750410183> ExceptionLogger.logException> Exception thrown java.lang.RuntimeException: Object tried to call nil in TCFillContextMenu at KahluaUtil.fail(KahluaUtil.java:101). Stack trace: (seu log continua aqui…)

---

🔍 O que mudou no jogo
A atualização 42.11 fez mudanças internas na API usada por mods para criar menus de contexto (o menu que aparece ao clicar com o botão direito).
Essas mudanças afetaram o arquivo worldContextBoombox.lua do True Music, porque o código original chamava funções que não existem mais em alguns contextos.

---

💡 Solução aplicada
Foi criado um patch no worldContextBoombox.lua para:
- Proteger chamadas a funções: agora o código checa se a função existe antes de chamar e usa alternativas ou fallbacks.
- Evitar erros em objetos sem item: criada uma função segura (`safeGetItemName`) que pega o nome de forma segura ou usa `"???"` como fallback.
- Remover/adicionar opções do menu mesmo quando os métodos originais não estão disponíveis.
- Substituir `getSquaresInRadius` por uma versão que usa apenas os squares já coletados caso a função não exista, mantendo a funcionalidade básica.

---

📂 Arquivo modificado
O arquivo alterado foi:
D:\Program Files (x86)\Steam\steamapps\workshop\content\108600\3397198968\mods\TrueMusic_b42\media\lua\client\Context\WorldObject\worldContextBoombox.lua

e

D:\Program Files (x86)\Steam\steamapps\workshop\content\108600\3397198968\mods\TrueMusic_b42\42\media\lua\client\Context\WorldObject\worldContextBoombox.lua

---

🛠 Como aplicar
1. Feche o jogo.
2. Abra os `worldContextBoombox.lua` dos dois diretórios com o bloco de notas (recomendo o Notepad++).

FAÇA UM BACKUP DOS DOIS NA ÁREA DE TRABALHO PARA CASO PRECISE VOLTAR AO ORIGINAL. NÃO DEIXE O BACKUP NA PASTA.

4. Substitua todo o conteúdo pelo código no final da pagina:
5. Salve o arquivo e feche.

---


Limpe o cache de mods do PZ.
Garanta que a Tsar’s Common Library esteja atualizada e carregada acima do True Music.
Abra o jogo e teste (use o modo Debug para facilitar).



Espero que ajude. Não testei com muitos Addons ainda, mas até agora está tudo funcionando bem.


-- True Music (B42) – worldContextBoombox.lua compatível com 42.11 require "TCMusicDefenitions" -- Evita call em nil quando o objeto do mundo não tem item/nome local function safeGetItemName(worldObject) if worldObject and worldObject.getName then local n = worldObject:getName() if n and n ~= "" then return n end end if worldObject and worldObject.getItem then local itm = worldObject:getItem() if itm and itm.getName then return itm:getName() end end return "???" end -- Remove opção por texto, usando métodos novos/antigos ou varredura manual local function removeOptionByText(context, text) if not context or not text then return end local opt = context.getOptionFromName and context:getOptionFromName(text) or nil if opt then if context.removeOptionTsar then context:removeOptionTsar(opt); return end if context.removeOption then context:removeOption(opt); return end end -- Fallback: varre a tabela interna if context.options and type(context.options) == "table" then for i = #context.options, 1, -1 do local o = context.options
if o and o.name == text then
table.remove(context.options, i)
end
end
end
end

-- Adiciona a opção de abrir a janela do rádio
local function addDeviceOption(context, playerObj, isoRadio)
if not (context and playerObj and isoRadio) then return end
local label = getText("IGUI_DeviceOptions")
local cb = function(pl, obj)
if ISRadioWindow and ISRadioWindow.activate then
ISRadioWindow.activate(pl, obj, true)
end
end
if context.addOptionOnTop then
context:addOptionOnTop(label, playerObj, cb, isoRadio)
else
context:addOption(label, playerObj, cb, isoRadio)
end
end

function TCFillContextMenu(player, context, worldobjects, test)
-- Rotas de teste do menu do próprio PZ
if test and ISWorldObjectContextMenu.Test then return true end
if getCore():getGameMode() == "LastStand" then return end
if test then return ISWorldObjectContextMenu.setTest() end

local playerObj = getSpecificPlayer(player)
if not playerObj or playerObj:getVehicle() then return end
local pnum = playerObj:getPlayerNum()

-- Coletar squares únicos a partir do que foi clicado
local squares, doneSquare = {}, {}
for _, v in ipairs(worldobjects or {}) do
if v.getSquare then
local sq = v:getSquare()
if sq and not doneSquare[sq] then
doneSquare[sq] = true
table.insert(squares, sq)
end
end
end
if #squares == 0 then return false end

-- Montar lista de worldObjects próximos
local worldObjects = {}

if JoypadState and JoypadState.players and JoypadState.players[pnum + 1] then
-- Joypad: pega os objetos dos squares coletados
for _, sq in ipairs(squares) do
local arr = sq:getWorldObjects()
for i = 1, arr:size() do
table.insert(worldObjects, arr:get(i - 1))
end
end
else
-- Mouse: tenta expandir em raio se a API existir; senão, usa só os squares atuais
local squares2 = {}
for k, v in pairs(squares) do squares2[k] = v end

local function pushSquareObjects(sq)
local arr = sq:getWorldObjects()
for i = 1, arr:size() do
table.insert(worldObjects, arr:get(i - 1))
end
end

if ISWorldObjectContextMenu and ISWorldObjectContextMenu.getSquaresInRadius then
local radius = 1
for _, sq in ipairs(squares2) do
local worldX = screenToIsoX(pnum, context.x, context.y, sq:getZ())
local worldY = screenToIsoY(pnum, context.x, context.y, sq:getZ())
ISWorldObjectContextMenu.getSquaresInRadius(worldX, worldY, sq:getZ(), radius, doneSquare, squares)
end
for _, sq in pairs(squares) do pushSquareObjects(sq) end
else
-- Fallback 42.11: sem expandir raio
for _, sq in ipairs(squares2) do pushSquareObjects(sq) end
end
end

if #worldObjects == 0 then return false end

-- Agrupa por nome (de forma segura)
local itemList = {}
for _, wo in ipairs(worldObjects) do
local itemName = safeGetItemName(wo)
itemList[itemName] = itemList[itemName] or {}
table.insert(itemList[itemName], wo)
end

-- Lógica do True Music para rádios
for _, items in pairs(itemList) do
local first = items[1]
local square = first.getSquare and first:getSquare() or nil
local itm = first.getItem and first:getItem() or nil

if itm and instanceof(itm, "Radio") then
-- Se for Walkman, remove "Device Options" do vanilla
if TCMusic and TCMusic.WalkmanPlayer and TCMusic.WalkmanPlayer[itm:getFullType()] then
removeOptionByText(context, getText("IGUI_DeviceOptions"))
else
-- Procura o IsoRadio linkado ao item pelo RadioItemID
if square and square.getObjects then
local objs = square:getObjects()
local link = tostring(itm:getID()) .. "tm"
local found = nil
for i = 0, objs:size() - 1 do
local tObj = objs:get(i)
if instanceof(tObj, "IsoRadio") then
local md = tObj:getModData()
if md and md.RadioItemID == link then
found = tObj
break
end
end
end
if found then
addDeviceOption(context, playerObj, found)
end
end
end
end
end
end

-- Só registra se a função existir (evita "call nil" no bind de evento)
if TCFillContextMenu then
Events.OnFillWorldObjectContextMenu.Add(TCFillContextMenu)
end
Last edited by PensolPlays; 9 Aug @ 9:07am
< >
Showing 1-1 of 1 comments
done <3
< >
Showing 1-1 of 1 comments
Per page: 1530 50