--[[

Title: Pushable vehicle 
Description: Pushable vehicle specialization
FS Version: FS19
Release date: 27.12.2020

Version: 1.0.0.0 Initial Release
]]

local modName = g_currentModName

local function getSpecTable(self)
	return self["spec_" .. modName .. ".pushableVehicle"]
end

PushableVehicle = {
	PLAYER_COLLISION_MASK = 2147483679.0,
	prerequisitesPresent = function (specializations)
		return true
	end
}

function PushableVehicle.registerFunctions(vehicleType)
	SpecializationUtil.registerFunction(vehicleType, "getRaycastPosition", PushableVehicle.getRaycastPosition)
	SpecializationUtil.registerFunction(vehicleType, "playerRaycastCallback", PushableVehicle.playerRaycastCallback)
	SpecializationUtil.registerFunction(vehicleType, "readTestAreasStream", PushableVehicle.readTestAreasStream)
	SpecializationUtil.registerFunction(vehicleType, "writeTestAreasStream", PushableVehicle.writeTestAreasStream)
	SpecializationUtil.registerFunction(vehicleType, "postAnimationUpdate", PushableVehicle.postAnimationUpdate)
end

function PushableVehicle.registerOverwrittenFunctions(vehicleType)
	SpecializationUtil.registerOverwrittenFunction(vehicleType, "setVehicleCharacter", PushableVehicle.setVehicleCharacter)
	SpecializationUtil.registerOverwrittenFunction(vehicleType, "getAllowCharacterVisibilityUpdate", PushableVehicle.getAllowCharacterVisibilityUpdate)
	SpecializationUtil.registerOverwrittenFunction(vehicleType, "setActiveCameraIndex", PushableVehicle.setActiveCameraIndex)
end

function PushableVehicle.registerEventListeners(vehicleType)
	SpecializationUtil.registerEventListener(vehicleType, "onLoad", PushableVehicle)
	SpecializationUtil.registerEventListener(vehicleType, "onDelete", PushableVehicle)
	SpecializationUtil.registerEventListener(vehicleType, "onReadStream", PushableVehicle)
	SpecializationUtil.registerEventListener(vehicleType, "onWriteStream", PushableVehicle)
	SpecializationUtil.registerEventListener(vehicleType, "onReadUpdateStream", PushableVehicle)
	SpecializationUtil.registerEventListener(vehicleType, "onWriteUpdateStream", PushableVehicle)
	SpecializationUtil.registerEventListener(vehicleType, "onUpdate", PushableVehicle)
	SpecializationUtil.registerEventListener(vehicleType, "onEnterVehicle", PushableVehicle)
	SpecializationUtil.registerEventListener(vehicleType, "onLeaveVehicle", PushableVehicle)
	SpecializationUtil.registerEventListener(vehicleType, "onCameraChanged", PushableVehicle)
end

