Working
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
Binary file not shown.
Reference in New Issue
Block a user