diff --git a/42.13/media/lua/client/Landtrain/LandtrainTowbarChainsRebuild.lua b/42.13/media/lua/client/Landtrain/LandtrainTowbarChainsRebuild.lua index 719ee10..4739296 100644 --- a/42.13/media/lua/client/Landtrain/LandtrainTowbarChainsRebuild.lua +++ b/42.13/media/lua/client/Landtrain/LandtrainTowbarChainsRebuild.lua @@ -112,27 +112,21 @@ end local function clearChangedOffset(vehicle) if not vehicle then return end - local md = vehicle:getModData() - if not md or md["isChangedTowedAttachment"] ~= true then return end +end + +local function restoreTowBarDefaults(vehicle) + if not vehicle then return end local script = vehicle:getScript() - local changedId = md["towBarChangedAttachmentId"] or vehicle:getTowAttachmentSelf() - local attachment = script and script:getAttachmentById(changedId) or nil - if attachment then - local offset = attachment:getOffset() - local storedShift = tonumber(md["towBarChangedOffsetZShift"]) - if storedShift ~= nil then - attachment:getOffset():set(offset:x(), offset:y(), offset:z() - storedShift) - else - local zShift = offset:z() > 0 and -1 or 1 - attachment:getOffset():set(offset:x(), offset:y(), offset:z() + zShift) + if script and script.getMass then + local scriptMass = tonumber(script:getMass()) + if scriptMass and scriptMass > 0 then + vehicle:setMass(scriptMass) end end - md["isChangedTowedAttachment"] = false - md["towBarChangedAttachmentId"] = nil - md["towBarChangedOffsetZShift"] = nil - vehicle:transmitModData() + vehicle:constraintChanged() + vehicle:updateTotalMass() end local function applyRigidTow(towingVehicle, towedVehicle, attachmentA, attachmentB) @@ -169,31 +163,7 @@ end local function refreshTowBarState(vehicle) if not vehicle then return end - local md = vehicle:getModData() - if not md then return end - - local front = vehicle:getVehicleTowedBy() - local rear = vehicle:getVehicleTowing() - - local isTowed = false - if front then - local frontMd = front:getModData() - if md["towed"] == true or (frontMd and frontMd["isTowingByTowBar"] == true) then - isTowed = true - end - end - - local isTowing = false - if rear then - local rearMd = rear:getModData() - if rearMd and rearMd["towed"] == true and rearMd["isTowingByTowBar"] == true then - isTowing = true - end - end - - md["towed"] = isTowed - md["isTowingByTowBar"] = isTowed or isTowing - vehicle:transmitModData() + setTowBarModelVisible(vehicle, vehicle:getVehicleTowedBy() ~= nil) end local function refreshAround(vehicle) @@ -205,25 +175,10 @@ end local function restoreDefaultsIfLeadLost(vehicle) if not vehicle then return end - local md = vehicle:getModData() - if not md then return end if vehicle:getVehicleTowedBy() ~= nil then return end - if md["towed"] ~= true and md.towBarOriginalMass == nil and md.towBarOriginalBrakingForce == nil then return end - if md.towBarOriginalScriptName then vehicle:setScriptName(md.towBarOriginalScriptName) end - if md.towBarOriginalMass ~= nil then vehicle:setMass(md.towBarOriginalMass) end - if md.towBarOriginalBrakingForce ~= nil then vehicle:setBrakingForce(md.towBarOriginalBrakingForce) end - vehicle:constraintChanged() - vehicle:updateTotalMass() - - md["towed"] = false - md.towBarOriginalScriptName = nil - md.towBarOriginalMass = nil - md.towBarOriginalBrakingForce = nil - if vehicle:getVehicleTowing() == nil then - md["isTowingByTowBar"] = false - end - vehicle:transmitModData() + clearChangedOffset(vehicle) + restoreTowBarDefaults(vehicle) setTowBarModelVisible(vehicle, false) end @@ -277,18 +232,6 @@ local function restoreFrontLink(playerObj, link) middle:setScriptName("notTowingA_Trailer") sendAttach(playerObj, front, middle, a, b) - local frontMd = front:getModData() - local middleMd = middle:getModData() - if frontMd then - frontMd["isTowingByTowBar"] = true - front:transmitModData() - end - if middleMd then - middleMd["isTowingByTowBar"] = true - middleMd["towed"] = true - middle:transmitModData() - end - setTowBarModelVisible(middle, true) refreshAround(front) refreshAround(middle) @@ -305,6 +248,14 @@ local function resolveDetachPair(towingVehicle, towedVehicle) local resolvedTowing = towingVehicle local resolvedTowed = resolvedTowing and resolvedTowing:getVehicleTowing() or nil + if resolvedTowed == nil and towedVehicle == nil and resolvedTowing and resolvedTowing:getVehicleTowedBy() then + local front = resolvedTowing:getVehicleTowedBy() + if front and front:getVehicleTowing() == resolvedTowing then + resolvedTowed = resolvedTowing + resolvedTowing = front + end + end + if resolvedTowed == nil and towedVehicle and towedVehicle:getVehicleTowedBy() then resolvedTowing = towedVehicle:getVehicleTowedBy() resolvedTowed = towedVehicle @@ -488,13 +439,8 @@ LT.performDetachWrapper = function(playerObj, towingVehicle, towedVehicle) restoreDefaultsIfLeadLost(resolvedTowing) restoreDefaultsIfLeadLost(resolvedTowed) - refreshAround(resolvedTowing) - refreshAround(resolvedTowed) - queueAction(playerObj, 12, function(_, v) restoreDefaultsIfLeadLost(v); refreshAround(v) end, resolvedTowing) queueAction(playerObj, 12, function(_, v) restoreDefaultsIfLeadLost(v); refreshAround(v) end, resolvedTowed) - queueAction(playerObj, 30, function(_, v) restoreDefaultsIfLeadLost(v); refreshAround(v) end, resolvedTowing) - queueAction(playerObj, 30, function(_, v) restoreDefaultsIfLeadLost(v); refreshAround(v) end, resolvedTowed) setTowBarModelVisible(resolvedTowed, false) end diff --git a/tools/java/BaseVehicleConstraintPatch.java b/tools/java/BaseVehicleConstraintPatch.java index a6a8c34..e392c68 100644 --- a/tools/java/BaseVehicleConstraintPatch.java +++ b/tools/java/BaseVehicleConstraintPatch.java @@ -20,8 +20,13 @@ import org.objectweb.asm.tree.MethodNode; public final class BaseVehicleConstraintPatch { private static final String TARGET_NAME = "addPointConstraint"; private static final String CONSTRAINT_CHANGED_NAME = "constraintChanged"; + private static final String IS_ENTER_BLOCKED_NAME = "isEnterBlocked"; + private static final String IS_ENTER_BLOCKED2_NAME = "isEnterBlocked2"; private static final String CLINIT_NAME = ""; private static final String VOID_NOARG_DESC = "()V"; + private static final String ENTER_BLOCKED_DESC = "(Lzombie/characters/IsoGameCharacter;I)Z"; + private static final String EXIT_BLOCKED_DESC = "(Lzombie/characters/IsoGameCharacter;I)Z"; + private static final String EXIT_BLOCKED2_DESC = "(I)Z"; 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"; @@ -31,6 +36,11 @@ public final class BaseVehicleConstraintPatch { private static final String HELPER_METHOD = "resolveConstraintDriver"; private static final String HELPER_DESC = "(Lzombie/vehicles/BaseVehicle;)Lzombie/characters/IsoGameCharacter;"; + private static final String HELPER_ENTER_BLOCKED = "isEnterBlockedLandtrain"; + private static final String HELPER_ENTER_BLOCKED_DESC = + "(Lzombie/vehicles/BaseVehicle;Lzombie/characters/IsoGameCharacter;I)Z"; + private static final String HELPER_ENTER_BLOCKED2 = "isEnterBlocked2Landtrain"; + private static final String HELPER_ENTER_BLOCKED2_DESC = "(Lzombie/vehicles/BaseVehicle;I)Z"; private BaseVehicleConstraintPatch() { } @@ -51,6 +61,8 @@ public final class BaseVehicleConstraintPatch { int removedCalls = 0; int inspectedAddPointMethods = 0; int patchedConstraintDriverCalls = 0; + int patchedEnterBlockedCalls = 0; + int patchedEnterBlocked2Calls = 0; for (MethodNode method : classNode.methods) { if (TARGET_NAME.equals(method.name) && isTargetAddPointConstraint(method.desc)) { @@ -59,6 +71,22 @@ public final class BaseVehicleConstraintPatch { } else if (CONSTRAINT_CHANGED_NAME.equals(method.name) && VOID_NOARG_DESC.equals(method.desc)) { patchedConstraintDriverCalls += patchConstraintChangedDriverCalls(method); + } else if (IS_ENTER_BLOCKED_NAME.equals(method.name) + && ENTER_BLOCKED_DESC.equals(method.desc)) { + patchedEnterBlockedCalls += patchEnterBlockedCall( + method, + "isExitBlocked", + EXIT_BLOCKED_DESC, + HELPER_ENTER_BLOCKED, + HELPER_ENTER_BLOCKED_DESC); + } else if (IS_ENTER_BLOCKED2_NAME.equals(method.name) + && ENTER_BLOCKED_DESC.equals(method.desc)) { + patchedEnterBlocked2Calls += patchEnterBlockedCall( + method, + "isExitBlocked2", + EXIT_BLOCKED2_DESC, + HELPER_ENTER_BLOCKED2, + HELPER_ENTER_BLOCKED2_DESC); } } @@ -75,6 +103,14 @@ public final class BaseVehicleConstraintPatch { "Expected to patch at least 1 constraintChanged getDriver call, patched " + patchedConstraintDriverCalls); } + if (patchedEnterBlockedCalls < 1) { + throw new IllegalStateException( + "Expected to patch isEnterBlocked call, patched " + patchedEnterBlockedCalls); + } + if (patchedEnterBlocked2Calls < 1) { + throw new IllegalStateException( + "Expected to patch isEnterBlocked2 call, patched " + patchedEnterBlocked2Calls); + } if (!ensureClassInitLog(classNode)) { throw new IllegalStateException("Failed to inject BaseVehicle class-init debug log"); } @@ -89,6 +125,10 @@ public final class BaseVehicleConstraintPatch { + removedCalls + ", constraint driver hooks: " + patchedConstraintDriverCalls + + ", enter-block hooks: " + + patchedEnterBlockedCalls + + "/" + + patchedEnterBlocked2Calls + ", class-init debug log: enabled"); } @@ -158,6 +198,41 @@ public final class BaseVehicleConstraintPatch { return patched; } + private static int patchEnterBlockedCall( + MethodNode method, + String targetCallName, + String targetCallDesc, + String helperMethod, + String helperDesc) { + int patched = 0; + InsnList insns = method.instructions; + for (AbstractInsnNode node = insns.getFirst(); node != null; ) { + AbstractInsnNode next = node.getNext(); + if (!(node instanceof MethodInsnNode call)) { + node = next; + continue; + } + if (!BASE_VEHICLE_OWNER.equals(call.owner) + || !targetCallName.equals(call.name) + || !targetCallDesc.equals(call.desc)) { + node = next; + continue; + } + + MethodInsnNode replacement = + new MethodInsnNode( + Opcodes.INVOKESTATIC, + HELPER_OWNER, + helperMethod, + helperDesc, + false); + insns.set(call, replacement); + patched++; + node = next; + } + return patched; + } + private static boolean ensureClassInitLog(ClassNode classNode) { MethodNode clinit = null; for (MethodNode method : classNode.methods) { diff --git a/tools/java/LandtrainConstraintAuthHelper.java b/tools/java/LandtrainConstraintAuthHelper.java index 42f156e..a5e594b 100644 --- a/tools/java/LandtrainConstraintAuthHelper.java +++ b/tools/java/LandtrainConstraintAuthHelper.java @@ -1,10 +1,12 @@ package zombie.vehicles; +import java.util.HashSet; +import java.util.Set; import zombie.characters.IsoGameCharacter; /** * Resolves the effective driver for constraint auth in chained towing. - * For middle vehicles in a chain, prefer the front/lead driver's authority. + * For middle vehicles in a chain, scan through links so long trains still resolve authority. */ public final class LandtrainConstraintAuthHelper { private LandtrainConstraintAuthHelper() { @@ -15,24 +17,49 @@ public final class LandtrainConstraintAuthHelper { return null; } - IsoGameCharacter driver = vehicle.getDriver(); - if (driver != null) { - return driver; - } + IsoGameCharacter driver = findDriverAlongChain(vehicle, true); + if (driver != null) return driver; + return findDriverAlongChain(vehicle, false); + } - BaseVehicle front = vehicle.getVehicleTowedBy(); - if (front != null) { - driver = front.getDriver(); + private static IsoGameCharacter findDriverAlongChain(BaseVehicle start, boolean towardFront) { + BaseVehicle cursor = start; + Set visited = new HashSet<>(); + + while (cursor != null) { + int id = cursor.getId(); + if (!visited.add(id)) { + // Safety: malformed link graph; stop scanning. + return null; + } + + IsoGameCharacter driver = cursor.getDriver(); if (driver != null) { return driver; } - } - BaseVehicle rear = vehicle.getVehicleTowing(); - if (rear != null) { - return rear.getDriver(); + cursor = towardFront ? cursor.getVehicleTowedBy() : cursor.getVehicleTowing(); } return null; } + + public static boolean isEnterBlockedLandtrain( + BaseVehicle vehicle, IsoGameCharacter chr, int seat) { + if (isVehicleBeingTowedInLandtrain(vehicle)) { + return true; + } + return vehicle != null && vehicle.isExitBlocked(chr, seat); + } + + public static boolean isEnterBlocked2Landtrain(BaseVehicle vehicle, int seat) { + if (isVehicleBeingTowedInLandtrain(vehicle)) { + return true; + } + return vehicle != null && vehicle.isExitBlocked2(seat); + } + + private static boolean isVehicleBeingTowedInLandtrain(BaseVehicle vehicle) { + return vehicle != null && vehicle.getVehicleTowedBy() != null; + } } diff --git a/zombie/vehicles/BaseVehicle.class b/zombie/vehicles/BaseVehicle.class index 3319ada..7ac58c8 100644 Binary files a/zombie/vehicles/BaseVehicle.class and b/zombie/vehicles/BaseVehicle.class differ diff --git a/zombie/vehicles/LandtrainConstraintAuthHelper.class b/zombie/vehicles/LandtrainConstraintAuthHelper.class index 42c2fcd..6907135 100644 Binary files a/zombie/vehicles/LandtrainConstraintAuthHelper.class and b/zombie/vehicles/LandtrainConstraintAuthHelper.class differ