function PushableVehicle:onLoad(savegame)
	local spec = getSpecTable(self)
	spec.animationParameters = {
		absSmoothedForwardVelocity = {
			value = 0,
			id = 1,
			type = 1
		},
		smoothedForwardVelocity = {
			value = 0,
			id = 2,
			type = 1
		},
		accelerate = {
			value = false,
			id = 3,
			type = 0
		},
		leftRightWeight = {
			value = 0,
			id = 4,
			type = 1
		}
	}
	spec.raycastNode1 = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.pushableVehicle.raycast#node1"), self.i3dMappings)
	spec.raycastNode2 = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.pushableVehicle.raycast#node2"), self.i3dMappings)
	spec.playerNode = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.pushableVehicle.raycast#playerNode"), self.i3dMappings)
	spec.playerTargetNode = createTransformGroup("playerTargetNode")

	if spec.playerNode ~= nil then
		link(getParent(spec.playerNode), spec.playerTargetNode)
		setTranslation(spec.playerTargetNode, getTranslation(spec.playerNode))
	end

	local frontWheels = {
		StringUtil.getVectorFromString(getXMLString(self.xmlFile, "vehicle.pushableVehicle.wheels#front"))
	}
	spec.frontWheels = {}

	for i = 1, #frontWheels do
		local wheel = self:getWheelFromWheelIndex(frontWheels[i])

		if wheel ~= nil then
			table.insert(spec.frontWheels, wheel)
		end
	end

	local backWheels = {
		StringUtil.getVectorFromString(getXMLString(self.xmlFile, "vehicle.pushableVehicle.wheels#back"))
	}
	spec.backWheels = {}

	for i = 1, #backWheels do
		local wheel = self:getWheelFromWheelIndex(backWheels[i])

		if wheel ~= nil then
			table.insert(spec.backWheels, wheel)
		end
	end

	spec.handle = {
		node = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.pushableVehicle.handle#node"), self.i3dMappings),
		upperLimit = getXMLFloat(self.xmlFile, "vehicle.pushableVehicle.handle#upperLimit") or 0.4,
		lowerLimit = getXMLFloat(self.xmlFile, "vehicle.pushableVehicle.handle#lowerLimit") or 0.4,
		interpolateDistance = getXMLFloat(self.xmlFile, "vehicle.pushableVehicle.handle#interpolateDistance") or 0.4,
		minRot = math.rad(getXMLFloat(self.xmlFile, "vehicle.pushableVehicle.handle#minRot") or -20),
		maxRot = math.rad(getXMLFloat(self.xmlFile, "vehicle.pushableVehicle.handle#maxRot") or 20)
	}
	spec.characterIKNodes = {}
	spec.ikChainTargets = {}

	IKUtil.loadIKChainTargets(self.xmlFile, "vehicle.pushableVehicle.ikChains", self.components, spec.ikChainTargets, self.i3dMappings)

	spec.lastRaycastPosition = {
		0,
		0,
		0,
		0
	}
	spec.lastRaycastHit = false

	if self.isClient then
		spec.cutterEffects = {}
		local i = 0

		while true do
			local key = string.format("vehicle.pushableVehicle.cutterEffects.effectNode(%d)", i)

			if not hasXMLProperty(self.xmlFile, key) then
				break
			end

			local effect = CutterEffect:new()

			effect:load(self.xmlFile, key, self.components, self, FruitType.GRASS, self.i3dMappings)
			effect:setGrowthState(3)
			effect:setMinMaxWidth(0, 0)
			table.insert(spec.cutterEffects, effect)

			i = i + 1
		end
	end

	spec.testAreas = {}
	spec.testAreaBase = Utils.getNoNil(I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.pushableVehicle.testAreas#baseNode"), self.i3dMappings), self.rootNode)
	spec.testAreaMovingDirection = getXMLInt(self.xmlFile, "vehicle.pushableVehicle.testAreas#movingDirection") or 1
	local i = 0

	while true do
		local key = string.format("vehicle.pushableVehicle.testAreas.testArea(%d)", i)

		if not hasXMLProperty(self.xmlFile, key) then
			break
		end

		local start = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, key .. "#startNode"), self.i3dMappings)
		local width = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, key .. "#widthNode"), self.i3dMappings)
		local height = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, key .. "#heightNode"), self.i3dMappings)
		local groundReferenceNodeIndex = getXMLInt(self.xmlFile, key .. "#groundReferenceNodeIndex")
		local groundReferenceNode = nil

		if groundReferenceNodeIndex ~= nil then
			groundReferenceNode = self:getGroundReferenceNodeFromIndex(groundReferenceNodeIndex)
		end

		if start ~= nil and width ~= nil and height ~= nil then
			local testArea = {
				start = start,
				width = width,
				height = height,
				groundReferenceNode = groundReferenceNode,
				hasFruitContact = false,
				hasFruitContactSent = false
			}

			table.insert(spec.testAreas, testArea)
		end

		i = i + 1
	end

	spec.customChainLimits = {}
	i = 0

	while true do
		local key = string.format("vehicle.pushableVehicle.customChainLimits.customChainLimit(%d)", i)

		if not hasXMLProperty(self.xmlFile, key) then
			break
		end

		local entry = {
			chainId = getXMLString(self.xmlFile, key .. "#chainId"),
			nodeIndex = getXMLInt(self.xmlFile, key .. "#nodeIndex")
		}

		if entry.chainId ~= nil and entry.nodeIndex ~= nil then
			entry.minRx = Utils.getNoNilRad(getXMLFloat(self.xmlFile, key .. "#minRx"), nil)
			entry.maxRx = Utils.getNoNilRad(getXMLFloat(self.xmlFile, key .. "#maxRx"), nil)
			entry.minRy = Utils.getNoNilRad(getXMLFloat(self.xmlFile, key .. "#minRy"), nil)
			entry.maxRy = Utils.getNoNilRad(getXMLFloat(self.xmlFile, key .. "#maxRy"), nil)
			entry.minRz = Utils.getNoNilRad(getXMLFloat(self.xmlFile, key .. "#minRz"), nil)
			entry.maxRz = Utils.getNoNilRad(getXMLFloat(self.xmlFile, key .. "#maxRz"), nil)
			entry.damping = getXMLFloat(self.xmlFile, key .. "#damping")
			entry.localLimits = getXMLBool(self.xmlFile, key .. "#localLimits")

			table.insert(spec.customChainLimits, entry)
		end

		i = i + 1
	end

	spec.effectDirtyFlag = self:getNextDirtyFlag()
	spec.raycastsValid = true
	spec.lastSmoothSpeed = 0
	spec.postAnimationCallback = addPostAnimationCallback(self.postAnimationUpdate, self)
