--[[
    VehiclePart

	VehiclePart is a class that represents a part of a vehicle. It is used to load and attach parts to a vehicle.

	@author: 		BayernGamers
	@date: 			18.03.2025
	@version:		1.0

	History:		v1.0 @18.03.2025 - initial implementation in FS 25
                    ------------------------------------------------------------------------------------------------------
	
	License:        Terms:
                        Usage:
                            Feel free to use this work as-is as long as you adhere to the following terms:
						Attribution:
							You must give appropriate credit to the original author when using this work.
						No Derivatives:
							You may not alter, transform, or build upon this work in any way.
						Usage: 
							The work may be used for personal and commercial purposes, provided it is not modified or adapted.
						Additional Clause:
							This script may not be converted, adapted, or incorporated into any other game versions or platforms except by GIANTS Software.
]]
source(Utils.getFilename("scripts/utils/Logger.lua", g_currentModDirectory))
source(Utils.getFilename("scripts/ExtendedRealLight.lua", g_currentModDirectory))

local log = Logger.new(true, Logger.DEBUG_LEVELS.HIGH, "VehiclePart.lua")

VehiclePart = {}
local VehiclePart_mt = Class(VehiclePart)
VehiclePart.MOD_DIR = g_currentModDirectory
VehiclePart.MOD_NAME = g_currentModName
VehiclePart.SCHEMA = XMLSchema.new("VehiclePartSchema")
VehiclePart.ADDITIONAL_LIGHT_ATTRIBUTES_KEYS = {
	"vehiclePart.lights.sharedLight(?)",
	"vehiclePart.lights.realLights.low.light(?)",
	"vehiclePart.lights.realLights.low.topLight(?)",
	"vehiclePart.lights.realLights.low.bottomLight(?)",
	"vehiclePart.lights.realLights.low.brakeLight(?)",
	"vehiclePart.lights.realLights.low.reverseLight(?)",
	"vehiclePart.lights.realLights.low.turnLightLeft(?)",
	"vehiclePart.lights.realLights.low.turnLightRight(?)",
	"vehiclePart.lights.realLights.low.interiorLight(?)",
	"vehiclePart.lights.realLights.high.light(?)",
	"vehiclePart.lights.realLights.high.topLight(?)",
	"vehiclePart.lights.realLights.high.bottomLight(?)",
	"vehiclePart.lights.realLights.high.brakeLight(?)",
	"vehiclePart.lights.realLights.high.reverseLight(?)",
	"vehiclePart.lights.realLights.high.turnLightLeft(?)",
	"vehiclePart.lights.realLights.high.turnLightRight(?)",
	"vehiclePart.lights.realLights.high.interiorLight(?)",
	"vehiclePart.lights.defaultLights.defaultLight(?)",
	"vehiclePart.lights.topLights.topLight(?)",
	"vehiclePart.lights.bottomLights.bottomLight(?)",
	"vehiclePart.lights.brakeLights.brakeLight(?)",
	"vehiclePart.lights.reverseLights.reverseLight(?)",
	"vehiclePart.lights.dayTimeLights.dayTimeLight(?)",
	"vehiclePart.lights.turnLights.turnLightLeft(?)",
	"vehiclePart.lights.turnLights.turnLightRight(?)",
}


