Working MP
This commit is contained in:
@@ -1,397 +1,66 @@
|
|||||||
if not TowBarMod then TowBarMod = {} end
|
if not TowBarMod then TowBarMod = {} end
|
||||||
if not TowBarMod.Hook then TowBarMod.Hook = {} end
|
if not TowBarMod.Hook then TowBarMod.Hook = {} end
|
||||||
|
|
||||||
---------------------------------------------------------------------------
|
|
||||||
--- Tow bar functions
|
|
||||||
---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
local TowBarTowMass = 200
|
local TowBarTowMass = 200
|
||||||
local CannotDriveWhileTowedFallbackText = "Cannot drive while being towed"
|
local AutoReattachCooldownHours = 1 / 7200 -- 0.5 seconds
|
||||||
local CannotDriveMessageCooldownHours = 1 / 1800 -- 2 seconds
|
TowBarMod.Hook.lastAutoReattachAtByVehicle = TowBarMod.Hook.lastAutoReattachAtByVehicle or {}
|
||||||
local ForcedTowBarReapplyCooldownHours = 1 / 3600 -- 1 second
|
|
||||||
TowBarMod.Hook.reappliedVehicleIds = TowBarMod.Hook.reappliedVehicleIds or {}
|
|
||||||
TowBarMod.Hook.pendingReapplyVehicleIds = TowBarMod.Hook.pendingReapplyVehicleIds or {}
|
|
||||||
TowBarMod.Hook.reapplyTickCounter = TowBarMod.Hook.reapplyTickCounter or 0
|
|
||||||
TowBarMod.Hook.lastCannotDriveMessageAtByPlayer = TowBarMod.Hook.lastCannotDriveMessageAtByPlayer or {}
|
|
||||||
TowBarMod.Hook.lastForcedReapplyAtByVehicle = TowBarMod.Hook.lastForcedReapplyAtByVehicle or {}
|
|
||||||
TowBarMod.Hook.lastTowBarVehicleIdByPlayer = TowBarMod.Hook.lastTowBarVehicleIdByPlayer or {}
|
|
||||||
|
|
||||||
function TowBarMod.Hook.isTowedByTowBar(vehicle)
|
local function getTowBarItem(playerObj)
|
||||||
if not vehicle then return false end
|
if not playerObj then return nil end
|
||||||
|
local inventory = playerObj:getInventory()
|
||||||
local modData = vehicle:getModData()
|
if not inventory then return nil end
|
||||||
if not modData or not modData["isTowingByTowBar"] or not modData["towed"] then
|
return inventory:getItemFromTypeRecurse("TowBar.TowBar")
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local towingVehicle = vehicle:getVehicleTowedBy()
|
|
||||||
if not towingVehicle then return false end
|
|
||||||
|
|
||||||
local towingModData = towingVehicle:getModData()
|
|
||||||
return towingModData and towingModData["isTowingByTowBar"] == true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function TowBarMod.Hook.getCannotDriveWhileTowedText()
|
local function setTowBarModelVisible(vehicle, isVisible)
|
||||||
local msg = getText("UI_Text_Towing_cannotDriveWhileTowed")
|
|
||||||
if not msg or msg == "UI_Text_Towing_cannotDriveWhileTowed" then
|
|
||||||
return CannotDriveWhileTowedFallbackText
|
|
||||||
end
|
|
||||||
return msg
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.showCannotDriveWhileTowed(playerObj)
|
|
||||||
if not playerObj then return end
|
|
||||||
local playerNum = playerObj:getPlayerNum()
|
|
||||||
local nowHours = getGameTime() and getGameTime():getWorldAgeHours() or nil
|
|
||||||
local lastHours = TowBarMod.Hook.lastCannotDriveMessageAtByPlayer[playerNum]
|
|
||||||
if nowHours and lastHours and (nowHours - lastHours) < CannotDriveMessageCooldownHours then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
TowBarMod.Hook.lastCannotDriveMessageAtByPlayer[playerNum] = nowHours or 0
|
|
||||||
|
|
||||||
-- Match the overhead-style skill/feedback text.
|
|
||||||
HaloTextHelper.addBadText(playerObj, TowBarMod.Hook.getCannotDriveWhileTowedText())
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.installStartEngineBlock()
|
|
||||||
if not ISVehicleMenu or not ISVehicleMenu.onStartEngine then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if TowBarMod.Hook._startEngineBlockInstalled then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
TowBarMod.Hook.defaultOnStartEngine = ISVehicleMenu.onStartEngine
|
|
||||||
ISVehicleMenu.onStartEngine = function(playerObj)
|
|
||||||
local vehicle = playerObj and playerObj:getVehicle() or nil
|
|
||||||
if TowBarMod.Hook.isTowedByTowBar(vehicle) then
|
|
||||||
TowBarMod.Hook.showCannotDriveWhileTowed(playerObj)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
TowBarMod.Hook.defaultOnStartEngine(playerObj)
|
|
||||||
end
|
|
||||||
|
|
||||||
TowBarMod.Hook._startEngineBlockInstalled = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.getVehicleByIdSafe(vehicleId)
|
|
||||||
if getVehicleById then
|
|
||||||
return getVehicleById(vehicleId)
|
|
||||||
end
|
|
||||||
|
|
||||||
local cell = getCell()
|
|
||||||
if not cell then return nil end
|
|
||||||
local vehicles = cell:getVehicles()
|
|
||||||
if not vehicles then return nil end
|
|
||||||
|
|
||||||
for i = 0, vehicles:size() - 1 do
|
|
||||||
local vehicle = vehicles:get(i)
|
|
||||||
if vehicle and vehicle:getId() == vehicleId then
|
|
||||||
return vehicle
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.shouldBlockDriverSeatForTowBar(playerObj, vehicle, seat)
|
|
||||||
if not playerObj or not vehicle then return false end
|
|
||||||
if seat ~= 0 then return false end
|
|
||||||
return TowBarMod.Hook.isTowedByTowBar(vehicle)
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.hasPendingSeatSafetyAction(playerObj)
|
|
||||||
if not playerObj or not ISTimedActionQueue then return false end
|
|
||||||
|
|
||||||
return ISTimedActionQueue.hasActionType(playerObj, "ISSwitchVehicleSeat")
|
|
||||||
or ISTimedActionQueue.hasActionType(playerObj, "ISExitVehicle")
|
|
||||||
or ISTimedActionQueue.hasActionType(playerObj, "ISStopVehicle")
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.getBestSeatForDriverKick(playerObj, vehicle)
|
|
||||||
if not playerObj or not vehicle then return nil end
|
|
||||||
|
|
||||||
if ISVehicleMenu and ISVehicleMenu.getBestSwitchSeatExit then
|
|
||||||
local best = ISVehicleMenu.getBestSwitchSeatExit(playerObj, vehicle, 0)
|
|
||||||
if best and best > 0 and not vehicle:isSeatOccupied(best) and vehicle:canSwitchSeat(0, best) then
|
|
||||||
return best
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for seat = 1, vehicle:getMaxPassengers() - 1 do
|
|
||||||
if not vehicle:isSeatOccupied(seat) and vehicle:canSwitchSeat(0, seat) then
|
|
||||||
return seat
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.tryMoveOrExitTowedDriver(playerObj, vehicle)
|
|
||||||
if not playerObj or not vehicle then return false end
|
|
||||||
if not TowBarMod.Hook.isTowedByTowBar(vehicle) then return false end
|
|
||||||
if not vehicle:isDriver(playerObj) and vehicle:getSeat(playerObj) ~= 0 then return false end
|
|
||||||
if TowBarMod.Hook.hasPendingSeatSafetyAction(playerObj) then return false end
|
|
||||||
|
|
||||||
local seatTo = TowBarMod.Hook.getBestSeatForDriverKick(playerObj, vehicle)
|
|
||||||
if seatTo then
|
|
||||||
if ISVehicleMenu and ISVehicleMenu.onSwitchSeat then
|
|
||||||
ISVehicleMenu.onSwitchSeat(playerObj, seatTo)
|
|
||||||
elseif ISSwitchVehicleSeat then
|
|
||||||
ISTimedActionQueue.add(ISSwitchVehicleSeat:new(playerObj, seatTo))
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
if not vehicle:isStopped() then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
if ISVehicleMenu and ISVehicleMenu.onExit then
|
|
||||||
ISVehicleMenu.onExit(playerObj, 0)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
if ISExitVehicle then
|
|
||||||
ISTimedActionQueue.add(ISExitVehicle:new(playerObj))
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.forceTowBarReapply(vehicle, playerObj)
|
|
||||||
if not TowBarMod.Hook.hasTowBarTowData(vehicle) then return false end
|
|
||||||
|
|
||||||
local vehicleId = vehicle:getId()
|
|
||||||
local nowHours = getGameTime() and getGameTime():getWorldAgeHours() or nil
|
|
||||||
local lastHours = TowBarMod.Hook.lastForcedReapplyAtByVehicle[vehicleId]
|
|
||||||
if nowHours and lastHours and (nowHours - lastHours) < ForcedTowBarReapplyCooldownHours then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
TowBarMod.Hook.lastForcedReapplyAtByVehicle[vehicleId] = nowHours or 0
|
|
||||||
|
|
||||||
TowBarMod.Hook.clearReapplied(vehicle)
|
|
||||||
TowBarMod.Hook.queueTowBarReapply(vehicle)
|
|
||||||
return TowBarMod.Hook.tryTowBarReapply(vehicle, playerObj)
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.installSeatEntryBlock()
|
|
||||||
if not ISVehicleMenu or not ISVehicleMenu.onEnter or not ISVehicleMenu.onSwitchSeat then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if TowBarMod.Hook._seatEntryBlockInstalled then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
TowBarMod.Hook.defaultOnEnter = ISVehicleMenu.onEnter
|
|
||||||
TowBarMod.Hook.defaultOnEnter2 = ISVehicleMenu.onEnter2
|
|
||||||
TowBarMod.Hook.defaultOnSwitchSeat = ISVehicleMenu.onSwitchSeat
|
|
||||||
|
|
||||||
ISVehicleMenu.onEnter = function(playerObj, vehicle, seat)
|
|
||||||
if TowBarMod.Hook.shouldBlockDriverSeatForTowBar(playerObj, vehicle, seat) then
|
|
||||||
TowBarMod.Hook.showCannotDriveWhileTowed(playerObj)
|
|
||||||
TowBarMod.Hook.forceTowBarReapply(vehicle, playerObj)
|
|
||||||
TowBarMod.Hook.tryMoveOrExitTowedDriver(playerObj, vehicle)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
TowBarMod.Hook.defaultOnEnter(playerObj, vehicle, seat)
|
|
||||||
end
|
|
||||||
|
|
||||||
if TowBarMod.Hook.defaultOnEnter2 then
|
|
||||||
ISVehicleMenu.onEnter2 = function(playerObj, vehicle, seat)
|
|
||||||
if TowBarMod.Hook.shouldBlockDriverSeatForTowBar(playerObj, vehicle, seat) then
|
|
||||||
TowBarMod.Hook.showCannotDriveWhileTowed(playerObj)
|
|
||||||
TowBarMod.Hook.forceTowBarReapply(vehicle, playerObj)
|
|
||||||
TowBarMod.Hook.tryMoveOrExitTowedDriver(playerObj, vehicle)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
TowBarMod.Hook.defaultOnEnter2(playerObj, vehicle, seat)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
ISVehicleMenu.onSwitchSeat = function(playerObj, seatTo)
|
|
||||||
local vehicle = playerObj and playerObj:getVehicle() or nil
|
|
||||||
if TowBarMod.Hook.shouldBlockDriverSeatForTowBar(playerObj, vehicle, seatTo) then
|
|
||||||
TowBarMod.Hook.showCannotDriveWhileTowed(playerObj)
|
|
||||||
TowBarMod.Hook.forceTowBarReapply(vehicle, playerObj)
|
|
||||||
TowBarMod.Hook.tryMoveOrExitTowedDriver(playerObj, vehicle)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
TowBarMod.Hook.defaultOnSwitchSeat(playerObj, seatTo)
|
|
||||||
end
|
|
||||||
|
|
||||||
TowBarMod.Hook._seatEntryBlockInstalled = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.enforceTowedVehicleEngineOff(playerObj)
|
|
||||||
if not playerObj or not instanceof(playerObj, "IsoPlayer") or not playerObj:isLocalPlayer() then return end
|
|
||||||
|
|
||||||
local vehicle = playerObj:getVehicle()
|
|
||||||
if not TowBarMod.Hook.isTowedByTowBar(vehicle) then return end
|
|
||||||
|
|
||||||
if vehicle:isEngineRunning() or vehicle:isEngineStarted() or vehicle:isStarting() then
|
|
||||||
vehicle:shutOff()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.enforceTowedVehicleSeatSafety(playerObj)
|
|
||||||
if not playerObj or not instanceof(playerObj, "IsoPlayer") or not playerObj:isLocalPlayer() then return end
|
|
||||||
|
|
||||||
local vehicle = playerObj:getVehicle()
|
|
||||||
if not TowBarMod.Hook.isTowedByTowBar(vehicle) then return end
|
|
||||||
|
|
||||||
TowBarMod.Hook.lastTowBarVehicleIdByPlayer[playerObj:getPlayerNum()] = vehicle:getId()
|
|
||||||
|
|
||||||
if vehicle:isDriver(playerObj) or vehicle:getSeat(playerObj) == 0 then
|
|
||||||
TowBarMod.Hook.showCannotDriveWhileTowed(playerObj)
|
|
||||||
TowBarMod.Hook.forceTowBarReapply(vehicle, playerObj)
|
|
||||||
TowBarMod.Hook.tryMoveOrExitTowedDriver(playerObj, vehicle)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.handleTowBarSeatEvent(character)
|
|
||||||
if not character or not instanceof(character, "IsoPlayer") or not character:isLocalPlayer() then return end
|
|
||||||
|
|
||||||
local playerObj = character
|
|
||||||
local vehicle = playerObj:getVehicle()
|
|
||||||
if not TowBarMod.Hook.isTowedByTowBar(vehicle) then return end
|
|
||||||
|
|
||||||
TowBarMod.Hook.lastTowBarVehicleIdByPlayer[playerObj:getPlayerNum()] = vehicle:getId()
|
|
||||||
TowBarMod.Hook.forceTowBarReapply(vehicle, playerObj)
|
|
||||||
TowBarMod.Hook.enforceTowedVehicleSeatSafety(playerObj)
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.OnEnterVehicle(character)
|
|
||||||
TowBarMod.Hook.handleTowBarSeatEvent(character)
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.OnSwitchVehicleSeat(character)
|
|
||||||
TowBarMod.Hook.handleTowBarSeatEvent(character)
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.OnExitVehicle(character)
|
|
||||||
if not character or not instanceof(character, "IsoPlayer") or not character:isLocalPlayer() then return end
|
|
||||||
|
|
||||||
local playerNum = character:getPlayerNum()
|
|
||||||
local vehicleId = TowBarMod.Hook.lastTowBarVehicleIdByPlayer[playerNum]
|
|
||||||
if not vehicleId then return end
|
|
||||||
|
|
||||||
local vehicle = TowBarMod.Hook.getVehicleByIdSafe(vehicleId)
|
|
||||||
if vehicle and TowBarMod.Hook.hasTowBarTowData(vehicle) then
|
|
||||||
TowBarMod.Hook.forceTowBarReapply(vehicle, character)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.markReapplied(vehicle)
|
|
||||||
if not vehicle then return end
|
if not vehicle then return end
|
||||||
local vehicleId = vehicle:getId()
|
|
||||||
TowBarMod.Hook.reappliedVehicleIds[vehicleId] = true
|
|
||||||
TowBarMod.Hook.pendingReapplyVehicleIds[vehicleId] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.clearReapplied(vehicle)
|
local part = vehicle:getPartById("towbar")
|
||||||
if not vehicle then return end
|
if part == nil then return end
|
||||||
local vehicleId = vehicle:getId()
|
|
||||||
TowBarMod.Hook.reappliedVehicleIds[vehicleId] = nil
|
|
||||||
TowBarMod.Hook.pendingReapplyVehicleIds[vehicleId] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.hasTowBarTowData(vehicle)
|
for j = 0, 23 do
|
||||||
if not vehicle then return false end
|
part:setModelVisible("towbar" .. j, false)
|
||||||
local modData = vehicle:getModData()
|
|
||||||
return modData and modData["isTowingByTowBar"] and modData["towed"]
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.queueTowBarReapply(vehicle)
|
|
||||||
if not TowBarMod.Hook.hasTowBarTowData(vehicle) then return end
|
|
||||||
local vehicleId = vehicle:getId()
|
|
||||||
TowBarMod.Hook.pendingReapplyVehicleIds[vehicleId] = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.tryTowBarReapply(vehicle, playerObj)
|
|
||||||
if not TowBarMod.Hook.hasTowBarTowData(vehicle) then
|
|
||||||
if vehicle then
|
|
||||||
TowBarMod.Hook.pendingReapplyVehicleIds[vehicle:getId()] = nil
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local vehicleId = vehicle:getId()
|
if not isVisible then
|
||||||
if TowBarMod.Hook.reappliedVehicleIds[vehicleId] then
|
vehicle:doDamageOverlay()
|
||||||
TowBarMod.Hook.pendingReapplyVehicleIds[vehicleId] = nil
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local towingVehicle = vehicle:getVehicleTowedBy()
|
|
||||||
if not towingVehicle then return false end
|
|
||||||
local towingModData = towingVehicle:getModData()
|
|
||||||
if not towingModData or towingModData["isTowingByTowBar"] ~= true then return false end
|
|
||||||
|
|
||||||
local modData = vehicle:getModData()
|
|
||||||
if not modData.towBarOriginalScriptName then
|
|
||||||
modData.towBarOriginalScriptName = vehicle:getScriptName()
|
|
||||||
vehicle:transmitModData()
|
|
||||||
end
|
|
||||||
|
|
||||||
playerObj = playerObj or getPlayer()
|
|
||||||
if not playerObj then return false end
|
|
||||||
|
|
||||||
local attachmentA = towingVehicle:getTowAttachmentSelf() or "trailer"
|
|
||||||
local attachmentB = vehicle:getTowAttachmentSelf() or "trailerfront"
|
|
||||||
|
|
||||||
-- Re-run the same rigid tow offset + fake-trailer flow used during normal attach.
|
|
||||||
TowBarMod.Utils.updateAttachmentsForRigidTow(towingVehicle, vehicle, attachmentA, attachmentB)
|
|
||||||
vehicle:setScriptName("notTowingA_Trailer")
|
|
||||||
|
|
||||||
local args = { vehicleA = towingVehicle:getId(), vehicleB = vehicle:getId(), attachmentA = attachmentA, attachmentB = attachmentB }
|
|
||||||
sendClientCommand(playerObj, "vehicle", "attachTrailer", args)
|
|
||||||
ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, vehicle))
|
|
||||||
|
|
||||||
TowBarMod.Hook.markReapplied(vehicle)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.reapplyTowBarPostLoad(vehicle)
|
|
||||||
if not TowBarMod.Hook.hasTowBarTowData(vehicle) then
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
TowBarMod.Hook.queueTowBarReapply(vehicle)
|
|
||||||
TowBarMod.Hook.tryTowBarReapply(vehicle, getPlayer())
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.processPendingReapplies()
|
local script = vehicle:getScript()
|
||||||
TowBarMod.Hook.reapplyTickCounter = TowBarMod.Hook.reapplyTickCounter + 1
|
if not script then
|
||||||
if TowBarMod.Hook.reapplyTickCounter < 15 then
|
vehicle:doDamageOverlay()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
TowBarMod.Hook.reapplyTickCounter = 0
|
|
||||||
|
|
||||||
local playerObj = getPlayer()
|
local scale = script:getModelScale()
|
||||||
if not playerObj then return end
|
if scale >= 1.5 and scale <= 2 then
|
||||||
|
local z = script:getPhysicsChassisShape():z()/2 - 0.1
|
||||||
local resolvedVehicleIds = {}
|
part:setModelVisible("towbar" .. math.floor((z*2/3 - 1)*10), true)
|
||||||
for vehicleId, _ in pairs(TowBarMod.Hook.pendingReapplyVehicleIds) do
|
|
||||||
local vehicle = TowBarMod.Hook.getVehicleByIdSafe(vehicleId)
|
|
||||||
if vehicle and (TowBarMod.Hook.tryTowBarReapply(vehicle, playerObj) or not TowBarMod.Hook.hasTowBarTowData(vehicle)) then
|
|
||||||
table.insert(resolvedVehicleIds, vehicleId)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, vehicleId in ipairs(resolvedVehicleIds) do
|
vehicle:doDamageOverlay()
|
||||||
TowBarMod.Hook.pendingReapplyVehicleIds[vehicleId] = nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function TowBarMod.Hook.OnSpawnVehicle(vehicle)
|
function TowBarMod.Hook.setVehiclePostAttach(playerObj, towedVehicle)
|
||||||
TowBarMod.Hook.reapplyTowBarPostLoad(vehicle)
|
if not towedVehicle then return end
|
||||||
|
|
||||||
|
local towedModData = towedVehicle:getModData()
|
||||||
|
if towedModData and towedModData.towBarOriginalScriptName then
|
||||||
|
towedVehicle:setScriptName(towedModData.towBarOriginalScriptName)
|
||||||
|
end
|
||||||
|
|
||||||
|
towedVehicle:setMass(TowBarTowMass)
|
||||||
|
towedVehicle:setBrakingForce(0)
|
||||||
|
towedVehicle:constraintChanged()
|
||||||
|
towedVehicle:updateTotalMass()
|
||||||
end
|
end
|
||||||
|
|
||||||
function TowBarMod.Hook.performAttachTowBar(playerObj, towingVehicle, towedVehicle, attachmentA, attachmentB)
|
function TowBarMod.Hook.performAttachTowBar(playerObj, towingVehicle, towedVehicle, attachmentA, attachmentB)
|
||||||
|
if playerObj == nil or towingVehicle == nil or towedVehicle == nil then return end
|
||||||
if #(TowBarMod.Utils.getHookTypeVariants(towingVehicle, towedVehicle, true)) == 0 then return end
|
if #(TowBarMod.Utils.getHookTypeVariants(towingVehicle, towedVehicle, true)) == 0 then return end
|
||||||
|
|
||||||
local towBarItem = playerObj:getInventory():getItemFromType("TowBar.TowBar")
|
local towBarItem = getTowBarItem(playerObj)
|
||||||
if towBarItem ~= nil then
|
if towBarItem ~= nil then
|
||||||
sendClientCommand(playerObj, "towbar", "consumeTowBar", { itemId = towBarItem:getID() })
|
sendClientCommand(playerObj, "towbar", "consumeTowBar", { itemId = towBarItem:getID() })
|
||||||
end
|
end
|
||||||
@@ -401,6 +70,7 @@ function TowBarMod.Hook.performAttachTowBar(playerObj, towingVehicle, towedVehic
|
|||||||
|
|
||||||
local towingModData = towingVehicle:getModData()
|
local towingModData = towingVehicle:getModData()
|
||||||
local towedModData = towedVehicle:getModData()
|
local towedModData = towedVehicle:getModData()
|
||||||
|
|
||||||
towedModData.towBarOriginalScriptName = towedVehicle:getScriptName()
|
towedModData.towBarOriginalScriptName = towedVehicle:getScriptName()
|
||||||
towedModData.towBarOriginalMass = towedVehicle:getMass()
|
towedModData.towBarOriginalMass = towedVehicle:getMass()
|
||||||
towedModData.towBarOriginalBrakingForce = towedVehicle:getBrakingForce()
|
towedModData.towBarOriginalBrakingForce = towedVehicle:getBrakingForce()
|
||||||
@@ -411,44 +81,149 @@ function TowBarMod.Hook.performAttachTowBar(playerObj, towingVehicle, towedVehic
|
|||||||
towingVehicle:transmitModData()
|
towingVehicle:transmitModData()
|
||||||
towedVehicle:transmitModData()
|
towedVehicle:transmitModData()
|
||||||
|
|
||||||
local part = towedVehicle:getPartById("towbar")
|
setTowBarModelVisible(towedVehicle, true)
|
||||||
if part ~= nil then
|
|
||||||
if towedVehicle:getScript():getModelScale() >= 1.5 and towedVehicle:getScript():getModelScale() <= 2 then
|
|
||||||
local z = towedVehicle:getScript():getPhysicsChassisShape():z()/2 - 0.1
|
|
||||||
part:setModelVisible("towbar" .. math.floor((z*2/3-1)*10), true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
towedVehicle:doDamageOverlay()
|
-- Match the known-good rigid tow path: fake trailer + vanilla attach command.
|
||||||
|
|
||||||
-- Fake a trailer script so the base "attachTrailer" command creates rigid point-constraint towing.
|
|
||||||
towedVehicle:setScriptName("notTowingA_Trailer")
|
towedVehicle:setScriptName("notTowingA_Trailer")
|
||||||
local args = { vehicleA = towingVehicle:getId(), vehicleB = towedVehicle:getId(), attachmentA = attachmentA, attachmentB = attachmentB }
|
|
||||||
|
local args = {
|
||||||
|
vehicleA = towingVehicle:getId(),
|
||||||
|
vehicleB = towedVehicle:getId(),
|
||||||
|
attachmentA = attachmentA,
|
||||||
|
attachmentB = attachmentB
|
||||||
|
}
|
||||||
sendClientCommand(playerObj, "vehicle", "attachTrailer", args)
|
sendClientCommand(playerObj, "vehicle", "attachTrailer", args)
|
||||||
ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, towedVehicle))
|
ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, towedVehicle))
|
||||||
TowBarMod.Hook.markReapplied(towedVehicle)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function TowBarMod.Hook.setVehiclePostAttach(playerObj, towedVehicle)
|
function TowBarMod.Hook.performDetachTowBar(playerObj, towingVehicle, towedVehicle)
|
||||||
if not towedVehicle then return end
|
if playerObj == nil or towingVehicle == nil or towedVehicle == nil then return end
|
||||||
|
|
||||||
|
TowBarMod.Utils.updateAttachmentsOnDefaultValues(towingVehicle, towedVehicle)
|
||||||
|
|
||||||
|
local args = { towingVehicle = towingVehicle:getId(), vehicle = towedVehicle:getId() }
|
||||||
|
sendClientCommand(playerObj, "towbar", "detachTowBar", args)
|
||||||
|
|
||||||
local towedModData = towedVehicle:getModData()
|
local towedModData = towedVehicle:getModData()
|
||||||
if towedModData.towBarOriginalScriptName then
|
if towedModData.towBarOriginalScriptName then
|
||||||
towedVehicle:setScriptName(towedModData.towBarOriginalScriptName)
|
towedVehicle:setScriptName(towedModData.towBarOriginalScriptName)
|
||||||
end
|
end
|
||||||
|
if towedModData.towBarOriginalMass ~= nil then
|
||||||
towedVehicle:setMass(TowBarTowMass)
|
towedVehicle:setMass(towedModData.towBarOriginalMass)
|
||||||
towedVehicle:setBrakingForce(0)
|
end
|
||||||
|
if towedModData.towBarOriginalBrakingForce ~= nil then
|
||||||
|
towedVehicle:setBrakingForce(towedModData.towBarOriginalBrakingForce)
|
||||||
|
end
|
||||||
towedVehicle:constraintChanged()
|
towedVehicle:constraintChanged()
|
||||||
towedVehicle:updateTotalMass()
|
towedVehicle:updateTotalMass()
|
||||||
|
|
||||||
|
sendClientCommand(playerObj, "towbar", "giveTowBar", { equipPrimary = true })
|
||||||
|
|
||||||
|
local towingModData = towingVehicle:getModData()
|
||||||
|
towingModData["isTowingByTowBar"] = false
|
||||||
|
towedModData["isTowingByTowBar"] = false
|
||||||
|
towedModData["towed"] = false
|
||||||
|
towedModData.towBarOriginalScriptName = nil
|
||||||
|
towedModData.towBarOriginalMass = nil
|
||||||
|
towedModData.towBarOriginalBrakingForce = nil
|
||||||
|
towingVehicle:transmitModData()
|
||||||
|
towedVehicle:transmitModData()
|
||||||
|
|
||||||
|
TowBarMod.Hook.lastAutoReattachAtByVehicle[towingVehicle:getId()] = nil
|
||||||
|
|
||||||
|
setTowBarModelVisible(towedVehicle, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TowBarMod.Hook.reattachTowBarFromDriverSeat(playerObj, towingVehicle)
|
||||||
|
if not playerObj or not towingVehicle then return end
|
||||||
|
if not towingVehicle:isDriver(playerObj) then return end
|
||||||
|
|
||||||
|
local towedVehicle = towingVehicle:getVehicleTowing()
|
||||||
|
if not towedVehicle then return end
|
||||||
|
|
||||||
|
local towingModData = towingVehicle:getModData()
|
||||||
|
local towedModData = towedVehicle:getModData()
|
||||||
|
if not towingModData or not towedModData then return end
|
||||||
|
if not towingModData["isTowingByTowBar"] then return end
|
||||||
|
if not towedModData["isTowingByTowBar"] or not towedModData["towed"] then return end
|
||||||
|
|
||||||
|
local attachmentA = towingVehicle:getTowAttachmentSelf() or "trailer"
|
||||||
|
local attachmentB = towingVehicle:getTowAttachmentOther() or "trailerfront"
|
||||||
|
if not towingVehicle:canAttachTrailer(towedVehicle, attachmentA, attachmentB) then
|
||||||
|
if towingVehicle:canAttachTrailer(towedVehicle, "trailer", "trailerfront") then
|
||||||
|
attachmentA = "trailer"
|
||||||
|
attachmentB = "trailerfront"
|
||||||
|
elseif towingVehicle:canAttachTrailer(towedVehicle, "trailerfront", "trailer") then
|
||||||
|
attachmentA = "trailerfront"
|
||||||
|
attachmentB = "trailer"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
TowBarMod.Utils.updateAttachmentsForRigidTow(towingVehicle, towedVehicle, attachmentA, attachmentB)
|
||||||
|
|
||||||
|
towedModData.towBarOriginalScriptName = towedModData.towBarOriginalScriptName or towedVehicle:getScriptName()
|
||||||
|
if towedModData.towBarOriginalMass == nil then
|
||||||
|
towedModData.towBarOriginalMass = towedVehicle:getMass()
|
||||||
|
end
|
||||||
|
if towedModData.towBarOriginalBrakingForce == nil then
|
||||||
|
towedModData.towBarOriginalBrakingForce = towedVehicle:getBrakingForce()
|
||||||
|
end
|
||||||
|
|
||||||
|
towingModData["isTowingByTowBar"] = true
|
||||||
|
towedModData["isTowingByTowBar"] = true
|
||||||
|
towedModData["towed"] = true
|
||||||
|
towingVehicle:transmitModData()
|
||||||
|
towedVehicle:transmitModData()
|
||||||
|
|
||||||
|
setTowBarModelVisible(towedVehicle, true)
|
||||||
|
towedVehicle:setScriptName("notTowingA_Trailer")
|
||||||
|
|
||||||
|
local args = {
|
||||||
|
vehicleA = towingVehicle:getId(),
|
||||||
|
vehicleB = towedVehicle:getId(),
|
||||||
|
attachmentA = attachmentA,
|
||||||
|
attachmentB = attachmentB
|
||||||
|
}
|
||||||
|
sendClientCommand(playerObj, "vehicle", "attachTrailer", args)
|
||||||
|
ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, towedVehicle))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function tryAutoReattachFromCharacter(character)
|
||||||
|
if not character or not instanceof(character, "IsoPlayer") or not character:isLocalPlayer() then return end
|
||||||
|
|
||||||
|
local playerObj = character
|
||||||
|
local towingVehicle = playerObj:getVehicle()
|
||||||
|
if not towingVehicle then return end
|
||||||
|
if not towingVehicle:isDriver(playerObj) then return end
|
||||||
|
if not towingVehicle:getVehicleTowing() then return end
|
||||||
|
|
||||||
|
local modData = towingVehicle:getModData()
|
||||||
|
if not modData or not modData["isTowingByTowBar"] then return end
|
||||||
|
|
||||||
|
local vehicleId = towingVehicle:getId()
|
||||||
|
local nowHours = getGameTime() and getGameTime():getWorldAgeHours() or 0
|
||||||
|
local lastHours = TowBarMod.Hook.lastAutoReattachAtByVehicle[vehicleId]
|
||||||
|
if lastHours and (nowHours - lastHours) < AutoReattachCooldownHours then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
TowBarMod.Hook.lastAutoReattachAtByVehicle[vehicleId] = nowHours
|
||||||
|
TowBarMod.Hook.reattachTowBarFromDriverSeat(playerObj, towingVehicle)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TowBarMod.Hook.OnEnterVehicle(character)
|
||||||
|
tryAutoReattachFromCharacter(character)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TowBarMod.Hook.OnSwitchVehicleSeat(character)
|
||||||
|
tryAutoReattachFromCharacter(character)
|
||||||
end
|
end
|
||||||
|
|
||||||
function TowBarMod.Hook.attachByTowBarAction(playerObj, towingVehicle, towedVehicle)
|
function TowBarMod.Hook.attachByTowBarAction(playerObj, towingVehicle, towedVehicle)
|
||||||
if playerObj == nil or towingVehicle == nil or towedVehicle == nil then return end
|
if playerObj == nil or towingVehicle == nil or towedVehicle == nil then return end
|
||||||
|
|
||||||
local item = playerObj:getInventory():getItemFromTypeRecurse("TowBar.TowBar")
|
local item = getTowBarItem(playerObj)
|
||||||
if item == nil then return end
|
if item == nil then return end
|
||||||
|
|
||||||
if #(TowBarMod.Utils.getHookTypeVariants(towingVehicle, towedVehicle, true)) == 0 then return end
|
if #(TowBarMod.Utils.getHookTypeVariants(towingVehicle, towedVehicle, true)) == 0 then return end
|
||||||
|
|
||||||
local hookPoint = towedVehicle:getAttachmentWorldPos("trailerfront", TowBarMod.Utils.tempVector1)
|
local hookPoint = towedVehicle:getAttachmentWorldPos("trailerfront", TowBarMod.Utils.tempVector1)
|
||||||
@@ -482,52 +257,10 @@ function TowBarMod.Hook.attachByTowBarAction(playerObj, towingVehicle, towedVehi
|
|||||||
))
|
))
|
||||||
end
|
end
|
||||||
|
|
||||||
function TowBarMod.Hook.performDeattachTowBar(playerObj, towingVehicle, towedVehicle)
|
|
||||||
TowBarMod.Utils.updateAttachmentsOnDefaultValues(towingVehicle, towedVehicle)
|
|
||||||
|
|
||||||
-- Detach from the towing side to mirror vanilla trailer detach behavior.
|
|
||||||
local args = { vehicle = towingVehicle:getId() }
|
|
||||||
sendClientCommand(playerObj, "vehicle", "detachTrailer", args)
|
|
||||||
|
|
||||||
local towedModData = towedVehicle:getModData()
|
|
||||||
if towedModData.towBarOriginalScriptName then
|
|
||||||
towedVehicle:setScriptName(towedModData.towBarOriginalScriptName)
|
|
||||||
end
|
|
||||||
if towedModData.towBarOriginalMass ~= nil then
|
|
||||||
towedVehicle:setMass(towedModData.towBarOriginalMass)
|
|
||||||
end
|
|
||||||
if towedModData.towBarOriginalBrakingForce ~= nil then
|
|
||||||
towedVehicle:setBrakingForce(towedModData.towBarOriginalBrakingForce)
|
|
||||||
end
|
|
||||||
towedVehicle:constraintChanged()
|
|
||||||
towedVehicle:updateTotalMass()
|
|
||||||
|
|
||||||
sendClientCommand(playerObj, "towbar", "giveTowBar", { equipPrimary = true })
|
|
||||||
|
|
||||||
towingVehicle:getModData()["isTowingByTowBar"] = false
|
|
||||||
towedModData["isTowingByTowBar"] = false
|
|
||||||
towedModData["towed"] = false
|
|
||||||
towedModData.towBarOriginalScriptName = nil
|
|
||||||
towedModData.towBarOriginalMass = nil
|
|
||||||
towedModData.towBarOriginalBrakingForce = nil
|
|
||||||
towingVehicle:transmitModData()
|
|
||||||
towedVehicle:transmitModData()
|
|
||||||
TowBarMod.Hook.clearReapplied(towedVehicle)
|
|
||||||
TowBarMod.Hook.lastForcedReapplyAtByVehicle[towedVehicle:getId()] = nil
|
|
||||||
|
|
||||||
local part = towedVehicle:getPartById("towbar")
|
|
||||||
if part ~= nil then
|
|
||||||
for j=0, 23 do
|
|
||||||
part:setModelVisible("towbar" .. j, false)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
towedVehicle:doDamageOverlay()
|
|
||||||
end
|
|
||||||
|
|
||||||
function TowBarMod.Hook.deattachTowBarAction(playerObj, vehicle)
|
function TowBarMod.Hook.deattachTowBarAction(playerObj, vehicle)
|
||||||
local towingVehicle = vehicle
|
local towingVehicle = vehicle
|
||||||
local towedVehicle = vehicle:getVehicleTowing()
|
local towedVehicle = vehicle and vehicle:getVehicleTowing() or nil
|
||||||
if vehicle:getVehicleTowedBy() then
|
if vehicle and vehicle:getVehicleTowedBy() then
|
||||||
towingVehicle = vehicle:getVehicleTowedBy()
|
towingVehicle = vehicle:getVehicleTowedBy()
|
||||||
towedVehicle = vehicle
|
towedVehicle = vehicle
|
||||||
end
|
end
|
||||||
@@ -562,22 +295,32 @@ function TowBarMod.Hook.deattachTowBarAction(playerObj, vehicle)
|
|||||||
playerObj,
|
playerObj,
|
||||||
300,
|
300,
|
||||||
TowBarMod.Config.lowLevelAnimation,
|
TowBarMod.Config.lowLevelAnimation,
|
||||||
TowBarMod.Hook.performDeattachTowBar,
|
TowBarMod.Hook.performDetachTowBar,
|
||||||
towingVehicle,
|
towingVehicle,
|
||||||
towedVehicle
|
towedVehicle
|
||||||
))
|
))
|
||||||
end
|
end
|
||||||
|
|
||||||
TowBarMod.Hook.installStartEngineBlock()
|
function TowBarMod.Hook.OnSpawnVehicle(vehicle)
|
||||||
TowBarMod.Hook.installSeatEntryBlock()
|
if not vehicle then return end
|
||||||
Events.OnGameStart.Add(TowBarMod.Hook.installStartEngineBlock)
|
|
||||||
Events.OnGameStart.Add(TowBarMod.Hook.installSeatEntryBlock)
|
local modData = vehicle:getModData()
|
||||||
Events.OnPlayerUpdate.Add(TowBarMod.Hook.enforceTowedVehicleEngineOff)
|
if not (modData and modData["isTowingByTowBar"] and modData["towed"]) then
|
||||||
Events.OnPlayerUpdate.Add(TowBarMod.Hook.enforceTowedVehicleSeatSafety)
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Keep behavior consistent after load/rejoin for active towbar tows.
|
||||||
|
vehicle:setScriptName("notTowingA_Trailer")
|
||||||
|
|
||||||
|
local playerObj = getPlayer()
|
||||||
|
if playerObj then
|
||||||
|
ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, vehicle))
|
||||||
|
else
|
||||||
|
TowBarMod.Hook.setVehiclePostAttach(nil, vehicle)
|
||||||
|
end
|
||||||
|
setTowBarModelVisible(vehicle, true)
|
||||||
|
end
|
||||||
|
|
||||||
Events.OnSpawnVehicleEnd.Add(TowBarMod.Hook.OnSpawnVehicle)
|
Events.OnSpawnVehicleEnd.Add(TowBarMod.Hook.OnSpawnVehicle)
|
||||||
Events.OnEnterVehicle.Add(TowBarMod.Hook.OnEnterVehicle)
|
Events.OnEnterVehicle.Add(TowBarMod.Hook.OnEnterVehicle)
|
||||||
Events.OnSwitchVehicleSeat.Add(TowBarMod.Hook.OnSwitchVehicleSeat)
|
Events.OnSwitchVehicleSeat.Add(TowBarMod.Hook.OnSwitchVehicleSeat)
|
||||||
Events.OnExitVehicle.Add(TowBarMod.Hook.OnExitVehicle)
|
|
||||||
Events.OnTick.Add(TowBarMod.Hook.processPendingReapplies)
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -126,6 +126,23 @@ function TowBarMod.UI.addUnhookOptionToMenu(playerObj, vehicle)
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function TowBarMod.UI.addDriverReattachOptionToMenu(playerObj, towingVehicle)
|
||||||
|
local menu = getPlayerRadialMenu(playerObj:getPlayerNum())
|
||||||
|
if menu == nil then return end
|
||||||
|
if not towingVehicle then return end
|
||||||
|
if not towingVehicle:isDriver(playerObj) then return end
|
||||||
|
if not towingVehicle:getVehicleTowing() then return end
|
||||||
|
if not towingVehicle:getModData()["isTowingByTowBar"] then return end
|
||||||
|
|
||||||
|
menu:addSlice(
|
||||||
|
"Reattach Towbar (Debug)",
|
||||||
|
getTexture("media/textures/tow_bar_attach.png"),
|
||||||
|
TowBarMod.Hook.reattachTowBarFromDriverSeat,
|
||||||
|
playerObj,
|
||||||
|
towingVehicle
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
---------------------------------------------------------------------------
|
---------------------------------------------------------------------------
|
||||||
--- Mod compability
|
--- Mod compability
|
||||||
---------------------------------------------------------------------------
|
---------------------------------------------------------------------------
|
||||||
@@ -146,9 +163,17 @@ end
|
|||||||
function ISVehicleMenu.showRadialMenu(playerObj)
|
function ISVehicleMenu.showRadialMenu(playerObj)
|
||||||
TowBarMod.UI.defaultShowRadialMenu(playerObj)
|
TowBarMod.UI.defaultShowRadialMenu(playerObj)
|
||||||
|
|
||||||
if playerObj:getVehicle() then return end
|
local vehicle = playerObj:getVehicle()
|
||||||
|
if vehicle then
|
||||||
|
if vehicle:isDriver(playerObj) and vehicle:getVehicleTowing() and vehicle:getModData()["isTowingByTowBar"] then
|
||||||
|
TowBarMod.UI.removeDefaultDetachOption(playerObj)
|
||||||
|
TowBarMod.UI.addUnhookOptionToMenu(playerObj, vehicle)
|
||||||
|
TowBarMod.UI.addDriverReattachOptionToMenu(playerObj, vehicle)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local vehicle = ISVehicleMenu.getVehicleToInteractWith(playerObj)
|
vehicle = ISVehicleMenu.getVehicleToInteractWith(playerObj)
|
||||||
if vehicle == nil then return end
|
if vehicle == nil then return end
|
||||||
|
|
||||||
if vehicle:getModData()["isTowingByTowBar"] then
|
if vehicle:getModData()["isTowingByTowBar"] then
|
||||||
|
|||||||
@@ -88,13 +88,16 @@ function TowBarMod.Utils.updateAttachmentsForRigidTow(towingVehicle, towedVehicl
|
|||||||
towedAttachment:setUpdateConstraint(false)
|
towedAttachment:setUpdateConstraint(false)
|
||||||
towedAttachment:setZOffset(0)
|
towedAttachment:setZOffset(0)
|
||||||
|
|
||||||
|
local towedModData = towedVehicle:getModData()
|
||||||
|
local alreadyShifted = towedModData["isChangedTowedAttachment"] and towedModData["towBarChangedAttachmentId"] == attachmentB
|
||||||
|
if not alreadyShifted then
|
||||||
local offset = towedAttachment:getOffset()
|
local offset = towedAttachment:getOffset()
|
||||||
local zShift = offset:z() > 0 and 1 or -1
|
local zShift = offset:z() > 0 and 1 or -1
|
||||||
towedAttachment:getOffset():set(offset:x(), offset:y(), offset:z() + zShift)
|
towedAttachment:getOffset():set(offset:x(), offset:y(), offset:z() + zShift)
|
||||||
local towedModData = towedVehicle:getModData()
|
|
||||||
towedModData["isChangedTowedAttachment"] = true
|
towedModData["isChangedTowedAttachment"] = true
|
||||||
towedModData["towBarChangedAttachmentId"] = attachmentB
|
towedModData["towBarChangedAttachmentId"] = attachmentB
|
||||||
towedModData["towBarChangedOffsetZShift"] = zShift
|
towedModData["towBarChangedOffsetZShift"] = zShift
|
||||||
|
end
|
||||||
towedVehicle:transmitModData()
|
towedVehicle:transmitModData()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,49 @@
|
|||||||
if isClient() then return end
|
if isClient() then return end
|
||||||
|
|
||||||
|
local TowingCommands = {}
|
||||||
local Commands = {}
|
local Commands = {}
|
||||||
local TowBarItemType = "TowBar.TowBar"
|
local TowBarItemType = "TowBar.TowBar"
|
||||||
|
|
||||||
function Commands.attachConstraint(player, args)
|
TowingCommands.wantNoise = getDebug() or false
|
||||||
local vehicleA = args and getVehicleById(args.vehicleA)
|
|
||||||
local vehicleB = args and getVehicleById(args.vehicleB)
|
|
||||||
local attachmentA = args and args.attachmentA
|
|
||||||
local attachmentB = args and args.attachmentB
|
|
||||||
if not vehicleA or not vehicleB or not attachmentA or not attachmentB then return end
|
|
||||||
|
|
||||||
vehicleA:addPointConstraint(player, vehicleB, attachmentA, attachmentB)
|
local noise = function(msg)
|
||||||
|
if TowingCommands.wantNoise then
|
||||||
|
print("TowBarCommands: " .. msg)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Commands.detachConstraint(player, args)
|
function Commands.attachTowBar(player, args)
|
||||||
local vehicle = args and getVehicleById(args.vehicle)
|
local vehicleA = getVehicleById(args.vehicleA)
|
||||||
if not vehicle then return end
|
local vehicleB = getVehicleById(args.vehicleB)
|
||||||
|
if not vehicleA then
|
||||||
|
noise("no such vehicle (A) id=" .. tostring(args.vehicleA))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not vehicleB then
|
||||||
|
noise("no such vehicle (B) id=" .. tostring(args.vehicleB))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
vehicle:breakConstraint(true, false)
|
vehicleA:addPointConstraint(player, vehicleB, args.attachmentA, args.attachmentB)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Commands.detachTowBar(player, args)
|
||||||
|
local towingVehicle = args.towingVehicle and getVehicleById(args.towingVehicle) or nil
|
||||||
|
local towedVehicle = args.vehicle and getVehicleById(args.vehicle) or nil
|
||||||
|
|
||||||
|
if not towingVehicle and towedVehicle then
|
||||||
|
towingVehicle = towedVehicle:getVehicleTowedBy()
|
||||||
|
end
|
||||||
|
if not towedVehicle and towingVehicle then
|
||||||
|
towedVehicle = towingVehicle:getVehicleTowing()
|
||||||
|
end
|
||||||
|
|
||||||
|
if towedVehicle then
|
||||||
|
towedVehicle:breakConstraint(true, false)
|
||||||
|
end
|
||||||
|
if towingVehicle and towingVehicle ~= towedVehicle then
|
||||||
|
towingVehicle:breakConstraint(true, false)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Commands.consumeTowBar(player, args)
|
function Commands.consumeTowBar(player, args)
|
||||||
@@ -61,13 +87,20 @@ function Commands.giveTowBar(player, args)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function onClientCommand(module, command, player, args)
|
-- Compatibility aliases for older command names.
|
||||||
if module ~= "towbar" then return end
|
Commands.attachConstraint = Commands.attachTowBar
|
||||||
|
Commands.detachConstraint = Commands.detachTowBar
|
||||||
|
|
||||||
local fn = Commands[command]
|
TowingCommands.OnClientCommand = function(module, command, player, args)
|
||||||
if fn then
|
if module == "towbar" and Commands[command] then
|
||||||
fn(player, args or {})
|
local argStr = ""
|
||||||
|
args = args or {}
|
||||||
|
for k, v in pairs(args) do
|
||||||
|
argStr = argStr .. " " .. tostring(k) .. "=" .. tostring(v)
|
||||||
|
end
|
||||||
|
noise("received " .. module .. " " .. command .. " " .. tostring(player) .. argStr)
|
||||||
|
Commands[command](player, args)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Events.OnClientCommand.Add(onClientCommand)
|
Events.OnClientCommand.Add(TowingCommands.OnClientCommand)
|
||||||
|
|||||||
111
Migration Guide.md
Normal file
111
Migration Guide.md
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
# Migration Guide
|
||||||
|
|
||||||
|
Auto-converted from PDF using `uv run --with pypdf`.
|
||||||
|
|
||||||
|
## Page 1
|
||||||
|
|
||||||
|
Migration Guide
|
||||||
|
Registries
|
||||||
|
In version 42.13, the way to add some identifiers has been changed. The IDs are
|
||||||
|
used in scripts and recipes.
|
||||||
|
Identifiers:
|
||||||
|
CharacterTrait
|
||||||
|
CharacterProfession
|
||||||
|
ItemTag
|
||||||
|
Brochure
|
||||||
|
Flier
|
||||||
|
ItemBodyLocation
|
||||||
|
ItemType
|
||||||
|
MoodleType
|
||||||
|
WeaponCategory
|
||||||
|
Newspaper
|
||||||
|
AmmoType
|
||||||
|
To add these identifiers and use them in scripts, you need to add them using Lua in
|
||||||
|
the registries.lua file. This file must be stored in the media folder. IT MUST
|
||||||
|
HAVE THIS EXACT NAME, and it is loaded before scripts and before any other Lua
|
||||||
|
files.
|
||||||
|
Example of adding IDs:
|
||||||
|
CharacterTrait.register("testmod:nimblefingers")
|
||||||
|
CharacterProfession.register("testmod:thief")
|
||||||
|
ItemTag.register("testmod:bobbypin")
|
||||||
|
Brochure.register("testmod:Village")
|
||||||
|
Flier.register("testmod:BirdMilk")
|
||||||
|
ItemBodyLocation.register("testmod:MiddleFinger")
|
||||||
|
ItemType.register("testmod:gamedev")
|
||||||
|
MoodleType.register("testmod:Happy")
|
||||||
|
WeaponCategory.register("testmod:birb")
|
||||||
|
Newspaper.register("testmod:BirdNews", List.of("BirdKnews_July30",
|
||||||
|
"BirdKnews_July2"))
|
||||||
|
local item_key = ItemKey.new("bullets_666", ItemType.NORMAL)
|
||||||
|
AmmoType.register("testmod:duck_bullets", item_key)
|
||||||
|
Example of usage in scripts:
|
||||||
|
|
||||||
|
## Page 2
|
||||||
|
|
||||||
|
character_trait_definition testmod:nimblefingers
|
||||||
|
{
|
||||||
|
IsProfessionTrait = false,
|
||||||
|
DisabledInMultiplayer = false,
|
||||||
|
CharacterTrait = testmod:nimblefingers,
|
||||||
|
Cost = 3,
|
||||||
|
UIName = UI_trait_nimblefingers,
|
||||||
|
UIDescription = UI_trait_nimblefingersDesc,
|
||||||
|
XPBoosts = Lockpicking=2,
|
||||||
|
GrantedRecipes =
|
||||||
|
Lockpicking;AlarmCheck;CreateBobbyPin;CreateBobbyPin2,
|
||||||
|
}
|
||||||
|
craftRecipe CreateBobbyPin
|
||||||
|
{
|
||||||
|
timedAction = Making,
|
||||||
|
Time = 40,
|
||||||
|
Tags = InHandCraft;CanBeDoneInDark,
|
||||||
|
needTobeLearn = true,
|
||||||
|
inputs
|
||||||
|
{
|
||||||
|
item 1 tags[base:screwdriver] mode:keep
|
||||||
|
flags[MayDegradeLight;Prop1],
|
||||||
|
item 1 [Base.Paperclip],
|
||||||
|
}
|
||||||
|
outputs
|
||||||
|
{
|
||||||
|
item 1 TestMod.HandmadeBobbyPin,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
character_profession_definition testmod:thief
|
||||||
|
{
|
||||||
|
CharacterProfession = testmod:thief,
|
||||||
|
Cost = 2,
|
||||||
|
UIName = UI_prof_Thief,
|
||||||
|
IconPathName = profession_burglar2,
|
||||||
|
XPBoosts = Nimble=3;Sneak=2;Lightfoot=1;Lockpicking=2,
|
||||||
|
GrantedTraits = testmod:nimblefingers,
|
||||||
|
}
|
||||||
|
item HandmadeBobbyPin
|
||||||
|
{
|
||||||
|
Weight = 0.01,
|
||||||
|
ItemType = base:normal,
|
||||||
|
Icon = HandmadeBobbyPin,
|
||||||
|
Tags = testmod:bobbypin,
|
||||||
|
Tooltip = Tooltip_TestMod_BobbyPin,
|
||||||
|
WorldStaticModel = Paperclip,
|
||||||
|
|
||||||
|
## Page 3
|
||||||
|
|
||||||
|
Lua
|
||||||
|
Scripts
|
||||||
|
P.S.
|
||||||
|
Future content patches will include modding changes based on your reports and
|
||||||
|
requests, and new API documentation will gradually become available.
|
||||||
|
}
|
||||||
|
More details check in mod example
|
||||||
|
Some Lua API has been modified. If something has stopped working for you, check
|
||||||
|
the decompiled Java code.
|
||||||
|
There will be more API changes in upcoming unstable patches.
|
||||||
|
Item Script: DisplayName has been removed. Now translation is taken only
|
||||||
|
from Module.ItemId.
|
||||||
|
Item Script: Type has been renamed to ItemType and requires
|
||||||
|
the ItemType registry.
|
||||||
|
Tags now require the ItemTag registry.
|
||||||
|
It will also be useful to study script examples from the base game. They are now
|
||||||
|
generated from Java code and are read by game as before.
|
||||||
|
|
||||||
BIN
Migration Guide.pdf
Normal file
BIN
Migration Guide.pdf
Normal file
Binary file not shown.
1375
Project Zomboid_ API for Inventory Items.md
Normal file
1375
Project Zomboid_ API for Inventory Items.md
Normal file
File diff suppressed because it is too large
Load Diff
BIN
Project Zomboid_ API for Inventory Items.pdf
Normal file
BIN
Project Zomboid_ API for Inventory Items.pdf
Normal file
Binary file not shown.
Reference in New Issue
Block a user