Intial Commit

This commit is contained in:
2026-02-06 14:50:37 -05:00
parent ba773ae6a3
commit b9f61adafe
62 changed files with 5055 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
if not TowBarMod then TowBarMod = {} end
if not TowBarMod.Config then TowBarMod.Config = {} end
TowBarMod.Config.lowLevelAnimation = "RemoveGrass"

View File

@@ -0,0 +1,72 @@
require "TimedActions/ISBaseTimedAction"
TowBarCustomPathFind = ISBaseTimedAction:derive("TowBarCustomPathFind")
function TowBarCustomPathFind:isValid()
return true
end
function TowBarCustomPathFind:update()
if instanceof(self.character, "IsoPlayer") and
(self.character:pressedMovement(false) or self.character:pressedCancelAction()) then
self:forceStop()
return
end
local result = self.character:getPathFindBehavior2():update()
if result == BehaviorResult.Succeeded then
self:forceComplete()
end
local x = self.character:getX()
local y = self.character:getY()
if x == self.lastX and y == self.lastY then
self.currentTimeInOnePosition = self.currentTimeInOnePosition + 1
else
self.currentTimeInOnePosition = 0
self.lastX = x
self.lastY = y
end
if self.currentTimeInOnePosition > self.maxTimeInOnePosition then
self:forceComplete()
end
end
function TowBarCustomPathFind:start()
self.character:facePosition(self.goal[2], self.goal[3])
self.character:getPathFindBehavior2():pathToLocationF(self.goal[2], self.goal[3], self.goal[4])
end
function TowBarCustomPathFind:stop()
ISBaseTimedAction.stop(self)
self.character:getPathFindBehavior2():cancel()
self.character:setPath2(nil)
end
function TowBarCustomPathFind:perform()
self.character:getPathFindBehavior2():cancel()
self.character:setPath2(nil)
ISBaseTimedAction.perform(self)
end
function TowBarCustomPathFind:pathToLocationF(character, targetX, targetY, targetZ)
local o = {}
setmetatable(o, self)
self.__index = self
o.character = character
o.stopOnWalk = false
o.stopOnRun = false
o.maxTime = -1
o.maxTimeInOnePosition = 15
o.currentTimeInOnePosition = 0
o.lastX = -1
o.lastY = -1
o.goal = { 'LocationF', targetX, targetY, targetZ }
return o
end

View File

@@ -0,0 +1,56 @@
require('TimedActions/ISBaseTimedAction')
TowBarHookVehicle = ISBaseTimedAction:derive("TowBarHookVehicle")
-- The condition which tells the timed action if it is still valid
function TowBarHookVehicle:isValid()
return true;
end
-- Starts the Timed Action
function TowBarHookVehicle:start()
self:setActionAnim(self.animation)
self.sound = getSoundManager():PlayWorldSound("towbar_hookingSound", false, self.character:getSquare(), 0, 5, 1, true)
end
-- Is called when the time has passed
function TowBarHookVehicle:perform()
self.sound:stop();
if self.performFunc ~= nil then
self.performFunc(self.character, self.arg1, self.arg2, self.arg3, self.arg4)
end
ISBaseTimedAction.perform(self);
end
function TowBarHookVehicle:stop()
if self.sound then
self.sound:stop()
end
ISBaseTimedAction.stop(self)
end
function TowBarHookVehicle:new(character, time, animation, performFunc, arg1, arg2, arg3, arg4)
local o = {};
setmetatable(o, self)
self.__index = self
o.stopOnWalk = true
o.stopOnRun = true
o.maxTime = time
o.character = character;
o.animation = animation
o.performFunc = performFunc
o.arg1 = arg1
o.arg2 = arg2
o.arg3 = arg3
o.arg4 = arg4
return o;
end

View File

@@ -0,0 +1,42 @@
require("TimedActions/ISBaseTimedAction")
TowBarScheduleAction = ISBaseTimedAction:derive("TowBarScheduleAction")
function TowBarScheduleAction:isValid()
return true
end
function TowBarScheduleAction:start()
end
function TowBarScheduleAction:perform()
if self.performFunc ~= nil then
self.performFunc(self.character, self.arg1, self.arg2, self.arg3, self.arg4)
end
ISBaseTimedAction.perform(self)
end
function TowBarScheduleAction:stop()
ISBaseTimedAction.stop(self)
end
function TowBarScheduleAction:new(character, time, performFunc, arg1, arg2, arg3, arg4)
local o = ISBaseTimedAction.new(self, character)
o.useProgressBar = false
o.stopOnWalk = false
o.stopOnRun = false
o.maxTime = time
o.character = character
o.performFunc = performFunc
o.arg1 = arg1
o.arg2 = arg2
o.arg3 = arg3
o.arg4 = arg4
return o
end

View File