function VehiclePart.initXMLSchema()
    local schema = VehiclePart.SCHEMA
    schema:setXMLSpecializationType("VehiclePart")

    schema:register(XMLValueType.STRING, "vehiclePart.base.filename", nil)
	schema:register(XMLValueType.NODE_INDEX, "vehiclePart.base.collisionComponents.component(?)#node", nil)

	Lights.registerRealLightSetupXMLPath(schema, "vehiclePart.lights.realLights.low")
    Lights.registerRealLightSetupXMLPath(schema, "vehiclePart.lights.realLights.high")

	SharedLight.registerXMLPaths(schema, "vehiclePart.lights.sharedLight(?)")
	schema:register(XMLValueType.INT, "vehiclePart.lights.sharedLight(?)#enableDirection", "Light is enabled when driving into this direction [-1, 1]")

	StaticLight.registerXMLPaths(schema, "vehiclePart.lights.defaultLights.defaultLight(?)")
    StaticLight.registerXMLPaths(schema, "vehiclePart.lights.topLights.topLight(?)")
    StaticLight.registerXMLPaths(schema, "vehiclePart.lights.bottomLights.bottomLight(?)")
    StaticLight.registerXMLPaths(schema, "vehiclePart.lights.brakeLights.brakeLight(?)")
    StaticLight.registerXMLPaths(schema, "vehiclePart.lights.reverseLights.reverseLight(?)")
    StaticLight.registerXMLPaths(schema, "vehiclePart.lights.dayTimeLights.dayTimeLight(?)")
    StaticLight.registerXMLPaths(schema, "vehiclePart.lights.turnLights.turnLightLeft(?)")
    StaticLight.registerXMLPaths(schema, "vehiclePart.lights.turnLights.turnLightRight(?)")

	StaticLightCompound.registerXMLPaths(schema, "vehiclePart.lights.staticLightCompounds.staticLightCompound(?)")

	BeaconLight.registerVehicleXMLPaths(schema, "vehiclePart.lights.beaconLights.beaconLight(?)")
    schema:register(XMLValueType.BOOL, "vehiclePart.lights.beaconLights.beaconLight(?)#alwaysActive", "Defines if the beacon light is always active while the vehicle is entered", false)

	for i, _ in ipairs(VehiclePart.ADDITIONAL_LIGHT_ATTRIBUTES_KEYS) do
        local key = VehiclePart.ADDITIONAL_LIGHT_ATTRIBUTES_KEYS[i]
        schema:register(XMLValueType.BOOL, key .. "#isTopLight", "Light is only active when switched to top light mode", false)
        schema:register(XMLValueType.BOOL, key .. "#isBottomLight", "Light is only active when not switched to top light mode", false)
    end

	-- Dynamic Mount Attacher
	schema:register(XMLValueType.NODE_INDEX, "vehiclePart.dynamicMountAttacher#node", "Attacher node")
	schema:register(XMLValueType.FLOAT, "vehiclePart.dynamicMountAttacher#forceLimitScale", "Force limit", 1)
	schema:register(XMLValueType.FLOAT, "vehiclePart.dynamicMountAttacher#timeToMount", "No movement time until mounting", 1000)
	schema:register(XMLValueType.INT, "vehiclePart.dynamicMountAttacher#numObjectBits", "Number of object bits to sync", 5)
	schema:register(XMLValueType.STRING, "vehiclePart.dynamicMountAttacher.grab#openMountType", "Open mount type", "TYPE_FORK")
	schema:register(XMLValueType.STRING, "vehiclePart.dynamicMountAttacher.grab#closedMountType", "Closed mount type", "TYPE_AUTO_ATTACH_XYZ")
	schema:register(XMLValueType.NODE_INDEX, "vehiclePart.dynamicMountAttacher.mountCollisionMask(?)#node", "Collision node")
	schema:register(XMLValueType.NODE_INDEX, "vehiclePart.dynamicMountAttacher.mountCollisionMask(?)#triggerNode", "Trigger node")
	schema:register(XMLValueType.STRING, "vehiclePart.dynamicMountAttacher.mountCollisionMask(?)#mountType", "Mount type name", "FORK")
	schema:register(XMLValueType.FLOAT, "vehiclePart.dynamicMountAttacher.mountCollisionMask(?)#forceLimitScale", "Force limit", 1)
	schema:register(XMLValueType.INT, "vehiclePart.dynamicMountAttacher.mountCollisionMask(?)#collisionMask", "Collision mask while object mounted")
	schema:register(XMLValueType.NODE_INDEX, "vehiclePart.dynamicMountAttacher#triggerNode", "Trigger node")
	schema:register(XMLValueType.NODE_INDEX, "vehiclePart.dynamicMountAttacher#rootNode", "Root node")
	schema:register(XMLValueType.NODE_INDEX, "vehiclePart.dynamicMountAttacher#jointNode", "Joint node")
	schema:register(XMLValueType.FLOAT, "vehiclePart.dynamicMountAttacher#forceAcceleration", "Force acceleration", 30)
	schema:register(XMLValueType.STRING, "vehiclePart.dynamicMountAttacher#mountType", "Mount type", "TYPE_AUTO_ATTACH_XZ")
	schema:register(XMLValueType.BOOL, "vehiclePart.dynamicMountAttacher#transferMass", "If this is set to 'true' the mass of the object to mount is tranfered to our own component. This improves phyiscs stability", false)
	schema:register(XMLValueType.STRING, "vehiclePart.dynamicMountAttacher.lockPosition(?)#xmlFilename", "XML filename of vehicle to lock (needs to match only the end of the filename)")
	schema:register(XMLValueType.NODE_INDEX, "vehiclePart.dynamicMountAttacher.lockPosition(?)#jointNode", "Joint node (Representens the position of the other vehicles root node)")
	ObjectChangeUtil.registerObjectChangeXMLPaths(schema, "vehiclePart.dynamicMountAttacher.lockPosition(?)")
	schema:register(XMLValueType.STRING, "vehiclePart.dynamicMountAttacher.animation#name", "Animation name")
	schema:register(XMLValueType.FLOAT, "vehiclePart.dynamicMountAttacher.animation#speed", "Animation speed", 1)
	schema:register(XMLValueType.BOOL, "vehiclePart.dynamicMountAttacher#allowFoldingWhileMounted", "Folding is allowed while a object is mounted", true)
	schema:register(XMLValueType.BOOL, "vehiclePart.cylindered.movingTools.movingTool(?).dynamicMountAttacher#value", "Update dynamic mount attacher joints")
	schema:register(XMLValueType.BOOL, "vehiclePart.cylindered.movingParts.movingPart(?).dynamicMountAttacher#value", "Update dynamic mount attacher joints")

    I3DUtil.registerI3dMappingXMLPaths(schema, "vehiclePart")

    schema:setXMLSpecializationType()
