This commit is contained in:
2026-02-12 17:55:25 -05:00
parent 0127c1d0c7
commit 9e98c54057
7 changed files with 512 additions and 42 deletions

View File

@@ -45,14 +45,51 @@ local function resolveVehicle(id)
return getVehicleById(id)
end
local function reconcilePairState(vehicleA, vehicleB, attachmentA, attachmentB)
if not vehicleA or not vehicleB then return end
if TowBarMod.Utils and TowBarMod.Utils.updateAttachmentsForRigidTow then
TowBarMod.Utils.updateAttachmentsForRigidTow(vehicleA, vehicleB, attachmentA, attachmentB)
end
local towingMd = vehicleA:getModData()
local towedMd = vehicleB:getModData()
local currentScript = vehicleB:getScriptName()
if towingMd then
towingMd["isTowingByTowBar"] = true
vehicleA:transmitModData()
end
if towedMd then
if towedMd.towBarOriginalScriptName == nil and currentScript ~= "notTowingA_Trailer" then
towedMd.towBarOriginalScriptName = currentScript
end
if towedMd.towBarOriginalMass == nil then
towedMd.towBarOriginalMass = vehicleB:getMass()
end
if towedMd.towBarOriginalBrakingForce == nil then
towedMd.towBarOriginalBrakingForce = vehicleB:getBrakingForce()
end
towedMd["isTowingByTowBar"] = true
towedMd["towed"] = true
vehicleB:transmitModData()
end
vehicleB:setScriptName("notTowingA_Trailer")
if TowBarMod.Hook and TowBarMod.Hook.setVehiclePostAttach then
pcall(TowBarMod.Hook.setVehiclePostAttach, nil, vehicleB)
end
log("reconcilePairState A=" .. tostring(vehicleA:getId()) .. " B=" .. tostring(vehicleB:getId())
.. " attachmentA=" .. tostring(attachmentA) .. " attachmentB=" .. tostring(attachmentB))
end
local function applyAttachSync(args)
if not args then return end
local vehicleA = resolveVehicle(args.vehicleA)
local vehicleB = resolveVehicle(args.vehicleB)
if not vehicleA or not vehicleB then return end
if vehicleA:getVehicleTowing() == vehicleB and vehicleB:getVehicleTowedBy() == vehicleA then
if not vehicleA or not vehicleB then
log("applyAttachSync skipped: missing vehicle A=" .. tostring(args.vehicleA) .. " B=" .. tostring(args.vehicleB))
return
end
@@ -63,7 +100,15 @@ local function applyAttachSync(args)
return
end
vehicleA:addPointConstraint(nil, vehicleB, attachmentA, attachmentB)
if vehicleA:getVehicleTowing() == vehicleB and vehicleB:getVehicleTowedBy() == vehicleA then
log("applyAttachSync already linked A=" .. tostring(vehicleA:getId()) .. " B=" .. tostring(vehicleB:getId()))
else
log("applyAttachSync addPointConstraint A=" .. tostring(vehicleA:getId()) .. " B=" .. tostring(vehicleB:getId())
.. " attachmentA=" .. tostring(attachmentA) .. " attachmentB=" .. tostring(attachmentB))
vehicleA:addPointConstraint(nil, vehicleB, attachmentA, attachmentB)
end
reconcilePairState(vehicleA, vehicleB, attachmentA, attachmentB)
end
local function safeBreak(vehicle)

View File