@@ -0,0 +1,565 @@
if not TowBarMod then TowBarMod = {} end
if not TowBarMod.Hook then TowBarMod.Hook = {} end
---------------------------------------------------------------------------
--- Tow bar functions
---------------------------------------------------------------------------
local TowBarTowMass = 200
local CannotDriveWhileTowedFallbackText = "Cannot drive while being towed"
local CannotDriveMessageCooldownHours = 1 / 1800 -- 2 seconds
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)
if not vehicle then return false end
local modData = vehicle:getModData()
if not modData or not modData["isTowingByTowBar"] or not modData["towed"] then
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
function TowBarMod.Hook.getCannotDriveWhileTowedText()
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
local vehicleId = vehicle:getId()
TowBarMod.Hook.reappliedVehicleIds[vehicleId] = true
TowBarMod.Hook.pendingReapplyVehicleIds[vehicleId] = nil
end
function TowBarMod.Hook.clearReapplied(vehicle)
if not vehicle then return end
local vehicleId = vehicle:getId()
TowBarMod.Hook.reappliedVehicleIds[vehicleId] = nil
TowBarMod.Hook.pendingReapplyVehicleIds[vehicleId] = nil
end
function TowBarMod.Hook.hasTowBarTowData(vehicle)
if not vehicle then return false end
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 return false end
local vehicleId = vehicle:getId()
if TowBarMod.Hook.reappliedVehicleIds[vehicleId] then 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
playerObj = playerObj or getPlayer()
if not playerObj then return false end
local modData = vehicle:getModData()
if not modData.towBarOriginalScriptName then
modData.towBarOriginalScriptName = vehicle:getScriptName()
vehicle:transmitModData()
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)
TowBarMod.Hook.queueTowBarReapply(vehicle)
TowBarMod.Hook.tryTowBarReapply(vehicle, getPlayer())
end
function TowBarMod.Hook.processPendingReapplies()
TowBarMod.Hook.reapplyTickCounter = TowBarMod.Hook.reapplyTickCounter + 1
if TowBarMod.Hook.reapplyTickCounter < 15 then
return
end
TowBarMod.Hook.reapplyTickCounter = 0
local playerObj = getPlayer()
if not playerObj then return end
for vehicleId, _ in pairs(TowBarMod.Hook.pendingReapplyVehicleIds) do
local vehicle = TowBarMod.Hook.getVehicleByIdSafe(vehicleId)
if vehicle then
TowBarMod.Hook.tryTowBarReapply(vehicle, playerObj)
end
end
end
function TowBarMod.Hook.OnSpawnVehicle(vehicle)
TowBarMod.Hook.reapplyTowBarPostLoad(vehicle)
end
function TowBarMod.Hook.performAttachTowBar(playerObj, towingVehicle, towedVehicle, attachmentA, attachmentB)
if #(TowBarMod.Utils.getHookTypeVariants(towingVehicle, towedVehicle, true)) == 0 then return end
local towBarItem = playerObj:getInventory():getItemFromType("TowBar.TowBar")
if towBarItem ~= nil then
sendClientCommand(playerObj, "towbar", "consumeTowBar", { itemId = towBarItem:getID() })
end
playerObj:setPrimaryHandItem(nil)
TowBarMod.Utils.updateAttachmentsForRigidTow(towingVehicle, towedVehicle, attachmentA, attachmentB)
local towingModData = towingVehicle:getModData()
local towedModData = towedVehicle:getModData()
towedModData.towBarOriginalScriptName = towedVehicle:getScriptName()
towedModData.towBarOriginalMass = towedVehicle:getMass()
towedModData.towBarOriginalBrakingForce = towedVehicle:getBrakingForce()
towingModData["isTowingByTowBar"] = true
towedModData["isTowingByTowBar"] = true
towedModData["towed"] = true
towingVehicle:transmitModData()
towedVehicle:transmitModData()
local part = towedVehicle:getPartById("towbar")
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()
-- Fake a trailer script so the base "attachTrailer" command creates rigid point-constraint towing.
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))
TowBarMod.Hook.markReapplied(towedVehicle)
end
function TowBarMod.Hook.setVehiclePostAttach(playerObj, towedVehicle)
if not towedVehicle then return end
local towedModData = towedVehicle:getModData()
if towedModData.towBarOriginalScriptName then
towedVehicle:setScriptName(towedModData.towBarOriginalScriptName)
end
towedVehicle:setMass(TowBarTowMass)
towedVehicle:setBrakingForce(0)
towedVehicle:constraintChanged()
towedVehicle:updateTotalMass()
end
function TowBarMod.Hook.attachByTowBarAction(playerObj, towingVehicle, towedVehicle)
if playerObj == nil or towingVehicle == nil or towedVehicle == nil then return end
local item = playerObj:getInventory():getItemFromTypeRecurse("TowBar.TowBar")
if item == nil then return end
if #(TowBarMod.Utils.getHookTypeVariants(towingVehicle, towedVehicle, true)) == 0 then return end
local hookPoint = towedVehicle:getAttachmentWorldPos("trailerfront", TowBarMod.Utils.tempVector1)
if hookPoint == nil then return end
ISTimedActionQueue.add(TowBarCustomPathFind:pathToLocationF(playerObj, hookPoint:x(), hookPoint:y(), hookPoint:z()))
if not playerObj:getInventory():contains("TowBar.TowBar") then
ISTimedActionQueue.add(ISInventoryTransferAction:new(playerObj, item, item:getContainer(), playerObj:getInventory(), nil))
end
local storePrim = playerObj:getPrimaryHandItem()
if storePrim == nil or storePrim ~= item then
ISTimedActionQueue.add(ISEquipWeaponAction:new(playerObj, item, 12, true))
end
ISTimedActionQueue.add(TowBarHookVehicle:new(playerObj, 300, TowBarMod.Config.lowLevelAnimation))
hookPoint = towingVehicle:getAttachmentWorldPos("trailer", TowBarMod.Utils.tempVector1)
if hookPoint == nil then return end
ISTimedActionQueue.add(TowBarCustomPathFind:pathToLocationF(playerObj, hookPoint:x(), hookPoint:y(), hookPoint:z()))
ISTimedActionQueue.add(TowBarHookVehicle:new(
playerObj,
100,
TowBarMod.Config.lowLevelAnimation,
TowBarMod.Hook.performAttachTowBar,
towingVehicle,
towedVehicle,
"trailer",
"trailerfront"
))
end
function TowBarMod.Hook.performDeattachTowBar(playerObj, towingVehicle, towedVehicle)
TowBarMod.Utils.updateAttachmentsOnDefaultValues(towingVehicle, towedVehicle)
local args = { vehicle = towedVehicle:getId() }
sendClientCommand(playerObj, "towbar", "detachConstraint", 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)
local towingVehicle = vehicle
local towedVehicle = vehicle:getVehicleTowing()
if vehicle:getVehicleTowedBy() then
towingVehicle = vehicle:getVehicleTowedBy()
towedVehicle = vehicle
end
if towingVehicle == nil or towedVehicle == nil then return end
local localPoint = towingVehicle:getAttachmentLocalPos(towingVehicle:getTowAttachmentSelf(), TowBarMod.Utils.tempVector1)
local shift = 0
if towingVehicle:getModData()["isChangedTowedAttachment"] then
shift = localPoint:z() > 0 and -1 or 1
end
local hookPoint = towingVehicle:getWorldPos(localPoint:x(), localPoint:y(), localPoint:z() + shift, TowBarMod.Utils.tempVector2)
if hookPoint == nil then return end
ISTimedActionQueue.add(TowBarCustomPathFind:pathToLocationF(playerObj, hookPoint:x(), hookPoint:y(), hookPoint:z()))
local storePrim = playerObj:getPrimaryHandItem()
if storePrim ~= nil then
ISTimedActionQueue.add(ISUnequipAction:new(playerObj, storePrim, 12))
end
ISTimedActionQueue.add(TowBarHookVehicle:new(playerObj, 100, TowBarMod.Config.lowLevelAnimation))
localPoint = towedVehicle:getAttachmentLocalPos(towedVehicle:getTowAttachmentSelf(), TowBarMod.Utils.tempVector1)
shift = 0
if towedVehicle:getModData()["isChangedTowedAttachment"] then
shift = localPoint:z() > 0 and -1 or 1
end
hookPoint = towedVehicle:getWorldPos(localPoint:x(), localPoint:y(), localPoint:z() + shift, TowBarMod.Utils.tempVector2)
if hookPoint == nil then return end
ISTimedActionQueue.add(TowBarCustomPathFind:pathToLocationF(playerObj, hookPoint:x(), hookPoint:y(), hookPoint:z()))
ISTimedActionQueue.add(TowBarHookVehicle:new(
playerObj,
300,
TowBarMod.Config.lowLevelAnimation,
TowBarMod.Hook.performDeattachTowBar,
towingVehicle,
towedVehicle
))
end
TowBarMod.Hook.installStartEngineBlock()
TowBarMod.Hook.installSeatEntryBlock()
Events.OnGameStart.Add(TowBarMod.Hook.installStartEngineBlock)
Events.OnGameStart.Add(TowBarMod.Hook.installSeatEntryBlock)
Events.OnPlayerUpdate.Add(TowBarMod.Hook.enforceTowedVehicleEngineOff)
Events.OnPlayerUpdate.Add(TowBarMod.Hook.enforceTowedVehicleSeatSafety)
Events.OnSpawnVehicleEnd.Add(TowBarMod.Hook.OnSpawnVehicle)
Events.OnEnterVehicle.Add(TowBarMod.Hook.OnEnterVehicle)
Events.OnSwitchVehicleSeat.Add(TowBarMod.Hook.OnSwitchVehicleSeat)
Events.OnExitVehicle.Add(TowBarMod.Hook.OnExitVehicle)
Events.OnTick.Add(TowBarMod.Hook.processPendingReapplies)