end

function VehiclePart.new(xmlFilePath)
	local self = setmetatable({}, VehiclePart_mt)
	
	self.schema = VehiclePart.SCHEMA
	self.xmlFilePath = xmlFilePath
	self.xmlFile = XMLFile.loadIfExists("VehiclePartXML", xmlFilePath, self.schema)

	if self.xmlFile == nil then
		log:printError("Failed to load XML file: %s", xmlFilePath)
		return nil
	end

	return self
end

function VehiclePart:getXMLFile()
	return self.xmlFile
end

function VehiclePart:load(linkNodeIndex, vehicle)
	if self.xmlFile == nil then
		log:printError("XML file is not loaded")
		return
	end

	local i3dFilenamePart = self.xmlFile:getValue("vehiclePart.base.filename")

	local i3dFilename = nil
	if i3dFilenamePart ~= nil then
		i3dFilename = Utils.getFilename(i3dFilenamePart, VehiclePart.MOD_DIR)
	end

	if i3dFilename == nil then
		log:printError("Failed to load i3d file: %s", i3dFilename)
		return
	end

	local node, sharedLoadRequestId, failedReason = g_i3DManager:loadSharedI3DFile(i3dFilename, false, false)

    if failedReason ~= 0 then
        log:printError("Failed to load the PDA map. Error Id: " .. failedReason)
    end

	if node == nil or node == 0 then
		log:printError("Failed to load i3d file: %s", i3dFilename)
		return
	end

	local components = nil
	local mappings = nil
	if vehicle ~= nil then
		components = vehicle.components
		mappings = vehicle.i3dMappings
	end

	local linkNode = nil
	if linkNodeIndex ~= nil and components ~= nil and mappings ~= nil then
		linkNode = I3DUtil.indexToObject(components, linkNodeIndex, mappings)
	end

	if linkNode ~= nil then
		local object = getChildAt(node, 0)
		link(linkNode, object)
		self:addI3dMappingToRootVehicle(linkNodeIndex, components, mappings)
		self:addLightsToRootVehicle(vehicle)
		return
	else
		return node
	end
end

function VehiclePart:delete(vehicle)
	local spec = vehicle.spec_dynamicMountAttacher

	if vehicle.isServer and spec.dynamicMountedObjects ~= nil then
		for object, _ in pairs(spec.dynamicMountedObjects) do
			object:unmountDynamic()
		end
	end

	if spec.dynamicMountAttacherTrigger ~= nil then
		removeTrigger(spec.dynamicMountAttacherTrigger.triggerNode)
	end

	self.xmlFile:delete()
end

function VehiclePart:addI3dMappingToRootVehicle(rootPath, components, mappings, realNumComponents)
	local xmlFile = self.xmlFile
	xmlFile:iterate("vehiclePart.i3dMappings.i3dMapping", function (_, key)
		local id = xmlFile:getValue(key .. "#id")
		local node = xmlFile:getValue(key .. "#node")

		local index = tonumber(node:match("^(.-)>"))

		local formattedNode = rootPath .. "|" .. index .. "|" .. node:match(">(.*)")
		if formattedNode:sub(-1) == "|" then
			formattedNode = formattedNode:sub(1, -2)
		end

		if id ~= nil and formattedNode ~= nil then
			local nodeId, rootNode = I3DUtil.indexToObject(components, formattedNode, nil, realNumComponents)

			if nodeId ~= nil then
				mappings[id] = {
					nodeId = nodeId,
					rootNode = rootNode
				}
			else
				mappings[id] = formattedNode
			end
		end
	end)