end

function PushableVehicle:onDelete()
	local spec = getSpecTable(self)

	if self.isClient then
		g_effectManager:deleteEffects(spec.cutterEffects)
	end

	removePostAnimationCallback(spec.postAnimationCallback)
end

function PushableVehicle:onReadStream(streamId, connection)
	self:readTestAreasStream(streamId, connection)
end

function PushableVehicle:onWriteStream(streamId, connection)
	self:writeTestAreasStream(streamId, connection)
end

function PushableVehicle:onReadUpdateStream(streamId, timestamp, connection)
	if connection:getIsServer() and streamReadBool(streamId) then
		self:readTestAreasStream(streamId, connection)
	end
end

function PushableVehicle:onWriteUpdateStream(streamId, connection, dirtyMask)
	if not connection:getIsServer() then
		local spec = getSpecTable(self)

		if streamWriteBool(streamId, bitAND(dirtyMask, spec.effectDirtyFlag) ~= 0) then
			self:writeTestAreasStream(streamId, connection)
		end
	end
end

function PushableVehicle:readTestAreasStream(streamId, connection)
	local spec = getSpecTable(self)

	for _, testArea in ipairs(spec.testAreas) do
		testArea.hasFruitContact = streamReadBool(streamId)
	end
end

function PushableVehicle:writeTestAreasStream(streamId, connection)
	local spec = getSpecTable(self)

	for _, testArea in ipairs(spec.testAreas) do
		streamWriteBool(streamId, testArea.hasFruitContact)
	end
end