View File

@@ -0,0 +1,161 @@
if not TowBarMod then TowBarMod = {} end
if not TowBarMod.UI then TowBarMod.UI = {} end
---------------------------------------------------------------------------
--- UI functions
---------------------------------------------------------------------------
function TowBarMod.UI.removeDefaultDetachOption(playerObj)
local menu = getPlayerRadialMenu(playerObj:getPlayerNum())
if menu == nil then return end
local tmpSlices = menu.slices
menu:clear()
for _, slice in ipairs(tmpSlices) do
local command = slice.command and slice.command[1]
local args = slice.command or {}
if command ~= ISVehicleMenu.onDetachTrailer then
menu:addSlice(
slice.text,
slice.texture,
args[1],
args[2],
args[3],
args[4],
args[5],
args[6],
args[7]
)
end
end
end
--- Show menu with available vehicles for tow bar hook.
function TowBarMod.UI.showChooseVehicleMenu(playerObj, vehicle, vehicles, hasTowBar)
local playerIndex = playerObj:getPlayerNum()
local menu = getPlayerRadialMenu(playerIndex)
menu:clear()
local added = 0
for _, veh in ipairs(vehicles) do
local hookTypeVariants = TowBarMod.Utils.getHookTypeVariants(vehicle, veh, hasTowBar)
if #hookTypeVariants > 0 then
local hookType = hookTypeVariants[1]
menu:addSlice(
hookType.name,
getTexture("media/textures/tow_bar_attach.png"),
hookType.func,
playerObj,
hookType.towingVehicle,
hookType.towedVehicle,
hookType.towingPoint,
hookType.towedPoint
)
added = added + 1
end
end
if added == 0 then return end
menu:setX(getPlayerScreenLeft(playerIndex) + getPlayerScreenWidth(playerIndex) / 2 - menu:getWidth() / 2)
menu:setY(getPlayerScreenTop(playerIndex) + getPlayerScreenHeight(playerIndex) / 2 - menu:getHeight() / 2)
menu:addToUIManager()
if JoypadState.players[playerObj:getPlayerNum()+1] then
menu:setHideWhenButtonReleased(Joypad.DPadUp)
setJoypadFocus(playerObj:getPlayerNum(), menu)
playerObj:setJoypadIgnoreAimUntilCentered(true)
end
end
function TowBarMod.UI.addHookOptionToMenu(playerObj, vehicle)
local menu = getPlayerRadialMenu(playerObj:getPlayerNum())
if menu == nil then return end
local hasTowBar = playerObj:getInventory():getItemFromTypeRecurse("TowBar.TowBar") ~= nil
if not hasTowBar then return end
local vehicles = TowBarMod.Utils.getAviableVehicles(vehicle, hasTowBar)
if #vehicles == 0 then
return
elseif #vehicles == 1 then
local hookTypeVariants = TowBarMod.Utils.getHookTypeVariants(vehicle, vehicles[1], hasTowBar)
if #hookTypeVariants > 0 then
local hookType = hookTypeVariants[1]
menu:addSlice(
hookType.name,
getTexture("media/textures/tow_bar_attach.png"),
hookType.func,
playerObj,
hookType.towingVehicle,
hookType.towedVehicle,
hookType.towingPoint,
hookType.towedPoint
)
end
else
menu:addSlice(
getText("UI_Text_Towing_attach") .. "...",
getTexture("media/textures/tow_bar_attach.png"),
TowBarMod.UI.showChooseVehicleMenu,
playerObj,
vehicle,
vehicles,
hasTowBar
)
end
end
function TowBarMod.UI.addUnhookOptionToMenu(playerObj, vehicle)
local menu = getPlayerRadialMenu(playerObj:getPlayerNum())
if menu == nil then return end
if not vehicle:getModData()["isTowingByTowBar"] then return end
if not vehicle:getVehicleTowing() and not vehicle:getVehicleTowedBy() then return end
local towedVehicle = vehicle
if vehicle:getVehicleTowing() then
towedVehicle = vehicle:getVehicleTowing()
end
menu:addSlice(
getText("ContextMenu_Vehicle_DetachTrailer", ISVehicleMenu.getVehicleDisplayName(towedVehicle)),
getTexture("media/textures/tow_bar_detach.png"),
TowBarMod.Hook.deattachTowBarAction,
playerObj,
towedVehicle
)
end
---------------------------------------------------------------------------
--- Mod compability
---------------------------------------------------------------------------
if getActivatedMods():contains("vehicle_additions") then
require("Vehicles/ISUI/Oven_Mattress_RadialMenu")
require("Vehicles/ISUI/FuelTruckTank_ISVehicleMenu_FillPartMenu")
end
---------------------------------------------------------------------------
--- Attach to default menu method
---------------------------------------------------------------------------
if TowBarMod.UI.defaultShowRadialMenu == nil then
TowBarMod.UI.defaultShowRadialMenu = ISVehicleMenu.showRadialMenu
end
function ISVehicleMenu.showRadialMenu(playerObj)
TowBarMod.UI.defaultShowRadialMenu(playerObj)
if playerObj:getVehicle() then return end
local vehicle = ISVehicleMenu.getVehicleToInteractWith(playerObj)
if vehicle == nil then return end
if vehicle:getModData()["isTowingByTowBar"] then
TowBarMod.UI.removeDefaultDetachOption(playerObj)
TowBarMod.UI.addUnhookOptionToMenu(playerObj, vehicle)
elseif not vehicle:getVehicleTowing() and not vehicle:getVehicleTowedBy() then
TowBarMod.UI.addHookOptionToMenu(playerObj, vehicle)
end
end