end

function VehiclePart:addLightsToRootVehicle(vehicle)
	local spec_lights = vehicle.spec_lights

	if spec_lights ~= nil then

		-- BeaconLights
		self.xmlFile:iterate("vehiclePart.lights.beaconLights.beaconLight", function (_, key)
			if self.xmlFile:hasProperty(key.."#alwaysActive") then
				BeaconLight.loadFromVehicleXML(spec_lights.alwaysActiveBeaconLights, self.xmlFile, key, self)
			else
				BeaconLight.loadFromVehicleXML(spec_lights.beaconLights, self.xmlFile, key, vehicle)
			end
		end)
	
		-- RealLights
		local lowLights = {}
		local highLights = {}
		vehicle:loadRealLightSetup(self.xmlFile, "vehiclePart.lights.realLights.low", lowLights)
		vehicle:loadRealLightSetup(self.xmlFile, "vehiclePart.lights.realLights.high", highLights)

		vehicle:applyAdditionalActiveLightType(lowLights.topLights, spec_lights.additionalLightTypes.topLight)
		vehicle:applyAdditionalActiveLightType(lowLights.bottomLights, spec_lights.additionalLightTypes.bottomLight)
		vehicle:applyAdditionalActiveLightType(lowLights.brakeLights, spec_lights.additionalLightTypes.brakeLight)
		vehicle:applyAdditionalActiveLightType(lowLights.reverseLights, spec_lights.additionalLightTypes.reverseLight)
		vehicle:applyAdditionalActiveLightType(lowLights.turnLightsLeft, spec_lights.additionalLightTypes.turnLightLeft, true)
		vehicle:applyAdditionalActiveLightType(lowLights.turnLightsLeft, spec_lights.additionalLightTypes.turnLightAny, true)
		vehicle:applyAdditionalActiveLightType(lowLights.turnLightsRight, spec_lights.additionalLightTypes.turnLightRight, true)
		vehicle:applyAdditionalActiveLightType(lowLights.turnLightsRight, spec_lights.additionalLightTypes.turnLightAny, true)
		vehicle:applyAdditionalActiveLightType(lowLights.interiorLights, spec_lights.additionalLightTypes.interiorLight)
		for key, lightTable in pairs(lowLights) do
			for _, light in ipairs(lightTable) do
				light:finalize()
				table.insert(spec_lights.realLights.low[key], light)
			end
		end

		vehicle:applyAdditionalActiveLightType(highLights.topLights, spec_lights.additionalLightTypes.topLight)
		vehicle:applyAdditionalActiveLightType(highLights.bottomLights, spec_lights.additionalLightTypes.bottomLight)
		vehicle:applyAdditionalActiveLightType(highLights.brakeLights, spec_lights.additionalLightTypes.brakeLight)
		vehicle:applyAdditionalActiveLightType(highLights.reverseLights, spec_lights.additionalLightTypes.reverseLight)
		vehicle:applyAdditionalActiveLightType(highLights.turnLightsLeft, spec_lights.additionalLightTypes.turnLightLeft, true)
		vehicle:applyAdditionalActiveLightType(highLights.turnLightsLeft, spec_lights.additionalLightTypes.turnLightAny, true)
		vehicle:applyAdditionalActiveLightType(highLights.turnLightsRight, spec_lights.additionalLightTypes.turnLightRight, true)
		vehicle:applyAdditionalActiveLightType(highLights.turnLightsRight, spec_lights.additionalLightTypes.turnLightAny, true)
		vehicle:applyAdditionalActiveLightType(highLights.interiorLights, spec_lights.additionalLightTypes.interiorLight)
		for key, lightTable in pairs(highLights) do
			for _, light in ipairs(lightTable) do
				light:finalize()
				table.insert(spec_lights.realLights.high[key], light)
			end
		end

		for _, profile in pairs(spec_lights.realLights) do
			for _, lights in pairs(profile) do
				for _, realLight in ipairs(lights) do
					if realLight.lightTypes ~= nil then
						spec_lights.maxLightState = math.max(spec_lights.maxLightState, unpack(realLight.lightTypes))
					end
				end
			end
		end

		-- StaticLights
		local staticLights = {}
		staticLights.defaultLights = StaticLight.loadLightsFromXML(nil, self.xmlFile, "vehiclePart.lights.defaultLights.defaultLight", vehicle, vehicle.components, vehicle.i3dMappings, true)
		staticLights.topLights = StaticLight.loadLightsFromXML(nil, self.xmlFile, "vehiclePart.lights.topLights.topLight", vehicle, vehicle.components, vehicle.i3dMappings, false)
		staticLights.bottomLights = StaticLight.loadLightsFromXML(nil, self.xmlFile, "vehiclePart.lights.bottomLights.bottomLight", vehicle, vehicle.components, vehicle.i3dMappings, false)
		staticLights.brakeLights = StaticLight.loadLightsFromXML(nil, self.xmlFile, "vehiclePart.lights.brakeLights.brakeLight", vehicle, vehicle.components, vehicle.i3dMappings, false)
		staticLights.reverseLights = StaticLight.loadLightsFromXML(nil, self.xmlFile, "vehiclePart.lights.reverseLights.reverseLight", vehicle, vehicle.components, vehicle.i3dMappings, false)
		staticLights.dayTimeLights = StaticLight.loadLightsFromXML(nil, self.xmlFile, "vehiclePart.lights.dayTimeLights.dayTimeLight", vehicle, vehicle.components, vehicle.i3dMappings, false)
		staticLights.turnLightsLeft = StaticLight.loadLightsFromXML(nil, self.xmlFile, "vehiclePart.lights.turnLights.turnLightLeft", vehicle, vehicle.components, vehicle.i3dMappings, false)
		staticLights.turnLightsRight = StaticLight.loadLightsFromXML(nil, self.xmlFile, "vehiclePart.lights.turnLights.turnLightRight", vehicle, vehicle.components, vehicle.i3dMappings, false)

		vehicle:applyAdditionalActiveLightType(staticLights.topLights, spec_lights.additionalLightTypes.topLight)
		vehicle:applyAdditionalActiveLightType(staticLights.bottomLights, spec_lights.additionalLightTypes.bottomLight)
		vehicle:applyAdditionalActiveLightType(staticLights.brakeLights, spec_lights.additionalLightTypes.brakeLight)
		vehicle:applyAdditionalActiveLightType(staticLights.reverseLights, spec_lights.additionalLightTypes.reverseLight)
		vehicle:applyAdditionalActiveLightType(staticLights.turnLightsLeft, spec_lights.additionalLightTypes.turnLightLeft, true)
		vehicle:applyAdditionalActiveLightType(staticLights.turnLightsLeft, spec_lights.additionalLightTypes.turnLightAny, true)
		vehicle:applyAdditionalActiveLightType(staticLights.turnLightsRight, spec_lights.additionalLightTypes.turnLightRight, true)
		vehicle:applyAdditionalActiveLightType(staticLights.turnLightsRight, spec_lights.additionalLightTypes.turnLightAny, true)

		for key, lightTable in pairs(staticLights) do
			for _, light in ipairs(lightTable) do
				table.insert(spec_lights.staticLights[key], light)
			end
		end

		if vehicle:getIsInShowroom() then
			for _, staticLight in ipairs(spec_lights.staticLights.dayTimeLights) do
				staticLight:setState(true)
			end
		end

		-- StaticLightCompounds
		for _, compoundKey in self.xmlFile:iterator("vehiclePart.lights.staticLightCompounds.staticLightCompound") do
			local staticLightCompound = StaticLightCompound.new(vehicle)
			if staticLightCompound:loadFromXML(self.xmlFile, compoundKey, vehicle.components, vehicle.i3dMappings, vehicle) then
				table.insert(spec_lights.staticLightCompounds, staticLightCompound)
			end
		end

		-- SharedLights
		self.xmlFile:iterate("vehiclePart.lights.sharedLight", function (_, key)
			local sharedLight = SharedLight.new(vehicle, spec_lights.staticLights)
			VehiclePart.loadFromVehicleXML(sharedLight, self, key, VehiclePart.MOD_DIR, function(success)
				if success then
					table.insert(spec_lights.sharedLights, sharedLight)

					if sharedLight.staticLightCompound ~= nil then
						table.insert(spec_lights.staticLightCompounds, sharedLight.staticLightCompound)
					end
				end
			end)
		end)
	end