function PushableVehicle:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
	local spec = getSpecTable(self)

	

	local lastSpeed = self.lastSignedSpeed * 1000
	local avgSpeed = 0
	local numWheels = 0

	for _, wheel in pairs(spec.backWheels) do
		if wheel.netInfo.xDriveSpeed ~= nil then
			local wheelSpeed = MathUtil.rpmToMps(wheel.netInfo.xDriveSpeed / (2 * math.pi) * 60, wheel.radius) * 1000
			avgSpeed = avgSpeed + wheelSpeed
			numWheels = numWheels + 1
		end
	end

	if numWheels > 0 then
		lastSpeed = avgSpeed / numWheels
	end

	spec.lastSmoothSpeed = spec.lastSmoothSpeed * 0.9 + lastSpeed * 0.1
	spec.animationParameters.smoothedForwardVelocity.value = spec.lastSmoothSpeed
	spec.animationParameters.absSmoothedForwardVelocity.value = math.abs(spec.lastSmoothSpeed)
	spec.animationParameters.leftRightWeight.value = self.rotatedTime
	spec.animationParameters.accelerate.value = self:getAccelerationAxis() > 0

	if self:getIsEntered() or self:getIsControlled() then
		local character = self:getVehicleCharacter()

		if character ~= nil and character.animationCharsetId ~= 0 and character.animationPlayer ~= nil then
			for _, parameter in pairs(spec.animationParameters) do
				if parameter.type == 0 then
					setConditionalAnimationBoolValue(character.animationPlayer, parameter.id, parameter.value)
				elseif parameter.type == 1 then
					setConditionalAnimationFloatValue(character.animationPlayer, parameter.id, parameter.value)
				end
			end

			setConditionalAnimationSpecificParameterIds(character.animationPlayer, spec.animationParameters.absSmoothedForwardVelocity.id, 0)
			updateConditionalAnimation(character.animationPlayer, dt)
		end
	end

	if spec.raycastNode1 ~= nil and spec.raycastNode2 ~= nil and spec.playerNode ~= nil then
		local x1, y1, z1 = self:getRaycastPosition(spec.raycastNode1)
		local x2, y2, z2 = self:getRaycastPosition(spec.raycastNode2)

		if x1 ~= nil and x2 ~= nil then
			local tx = (x1 + x2) * 0.5
			local ty = (y1 + y2) * 0.5
			local tz = (z1 + z2) * 0.5

			setWorldTranslation(spec.playerTargetNode, tx, ty, tz)

			local dirX = x1 - x2
			local dirY = y1 - y2
			local dirZ = z1 - z2
			dirX, dirY, dirZ = MathUtil.vector3Normalize(dirX, dirY, dirZ)

			if spec.lastYDirection == nil then
				spec.lastYDirection = dirY
			else
				dirY = spec.lastYDirection * 0.9 + dirY * 0.1
				spec.lastYDirection = dirY
			end

			I3DUtil.setWorldDirection(spec.playerTargetNode, dirX, dirY, dirZ, 0, 1, 0)

			if spec.lastWorldTrans == nil then
				spec.lastWorldTrans = {
					getWorldTranslation(spec.playerNode)
				}
			end

			local cx = spec.lastWorldTrans[1]
			local cy = spec.lastWorldTrans[2]
			local cz = spec.lastWorldTrans[3]
			local smoothFactor = 0.5 - math.abs(math.min(self.rotatedTime / 0.5), 1) * 0.15
			local moveX = (cx - tx) * smoothFactor
			local moveY = (cy - ty) * smoothFactor
			local moveZ = (cz - tz) * smoothFactor
			local newX = cx - moveX
			local newY = cy - moveY
			local newZ = cz - moveZ

			setWorldTranslation(spec.playerNode, newX, newY, newZ)

			spec.lastWorldTrans[3] = newZ
			spec.lastWorldTrans[2] = newY
			spec.lastWorldTrans[1] = newX
			local direction = self.movingDirection

			if direction == 0 then
				direction = 1
			end

			tx, ty, tz = localToWorld(spec.playerTargetNode, 0, 0, 0.2 * direction)
			local dirY2, _ = nil
			dirZ = tz - newZ
			dirY2 = ty - newY
			dirX = tx - newX
			dirX, _, dirZ = MathUtil.vector3Normalize(dirX, dirY2, dirZ)

			if direction < 0 then
				dirZ = -dirZ
				dirX = -dirX
			end

			local fcx = 0
			local fcy = 0
			local fcz = 0
			local numFrontWheels = #spec.frontWheels

			for i = 1, numFrontWheels do
				local wheel = spec.frontWheels[i]
				local wx = wheel.netInfo.x
				local wy = wheel.netInfo.y
				local wz = wheel.netInfo.z
				wy = wy - wheel.radius
				wx = wx + wheel.xOffset
				wx, wy, wz = localToWorld(wheel.node, wx, wy, wz)
				fcz = fcz + wz
				fcy = fcy + wy
				fcx = fcx + wx
			end

			fcz = fcz / numFrontWheels
			fcy = fcy / numFrontWheels
			fcx = fcx / numFrontWheels
			local bcx = 0
			local bcy = 0
			local bcz = 0
			local numBackWheels = #spec.backWheels

			for i = 1, numBackWheels do
				local wheel = spec.backWheels[i]
				local wx = wheel.netInfo.x
				local wy = wheel.netInfo.y
				local wz = wheel.netInfo.z
				wy = wy - wheel.radius
				wx = wx + wheel.xOffset
				wx, wy, wz = localToWorld(wheel.node, wx, wy, wz)
				bcz = bcz + wz
				bcy = bcy + wy
				bcx = bcx + wx
			end

			bcz = bcz / numBackWheels
			bcy = bcy / numBackWheels
			bcx = bcx / numBackWheels
			local wDirX = bcx - fcx
			local wDirY = bcy - fcy
			local wDirZ = bcz - fcz
			_, wDirY, _ = MathUtil.vector3Normalize(wDirX, wDirY, wDirZ)
			local dir = wDirY < 0 and 1 or -1
			dirY = wDirY + math.min(0.15, math.abs(wDirY)) * dir
			local upX, upY, upZ = localDirectionToWorld(self.rootNode, 0, 1, 0)
			upY = upY + 0.5
			upX, upY, upZ = MathUtil.vector3Normalize(upX, upY, upZ)

			I3DUtil.setWorldDirection(spec.playerNode, dirX, dirY, dirZ, upX, upY, upZ)

			spec.raycastsValid = true
		elseif spec.raycastsValid and self:getIsEntered() then
			spec.raycastsValid = false
			local character = self:getVehicleCharacter()

			if character ~= nil then
				character:setCharacterVisibility(false)
			end

			self:setActiveCameraIndex(self.spec_enterable.camIndex)
		end
	end