View File

@@ -0,0 +1,173 @@
if not TowBarMod then TowBarMod = {} end
if not TowBarMod.Utils then TowBarMod.Utils = {} end
TowBarMod.Utils.tempVector1 = Vector3f.new()
TowBarMod.Utils.tempVector2 = Vector3f.new()
---------------------------------------------------------------------------
--- Util functions
---------------------------------------------------------------------------
function TowBarMod.Utils.isTrailer(vehicle)
return string.match(string.lower(vehicle:getScript():getName()), "trailer")
end
--- Return vehicles from sector that player can tow by tow bar.
function TowBarMod.Utils.getAviableVehicles(mainVehicle, hasTowBar)
local vehicles = {}
if not hasTowBar then return vehicles end
local square = mainVehicle:getSquare()
if square == nil then return vehicles end
-- Match vanilla towing search radius.
for y=square:getY() - 6, square:getY() + 6 do
for x=square:getX() - 6, square:getX() + 6 do
local square2 = getCell():getGridSquare(x, y, square:getZ())
if square2 then
for i=1, square2:getMovingObjects():size() do
local obj = square2:getMovingObjects():get(i-1)
if obj ~= nil
and instanceof(obj, "BaseVehicle")
and obj ~= mainVehicle
and #(TowBarMod.Utils.getHookTypeVariants(mainVehicle, obj, hasTowBar)) ~= 0 then
table.insert(vehicles, obj)
end
end
end
end
end
return vehicles
end
--- Return a table with towbar-only hook options for vehicles.
function TowBarMod.Utils.getHookTypeVariants(vehicleA, vehicleB, hasTowBar)
local hookTypeVariants = {}
if not hasTowBar then return hookTypeVariants end
if vehicleA:getVehicleTowing() or vehicleA:getVehicleTowedBy()
or vehicleB:getVehicleTowing() or vehicleB:getVehicleTowedBy() then
return hookTypeVariants
end
-- Keep tow bars for vehicle-to-vehicle towing only.
if TowBarMod.Utils.isTrailer(vehicleA) or TowBarMod.Utils.isTrailer(vehicleB) then
return hookTypeVariants
end
if vehicleA:canAttachTrailer(vehicleB, "trailerfront", "trailer") then
local hookType = {}
hookType.name = getText("UI_Text_Towing_attach") .. "\n" .. ISVehicleMenu.getVehicleDisplayName(vehicleB) .. "\n" .. getText("UI_Text_Towing_byTowBar")
hookType.func = TowBarMod.Hook.attachByTowBarAction
hookType.towingVehicle = vehicleB
hookType.towedVehicle = vehicleA
hookType.textureName = "tow_bar_icon"
table.insert(hookTypeVariants, hookType)
elseif vehicleA:canAttachTrailer(vehicleB, "trailer", "trailerfront") then
local hookType = {}
hookType.name = getText("UI_Text_Towing_attach") .. "\n" .. ISVehicleMenu.getVehicleDisplayName(vehicleB) .. "\n" .. getText("UI_Text_Towing_byTowBar")
hookType.func = TowBarMod.Hook.attachByTowBarAction
hookType.towingVehicle = vehicleA
hookType.towedVehicle = vehicleB
hookType.textureName = "tow_bar_icon"
table.insert(hookTypeVariants, hookType)
end
return hookTypeVariants
end
function TowBarMod.Utils.updateAttachmentsForRigidTow(towingVehicle, towedVehicle, attachmentA, attachmentB)
local towingAttachment = towingVehicle:getScript():getAttachmentById(attachmentA)
local towedAttachment = towedVehicle:getScript():getAttachmentById(attachmentB)
if towingAttachment == nil or towedAttachment == nil then return end
towingAttachment:setUpdateConstraint(false)
towingAttachment:setZOffset(0)
towedAttachment:setUpdateConstraint(false)
towedAttachment:setZOffset(0)
local offset = towedAttachment:getOffset()
local zShift = offset:z() > 0 and 1 or -1
towedAttachment:getOffset():set(offset:x(), offset:y(), offset:z() + zShift)
local towedModData = towedVehicle:getModData()
towedModData["isChangedTowedAttachment"] = true
towedModData["towBarChangedAttachmentId"] = attachmentB
towedModData["towBarChangedOffsetZShift"] = zShift
towedVehicle:transmitModData()
end
function TowBarMod.Utils.updateAttachmentsOnDefaultValues(towingVehicle, towedVehicle)
local towingAttachmentId = towingVehicle:getTowAttachmentSelf()
local towingAttachment = towingVehicle:getScript():getAttachmentById(towingAttachmentId)
if towingAttachment ~= nil then
towingAttachment:setUpdateConstraint(true)
local zOffset = (towingAttachmentId == "trailer") and -1 or 1
towingAttachment:setZOffset(zOffset)
end
local towedModData = towedVehicle:getModData()
local changedAttachmentId = towedModData["towBarChangedAttachmentId"] or towedVehicle:getTowAttachmentSelf()
local towedAttachment = towedVehicle:getScript():getAttachmentById(changedAttachmentId)
if towedAttachment ~= nil then
towedAttachment:setUpdateConstraint(true)
local zOffset = (changedAttachmentId == "trailer") and -1 or 1
towedAttachment:setZOffset(zOffset)
if towedModData["isChangedTowedAttachment"] then
local offset = towedAttachment:getOffset()
local storedShift = tonumber(towedModData["towBarChangedOffsetZShift"])
if storedShift ~= nil then
towedAttachment:getOffset():set(offset:x(), offset:y(), offset:z() - storedShift)
else
local zShift = offset:z() > 0 and -1 or 1
towedAttachment:getOffset():set(offset:x(), offset:y(), offset:z() + zShift)
end
end
end
towedModData["isChangedTowedAttachment"] = false
towedModData["towBarChangedAttachmentId"] = nil
towedModData["towBarChangedOffsetZShift"] = nil
towedVehicle:transmitModData()
end
-----------------------------------------------------------
--- Fix mods that add vehicles without tow attachments
local function fixTowAttachmentsForOtherVehicleMods()
local scriptManager = getScriptManager()
local vehicleScripts = scriptManager:getAllVehicleScripts()
for i = 0, vehicleScripts:size()-1 do
local script = vehicleScripts:get(i)
local wheelCount = script:getWheelCount()
local attachHeigtOffset = -0.5
if wheelCount > 0 then
attachHeigtOffset = script:getWheel(0):getOffset():y() + 0.1
end
if not string.match(string.lower(script:getName()), "trailer") then
local trailerAttachment = script:getAttachmentById("trailer")
if trailerAttachment == nil then
local attach = ModelAttachment.new("trailer")
attach:getOffset():set(0, attachHeigtOffset, -script:getPhysicsChassisShape():z()/2 - 0.1)
attach:setZOffset(-1)
script:addAttachment(attach)
end
local trailerFrontAttachment = script:getAttachmentById("trailerfront")
if trailerFrontAttachment == nil then
local attach = ModelAttachment.new("trailerfront")
attach:getOffset():set(0, attachHeigtOffset, script:getPhysicsChassisShape():z()/2 + 0.1)
attach:setZOffset(1)
script:addAttachment(attach)
end
end
end
end
Events.OnGameBoot.Add(fixTowAttachmentsForOtherVehicleMods)