end

function VehiclePart.loadFromVehicleXML(self, vehiclePart, key, baseDirectory, callback)
    local xmlFile = vehiclePart.xmlFile
    local xmlFilename = xmlFile:getValue(key .. "#filename")
    if xmlFilename ~= nil then
        xmlFilename = Utils.getFilename(xmlFilename, baseDirectory)

        local linkNode = xmlFile:getValue(key .. "#linkNode", "0>", self.vehicle.components, self.vehicle.i3dMappings)
        if linkNode == nil then
            Logging.xmlWarning(xmlFile, "Missing light linkNode in '%s'!", key)
            return
        else
            local isReference, filename, runtimeLoaded = getReferenceInfo(linkNode)
            if isReference and runtimeLoaded then
                local xmlName = Utils.getFilenameInfo(xmlFilename, true)
                local i3dName = Utils.getFilenameInfo(filename, true)

                if xmlName ~= i3dName then
                    Logging.xmlWarning(xmlFile, "Shared light '%s' loading different file from XML compared to i3D. (XML: %s vs i3D: %s)", getName(linkNode), xmlName, i3dName)
                end

                Logging.xmlWarning(xmlFile, "Shared light link node '%s' is a runtime loaded reference. Please load functional lights via XML and non-functional (e.g. reflectors) as i3D reference, but not both!", getName(linkNode))
                return
            end

            if not getVisibility(linkNode) then
                Logging.xmlWarning(xmlFile, "Shared light link node '%s' is hidden!", getName(linkNode))
                return
            end
        end

        local rotationNodes = {}
        for _, rotKey in xmlFile:iterator(key .. ".rotationNode") do
            local name = xmlFile:getValue(rotKey .. "#name")
            if name ~= nil then
                rotationNodes[name] = xmlFile:getValue(rotKey .. "#rotation", nil, true)
            end
        end

        local lightTypes = xmlFile:getValue(key.."#lightTypes", nil, true)
        local excludedLightTypes = xmlFile:getValue(key.."#excludedLightTypes", nil, true)

        self.reverseLight = xmlFile:getValue(key.."#reverseLight", self.reverseLight)
        self.turnLightLeft = xmlFile:getValue(key.."#turnLightLeft", self.turnLightLeft)
        self.turnLightRight = xmlFile:getValue(key.."#turnLightRight", self.turnLightRight)

        self.functionMappingData = StaticLightCompound.loadFunctionMappingData(xmlFile, key)

        self.additionalAttributes = {}
        self.vehicle:loadAdditionalLightAttributesFromXML(xmlFile, key, self.additionalAttributes)

        self:setRotationNodes(rotationNodes)
        self:setLightTypes(lightTypes, excludedLightTypes)
        self:setCallback(function(success)
            callback(success, success and self or nil)
        end)

        self:loadFromXML(linkNode, xmlFilename, baseDirectory)
    end