end

function PushableVehicle:setVehicleCharacter(superFunc, xmlFilename, playerStyle)
	local enterableSpec = self.spec_enterable

	if enterableSpec.vehicleCharacter ~= nil then
		enterableSpec.vehicleCharacter:delete()
		enterableSpec.vehicleCharacter:loadCharacter(xmlFilename, playerStyle)
		enterableSpec.vehicleCharacter:updateVisibility()
	end

	SpecializationUtil.raiseEvent(self, "onVehicleCharacterChanged", enterableSpec.vehicleCharacter)

	local character = enterableSpec.vehicleCharacter
	local spec = getSpecTable(self)
	spec.characterIKNodes = {}

	for name, ikChain in pairs(character.ikChains) do
		if spec.ikChainTargets[name] ~= nil then
			for k, nodeData in pairs(ikChain.nodes) do
				if spec.characterIKNodes[nodeData.node] == nil then
					local duplicate = createTransformGroup(getName(nodeData.node) .. "_ikChain")
					local parent = getParent(nodeData.node)

					if spec.characterIKNodes[parent] ~= nil then
						parent = spec.characterIKNodes[parent]
					end

					link(parent, duplicate)
					setTranslation(duplicate, getTranslation(nodeData.node))
					setRotation(duplicate, getRotation(nodeData.node))

					spec.characterIKNodes[nodeData.node] = duplicate
				end
			end
		end

		for k, nodeData in pairs(ikChain.nodes) do
			if spec.characterIKNodes[nodeData.node] ~= nil then
				nodeData.node = spec.characterIKNodes[nodeData.node]
			end
		end
	end

	character.ikChainTargets = spec.ikChainTargets

	for ikChainId, target in pairs(spec.ikChainTargets) do
		IKUtil.setTarget(character.ikChains, ikChainId, target)
	end

	spec.ikChains = character.ikChains

	for name, ikChain in pairs(character.ikChains) do
		ikChain.ikChainSolver = IKChain:new(table.getn(ikChain.nodes))

		for i, node in ipairs(ikChain.nodes) do
			local minRx = node.minRx
			local maxRx = node.maxRx
			local minRy = node.minRy
			local maxRy = node.maxRy
			local minRz = node.minRz
			local maxRz = node.maxRz
			local damping = node.damping
			local localLimits = node.localLimits

			for j = 1, #spec.customChainLimits do
				local customLimit = spec.customChainLimits[j]

				if customLimit.chainId == name and customLimit.nodeIndex == i then
					minRx = customLimit.minRx or minRx
					maxRx = customLimit.maxRx or maxRx
					minRy = customLimit.minRy or minRy
					maxRy = customLimit.maxRy or maxRy
					minRz = customLimit.minRz or minRz
					maxRz = customLimit.maxRz or maxRz
					damping = customLimit.damping or damping

					if customLimit.localLimits ~= nil then
						localLimits = customLimit.localLimits
					end
				end
			end

			ikChain.ikChainSolver:setJointTransformGroup(i - 1, node.node, minRx, maxRx, minRy, maxRy, minRz, maxRz, damping, localLimits)
		end

		ikChain.numIterations = 40
		ikChain.positionThreshold = 0.0001
	end

	character:setCharacterDirty()

	if character ~= nil and character.animationCharsetId ~= 0 and character.animationPlayer ~= 0 then
		for key, parameter in pairs(spec.animationParameters) do
			conditionalAnimationRegisterParameter(character.animationPlayer, parameter.id, parameter.type, key)
		end

		initConditionalAnimation(character.animationPlayer, character.animationCharsetId, self.configFileName, "vehicle.pushableVehicle.playerConditionalAnimation")
		conditionalAnimationZeroiseTrackTimes(character.animationPlayer)
	end

	IKUtil.updateAlignNodes(self.spec_ikChains.chains, self.getParentComponent, self)
end

function PushableVehicle:getAllowCharacterVisibilityUpdate(superFunc)
	if not superFunc(self) then
		return false
	end

	if self:getIsEntered() then
		local activeCamera = self:getActiveCamera()

		if activeCamera ~= nil and activeCamera.isInside then
			return false
		end
	end

	local spec = getSpecTable(self)

	if not spec.raycastsValid then
		return false
	end

	return true