View File

@@ -0,0 +1,22 @@
BTtow = {}
BTtow.Create = {}
BTtow.Init = {}
function BTtow.Create.towbar(vehicle, part)
if part == nil then return end
for j=0, 23 do
part:setModelVisible("towbar" .. j, false)
end
end
function BTtow.Init.towbar(vehicle, part)
if part == nil then return end
for j=0, 23 do
part:setModelVisible("towbar" .. j, false)
end
if vehicle:getScript():getModelScale() > 2 or vehicle:getScript():getModelScale() < 1.5 then return end
if vehicle:getModData()["isTowingByTowBar"] and vehicle:getModData()["towed"] then
local z = vehicle:getScript():getPhysicsChassisShape():z()/2 - 0.1
part:setModelVisible("towbar" .. math.floor((z*2/3-1)*10), true)
end
end

View File

@@ -0,0 +1,71 @@
require 'Items/ProceduralDistributions'
require 'Items/SuburbsDistributions'
require 'Items/Distributions'
require 'Items/Distribution_BinJunk'
require 'Items/Distribution_ClosetJunk'
require 'Items/Distribution_DeskJunk'
require 'Items/Distribution_ShelfJunk'
require 'Items/Distribution_CounterJunk'
require 'Items/Distribution_SideTableJunk'
require 'Vehicles/VehicleDistributions'
require 'Vehicles/VehicleDistribution_GloveBoxJunk'
require 'Vehicles/VehicleDistribution_SeatJunk'
require 'Vehicles/VehicleDistribution_TrunkJunk'
----------------- TOW BAR -----------------------
-- Mirror Jack spawn chance into TowBar in container distributions (world + vehicle containers).
-- Intentionally excludes story-clutter floor placement tables (RandomizedWorldContent/StoryClutter).
local TOWBAR_ITEM_TYPE = "TowBar.TowBar"
local JACK_ITEM_TYPES = {
["Jack"] = true,
["Base.Jack"] = true,
}
local function addMissingTowBarsForJack(items)
if type(items) ~= "table" then return end
local jackCountByChance = {}
local towBarCountByChance = {}
for i = 1, #items, 2 do
local itemType = items[i]
local chance = tonumber(items[i + 1])
if type(itemType) == "string" and chance ~= nil then
if JACK_ITEM_TYPES[itemType] then
jackCountByChance[chance] = (jackCountByChance[chance] or 0) + 1
elseif itemType == TOWBAR_ITEM_TYPE then
towBarCountByChance[chance] = (towBarCountByChance[chance] or 0) + 1
end
end
end
for chance, jackCount in pairs(jackCountByChance) do
local missing = jackCount - (towBarCountByChance[chance] or 0)
for _ = 1, missing do
table.insert(items, TOWBAR_ITEM_TYPE)
table.insert(items, chance)
end
end
end
local function walkContainerDistributions(root, seen)
if type(root) ~= "table" or seen[root] then return end
seen[root] = true
for key, value in pairs(root) do
if key == "items" and type(value) == "table" then
addMissingTowBarsForJack(value)
elseif type(value) == "table" then
walkContainerDistributions(value, seen)
end
end
end
local seen = {}
walkContainerDistributions(ProceduralDistributions, seen)
walkContainerDistributions(SuburbsDistributions, seen)
walkContainerDistributions(Distributions, seen)
walkContainerDistributions(VehicleDistributions, seen)
walkContainerDistributions(ClutterTables, seen)

