diff --git a/42.13/media/lua/client/Landtrain/UnlimitedTowbarChains.lua b/42.13/media/lua/client/Landtrain/UnlimitedTowbarChains.lua index 925a8cc..c6443fa 100644 --- a/42.13/media/lua/client/Landtrain/UnlimitedTowbarChains.lua +++ b/42.13/media/lua/client/Landtrain/UnlimitedTowbarChains.lua @@ -554,7 +554,32 @@ local function breakConstraintSafe(vehicle, reason) local playerObj = getSpecificPlayer(0) if playerObj ~= nil then - sendClientCommand(playerObj, "towbar", "detachConstraint", { vehicle = vehicle:getId() }) + local towingVehicle = vehicle + local towedVehicle = vehicle:getVehicleTowing() + if towedVehicle == nil then + local frontVehicle = vehicle:getVehicleTowedBy() + if frontVehicle ~= nil then + towingVehicle = frontVehicle + towedVehicle = vehicle + end + end + + local detachCommand = "detachTowBar" + if TowBarMod and TowBarMod.Hook and TowBarMod.Hook.performDetachTowBar == nil then + detachCommand = "detachConstraint" + end + + local args = {} + if towingVehicle ~= nil then + args.towingVehicle = towingVehicle:getId() + end + if towedVehicle ~= nil then + args.vehicle = towedVehicle:getId() + else + args.vehicle = vehicle:getId() + end + + sendClientCommand(playerObj, "towbar", detachCommand, args) return true end return false @@ -1208,9 +1233,19 @@ local function menuHasTowbarAttachSlice(menu) return false end +local function getTowbarDetachAction() + if TowBarMod == nil or TowBarMod.Hook == nil then return nil end + return TowBarMod.Hook.deattachTowBarAction or TowBarMod.Hook.detachTowBarAction +end + +local function getTowbarPerformDetachHook() + if TowBarMod == nil or TowBarMod.Hook == nil then return nil end + return TowBarMod.Hook.performDetachTowBar or TowBarMod.Hook.performDeattachTowBar +end + local function menuHasTowbarDetachSlice(menu) if menu == nil or menu.slices == nil then return false end - local detachAction = TowBarMod and TowBarMod.Hook and TowBarMod.Hook.deattachTowBarAction or nil + local detachAction = getTowbarDetachAction() if detachAction == nil then return false end @@ -1288,7 +1323,8 @@ end local function addLandtrainUnhookOptionToMenu(playerObj, vehicle) if playerObj == nil or vehicle == nil then return end - if TowBarMod == nil or TowBarMod.Hook == nil or TowBarMod.Hook.deattachTowBarAction == nil then return end + local detachAction = getTowbarDetachAction() + if detachAction == nil then return end local menu = getPlayerRadialMenu(playerObj:getPlayerNum()) if menu == nil then return end @@ -1300,7 +1336,7 @@ local function addLandtrainUnhookOptionToMenu(playerObj, vehicle) menu:addSlice( getText("ContextMenu_Vehicle_DetachTrailer", ISVehicleMenu.getVehicleDisplayName(towedVehicle)), getTexture("media/textures/tow_bar_detach.png"), - TowBarMod.Hook.deattachTowBarAction, + detachAction, playerObj, towedVehicle ) @@ -1538,7 +1574,7 @@ local function installLandtrainTowbarPatch() return resolvedTowing, resolvedTowed end - local originalPerformDetach = TowBarMod.Hook.performDeattachTowBar + local originalPerformDetach = getTowbarPerformDetachHook() if originalPerformDetach and originalPerformDetach ~= TowBarMod.Hook._landtrainPerformDetachWrapper then local performDetachWrapper = function(playerObj, towingVehicle, towedVehicle) local resolvedTowingVehicle, resolvedTowedVehicle = resolveTowbarDetachPair(towingVehicle, towedVehicle) @@ -1550,11 +1586,11 @@ local function installLandtrainTowbarPatch() return end - dumpTowState("performDeattachTowBar pre towing", resolvedTowingVehicle) - dumpTowState("performDeattachTowBar pre towed", resolvedTowedVehicle) + dumpTowState("performDetachTowBar pre towing", resolvedTowingVehicle) + dumpTowState("performDetachTowBar pre towed", resolvedTowedVehicle) originalPerformDetach(playerObj, resolvedTowingVehicle, resolvedTowedVehicle) - dumpTowState("performDeattachTowBar post-original towing", resolvedTowingVehicle) - dumpTowState("performDeattachTowBar post-original towed", resolvedTowedVehicle) + dumpTowState("performDetachTowBar post-original towing", resolvedTowingVehicle) + dumpTowState("performDetachTowBar post-original towed", resolvedTowedVehicle) clearLandtrainFrontLinkData(resolvedTowedVehicle) reconcileTowbarSplitAround(resolvedTowingVehicle) @@ -1573,13 +1609,14 @@ local function installLandtrainTowbarPatch() refreshTowBarState(resolvedTowedVehicle:getVehicleTowing()) saveActiveLandtrainTowbarSnapshot() - dumpTowState("performDeattachTowBar post-reconcile towing", resolvedTowingVehicle) - dumpTowState("performDeattachTowBar post-reconcile towed", resolvedTowedVehicle) + dumpTowState("performDetachTowBar post-reconcile towing", resolvedTowingVehicle) + dumpTowState("performDetachTowBar post-reconcile towed", resolvedTowedVehicle) end + TowBarMod.Hook.performDetachTowBar = performDetachWrapper TowBarMod.Hook.performDeattachTowBar = performDetachWrapper TowBarMod.Hook._landtrainPerformDetachWrapper = performDetachWrapper - ltLog("installLandtrainTowbarPatch patched TowBarMod.Hook.performDeattachTowBar") + ltLog("installLandtrainTowbarPatch patched TowBarMod.Hook.performDetachTowBar") end -- If vanilla detach sneaks into the radial menu, redirect it to Towbar timed detach. @@ -1592,9 +1629,9 @@ local function installLandtrainTowbarPatch() end local towbarDetachTarget = getTowbarDetachTargetVehicle(vehicleToCheck) - if playerObj ~= nil and towbarDetachTarget ~= nil - and TowBarMod and TowBarMod.Hook and TowBarMod.Hook.deattachTowBarAction then - TowBarMod.Hook.deattachTowBarAction(playerObj, towbarDetachTarget) + local detachAction = getTowbarDetachAction() + if playerObj ~= nil and towbarDetachTarget ~= nil and detachAction ~= nil then + detachAction(playerObj, towbarDetachTarget) return end diff --git a/tools/java/BaseVehicleConstraintPatch.java b/tools/java/BaseVehicleConstraintPatch.java index b3dc692..445e768 100644 --- a/tools/java/BaseVehicleConstraintPatch.java +++ b/tools/java/BaseVehicleConstraintPatch.java @@ -7,6 +7,7 @@ import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.Opcodes; @@ -17,6 +18,9 @@ import org.objectweb.asm.Opcodes; */ public final class BaseVehicleConstraintPatch { private static final String TARGET_NAME = "addPointConstraint"; + private static final String CLINIT_NAME = ""; + private static final String VOID_NOARG_DESC = "()V"; + private static final String PATCH_LOG_LINE = "[Landtrain][BaseVehiclePatch] BaseVehicle override enabled"; private static final String BREAK_DESC_OBJECT_BOOL = "(ZLjava/lang/Boolean;)V"; private static final String BREAK_DESC_PRIMITIVE_BOOL = "(ZZ)V"; private static final String BASE_VEHICLE_OWNER = "zombie/vehicles/BaseVehicle"; @@ -55,13 +59,19 @@ public final class BaseVehicleConstraintPatch { + inspectedAddPointMethods + ")"); } + if (!ensureClassInitLog(classNode)) { + throw new IllegalStateException("Failed to inject BaseVehicle class-init debug log"); + } ClassWriter writer = new ClassWriter(0); classNode.accept(writer); Files.createDirectories(output.getParent()); Files.write(output, writer.toByteArray()); - System.out.println("Patched BaseVehicle.class; removed breakConstraint calls: " + removedCalls); + System.out.println( + "Patched BaseVehicle.class; removed breakConstraint calls: " + + removedCalls + + ", class-init debug log: enabled"); } private static boolean isTargetAddPointConstraint(String methodDesc) { @@ -102,4 +112,72 @@ public final class BaseVehicleConstraintPatch { return patched; } + + private static boolean ensureClassInitLog(ClassNode classNode) { + MethodNode clinit = null; + for (MethodNode method : classNode.methods) { + if (CLINIT_NAME.equals(method.name) && VOID_NOARG_DESC.equals(method.desc)) { + clinit = method; + break; + } + } + + if (clinit == null) { + clinit = new MethodNode(Opcodes.ACC_STATIC, CLINIT_NAME, VOID_NOARG_DESC, null, null); + clinit.instructions = new InsnList(); + clinit.instructions.add(createPatchLogInstructions()); + clinit.instructions.add(new InsnNode(Opcodes.RETURN)); + clinit.maxStack = 2; + clinit.maxLocals = 0; + classNode.methods.add(clinit); + return true; + } + + if (hasPatchLog(clinit)) { + return true; + } + + boolean inserted = false; + for (AbstractInsnNode node = clinit.instructions.getFirst(); node != null; ) { + AbstractInsnNode next = node.getNext(); + if (node.getOpcode() == Opcodes.RETURN) { + clinit.instructions.insertBefore(node, createPatchLogInstructions()); + inserted = true; + } + node = next; + } + + if (inserted) { + clinit.maxStack = Math.max(clinit.maxStack, 2); + } + return inserted; + } + + private static boolean hasPatchLog(MethodNode method) { + for (AbstractInsnNode node = method.instructions.getFirst(); node != null; node = node.getNext()) { + if (node instanceof LdcInsnNode ldc + && ldc.cst instanceof String text + && PATCH_LOG_LINE.equals(text)) { + return true; + } + } + return false; + } + + private static InsnList createPatchLogInstructions() { + InsnList insns = new InsnList(); + insns.add(new org.objectweb.asm.tree.FieldInsnNode( + Opcodes.GETSTATIC, + "java/lang/System", + "out", + "Ljava/io/PrintStream;")); + insns.add(new LdcInsnNode(PATCH_LOG_LINE)); + insns.add(new MethodInsnNode( + Opcodes.INVOKEVIRTUAL, + "java/io/PrintStream", + "println", + "(Ljava/lang/String;)V", + false)); + return insns; + } } diff --git a/zombie/vehicles/BaseVehicle.class b/zombie/vehicles/BaseVehicle.class index 314d1c7..3d1d73f 100644 Binary files a/zombie/vehicles/BaseVehicle.class and b/zombie/vehicles/BaseVehicle.class differ