end

function VehiclePart:loadDynamicMountAttacher(vehicle)
    local spec = vehicle.spec_dynamicMountAttacher
	if spec ~= nil then
		XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehiclePart.dynamicMountAttacher#index", "vehiclePart.dynamicMountAttacher#node")
		XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehiclePart.dynamicMountAttacher.mountCollisionMask", "vehiclePart.dynamicMountAttacher.fork")
		spec.dynamicMountAttacherNode = self.xmlFile:getValue("vehiclePart.dynamicMountAttacher#node", nil, vehicle.components, vehicle.i3dMappings)
		spec.dynamicMountAttacherForceLimitScale = self.xmlFile:getValue("vehiclePart.dynamicMountAttacher#forceLimitScale", 1)
		spec.dynamicMountAttacherTimeToMount = self.xmlFile:getValue("vehiclePart.dynamicMountAttacher#timeToMount", 1000)
		spec.numObjectBits = self.xmlFile:getValue("vehiclePart.dynamicMountAttacher#numObjectBits", 5)
		spec.maxNumObjectsToSend = 2 ^ spec.numObjectBits - 1
		if self.xmlFile:hasProperty("vehiclePart.dynamicMountAttacher.grab") then
			spec.dynamicMountAttacherGrab = {}
			vehicle:loadDynamicMountGrabFromXML(self.xmlFile, "vehiclePart.dynamicMountAttacher.grab", spec.dynamicMountAttacherGrab)
		end
		spec.pendingDynamicMountObjects = {}
		spec.lockPositions = spec.lockPositions or {}
		if vehicle.isServer then
			spec.forks = {}
			local triggerData = {
				["triggerNode"] = self.xmlFile:getValue("vehiclePart.dynamicMountAttacher#triggerNode", nil, vehicle.components, vehicle.i3dMappings),
				["rootNode"] = vehicle.components[1].node,
				["jointNode"] = self.xmlFile:getValue("vehiclePart.dynamicMountAttacher#jointNode", nil, vehicle.components, vehicle.i3dMappings)
			}
			if triggerData.triggerNode ~= nil and (triggerData.rootNode ~= nil and triggerData.jointNode ~= nil) then
				local collisionMask = getCollisionFilterMask(triggerData.triggerNode)
				if bitAND(collisionMask, CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE) > 0 then
					addTrigger(triggerData.triggerNode, "dynamicMountTriggerCallback", vehicle)
					triggerData.forceAcceleration = self.xmlFile:getValue("vehiclePart.dynamicMountAttacher#forceAcceleration", 30)
					local mountTypeStr = self.xmlFile:getValue("vehiclePart.dynamicMountAttacher#mountType", "TYPE_AUTO_ATTACH_XZ")
					triggerData.mountType = Utils.getNoNil(DynamicMountUtil[mountTypeStr], DynamicMountUtil.TYPE_AUTO_ATTACH_XZ)
					triggerData.currentMountType = triggerData.mountType
					triggerData.component = vehicle:getParentComponent(triggerData.triggerNode)
					spec.dynamicMountAttacherTrigger = triggerData
				else
					Logging.xmlWarning(self.xmlFile, "Dynamic Mount trigger has invalid collision filter mask, should have %s or %s!", CollisionFlag.getBitAndName(CollisionFlag.DYNAMIC_OBJECT), CollisionFlag.getBitAndName(CollisionFlag.VEHICLE))
				end
				if string.contains(string.lower(getName(triggerData.jointNode)), "cutter") and bitAND(collisionMask, CollisionFlag.VEHICLE) == 0 then
					Logging.xmlWarning(self.xmlFile, "Dynamic Mount trigger has invalid collision filter mask, should have %s for cutter trailers!", CollisionFlag.getBitAndName(CollisionFlag.VEHICLE))
				end
				g_currentMission:addNodeObject(triggerData.triggerNode, vehicle)
			end
			spec.transferMass = self.xmlFile:getValue("vehiclePart.dynamicMountAttacher#transferMass", false)
			self.xmlFile:iterate("vehiclePart.dynamicMountAttacher.lockPosition", function(_, lockKey)
				local lockPosition = {
					["xmlFilename"] = self.xmlFile:getValue(lockKey .. "#xmlFilename"),
					["jointNode"] = self.xmlFile:getValue(lockKey .. "#jointNode", nil, vehicle.components, vehicle.i3dMappings)
				}
				if lockPosition.xmlFilename == nil or lockPosition.jointNode == nil then
					Logging.xmlWarning(self.xmlFile, "Invalid lock position \'%s\'. Missing xmlFilename or jointNode!", lockKey)
				else
					lockPosition.xmlFilename = lockPosition.xmlFilename:gsub("$data", "data")
					lockPosition.objectChanges = {}
					lockPosition.configurations = {}
					ObjectChangeUtil.loadObjectChangeFromXML(self.xmlFile, lockKey, lockPosition.objectChanges, vehicle.components, vehicle)
					table.insert(spec.lockPositions, lockPosition)
				end
			end)
		end
		spec.animationName = self.xmlFile:getValue("vehiclePart.dynamicMountAttacher.animation#name")
		spec.animationSpeed = self.xmlFile:getValue("vehiclePart.dynamicMountAttacher.animation#speed", 1)
		if spec.animationName ~= nil then
			log:printError("Animations are not supported for VehiclePart dynamic mount attachers yet!")
		end
		spec.allowFoldingWhileMounted = self.xmlFile:getValue("vehiclePart.dynamicMountAttacher#allowFoldingWhileMounted", true)
		spec.dynamicMountedObjects = {}
		spec.dynamicMountedObjectsDirtyFlag = vehicle:getNextDirtyFlag()
	else
		log:printError("Vehicle " .. vehicle:getName() .. " does not support dynamic mount attachers!")
	end
end

VehiclePart.initXMLSchema()