View File

@@ -0,0 +1,73 @@
if isClient() then return end
local Commands = {}
local TowBarItemType = "TowBar.TowBar"
function Commands.attachConstraint(player, args)
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)
end
function Commands.detachConstraint(player, args)
local vehicle = args and getVehicleById(args.vehicle)
if not vehicle then return end
vehicle:breakConstraint(true, false)
end
function Commands.consumeTowBar(player, args)
if not player then return end
local inventory = player:getInventory()
if not inventory then return end
local towBarItem = nil
local itemId = args and args.itemId
if itemId then
towBarItem = inventory:getItemWithID(itemId)
end
if not towBarItem then
towBarItem = inventory:getFirstTypeRecurse(TowBarItemType)
end
if not towBarItem then return end
local wasPrimary = player:isPrimaryHandItem(towBarItem)
local wasSecondary = player:isSecondaryHandItem(towBarItem)
player:removeFromHands(towBarItem)
inventory:Remove(towBarItem)
sendRemoveItemFromContainer(inventory, towBarItem)
if wasPrimary or wasSecondary then
sendEquip(player)
end
end
function Commands.giveTowBar(player, args)
if not player then return end
local inventory = player:getInventory()
if not inventory then return end
local towBarItem = inventory:AddItem(TowBarItemType)
if not towBarItem then return end
sendAddItemToContainer(inventory, towBarItem)
if args and args.equipPrimary then
player:setPrimaryHandItem(towBarItem)
sendEquip(player)
end
end
local function onClientCommand(module, command, player, args)
if module ~= "towbar" then return end
local fn = Commands[command]
if fn then
fn(player, args or {})
end
end
Events.OnClientCommand.Add(onClientCommand)