@@ -20,6 +20,10 @@ local function log(msg)
end
end
local function vehicleId(vehicle)
return vehicle and vehicle:getId() or "nil"
end
local function hasAttachment(vehicle, attachmentId)
if vehicle == nil or attachmentId == nil then return false end
local script = vehicle:getScript()
@@ -180,6 +184,18 @@ local function restoreDefaultsIfLeadLost(vehicle)
clearChangedOffset(vehicle)
restoreTowBarDefaults(vehicle)
local md = vehicle:getModData()
if md then
md["towed"] = false
if vehicle:getVehicleTowing() == nil then
md["isTowingByTowBar"] = false
md.towBarOriginalScriptName = nil
md.towBarOriginalMass = nil
md.towBarOriginalBrakingForce = nil
end
vehicle:transmitModData()
end
setTowBarModelVisible(vehicle, false)
end
@@ -208,12 +224,104 @@ local function sendAttach(playerObj, towingVehicle, towedVehicle, attachmentA, a
end
end
local function captureFrontLink(middleVehicle)
local function isLinkedPair(towingVehicle, towedVehicle)
if not towingVehicle or not towedVehicle then return false end
return towingVehicle:getVehicleTowing() == towedVehicle and towedVehicle:getVehicleTowedBy() == towingVehicle
end
local function markTowBarPair(towingVehicle, towedVehicle)
local towingMd = towingVehicle and towingVehicle:getModData() or nil
local towedMd = towedVehicle and towedVehicle:getModData() or nil
if towingMd then
towingMd["isTowingByTowBar"] = true
towingVehicle:transmitModData()
end
if towedMd then
local currentScript = towedVehicle and towedVehicle:getScriptName() or nil
if towedMd.towBarOriginalScriptName == nil and currentScript ~= "notTowingA_Trailer" then
towedMd.towBarOriginalScriptName = currentScript
end
if towedMd.towBarOriginalMass == nil and towedVehicle then
towedMd.towBarOriginalMass = towedVehicle:getMass()
end
if towedMd.towBarOriginalBrakingForce == nil and towedVehicle then
towedMd.towBarOriginalBrakingForce = towedVehicle:getBrakingForce()
end
towedMd["isTowingByTowBar"] = true
towedMd["towed"] = true
towedVehicle:transmitModData()
end
end
local function ensurePairAttached(playerObj, towingVehicle, towedVehicle, preferredA, preferredB, alwaysSend)
if not playerObj or not towingVehicle or not towedVehicle then
log("ensurePairAttached skipped: missing refs A=" .. tostring(vehicleId(towingVehicle)) .. " B=" .. tostring(vehicleId(towedVehicle)))
return false
end
if towingVehicle == towedVehicle or towingVehicle:getId() == towedVehicle:getId() then
log("ensurePairAttached skipped: identical vehicles id=" .. tostring(vehicleId(towingVehicle)))
return false
end
if wouldCreateTowLoop(towingVehicle, towedVehicle) then
log("ensurePairAttached skipped: loop A=" .. tostring(vehicleId(towingVehicle)) .. " B=" .. tostring(vehicleId(towedVehicle)))
return false
end
local attachmentA, attachmentB = resolvePair(towingVehicle, towedVehicle, preferredA, preferredB)
if not hasAttachment(towingVehicle, attachmentA) or not hasAttachment(towedVehicle, attachmentB) then
log("ensurePairAttached skipped: missing attachment A=" .. tostring(attachmentA) .. " B=" .. tostring(attachmentB)
.. " ids=" .. tostring(vehicleId(towingVehicle)) .. "->" .. tostring(vehicleId(towedVehicle)))
return false
end
applyRigidTow(towingVehicle, towedVehicle, attachmentA, attachmentB)
towedVehicle:setScriptName("notTowingA_Trailer")
markTowBarPair(towingVehicle, towedVehicle)
local linked = isLinkedPair(towingVehicle, towedVehicle)
log("ensurePairAttached pair=" .. tostring(vehicleId(towingVehicle)) .. "->" .. tostring(vehicleId(towedVehicle))
.. " linked=" .. tostring(linked) .. " alwaysSend=" .. tostring(alwaysSend)
.. " attachmentA=" .. tostring(attachmentA) .. " attachmentB=" .. tostring(attachmentB))
if linked and TowBarMod.Hook and TowBarMod.Hook.setVehiclePostAttach then
pcall(TowBarMod.Hook.setVehiclePostAttach, nil, towedVehicle)
end
if alwaysSend or not linked then
sendAttach(playerObj, towingVehicle, towedVehicle, attachmentA, attachmentB)
elseif TowBarMod.Hook and TowBarMod.Hook.setVehiclePostAttach then
queueAction(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, towedVehicle)
end
setTowBarModelVisible(towedVehicle, true)
refreshAround(towingVehicle)
refreshAround(towedVehicle)
return true
end
local function captureFrontLinks(middleVehicle)
if not middleVehicle then return nil end
local frontVehicle = middleVehicle:getVehicleTowedBy()
if not frontVehicle or frontVehicle == middleVehicle then return nil end
local a, b = resolvePair(frontVehicle, middleVehicle)
return { front = frontVehicle, middle = middleVehicle, a = a, b = b }
local links = {}
local cursor = middleVehicle
local visited = {}
while cursor do
if visited[cursor] then break end
visited[cursor] = true
local frontVehicle = cursor:getVehicleTowedBy()
if not frontVehicle then break end
if frontVehicle == cursor or visited[frontVehicle] then break end
local a, b = resolvePair(frontVehicle, cursor)
table.insert(links, { front = frontVehicle, middle = cursor, a = a, b = b })
cursor = frontVehicle
end
if #links == 0 then return nil end
return links
end
local function restoreFrontLink(playerObj, link)
@@ -225,23 +333,21 @@ local function restoreFrontLink(playerObj, link)
if front == middle or front:getId() == middle:getId() then return end
if wouldCreateTowLoop(front, middle) then return end
local a, b = resolvePair(front, middle, link.a, link.b)
if not hasAttachment(front, a) or not hasAttachment(middle, b) then return end
applyRigidTow(front, middle, a, b)
middle:setScriptName("notTowingA_Trailer")
sendAttach(playerObj, front, middle, a, b)
setTowBarModelVisible(middle, true)
refreshAround(front)
refreshAround(middle)
ensurePairAttached(playerObj, front, middle, link.a, link.b, false)
end
local function queueRestoreFront(playerObj, link, delay)
if not playerObj or not link then return end
queueAction(playerObj, delay or 12, function(character, linkArg)
restoreFrontLink(character, linkArg)
end, link)
local function restoreFrontLinks(playerObj, links)
if not links then return end
for _, link in ipairs(links) do
restoreFrontLink(playerObj, link)
end
end
local function queueRestoreFrontLinks(playerObj, links, delay)
if not playerObj or not links then return end
queueAction(playerObj, delay or 12, function(character, linksArg)
restoreFrontLinks(character, linksArg)
end, links)
end
local function resolveDetachPair(towingVehicle, towedVehicle)
@@ -411,16 +517,31 @@ LT.performAttachWrapper = function(playerObj, towingVehicle, towedVehicle, attac
if towingVehicle == towedVehicle or towingVehicle:getId() == towedVehicle:getId() then return end
if wouldCreateTowLoop(towingVehicle, towedVehicle) then return end
local frontLink = captureFrontLink(towingVehicle)
local frontLinks = captureFrontLinks(towingVehicle)
log("performAttachWrapper begin pair=" .. tostring(vehicleId(towingVehicle)) .. "->" .. tostring(vehicleId(towedVehicle))
.. " frontLinks=" .. tostring(frontLinks and #frontLinks or 0))
original(playerObj, towingVehicle, towedVehicle, attachmentA, attachmentB)
restoreFrontLink(playerObj, frontLink)
queueRestoreFront(playerObj, frontLink, 12)
queueRestoreFront(playerObj, frontLink, 30)
restoreFrontLinks(playerObj, frontLinks)
queueRestoreFrontLinks(playerObj, frontLinks, 12)
queueRestoreFrontLinks(playerObj, frontLinks, 30)
setTowBarModelVisible(towedVehicle, true)
refreshAround(towingVehicle)
refreshAround(towedVehicle)
ensurePairAttached(playerObj, towingVehicle, towedVehicle, attachmentA, attachmentB, false)
local attachState = {
towing = towingVehicle,
towed = towedVehicle,
attachmentA = attachmentA,
attachmentB = attachmentB
}
queueAction(playerObj, 14, function(character, state)
if not state then return end
ensurePairAttached(character, state.towing, state.towed, state.attachmentA, state.attachmentB, true)
end, attachState)
queueAction(playerObj, 34, function(character, state)
if not state then return end
ensurePairAttached(character, state.towing, state.towed, state.attachmentA, state.attachmentB, true)
end, attachState)
end
LT.performDetachWrapper = function(playerObj, towingVehicle, towedVehicle)
@@ -430,12 +551,12 @@ LT.performDetachWrapper = function(playerObj, towingVehicle, towedVehicle)
local resolvedTowing, resolvedTowed = resolveDetachPair(towingVehicle, towedVehicle)
if resolvedTowing == nil or resolvedTowed == nil then return end
local frontLink = captureFrontLink(resolvedTowing)
local frontLinks = captureFrontLinks(resolvedTowing)
original(playerObj, resolvedTowing, resolvedTowed)
restoreFrontLink(playerObj, frontLink)
queueRestoreFront(playerObj, frontLink, 12)
queueRestoreFront(playerObj, frontLink, 30)
restoreFrontLinks(playerObj, frontLinks)
queueRestoreFrontLinks(playerObj, frontLinks, 12)
queueRestoreFrontLinks(playerObj, frontLinks, 30)
restoreDefaultsIfLeadLost(resolvedTowing)
restoreDefaultsIfLeadLost(resolvedTowed)

View File

@@ -70,8 +70,12 @@ local function processAttach(item)
log("attach sync failed: server link not established A=" .. tostring(vehicleA:getId()) .. " B=" .. tostring(vehicleB:getId()))
return
end
else
log("attach sync already linked A=" .. tostring(vehicleA:getId()) .. " B=" .. tostring(vehicleB:getId()))
end
log("attach sync broadcast A=" .. tostring(vehicleA:getId()) .. " B=" .. tostring(vehicleB:getId())
.. " attachmentA=" .. tostring(attachmentA) .. " attachmentB=" .. tostring(attachmentB))
sendServerCommand("landtrain", "forceAttachSync", {
vehicleA = vehicleA:getId(),
vehicleB = vehicleB:getId(),
@@ -94,7 +98,8 @@ end
local function processPending()
if #pending == 0 then return end
for i = #pending, 1, -1 do
local remaining = {}
for i = 1, #pending do
local item = pending[i]
item.ticks = item.ticks - 1
if item.ticks <= 0 then
@@ -103,17 +108,24 @@ local function processPending()
elseif item.kind == "detach" then
processDetach(item)
end
table.remove(pending, i)
else
table.insert(remaining, item)
end
end
pending = remaining
end
local function onClientCommand(module, command, player, args)
if module == "vehicle" and command == "attachTrailer" then
log("queue attach from " .. tostring(player and player:getUsername() or "unknown")
.. " A=" .. tostring(args and args.vehicleA) .. " B=" .. tostring(args and args.vehicleB)
.. " attachmentA=" .. tostring(args and args.attachmentA) .. " attachmentB=" .. tostring(args and args.attachmentB))
queueSync("attach", player, args)
elseif module == "vehicle" and command == "detachTrailer" then
log("queue detach(vehicle) from " .. tostring(player and player:getUsername() or "unknown"))
queueSync("detach", player, args)
elseif module == "towbar" and command == "detachTowBar" then
log("queue detach(towbar) from " .. tostring(player and player:getUsername() or "unknown"))
queueSync("detach", player, args)
end
end

View File

@@ -4,6 +4,7 @@ import java.nio.file.Paths;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnList;
@@ -11,6 +12,7 @@ import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;
/**
* Patches zombie.vehicles.BaseVehicle for Landtrain chain support:
@@ -20,10 +22,21 @@ 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 AUTHORIZATION_CHANGED_NAME = "authorizationChanged";
private static final String AUTHORIZATION_CLIENT_COLLIDE_NAME = "authorizationClientCollide";
private static final String AUTHORIZATION_SERVER_COLLIDE_NAME = "authorizationServerCollide";
private static final String AUTHORIZATION_SERVER_ON_SEAT_NAME = "authorizationServerOnSeat";
private static final String IS_ENTER_BLOCKED_NAME = "isEnterBlocked";
private static final String IS_ENTER_BLOCKED2_NAME = "isEnterBlocked2";
private static final String CLINIT_NAME = "<clinit>";
private static final String VOID_NOARG_DESC = "()V";
private static final String AUTHORIZATION_CHANGED_DESC =
"(Lzombie/characters/IsoGameCharacter;)V";
private static final String AUTHORIZATION_CLIENT_COLLIDE_DESC =
"(Lzombie/characters/IsoPlayer;)V";
private static final String AUTHORIZATION_SERVER_COLLIDE_DESC = "(SZ)V";
private static final String AUTHORIZATION_SERVER_ON_SEAT_DESC =
"(Lzombie/characters/IsoPlayer;Z)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";
@@ -31,6 +44,11 @@ public final class BaseVehicleConstraintPatch {
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";
private static final String BULLET_OWNER = "zombie/core/physics/Bullet";
private static final String BULLET_ADD_ROPE = "addRopeConstraint";
private static final String BULLET_ADD_POINT = "addPointConstraint";
private static final String BULLET_ADD_ROPE_DESC = "(IIFFFFFFF)I";
private static final String BULLET_ADD_POINT_DESC = "(IIFFFFFF)I";
private static final String GET_DRIVER_DESC = "()Lzombie/characters/IsoGameCharacter;";
private static final String HELPER_OWNER = "zombie/vehicles/LandtrainConstraintAuthHelper";
private static final String HELPER_METHOD = "resolveConstraintDriver";
@@ -41,6 +59,27 @@ public final class BaseVehicleConstraintPatch {
"(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 static final String HELPER_AUTHORIZATION_CHANGED =
"authorizationChangedLandtrain";
private static final String HELPER_AUTHORIZATION_CHANGED_DESC =
"(Lzombie/vehicles/BaseVehicle;Lzombie/characters/IsoGameCharacter;)V";
private static final String HELPER_AUTHORIZATION_CLIENT_COLLIDE =
"authorizationClientCollideLandtrain";
private static final String HELPER_AUTHORIZATION_CLIENT_COLLIDE_DESC =
"(Lzombie/vehicles/BaseVehicle;Lzombie/characters/IsoPlayer;)V";
private static final String HELPER_AUTHORIZATION_SERVER_COLLIDE =
"authorizationServerCollideLandtrain";
private static final String HELPER_AUTHORIZATION_SERVER_COLLIDE_DESC =
"(Lzombie/vehicles/BaseVehicle;SZ)V";
private static final String HELPER_AUTHORIZATION_SERVER_ON_SEAT =
"authorizationServerOnSeatLandtrain";
private static final String HELPER_AUTHORIZATION_SERVER_ON_SEAT_DESC =
"(Lzombie/vehicles/BaseVehicle;Lzombie/characters/IsoPlayer;Z)V";
private static final class AddPointPatchStats {
int removedBreakCalls;
int forcedRigidCalls;
}
private BaseVehicleConstraintPatch() {
}
@@ -59,18 +98,49 @@ public final class BaseVehicleConstraintPatch {
new ClassReader(original).accept(classNode, 0);
int removedCalls = 0;
int forcedRigidCalls = 0;
int inspectedAddPointMethods = 0;
int patchedConstraintDriverCalls = 0;
int patchedEnterBlockedCalls = 0;
int patchedEnterBlocked2Calls = 0;
int patchedAuthorizationChangedMethods = 0;
int patchedAuthorizationClientCollideMethods = 0;
int patchedAuthorizationServerCollideMethods = 0;
int patchedAuthorizationServerOnSeatMethods = 0;
for (MethodNode method : classNode.methods) {
if (TARGET_NAME.equals(method.name) && isTargetAddPointConstraint(method.desc)) {
inspectedAddPointMethods++;
removedCalls += patchAddPointConstraint(method);
AddPointPatchStats stats = patchAddPointConstraint(method);
removedCalls += stats.removedBreakCalls;
forcedRigidCalls += stats.forcedRigidCalls;
} else if (CONSTRAINT_CHANGED_NAME.equals(method.name)
&& VOID_NOARG_DESC.equals(method.desc)) {
patchedConstraintDriverCalls += patchConstraintChangedDriverCalls(method);
} else if (AUTHORIZATION_CHANGED_NAME.equals(method.name)
&& AUTHORIZATION_CHANGED_DESC.equals(method.desc)) {
patchedAuthorizationChangedMethods += patchMethodDelegateToHelper(
method,
HELPER_AUTHORIZATION_CHANGED,
HELPER_AUTHORIZATION_CHANGED_DESC);
} else if (AUTHORIZATION_CLIENT_COLLIDE_NAME.equals(method.name)
&& AUTHORIZATION_CLIENT_COLLIDE_DESC.equals(method.desc)) {
patchedAuthorizationClientCollideMethods += patchMethodDelegateToHelper(
method,
HELPER_AUTHORIZATION_CLIENT_COLLIDE,
HELPER_AUTHORIZATION_CLIENT_COLLIDE_DESC);
} else if (AUTHORIZATION_SERVER_COLLIDE_NAME.equals(method.name)
&& AUTHORIZATION_SERVER_COLLIDE_DESC.equals(method.desc)) {
patchedAuthorizationServerCollideMethods += patchMethodDelegateToHelper(
method,
HELPER_AUTHORIZATION_SERVER_COLLIDE,
HELPER_AUTHORIZATION_SERVER_COLLIDE_DESC);
} else if (AUTHORIZATION_SERVER_ON_SEAT_NAME.equals(method.name)
&& AUTHORIZATION_SERVER_ON_SEAT_DESC.equals(method.desc)) {
patchedAuthorizationServerOnSeatMethods += patchMethodDelegateToHelper(
method,
HELPER_AUTHORIZATION_SERVER_ON_SEAT,
HELPER_AUTHORIZATION_SERVER_ON_SEAT_DESC);
} else if (IS_ENTER_BLOCKED_NAME.equals(method.name)
&& ENTER_BLOCKED_DESC.equals(method.desc)) {
patchedEnterBlockedCalls += patchEnterBlockedCall(
@@ -98,11 +168,36 @@ public final class BaseVehicleConstraintPatch {
+ inspectedAddPointMethods
+ ")");
}
if (forcedRigidCalls < 1) {
throw new IllegalStateException(
"Expected to force at least 1 rope->point constraint call, patched "
+ forcedRigidCalls);
}
if (patchedConstraintDriverCalls < 1) {
throw new IllegalStateException(
"Expected to patch at least 1 constraintChanged getDriver call, patched "
+ patchedConstraintDriverCalls);
}
if (patchedAuthorizationChangedMethods < 1) {
throw new IllegalStateException(
"Expected to patch authorizationChanged, patched "
+ patchedAuthorizationChangedMethods);
}
if (patchedAuthorizationClientCollideMethods < 1) {
throw new IllegalStateException(
"Expected to patch authorizationClientCollide, patched "
+ patchedAuthorizationClientCollideMethods);
}
if (patchedAuthorizationServerCollideMethods < 1) {
throw new IllegalStateException(
"Expected to patch authorizationServerCollide, patched "
+ patchedAuthorizationServerCollideMethods);
}
if (patchedAuthorizationServerOnSeatMethods < 1) {
throw new IllegalStateException(
"Expected to patch authorizationServerOnSeat, patched "
+ patchedAuthorizationServerOnSeatMethods);
}
if (patchedEnterBlockedCalls < 1) {
throw new IllegalStateException(
"Expected to patch isEnterBlocked call, patched " + patchedEnterBlockedCalls);
@@ -123,8 +218,18 @@ public final class BaseVehicleConstraintPatch {
System.out.println(
"Patched BaseVehicle.class; removed breakConstraint calls: "
+ removedCalls
+ ", forced rigid constraints: "
+ forcedRigidCalls
+ ", constraint driver hooks: "
+ patchedConstraintDriverCalls
+ ", auth hooks (changed/client/server/seat): "
+ patchedAuthorizationChangedMethods
+ "/"
+ patchedAuthorizationClientCollideMethods
+ "/"
+ patchedAuthorizationServerCollideMethods
+ "/"
+ patchedAuthorizationServerOnSeatMethods
+ ", enter-block hooks: "
+ patchedEnterBlockedCalls
+ "/"
@@ -139,8 +244,8 @@ public final class BaseVehicleConstraintPatch {
.equals(methodDesc);
}
private static int patchAddPointConstraint(MethodNode method) {
int patched = 0;
private static AddPointPatchStats patchAddPointConstraint(MethodNode method) {
AddPointPatchStats stats = new AddPointPatchStats();
InsnList insns = method.instructions;
for (AbstractInsnNode node = insns.getFirst(); node != null; ) {
@@ -160,12 +265,27 @@ public final class BaseVehicleConstraintPatch {
replacement.add(new InsnNode(Opcodes.POP));
insns.insert(node, replacement);
insns.remove(node);
patched++;
stats.removedBreakCalls++;
} else if (node instanceof MethodInsnNode call
&& BULLET_OWNER.equals(call.owner)
&& BULLET_ADD_ROPE.equals(call.name)
&& BULLET_ADD_ROPE_DESC.equals(call.desc)) {
// Drop the rope-length float and call Bullet.addPointConstraint(...) instead.
insns.insertBefore(call, new InsnNode(Opcodes.POP));
MethodInsnNode replacement =
new MethodInsnNode(
Opcodes.INVOKESTATIC,
BULLET_OWNER,
BULLET_ADD_POINT,
BULLET_ADD_POINT_DESC,
false);
insns.set(call, replacement);
stats.forcedRigidCalls++;
}
node = next;
}
return patched;
return stats;
}
private static int patchConstraintChangedDriverCalls(MethodNode method) {
@@ -198,6 +318,45 @@ public final class BaseVehicleConstraintPatch {
return patched;
}
private static int patchMethodDelegateToHelper(
MethodNode method, String helperMethod, String helperDesc) {
InsnList insns = new InsnList();
int localIndex = 0;
int maxStack = 0;
if ((method.access & Opcodes.ACC_STATIC) == 0) {
insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
localIndex = 1;
maxStack = 1;
}
Type[] argumentTypes = Type.getArgumentTypes(method.desc);
for (Type argumentType : argumentTypes) {
insns.add(new VarInsnNode(argumentType.getOpcode(Opcodes.ILOAD), localIndex));
localIndex += argumentType.getSize();
maxStack += argumentType.getSize();
}
insns.add(new MethodInsnNode(Opcodes.INVOKESTATIC, HELPER_OWNER, helperMethod, helperDesc, false));
Type returnType = Type.getReturnType(method.desc);
if (returnType.getSort() == Type.VOID) {
insns.add(new InsnNode(Opcodes.RETURN));
} else {
insns.add(new InsnNode(returnType.getOpcode(Opcodes.IRETURN)));
}
method.instructions.clear();
method.instructions.add(insns);
method.tryCatchBlocks.clear();
if (method.localVariables != null) {
method.localVariables.clear();
}
method.maxLocals = Math.max(method.maxLocals, localIndex);
method.maxStack = Math.max(method.maxStack, maxStack);
return 1;
}
private static int patchEnterBlockedCall(
MethodNode method,
String targetCallName,

View File

@@ -1,8 +1,12 @@
package zombie.vehicles;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import zombie.characters.IsoGameCharacter;
import zombie.characters.IsoPlayer;
/**
* Resolves the effective driver for constraint auth in chained towing.
@@ -62,4 +66,133 @@ public final class LandtrainConstraintAuthHelper {
private static boolean isVehicleBeingTowedInLandtrain(BaseVehicle vehicle) {
return vehicle != null && vehicle.getVehicleTowedBy() != null;
}
public static void authorizationChangedLandtrain(BaseVehicle vehicle, IsoGameCharacter character) {
if (vehicle == null) {
return;
}
if (character != null) {
applyAuthorizationAcrossChain(
vehicle, BaseVehicle.Authorization.Local, character.getOnlineID(), false);
} else {
applyAuthorizationAcrossChain(vehicle, BaseVehicle.Authorization.Server, -1, false);
}
}
public static void authorizationClientCollideLandtrain(BaseVehicle vehicle, IsoPlayer driver) {
if (vehicle == null || driver == null || vehicle.getDriver() != null) {
return;
}
applyAuthorizationAcrossChain(
vehicle, BaseVehicle.Authorization.LocalCollide, driver.getOnlineID(), true);
}
public static void authorizationServerCollideLandtrain(
BaseVehicle vehicle, short playerID, boolean isCollide) {
if (vehicle == null) {
return;
}
if (vehicle.isNetPlayerAuthorization(BaseVehicle.Authorization.Local)) {
return;
}
if (isCollide) {
applyAuthorizationAcrossChain(
vehicle, BaseVehicle.Authorization.LocalCollide, playerID, false);
return;
}
BaseVehicle.Authorization auth =
playerID == -1 ? BaseVehicle.Authorization.Server : BaseVehicle.Authorization.Local;
applyAuthorizationAcrossChain(vehicle, auth, playerID, false);
}
public static void authorizationServerOnSeatLandtrain(
BaseVehicle vehicle, IsoPlayer player, boolean enter) {
if (vehicle == null || player == null) {
return;
}
BaseVehicle vehicleA = vehicle.getVehicleTowing();
BaseVehicle vehicleB = vehicle.getVehicleTowedBy();
if (vehicle.isNetPlayerId((short) -1) && enter) {
if (vehicleA != null && vehicleA.getDriver() == null) {
vehicle.addPointConstraint(
null, vehicleA, vehicle.getTowAttachmentSelf(), vehicleA.getTowAttachmentSelf());
} else if (vehicleB != null && vehicleB.getDriver() == null) {
vehicle.addPointConstraint(
null, vehicleB, vehicle.getTowAttachmentSelf(), vehicleB.getTowAttachmentSelf());
} else {
applyAuthorizationAcrossChain(
vehicle, BaseVehicle.Authorization.Local, player.getOnlineID(), false);
}
} else if (vehicle.isNetPlayerId(player.getOnlineID()) && !enter) {
if (vehicleA != null && vehicleA.getDriver() != null) {
vehicleA.addPointConstraint(
null, vehicle, vehicleA.getTowAttachmentSelf(), vehicle.getTowAttachmentSelf());
} else if (vehicleB != null && vehicleB.getDriver() != null) {
vehicleB.addPointConstraint(
null, vehicle, vehicleB.getTowAttachmentSelf(), vehicle.getTowAttachmentSelf());
} else {
applyAuthorizationAcrossChain(vehicle, BaseVehicle.Authorization.Server, -1, false);
}
}
}
private static List<BaseVehicle> collectConnectedChain(BaseVehicle start) {
ArrayList<BaseVehicle> chain = new ArrayList<>();
if (start == null) {
return chain;
}
ArrayDeque<BaseVehicle> pending = new ArrayDeque<>();
Set<Integer> visitedIds = new HashSet<>();
pending.add(start);
while (!pending.isEmpty()) {
BaseVehicle cursor = pending.removeFirst();
if (cursor == null) {
continue;
}
int id = cursor.getId();
if (!visitedIds.add(id)) {
continue;
}
chain.add(cursor);
BaseVehicle front = cursor.getVehicleTowedBy();
BaseVehicle rear = cursor.getVehicleTowing();
if (front != null && front != cursor) {
pending.add(front);
}
if (rear != null && rear != cursor) {
pending.add(rear);
}
}
return chain;
}
private static void applyAuthorizationAcrossChain(
BaseVehicle start,
BaseVehicle.Authorization authorization,
int playerId,
boolean refreshSimulation) {
long now = System.currentTimeMillis();
for (BaseVehicle vehicle : collectConnectedChain(start)) {
if (vehicle == null) {
continue;
}
vehicle.setNetPlayerAuthorization(authorization, playerId);
if (refreshSimulation) {
vehicle.authSimulationTime = now;
if (vehicle.interpolation != null) {
vehicle.interpolation.clear();
}
}
}
}
}

Binary file not shown.