end

function PushableVehicle:setActiveCameraIndex(superFunc, index)
	local spec = getSpecTable(self)

	if not spec.raycastsValid then
		local specEnterable = self.spec_enterable

		if specEnterable.numCameras < index then
			index = 1
		end

		local activeCamera = specEnterable.cameras[index]

		if activeCamera.isInside then
			for i, camera in pairs(specEnterable.cameras) do
				if not camera.isInside then
					index = i

					break
				end
			end
		end
	end

	return superFunc(self, index)
end

function PushableVehicle:onEnterVehicle(isControlling)
	local hideCharacter = false

	if isControlling then
		local activeCamera = self:getActiveCamera()

		if activeCamera ~= nil and activeCamera.isInside then
			hideCharacter = true
		end
	end

	local spec = getSpecTable(self)

	if not spec.raycastsValid then
		hideCharacter = true
	end

	if hideCharacter then
		local character = self:getVehicleCharacter()

		if character ~= nil then
			character:setCharacterVisibility(false)
		end
	end
end

function PushableVehicle:onLeaveVehicle()
	local spec = getSpecTable(self)
	spec.characterIKNodes = {}
end

function PushableVehicle:onCameraChanged(activeCamera, camIndex)
	if self:getIsEntered() then
		local character = self:getVehicleCharacter()

		if character ~= nil and activeCamera.isInside then
			character:setCharacterVisibility(false)
		end
	end
end

function PushableVehicle:postAnimationUpdate(dt)
	if self.isActive then
		local spec = getSpecTable(self)

		if spec.raycastsValid and spec.handle.node ~= nil then
			local yDifference = nil

			for name, ikChain in pairs(spec.ikChains) do
				local node = ikChain.nodes[1].node

				if node ~= nil and spec.ikChainTargets[name] ~= nil then
					local targetNode = spec.ikChainTargets[name].targetNode

					if targetNode ~= nil then
						local x, y, z = getRotation(spec.handle.node)

						setRotation(spec.handle.node, 0, 0, 0)

						if yDifference == nil then
							yDifference = calcDistanceFrom(node, targetNode)
						else
							yDifference = (yDifference + calcDistanceFrom(node, targetNode)) / 2
						end

						setRotation(spec.handle.node, x, y, z)
					end
				end
			end

			if yDifference ~= nil then
				if yDifference < spec.handle.upperLimit then
					local alpha = (spec.handle.upperLimit - yDifference) / spec.handle.interpolateDistance

					setRotation(spec.handle.node, spec.handle.minRot * alpha, 0, 0)
				elseif spec.handle.lowerLimit < yDifference then
					local alpha = (yDifference - spec.handle.lowerLimit) / spec.handle.interpolateDistance

					setRotation(spec.handle.node, spec.handle.maxRot * alpha, 0, 0)
				end
			end
		end

		IKUtil.updateIKChains(self.spec_ikChains.chains)

		for target, source in pairs(spec.characterIKNodes) do
			setRotation(target, getRotation(source))
		end

		for ikChainId, target in pairs(spec.ikChainTargets) do
			IKUtil.setIKChainPose(self.spec_ikChains.chains, ikChainId, target.poseId)
		end
	end
end

function PushableVehicle:getRaycastPosition(node)
	local spec = getSpecTable(self)
	local x, y, z = getWorldTranslation(node)
	local dirX, dirY, dirZ = localDirectionToWorld(node, 0, -1, 0)
	dirY = dirY * 1.5
	spec.lastRaycastHit = false

	raycastAll(x, y, z, dirX, dirY, dirZ, "playerRaycastCallback", 2, self, PushableVehicle.PLAYER_COLLISION_MASK)

	if spec.lastRaycastHit and spec.lastRaycastPosition[4] > 0.35 and spec.lastRaycastPosition[2] < y - 0.25 then
		return unpack(spec.lastRaycastPosition)
	end

	return nil
end

function PushableVehicle:playerRaycastCallback(hitObjectId, x, y, z, distance)
	local spec = getSpecTable(self)
	local vehicle = g_currentMission.nodeToObject[hitObjectId]

	if vehicle ~= nil and vehicle == self then
		return true
	end

	spec.lastRaycastPosition[1] = x
	spec.lastRaycastPosition[2] = y
	spec.lastRaycastPosition[3] = z
	spec.lastRaycastPosition[4] = distance
	spec.lastRaycastHit = true

	return false
end