View File

@@ -0,0 +1,3 @@
-- Build 42 registry file.
-- This mod currently uses only base registries (for example ItemType = base:normal),
-- so no custom identifier registrations are required here.

View File

@@ -0,0 +1,15 @@
module TowBar
{
/*******************Towing Car*******************/
item TowBar
{
DisplayCategory = Tool,
Weight = 8.0,
ItemType = base:normal,
Icon = TowBar,
Tooltip = Tooltip_TowBar,
StaticModel = towbarModel,
WorldStaticModel = towbarModel,
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,184 @@
module Base
{
template vehicle Battery
{
part towbar
{
model towbar0
{
file = towbarModel,
offset = 0 -0.3 1.0,
}
model towbar1
{
file = towbarModel,
offset = 0 -0.3 1.1,
}
model towbar2
{
file = towbarModel,
offset = 0 -0.3 1.2,
}
model towbar3
{
file = towbarModel,
offset = 0 -0.3 1.3,
}
model towbar4
{
file = towbarModel,
offset = 0 -0.3 1.4,
}
model towbar5
{
file = towbarModel,
offset = 0 -0.3 1.5,
}
model towbar6
{
file = towbarModel,
offset = 0 -0.3 1.6,
}
model towbar7
{
file = towbarModel,
offset = 0 -0.3 1.7,
}
model towbar8
{
file = towbarModel,
offset = 0 -0.3 1.8,
}
model towbar9
{
file = towbarModel,
offset = 0 -0.3 1.9,
}
model towbar10
{
file = towbarModel,
offset = 0 -0.3 2.0,
}
model towbar11
{
file = towbarModel,
offset = 0 -0.3 2.1,
}
model towbar12
{
file = towbarModel,
offset = 0 -0.3 2.2,
}
model towbar13
{
file = towbarModel,
offset = 0 -0.3 2.3,
}
model towbar14
{
file = towbarModel,
offset = 0 -0.3 2.4,
}
model towbar15
{
file = towbarModel,
offset = 0 -0.3 2.5,
}
model towbar16
{
file = towbarModel,
offset = 0 -0.3 2.6,
}
model towbar17
{
file = towbarModel,
offset = 0 -0.3 2.7,
}
model towbar18
{
file = towbarModel,
offset = 0 -0.3 2.8,
}
model towbar19
{
file = towbarModel,
offset = 0 -0.3 2.9,
}
model towbar20
{
file = towbarModel,
offset = 0 -0.3 3.0,
}
model towbar21
{
file = towbarModel,
offset = 0 -0.3 3.1,
}
model towbar22
{
file = towbarModel,
offset = 0 -0.3 3.2,
}
model towbar23
{
file = towbarModel,
offset = 0 -0.3 3.3,
}
area = Engine,
mechanicRequireKey = false,
lua
{
create = BTtow.Create.towbar,
init = BTtow.Init.towbar,
}
}
part Battery
{
area = Engine,
itemType = Base.CarBattery,
mechanicRequireKey = true,
category = engine,
table install
{
items
{
1
{
type = Base.Screwdriver,
count = 1,
keep = true,
equip = primary,
}
}
time = 100,
professions = ,
skills = ,
traits = ,
recipes = ,
test = Vehicles.InstallTest.Default,
door = EngineDoor,
}
table uninstall
{
items
{
1
{
type = Base.Screwdriver,
count = 1,
keep = true,
equip = primary,
}
}
time = 100,
test = Vehicles.UninstallTest.Battery,
}
lua
{
create = Vehicles.Create.Battery,
update = Vehicles.Update.Battery,
}
}
}
}

View File

@@ -0,0 +1,144 @@
module Base
{
model towbarModel
{
mesh = vehicles/Towbar,
texture = Vehicles/Towbar_Texture,
scale = 0.01,
}
template vehicle Towbar
{
part towbar
{
model towbar0
{
file = towbarModel,
offset = 0 -0.3 1.0,
}
model towbar1
{
file = towbarModel,
offset = 0 -0.3 1.1,
}
model towbar2
{
file = towbarModel,
offset = 0 -0.3 1.2,
}
model towbar3
{
file = towbarModel,
offset = 0 -0.3 1.3,
}
model towbar4
{
file = towbarModel,
offset = 0 -0.3 1.4,
}
model towbar5
{
file = towbarModel,
offset = 0 -0.3 1.5,
}
model towbar6
{
file = towbarModel,
offset = 0 -0.3 1.6,
}
model towbar7
{
file = towbarModel,
offset = 0 -0.3 1.7,
}
model towbar8
{
file = towbarModel,
offset = 0 -0.3 1.8,
}
model towbar9
{
file = towbarModel,
offset = 0 -0.3 1.9,
}
model towbar10
{
file = towbarModel,
offset = 0 -0.3 2.0,
}
model towbar11
{
file = towbarModel,
offset = 0 -0.3 2.1,
}
model towbar12
{
file = towbarModel,
offset = 0 -0.3 2.2,
}
model towbar13
{
file = towbarModel,
offset = 0 -0.3 2.3,
}
model towbar14
{
file = towbarModel,
offset = 0 -0.3 2.4,
}
model towbar15
{
file = towbarModel,
offset = 0 -0.3 2.5,
}
model towbar16
{
file = towbarModel,
offset = 0 -0.3 2.6,
}
model towbar17
{
file = towbarModel,
offset = 0 -0.3 2.7,
}
model towbar18
{
file = towbarModel,
offset = 0 -0.3 2.8,
}
model towbar19
{
file = towbarModel,
offset = 0 -0.3 2.9,
}
model towbar20
{
file = towbarModel,
offset = 0 -0.3 3.0,
}
model towbar21
{
file = towbarModel,
offset = 0 -0.3 3.1,
}
model towbar22
{
file = towbarModel,
offset = 0 -0.3 3.2,
}
model towbar23
{
file = towbarModel,
offset = 0 -0.3 3.3,
}
area = Engine,
mechanicRequireKey = false,
lua
{
create = BTtow.Create.towbar,
init = BTtow.Init.towbar,
}
}
}
}