--[[
AddConfiguration

Specialization for extended configurations and register new configurations.

Author:		Ifko[nator]
Date:		08.06.2023

Version:	v15.4

	   
History:	v1.0 @28.02.2017 - initial implementation in FS 17 - added possibility to change capacity via configuration --## FS17 (removed)
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v2.0 @25.03.2017 - added possibility to change rim and axis color via configuration --## FS17 and FS 19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v3.0 @21.07.2017 - added possibility to change fillable fill types and cutable fruit types via configuration --## FS17 (removed)
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v3.1 @13.12.2017 - a little bit code optimation --## FS17
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v3.2 @31.12.2017 - increased the limit from 64 to 134.217.728 configurations, i hope thats enough now! --## FS17
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v3.3 @04.03.2018 - the capacity value from the fillUnit in the xml file will now set to the new capacity to avoid fill the fill volume too fast or too slow --## FS17 (removed)
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v3.4 @04.07.2018 - added possibility to change the mass and possiblity to deactivate the fillable Spezi of an vehicle via configuration --## FS17 (removed)
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v3.9 BETA @07.01.2019 - first convert to FS 19, current only color configs working! --## FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v4.0 @10.01.2019 - added support for 'design' configurations and finish the convert but this is currentlly still an TEST Version! --## FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v4.2 @23.01.2019 - added support for default configurations and possibility to change the honk sound via configuration --## FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v4.5 @27.01.2019 - added possibility to change the external sound file for motor sounds (wish from Unguided) --## FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v4.6 @08.02.2019 - added possibility to change the beacon lights (wish from juli7250) --## FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v4.7 @10.02.2019 - added possibility to make any vehicle selectable (wish from Ahran Modding) --## FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v4.8 @12.02.2019 - fixed register of an exists configuration, script will stop now --## FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v4.9 @05.03.2019 - fixed issue that additonial wheels won't be colered with color changes and added the TAG 'getColorFromConfig' to get the color from given config --## FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v5.0 @17.06.2019 - fixed issue that th connectors from the additonial wheels won't be colered with color changes --## FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v5.1 @08.08.2019 - added compatibility with the FSIconGenerator from GIANTS, so it will not crash anymore. You can't select the new configurations in the generator --## FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v5.2 @23.08.2019 - added possibility to change the material in a new color configuration (thanks for the hint Unguided) --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v5.3 @24.08.2019 - increased again the limit from 64 to 134.217.728 configurations --## FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v5.5 @31.08.2019 - added compatibilty with some default configurations --## FS 19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v5.7 @02.09.2019 - added possibility to change the color from top arm and starfire and filename from the top arm --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v5.8 @08.09.2019 - added possibility to change the max forward speed and min forward gear ratio --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v5.9 @18.09.2019 - added possibility to change ikChains via configuration --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v6.0 @05.10.2019 - added support for strobe lights configuration --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v6.1 @11.10.2019 - a little bit code optimation --## FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v6.5 @16.10.2019 - added possibility to change the wheel filename --##FS19 (removed in v6.9 because this is not needed anymore!)
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v6.8 @04.11.2019 - added possibility to change the color from the screws of the wheels --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v6.9 @11.12.2019 - added the TAG 'getRimColorFromConfig' to get the color for the rims from given config --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v7.0 @29.12.2019 - removed possibility to set an configurationName with an 'Ö, Ä, Ü or ß' --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v7.5 @13.03.2020 - added support to change the collisions mask of root components and support for adding cameras --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v8.0 @18.03.2020 - added possibility to change the decal from the starfire --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v8.5 @21.03.2020 - added possibility to change the 'additional' and 'normal' charachter --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v8.6 @01.04.2020 - added possibility to change the typeDesc of an vehicle --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v8.7 @04.04.2020 - added support to color the hubs to any color configuration, like it is possible with 'baseColor' --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v8.8 @12.04.2020 - little script fix thanks to Unguided --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v8.9 @27.08.2020 - added possibility to scale the additional wheels and connectors --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v9.0 @13.09.2020 - added possibility to recolor beaconlights (2020 JD's from SiiD) --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v9.1 @29.09.2020 - added possibility to change the vehicle name --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v9.2 @17.10.2020 - added possibility to load connection hoses via configuration --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v9.3 @06.12.2020 - added possibility to deactivate suspension nodes via configuration --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v9.4 @26.12.2020 - added possibility to change the color from the pto --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v9.5 @27.12.2020 - added possibility to load movingParts and movingTools via configuration --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v9.6 @06.01.2021 - added possibility to change the vehicle brand name --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v9.7 @18.01.2021 - fixed loading of targets for connection hoses via configuration --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v9.8 @03.03.2021 - added possibility to change rotation of an object for an certain configuration --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v9.9 @08.03.2021 - added possibility to change the color of the agribumper via colorChanges --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v10.0 @01.08.2021 - fixed loading of local hoses for connection hoses via configuration --##FS19
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v11.0 ALPHA @10.12.2021 - first convert to FS 22! --## FS22
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v12.0 @28.02.2022 - fix the coloring screws of the hubs --## FS22
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v13.0 @05.04.2022 - many adjustments for FS 22 --## FS22
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v14.0 @03.05.2022 - added configurations info windows --## FS22
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v14.1 @01.06.2022 - added possibility for an configuration to switch between automatic and manual gears --## FS22
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v14.2 @06.10.2022 - fix loading of xml paths for color configurations --## FS22
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v14.3 @15.10.2022 - added possibility to change the wheel chock color --## FS22
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v14.4 @24.10.2022 - added possibility to remove wheels via configuration (wheelIndiciesToRemove) --## FS22
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v14.8 @26.10.2022 - added support for Patch 1.8.1.0 --## FS22
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v14.9 @30.10.2022 - added possibility to change color mat 3 separatly on wheel hubs --## FS22
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v15.0 @03.11.2022 - fixed loading connection hoses via configuration --## FS22
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v15.1 @24.11.2022 - added possibility to use "getColorFromConfig", "getColor2FromConfig" and "getDecalColorFromConfig" for the top arm color changes --## FS22
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v15.2 @08.12.2022 - minior fixes --## FS22
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v15.3 @24.01.2023 - added possibility to change the 'maxMass' value of an vehicle --## FS22
			--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
			v15.4 @06.06.2023 - added compatibility for patch 1.10.1.0 --## FS22
]]

AddConfiguration = {};

AddConfiguration.currentModDirectory = g_currentModDirectory;
AddConfiguration.currentModName = g_currentModName;
AddConfiguration.debugPriority = 0;
AddConfiguration.newConfigurations = {};
AddConfiguration.deprecatedModDescElements = {};

AddConfiguration.defaultChangeObjectsConfigurations = {
	"design",
	"design2",
	"design3",
	"design4",
	"design5",
	"design6",
	"design7",
	"design8",
	"vehicleType",
	"attacherJoint",
	"motor",
	"frontloader",
	"powerTakeOff",
	"trailer",
	"tensionBelts",
	"pipe",
	"inputAttacherJoint",
	"cover",
	"folding",
	"fillUnit",
	"fillVolume",
	"consumer",
	"differential",
	"ai",
	"wrappingAnimation",
	"multipleItemPurchaseAmount",
	"dischargeable",
	"plow",
	"roller",
	"treeSaplingType",
	"variableWorkWidth",
	"wheel",
	"workArea",
	"workMode",
};

AddConfiguration.defaultColorConfigurations = {
	"baseColor",
	"designColor",
	"rimColor",
	"baseMaterial",
	"designMaterial",
	"designMaterial2",
	"designMaterial3",
	"wrappingColor"
};

AddConfiguration.sounds = {
	"hydraulic",
	"attach",
	"detach"
};

local function printError(errorMessage, isWarning, isInfo)
	local prefix = "::ERROR:: ";

	if isWarning then
		prefix = "::WARNING:: ";
	elseif isInfo then
		prefix = "::INFO:: ";
	end;

	print(prefix .. "from the AddConfiguration.lua: " .. tostring(errorMessage));
end;

local function printDebug(debugMessage, priority, addString)
	if AddConfiguration.debugPriority >= priority then
		local prefix = "";

		if addString then
			prefix = "::DEBUG:: from the AddConfiguration.lua: ";
		end;

		setFileLogPrefixTimestamp(false);
		print(prefix .. tostring(debugMessage));
		setFileLogPrefixTimestamp(true);
	end;
end;

local function getSpecByName(self, specName, currentModName)
    local spec = self["spec_" .. Utils.getNoNil(currentModName, AddConfiguration.currentModName) .. "." .. specName];

	if spec ~= nil then
        return spec;
    end;

    return self["spec_" .. specName];
end;

if ConfigurationUtil.SEND_NUM_BITS ~= 12 then
	ConfigurationUtil.SEND_NUM_BITS = 12; --## = 4.096 possible configurations! I think, thats enough (2^12 = 4.096)!
	
	printError("Set max number of configurations to ".. g_i18n:formatNumber(2 ^ ConfigurationUtil.SEND_NUM_BITS), false, true);
end;

function AddConfiguration.prerequisitesPresent(specializations)
    return true;
end

function AddConfiguration.registerEventListeners(vehicleType)
	local functionNames = {
		"onPreLoad",
		"onLoad",
		"onPostLoad",
		"onUpdate",
		"onUpdateTick"
	};

	for _, functionName in ipairs(functionNames) do
		SpecializationUtil.registerEventListener(vehicleType, functionName, AddConfiguration);
	end;
end;

function AddConfiguration.registerFunctions(vehicleType)
	local functionNames = {
		"changeObjects",
		
		"initExtraOptions",
		"loadExtraOptions",
		
		"updateBeaconLightBlinking",
		"resetBeaconLightBlinking",
		
		"setColor",
		
		"getConfigurationKey",
		"getXMLPrefix",
		"getL10nText",
		"getIsColorConfiguration"
	};

	for _, functionName in ipairs(functionNames) do
		SpecializationUtil.registerFunction(vehicleType, functionName, AddConfiguration[functionName]);
	end;
end;

function AddConfiguration.registerOverwrittenFunctions(vehicleType)
	local overwrittenFunctionNames = {
		"getName",
		"getFullName",
		"onWheelHubI3DLoaded",
		"onWheelPartI3DLoaded",
		"onWheelChockI3DLoaded",
		"onAdditionalWheelConnectorI3DLoaded",
		"loadWheelsFromXML",
		"loadAttacherJointFromXML",
		"onTopArmI3DLoaded",
		"onDynamicallyPartI3DLoaded",
		"loadBeaconLightFromXML",
		"onPowerTakeOffI3DLoaded",
		"onCrawlerI3DLoaded",
		"loadAdditionalWheelConnectorFromXML",
	};

	for _, overwrittenFunctionName in ipairs(overwrittenFunctionNames) do
		SpecializationUtil.registerOverwrittenFunction(vehicleType, overwrittenFunctionName, AddConfiguration[overwrittenFunctionName]);
	end;
end;

function AddConfiguration.initSpecialization()
	local function checkDeprecatedModDescElements(modDesc, oldElement, newElement)
		local found = false;
		local deprecatedModDescElement = {};

		if getXMLString(modDesc, oldElement) ~= nil or hasXMLProperty(modDesc, oldElement) and not oldElement:find("#") then
			found = true;
		end;

		if found then
			deprecatedModDescElement.modDescWarning = string.format("'%s' is not supported anymore, use '%s' instead!", oldElement, newElement);

			table.insert(AddConfiguration.deprecatedModDescElements, deprecatedModDescElement);
		end;
	end;
	
	local modDesc = loadXMLFile("modDesc", AddConfiguration.currentModDirectory .. "modDesc.xml");
	local configurationNumber = 0;

	while true do
		local configurationKey = "modDesc.newConfigurations.newConfiguration(" .. tostring(configurationNumber) .. ")";

		if not hasXMLProperty(modDesc, configurationKey) then
			break;
		end;

		checkDeprecatedModDescElements(modDesc, configurationKey .. "#isColorConfig", configurationKey .. "#isColorConfiguration");
		checkDeprecatedModDescElements(modDesc, configurationKey .. "#configName", configurationKey .. "#configurationName");
		checkDeprecatedModDescElements(modDesc, configurationKey .. "#useTitleFromConfig", configurationKey .. "#useTitleFromConfiguration");
		
		local newConfiguration = {};

		newConfiguration.isColorConfiguration = Utils.getNoNil(getXMLBool(modDesc, configurationKey .. "#isColorConfiguration"), false);
		newConfiguration.name = Utils.getNoNil(getXMLString(modDesc, configurationKey .. "#configurationName"), "");
		newConfiguration.title = g_i18n:getText("configuration_" .. Utils.getNoNil(getXMLString(modDesc, configurationKey .. "#useTitleFromConfiguration"),  newConfiguration.name), AddConfiguration.currentModName);

		if newConfiguration.name ~= "" then 
			local function getIsValidConfigurationName(configurationName)
				if configurationName:find("%d") == 1 then
					printError("Invalid 'configurationName' '" .. newConfiguration.name .. "'! Characters allowed: (_, A-Z, a-z, 0-9). Characters not allowed: (Ö, Ä, Ü, ß). The first character must not be a digit! Skipping this configuration now!", false, false);

					return false;
				end;
				
				if configurationName:find("[^%w_]") ~= nil then
					printError("Invalid 'configurationName' '" .. newConfiguration.name .. "'! Characters allowed: (_, A-Z, a-z, 0-9). Characters not allowed: (Ö, Ä, Ü, ß). The first character must not be a digit! Skipping this configuration now!", false, false);

					return false;
				end;

				for _, defaultChangeObjectsConfiguration in pairs(AddConfiguration.defaultChangeObjectsConfigurations) do
					if defaultChangeObjectsConfiguration == configurationName then
						printError("The configuration '" .. configurationName .. "'! Already exists! Skipping this configuration now!", false, true);
					end;
				end;

				return true;
			end;

			if getIsValidConfigurationName(newConfiguration.name) then 
				if g_configurationManager.configurations[newConfiguration.name] == nil then	
					if newConfiguration.isColorConfiguration then	
						--## config to change color on vehicle

						printDebug("(newConfigurations) :: Register color configuration '" .. newConfiguration.name .. "' (title '" .. newConfiguration.title .. "') successfully.", 1, true);

						g_configurationManager:addConfigurationType(newConfiguration.name, newConfiguration.title, nil, nil, ConfigurationUtil.getConfigColorSingleItemLoad, ConfigurationUtil.getConfigColorPostLoad, ConfigurationUtil.SELECTOR_COLOR);
					else
						--## config to change objects on vehicle

						printDebug("(newConfigurations) :: Register multi option configuration '" .. newConfiguration.name .. "' (title '" .. newConfiguration.title .. "') successfully.", 1, true);

						g_configurationManager:addConfigurationType(newConfiguration.name, newConfiguration.title, nil, nil, nil, nil, ConfigurationUtil.SELECTOR_MULTIOPTION);
					end;
				end;

				table.insert(AddConfiguration.newConfigurations, newConfiguration);
			end;
		else
			printError("'configurationName' is empty! Skipping this configuration now!", false, false);
		end;

		configurationNumber = configurationNumber + 1;
	end;

	delete(modDesc);

	local schema = Vehicle.xmlSchema;

	schema:setXMLSpecializationType("AddConfiguration");

	for _, newConfiguration in pairs(AddConfiguration.newConfigurations) do
		local baseKey = "vehicle." .. newConfiguration.name .. "Configurations." .. newConfiguration.name .. "Configuration(?)";
		
		if newConfiguration.isColorConfiguration then
			ConfigurationUtil.registerColorConfigurationXMLPaths(schema, newConfiguration.name);
			AddConfiguration.registerMaterialConfigurationXMLPaths(schema,  "vehicle." .. newConfiguration.name .. "Configurations");
			AddConfiguration.registerColorConfigurationXMLPaths(schema, "vehicle." .. newConfiguration.name .. "Configurations.material(?)");
		else
			ObjectChangeUtil.registerObjectChangeXMLPaths(schema, baseKey);
			ConnectionHoses.registerConnectionHoseXMLPaths(schema, baseKey .. ".extraOption(?).connectionHoses");
			VehicleCharacter.registerCharacterXMLPaths(schema, baseKey .. ".extraOption(?).characterNode");
			AttacherJoints.registerAttacherJointXMLPaths(schema, baseKey .. ".extraOption(?).attacherJoints.attacherJoint(?)")
			Attachable.registerInputAttacherJointXMLPaths(schema, baseKey .. ".extraOption(?).attachable.inputAttacherJoints.inputAttacherJoint(?)")
			AddConfiguration.registerCharacterTargeNodeXMLPaths(schema, baseKey .. ".extraOption(?).characterTargetNodeModifier(?)");
			AddConfiguration.registerTransmissionXMLPaths(schema, baseKey .. ".extraOption(?)");
			
			for _, sound in pairs(AddConfiguration.sounds) do
				SoundManager.registerSampleXMLPaths(schema, baseKey .. ".extraOption(?).attacherJoints.sounds", sound);
			end;
		end;
	end;

	for _, defaultChangeObjectsConfiguration in pairs(AddConfiguration.defaultChangeObjectsConfigurations) do
		local baseKey = "vehicle." .. AddConfiguration:getXMLPrefix(defaultChangeObjectsConfiguration) .. "Configurations." .. defaultChangeObjectsConfiguration .. "Configuration(?).extraOption(?)";
		
		ConnectionHoses.registerConnectionHoseXMLPaths(schema, baseKey .. ".connectionHoses");
		VehicleCharacter.registerCharacterXMLPaths(schema, baseKey .. ".characterNode");
		AttacherJoints.registerAttacherJointXMLPaths(schema, baseKey .. ".attacherJoints.attacherJoint(?)")
		Attachable.registerInputAttacherJointXMLPaths(schema, baseKey .. ".attachable.inputAttacherJoints.inputAttacherJoint(?)")
		AddConfiguration.registerCharacterTargeNodeXMLPaths(schema, baseKey .. ".characterTargetNodeModifier(?)");
		AddConfiguration.registerTransmissionXMLPaths(schema, baseKey);

		for _, sound in pairs(AddConfiguration.sounds) do
			SoundManager.registerSampleXMLPaths(schema, baseKey .. ".attacherJoints.sounds", sound);
		end;
	end;
end;

function AddConfiguration.registerColorConfigurationXMLPaths(schema, basePath)
	schema:register(XMLValueType.STRING, basePath .. "#name", "Material name");
	schema:register(XMLValueType.STRING, basePath .. "#shaderParameter", "Material shader parameter");
	schema:register(XMLValueType.COLOR, basePath .. "#color", "Material color if it shouldn't be used from configuration");
	schema:register(XMLValueType.INT, basePath .. "#material", "Material id if it shouldn't be used from configuration");
	schema:register(XMLValueType.BOOL, basePath .. "#useContrastColor", "Use contrast color from configuration", false);
	schema:register(XMLValueType.FLOAT, basePath .. "#contrastThreshold", "Contrast color brightness threshold", 0.5);
end;

function AddConfiguration.registerMaterialConfigurationXMLPaths(schema, basePath)
	schema:register(XMLValueType.NODE_INDEX, basePath .. ".material(?)#node", "Material node");
	schema:register(XMLValueType.NODE_INDEX, basePath .. ".material(?)#refNode", "Material reference node");
	schema:register(XMLValueType.STRING, basePath .. ".material(?)#name", "Material name");
	schema:register(XMLValueType.STRING, basePath .. ".material(?)#shaderParameter", "Material shader parameter name");
	schema:register(XMLValueType.COLOR, basePath .. ".material(?)#color", "Color");
	schema:register(XMLValueType.INT, basePath .. ".material(?)#materialId", "Material id");
end;

function AddConfiguration.registerCharacterTargeNodeXMLPaths(schema, basePath)
	schema:register(XMLValueType.NODE_INDEX, basePath .. "#node", "Target node");
	schema:register(XMLValueType.STRING, basePath .. "#poseId", "Modifier pose id");
	schema:register(XMLValueType.NODE_INDEX, basePath .. ".state(?)#node", "State node");
	schema:register(XMLValueType.NODE_INDEX, basePath .. ".state(?)#referenceNode", "State is activated if this node moves or rotates");
	schema:register(XMLValueType.NODE_INDEX, basePath .. ".state(?)#directionReferenceNode", "State node is align to this node");
	schema:register(XMLValueType.STRING, basePath .. ".state(?)#poseId", "Pose id");
	schema:register(XMLValueType.FLOAT, basePath .. "#transitionTime", "Time between state changes", 0.1);
	schema:register(XMLValueType.FLOAT, basePath .. "#transitionIdleDelay", "State is changed after this delay", 0.5);
end;

function AddConfiguration.registerTransmissionXMLPaths(schema, baseKey)
	schema:register(XMLValueType.FLOAT, baseKey .. ".transmission#minForwardGearRatio", "Min. forward gear ratio");
	schema:register(XMLValueType.FLOAT, baseKey .. ".transmission#maxForwardGearRatio", "Max. forward gear ratio");
	schema:register(XMLValueType.FLOAT, baseKey .. ".transmission#minBackwardGearRatio", "Min. backward gear ratio");
	schema:register(XMLValueType.FLOAT, baseKey .. ".transmission#maxBackwardGearRatio", "Max. backward gear ratio");
	schema:register(XMLValueType.TIME, baseKey .. ".transmission#gearChangeTime", "Gear change time");
	schema:register(XMLValueType.TIME, baseKey .. ".transmission#autoGearChangeTime", "Auto gear change time");
	schema:register(XMLValueType.FLOAT, baseKey .. ".transmission#axleRatio", "Axle ratio", 1);
	schema:register(XMLValueType.FLOAT, baseKey .. ".transmission#startGearThreshold", "Adjusts which gear is used as start gear", VehicleMotor.GEAR_START_THRESHOLD);
	schema:register(XMLValueType.FLOAT, baseKey .. ".transmission.forwardGear(?)#gearRatio", "Gear ratio");
	schema:register(XMLValueType.FLOAT, baseKey .. ".transmission.forwardGear(?)#maxSpeed", "Gear ratio");
	schema:register(XMLValueType.BOOL, baseKey .. ".transmission.forwardGear(?)#defaultGear", "Gear ratio");
	schema:register(XMLValueType.STRING, baseKey .. ".transmission.forwardGear(?)#name", "Gear name to display");
	schema:register(XMLValueType.STRING, baseKey .. ".transmission.forwardGear(?)#reverseName", "Gear name to display (if reverse direction is active)");
	schema:register(XMLValueType.STRING, baseKey .. ".transmission.forwardGear(?)#actionName", "Input Action to select this gear", "SHIFT_GEAR_SELECT_X");
	schema:register(XMLValueType.FLOAT, baseKey .. ".transmission.backwardGear(?)#gearRatio", "Gear ratio");
	schema:register(XMLValueType.FLOAT, baseKey .. ".transmission.backwardGear(?)#maxSpeed", "Gear ratio");
	schema:register(XMLValueType.BOOL, baseKey .. ".transmission.backwardGear(?)#defaultGear", "Gear ratio");
	schema:register(XMLValueType.STRING, baseKey .. ".transmission.backwardGear(?)#name", "Gear name to display");
	schema:register(XMLValueType.STRING, baseKey .. ".transmission.backwardGear(?)#reverseName", "Gear name to display (if reverse direction is active)");
	schema:register(XMLValueType.STRING, baseKey .. ".transmission.backwardGear(?)#actionName", "Input Action to select this gear", "SHIFT_GEAR_SELECT_X");
	schema:register(XMLValueType.STRING, baseKey .. ".transmission.groups#type", "Type of groups (powershift/default)", "default");
	schema:register(XMLValueType.TIME, baseKey .. ".transmission.groups#changeTime", "Change time if default type", 0.5);
	schema:register(XMLValueType.FLOAT, baseKey .. ".transmission.groups.group(?)#ratio", "Ratio while stage active");
	schema:register(XMLValueType.BOOL, baseKey .. ".transmission.groups.group(?)#isDefault", "Is default stage", false);
	schema:register(XMLValueType.STRING, baseKey .. ".transmission.groups.group(?)#name", "Gear name to display");
	schema:register(XMLValueType.STRING, baseKey .. ".transmission.groups.group(?)#actionName", "Input Action to select this group", "SHIFT_GROUP_SELECT_X");
	schema:register(XMLValueType.BOOL, baseKey .. ".transmission.directionChange#useGroup", "Use group as reverse change", false);
	schema:register(XMLValueType.INT, baseKey .. ".transmission.directionChange#reverseGroupIndex", "Group will be activated while direction is changed", 1);
	schema:register(XMLValueType.BOOL, baseKey .. ".transmission.directionChange#useGear", "Use gear as reverse change", false);
	schema:register(XMLValueType.INT, baseKey .. ".transmission.directionChange#reverseGearIndex", "Gear will be activated while direction is changed", 1);
	schema:register(XMLValueType.TIME, baseKey .. ".transmission.directionChange#changeTime", "Direction change time", 0.5);
	schema:register(XMLValueType.BOOL, baseKey .. ".transmission.manualShift#gears", "Defines if gears can be shifted manually", true);
	schema:register(XMLValueType.BOOL, baseKey .. ".transmission.manualShift#groups", "Defines if groups can be shifted manually", true);
	schema:register(XMLValueType.L10N_STRING, baseKey .. ".transmission#name", "Name of transmission to display in the shop");
	schema:register(XMLValueType.STRING, baseKey .. ".transmission#param", "Parameter to insert in transmission name");
end;

function AddConfiguration:onPreLoad(savegame)
	for _, newConfiguration in pairs(AddConfiguration.newConfigurations) do
		if self.configurations[newConfiguration.name] ~= nil then
			if not newConfiguration.isColorConfiguration then	
				self:initExtraOptions(newConfiguration.name, self.configurations[newConfiguration.name], nil, false, false, false, true);
			end;
		end;
	end;

	for _, defaultChangeObjectsConfiguration in pairs(AddConfiguration.defaultChangeObjectsConfigurations) do
		if self.configurations[defaultChangeObjectsConfiguration] ~= nil then
			self:initExtraOptions(self:getXMLPrefix(defaultChangeObjectsConfiguration), self.configurations[defaultChangeObjectsConfiguration], defaultChangeObjectsConfiguration, false, false, false, true);
		end;
	end;
end;

function AddConfiguration:onLoad(savegame)
	for _, deprecatedModDescElement in pairs(AddConfiguration.deprecatedModDescElements) do
		printWarning("Warning: " .. tostring(deprecatedModDescElement.modDescWarning));
	end;
	
	local modDesc = loadXMLFile("modDesc", AddConfiguration.currentModDirectory .. "modDesc.xml");

	AddConfiguration.debugPriority = Utils.getNoNil(getXMLFloat(modDesc, "modDesc.newConfigurations#debugPriority"), AddConfiguration.debugPriority);
	
	delete(modDesc);
	
	for _, newConfiguration in pairs(AddConfiguration.newConfigurations) do
		if self.configurations[newConfiguration.name] ~= nil then	
			printDebug("(newConfigurations) :: Found configuration '" .. newConfiguration.name .. "' in vehicle '" .. self:getFullName() .. "'.", 1, true);

			if newConfiguration.isColorConfiguration then
				--## config to change color on vehicle
				
				self:setColor(newConfiguration.name, self.configurations[newConfiguration.name]);
				self:applyBaseMaterialConfiguration(self.xmlFile, newConfiguration.name, self.configurations[newConfiguration.name]);
				self:applyBaseMaterial();
			else
				--## config to change objects on vehicle
				
				self:changeObjects(newConfiguration.name, self.configurations[newConfiguration.name]);
			end;
		end;
	end;
	
	for _, defaultColorConfiguration in pairs(AddConfiguration.defaultColorConfigurations) do
		if self.configurations[defaultColorConfiguration] ~= nil then	
			--## config to change color on vehicle
			
			self:setColor(defaultColorConfiguration, self.configurations[defaultColorConfiguration]);
		end;
	end;

	for _, defaultChangeObjectsConfiguration in pairs(AddConfiguration.defaultChangeObjectsConfigurations) do
		if self.configurations[defaultChangeObjectsConfiguration] ~= nil then
			--## config to change objects on vehicle
			
			self:changeObjects(self:getXMLPrefix(defaultChangeObjectsConfiguration), self.configurations[defaultChangeObjectsConfiguration], defaultChangeObjectsConfiguration);
		end;
	end;

	self.hasFinishedFirstRun = false;

	local specLights = getSpecByName(self, "lights");

	if specLights ~= nil and #specLights.beaconLights > 0 then
		specLights.beaconLightsWasActive = false;
	end;

	for _, newConfiguration in pairs(AddConfiguration.newConfigurations) do
		if self.configurations[newConfiguration.name] ~= nil then
			if not newConfiguration.isColorConfiguration then	
				self:initExtraOptions(newConfiguration.name, self.configurations[newConfiguration.name], nil, false, true, false, false);
			end;
		end;
	end;

	for _, defaultChangeObjectsConfiguration in pairs(AddConfiguration.defaultChangeObjectsConfigurations) do
		if self.configurations[defaultChangeObjectsConfiguration] ~= nil then
			self:initExtraOptions(self:getXMLPrefix(defaultChangeObjectsConfiguration), self.configurations[defaultChangeObjectsConfiguration], defaultChangeObjectsConfiguration, false, true, false, false);
		end;
	end;

	local wheelConfigurationId = Utils.getNoNil(self.configurations.wheel, 1);
	local wheelConfigurationKey = string.format("vehicle.wheels.wheelConfigurations.wheelConfiguration(%d)", wheelConfigurationId - 1) .. ".wheels";

	for wheelNumber = 0, #self.spec_wheels.wheels do
		local additionalWheelNumber = 0;
		local wheelKey = wheelConfigurationKey .. string.format(".wheel(%d)", wheelNumber);
		
		while true do
			local additionalWheelKey = wheelKey .. string.format(".additionalWheel(%d)", additionalWheelNumber);
	
			if not self.xmlFile:hasProperty(additionalWheelKey) then
				break;
			end;
		
			local wheel = self.spec_wheels.wheels[wheelNumber + 1];
			
			if wheel.additionalWheels ~= nil then
				for _, additionalWheel in pairs(wheel.additionalWheels) do
					additionalWheel.scaleValue = Utils.getNoNil(string.getVectorN(getXMLString(self.xmlFile.handle, additionalWheelKey .. "#scale"), 3), nil);
					
					if additionalWheel.scaleValue ~= nil then
						setScale(additionalWheel.linkNode, additionalWheel.scaleValue[1], additionalWheel.scaleValue[2], additionalWheel.scaleValue[3]);
					end;
				end;
			end;

			additionalWheelNumber = additionalWheelNumber + 1;
		end;
	end;

	local specCover = getSpecByName(self, "cover");

	if specCover ~= nil then
		specCover.oldState = specCover.state;

		local coverConfigurationId = Utils.getNoNil(self.configurations.cover, 1);
		local coverconfigurationKey = string.format("vehicle.cover.coverConfigurations.coverConfiguration(%d)", coverConfigurationId - 1);
		
		specCover.openSimultaneously = Utils.getNoNil(getXMLBool(self.xmlFile.handle, coverconfigurationKey .. "#openSimultaneously"), false);
	end;
end;

function AddConfiguration:onPostLoad(savegame)
	for _, newConfiguration in pairs(AddConfiguration.newConfigurations) do
		if self.configurations[newConfiguration.name] ~= nil then
			if not newConfiguration.isColorConfiguration then	
				self:initExtraOptions(newConfiguration.name, self.configurations[newConfiguration.name], nil, false, false, true, false);
			end;
		end;
	end;

	for _, defaultChangeObjectsConfiguration in pairs(AddConfiguration.defaultChangeObjectsConfigurations) do
		if self.configurations[defaultChangeObjectsConfiguration] ~= nil then
			self:initExtraOptions(self:getXMLPrefix(defaultChangeObjectsConfiguration), self.configurations[defaultChangeObjectsConfiguration], defaultChangeObjectsConfiguration, false, false, true, false);
		end;
	end;
end;

function AddConfiguration:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
	if not self.hasFinishedFirstRun then
		for _, newConfiguration in pairs(AddConfiguration.newConfigurations) do
			if self.configurations[newConfiguration.name] ~= nil then
				if not newConfiguration.isColorConfiguration then	
					self:initExtraOptions(newConfiguration.name, self.configurations[newConfiguration.name], nil, true, false, false, false);
				end;
			end;
		end;

		for _, defaultChangeObjectsConfiguration in pairs(AddConfiguration.defaultChangeObjectsConfigurations) do
			if self.configurations[defaultChangeObjectsConfiguration] ~= nil then
				self:initExtraOptions(self:getXMLPrefix(defaultChangeObjectsConfiguration), self.configurations[defaultChangeObjectsConfiguration], defaultChangeObjectsConfiguration, true, false, false, false);
			end;
		end;

		--## fix floating tires after loading ...
		for _, wheel in ipairs(self.spec_wheels.wheels) do
			WheelsUtil.updateWheelGraphics(self, wheel, dt);
		end;

		self.hasFinishedFirstRun = true;
	end;

	local specCropSensor = getSpecByName(self, "cropSensor");
			
	if specCropSensor ~= nil and specCropSensor.sensorLinkNodeData ~= nil and specCropSensor.sensorLinkNodeData.linkNodes ~= nil and not specCropSensor.hasUpdatedColors then
		if self.getConfigurationKey ~= nil and self.getXMLPrefix ~= nil then
			for configurationName, _ in pairs(self.configurations) do
				local configurationKey = self:getConfigurationKey(self:getXMLPrefix(configurationName), self.configurations[configurationName], configurationName);
				local cropSensorColorStr = getXMLString(self.xmlFile.handle, configurationKey .. ".colorChanges.cropSensor#color");
				local materialId = getXMLInt(self.xmlFile.handle, configurationKey .. ".colorChanges.cropSensor#materialId");

				if cropSensorColorStr ~= nil then
					local cropSensorColor = g_brandColorManager:getBrandColorByName(cropSensorColorStr);
					
					if cropSensorColor == nil then
						cropSensorColor = string.getVectorN(cropSensorColorStr, 3);
					end;

					if cropSensorColor ~= nil then
						for _, linkNode in pairs(specCropSensor.sensorLinkNodeData.linkNodes) do
							I3DUtil.setShaderParameterRec(linkNode.node, "colorMat0", cropSensorColor[1], cropSensorColor[2], cropSensorColor[3], Utils.getNoNil(materialId, Utils.getNoNil(cropSensorColor[4], 1)));
						end;
					end;
				end;
			end;
		end;

		specCropSensor.hasUpdatedColors = true;
	end;

	local specLights = getSpecByName(self, "lights");
	
	if specLights ~= nil and #specLights.beaconLights > 0 then
		if self.getIsMotorStarted ~= nil then
			if not self:getIsMotorStarted() then
				if specLights.beaconLightsActive then
					specLights.beaconLightsWasActive = true;

					self:setBeaconLightsVisibility(false, true, nil);
				end;
			else
				if specLights.beaconLightsWasActive then
					self:setBeaconLightsVisibility(true, true, nil);

					specLights.beaconLightsWasActive = false;
				end;
			end;
		end;

		if specLights.beaconLightsActive then
			for _, newConfiguration in pairs(AddConfiguration.newConfigurations) do
				if self.configurations[newConfiguration.name] ~= nil then
					if not newConfiguration.isColorConfiguration then
						local extraOptionNumber = 0;
						
						while true do
							local configurationKey = self:getConfigurationKey(newConfiguration.name, self.configurations[newConfiguration.name], nil) .. ".extraOption(" .. tostring(extraOptionNumber) .. ")";
							
							if self.xmlFile:hasProperty(configurationKey .. "#beaconLightBlinkingSequences") then
								local beaconLightBlinkingSequences = string.getVectorN(getXMLString(self.xmlFile.handle, configurationKey .. "#beaconLightBlinkingSequences"), 3);
								local beaconLightIndices = {string.getVector(getXMLString(self.xmlFile.handle, configurationKey .. "#beaconLightIndices"))};

								if #beaconLightIndices > 0 then
									for _, beaconLightIndice in pairs(beaconLightIndices) do
										self:updateBeaconLightBlinking(beaconLightBlinkingSequences, beaconLightIndice + 1, specLights.beaconLights);
									end;
								else
									self:updateBeaconLightBlinking(beaconLightBlinkingSequences, extraOptionNumber + 1, specLights.beaconLights);
								end;
							else
								break;
							end;
							
							extraOptionNumber = extraOptionNumber + 1;
						end;
					end;
				end;
			end;

			for _, defaultChangeObjectsConfiguration in pairs(AddConfiguration.defaultChangeObjectsConfigurations) do
				if self.configurations[defaultChangeObjectsConfiguration] ~= nil then
					local extraOptionNumber = 0;
					
					while true do
						local configurationKey = self:getConfigurationKey(self:getXMLPrefix(defaultChangeObjectsConfiguration), self.configurations[defaultChangeObjectsConfiguration], defaultChangeObjectsConfiguration) .. ".extraOption(" .. tostring(extraOptionNumber) .. ")";
						
						if not self.xmlFile:hasProperty(configurationKey) then
							break;
						end;

						if self.xmlFile:hasProperty(configurationKey .. "#beaconLightBlinkingSequences") then
							local beaconLightBlinkingSequences = string.getVectorN(getXMLString(self.xmlFile.handle, configurationKey .. "#beaconLightBlinkingSequences"), 3);
							local beaconLightIndices = {string.getVector(getXMLString(self.xmlFile.handle, configurationKey .. "#beaconLightIndices"))};
									
							if #beaconLightIndices > 0 then
								for _, beaconLightIndice in pairs(beaconLightIndices) do
									self:updateBeaconLightBlinking(beaconLightBlinkingSequences, beaconLightIndice + 1, specLights.beaconLights);
								end;
							else
								self:updateBeaconLightBlinking(beaconLightBlinkingSequences, extraOptionNumber + 1, specLights.beaconLights);
							end;
						end;
						
						extraOptionNumber = extraOptionNumber + 1;
					end;
				end;
			end;

			specLights.hasResetBeaconLightBlinking = false;
		else
			if not specLights.hasResetBeaconLightBlinking then	
				self:resetBeaconLightBlinking(specLights.beaconLights);

				specLights.hasResetBeaconLightBlinking = true;
			end;
		end;
	end;
end;

function AddConfiguration:onUpdateTick(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
	local specCover = getSpecByName(self, "cover");

	if specCover ~= nil and specCover.oldState ~= specCover.state then
		specCover.oldState = specCover.state;

		if specCover.openSimultaneously then
			local specAttacherJoints = self.spec_attacherJoints;
			local state = specCover.state;
			local attacherVehicle = self:getAttacherVehicle();

			if specAttacherJoints ~= nil then
				if attacherVehicle ~= nil then
					local specCover = attacherVehicle.spec_cover;
		
					if specCover ~= nil then
						if attacherVehicle:getIsNextCoverStateAllowed(state) then
							attacherVehicle:setCoverState(state);

							specCover.isStateSetAutomatically = false;
						end;
					end;
				end;

				for _, attachedImplement in pairs(specAttacherJoints.attachedImplements) do
					local object = attachedImplement.object;
					local specCover = object.spec_cover;
		
					if specCover ~= nil then
						if object:getIsNextCoverStateAllowed(state) and specCover.oldState ~= state then
							object:setCoverState(state);

							specCover.isStateSetAutomatically = false;
						end;
					end;
				end;
			end;
		end;
	end;
end;

function AddConfiguration:updateBeaconLightBlinking(beaconLightBlinkingSequences, beaconLightIndice, beaconLights)
	for beaconLightNumber, beaconLight in pairs(beaconLights) do
		if beaconLightNumber == beaconLightIndice then	
			if beaconLight.realLightNode ~= nil and beaconLightBlinkingSequences ~= nil and #beaconLightBlinkingSequences == 3 then
				local interval, pause, speed = beaconLightBlinkingSequences[1], beaconLightBlinkingSequences[2], beaconLightBlinkingSequences[3];
				
				if interval ~= nil and pause ~= nil and speed ~= nil then
					local shaderTime = getShaderTimeSec();
					local alpha = MathUtil.clamp(math.sin(shaderTime * speed) - math.max(shaderTime * speed % ((interval * 2 + pause * 2) * math.pi) - (interval * 2 - 1) * math.pi, 0) + 0.2, 0, 1);

					local colorRed, colorGreen, colorBlue = beaconLight.defaultColor[1], beaconLight.defaultColor[2], beaconLight.defaultColor[3];
					
					setLightColor(beaconLight.realLightNode, colorRed * alpha, colorGreen * alpha, colorBlue * alpha);

					for realLightNumber = 0, getNumOfChildren(beaconLight.realLightNode) - 1 do
						setLightColor(getChildAt(beaconLight.realLightNode, realLightNumber), colorRed * alpha, colorGreen * alpha, colorBlue * alpha);
					end;

					if beaconLight.lightShaderNode ~= nil then
						local _, y, z, w = getShaderParameter(beaconLight.lightShaderNode, "lightControl");

						setShaderParameter(beaconLight.lightShaderNode, "lightControl", beaconLight.intensity * alpha, y, z, w, false);
					end;
				end;
			end;
		end;
	end;
end;

function AddConfiguration:resetBeaconLightBlinking(beaconLights)
	for _, beaconLight in pairs(beaconLights) do
		if beaconLight.realLightNode ~= nil then
			setVisibility(beaconLight.realLightNode, false);
		end;

		if beaconLight.lightNode ~= nil then
			setVisibility(beaconLight.lightNode, false);
		end;

		if beaconLight.lightShaderNode ~= nil then
			local _, y, z, w = getShaderParameter(beaconLight.lightShaderNode, "lightControl");
		
			setShaderParameter(beaconLight.lightShaderNode, "lightControl",  0, y, z, w, false);
		end;
	end;
end;

function AddConfiguration:getXMLPrefix(configurationName)
	local prefix = "";
	
	if configurationName == "motor" 
		or configurationName == "consumer" 
		or configurationName == "differential" 
	then
		prefix = "motorized.";

	elseif configurationName == "fillUnit" 
		or configurationName == "fillVolume"
		or configurationName == "trailer"
		or configurationName == "tensionBelts"
		or configurationName == "cover"
		or configurationName == "pipe"
		or configurationName == "ai"
		or configurationName == "dischargeable"
		or configurationName == "plow"
		or configurationName == "powerConsumer"
		or configurationName == "roller"
		or configurationName == "variableWorkWidth"
	then
		prefix = configurationName .. ".";

	elseif configurationName == "attacherJoint" 
		or configurationName == "powerTakeOff"
		or configurationName == "wheel"
		or configurationName == "workArea"
		or configurationName == "workMode"
	then
		prefix = configurationName .. "s.";

	elseif configurationName == "inputAttacherJoint" then
		prefix = "attachable.";

	elseif configurationName == "folding" then
		prefix = "foldable.";

	elseif configurationName == "wrappingAnimation" then
		prefix = "baleWrapper.";

	elseif configurationName == "treeSaplingType" then
		prefix = "treeSaplingPallet.";
	end;
	
	return prefix .. configurationName;
end;

function AddConfiguration:getConfigurationKey(configurationName, configurationId, secondConfigurationName)
	if secondConfigurationName == nil then
		secondConfigurationName = configurationName;
	end;

	local configurationKey = string.format("vehicle." .. configurationName .. "Configurations." .. secondConfigurationName .. "Configuration(%d)", configurationId - 1);

	if self.xmlFile == nil or not self.xmlFile:hasProperty(configurationKey) then
		--if self.xmlFile ~= nil and configurationName ~= "globalPositioningSystem" then
		--	printError("(getConfigurationKey) :: Invalid " .. configurationName .. " configuration " .. configurationId .. " (key = " .. configurationKey .. ")", true, false);
		--end;
        
		return "noKeyFound";
	end;

	return configurationKey;
end;

function AddConfiguration:getL10nText(text)
	if text ~= nil then
		if text:sub(1, 6) == "$l10n_" then
			text = g_i18n:getText(text:sub(7), AddConfiguration.currentModName);
		elseif g_i18n:hasText(text, AddConfiguration.currentModName) then
			text = g_i18n:getText(text, AddConfiguration.currentModName);
		end;
	end;

	return text;
end;

function AddConfiguration:getName(superFunc)
	local name = superFunc(self);
	local newVehicleName = nil;
	
	for _, newConfiguration in pairs(AddConfiguration.newConfigurations) do
		if self.configurations[newConfiguration.name] ~= nil then
			if not newConfiguration.isColorConfiguration then
				local extraOptionNumber = 0;
					
				while true do
					local extraOptionKey = self:getConfigurationKey(newConfiguration.name, self.configurations[newConfiguration.name], nil) .. ".extraOption(" .. tostring(extraOptionNumber) .. ")";

					if self.xmlFile == nil or not self.xmlFile:hasProperty(extraOptionKey) then
						break;
					end;

					newName = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. "#newName"), "");

					if newName ~= "" then
						newVehicleName = self:getL10nText(newName);

						break;
					end;

					extraOptionNumber = extraOptionNumber + 1;
				end;
			end;
		end;
	end;
		
	if newVehicleName == nil then
		for _, defaultChangeObjectsConfiguration in pairs(AddConfiguration.defaultChangeObjectsConfigurations) do
			if self.configurations[defaultChangeObjectsConfiguration] ~= nil then
				local extraOptionNumber = 0;

				while true do
					local extraOptionKey = self:getConfigurationKey(self:getXMLPrefix(defaultChangeObjectsConfiguration), self.configurations[defaultChangeObjectsConfiguration], defaultChangeObjectsConfiguration) .. ".extraOption(" .. tostring(extraOptionNumber) .. ")";
						
					if self.xmlFile == nil or not self.xmlFile:hasProperty(extraOptionKey) then
						break;
					end;

					newName = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. "#newName"), "");
						
					if newName ~= "" then
						newVehicleName = self:getL10nText(newName);

						break;
					end;

					extraOptionNumber = extraOptionNumber + 1;
				end;
			end;
		end;
	end;
			
	if newVehicleName ~= nil then
		name = newVehicleName;
	end;

	return name;
end

function AddConfiguration:getFullName(superFunc)
	local newVehicleBrandName = nil;
	
	for _, newConfiguration in pairs(AddConfiguration.newConfigurations) do
		if self.configurations[newConfiguration.name] ~= nil then
			if not newConfiguration.isColorConfiguration then
				local extraOptionNumber = 0;
					
				while true do
					local extraOptionKey = self:getConfigurationKey(newConfiguration.name, self.configurations[newConfiguration.name], nil) .. ".extraOption(" .. tostring(extraOptionNumber) .. ")";

					if not self.xmlFile:hasProperty(extraOptionKey) then
						break;
					end;

					newBrandName = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. "#newBrandName"), "");

					if newBrandName ~= "" then
						newVehicleBrandName = self:getL10nText(newBrandName);

						break;
					end;

					extraOptionNumber = extraOptionNumber + 1;
				end;
			end;
		end;
	end;

	if newVehicleBrandName == nil then
		for _, defaultChangeObjectsConfiguration in pairs(AddConfiguration.defaultChangeObjectsConfigurations) do
			if self.configurations[defaultChangeObjectsConfiguration] ~= nil then
				local extraOptionNumber = 0;

				while true do
					local extraOptionKey = self:getConfigurationKey(self:getXMLPrefix(defaultChangeObjectsConfiguration), self.configurations[defaultChangeObjectsConfiguration], defaultChangeObjectsConfiguration) .. ".extraOption(" .. tostring(extraOptionNumber) .. ")";
						
					if not self.xmlFile:hasProperty(extraOptionKey) then
						break;
					end;

					newBrandName = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. "#newBrandName"), "");
						
					if newBrandName ~= "" then
						newVehicleBrandName = self:getL10nText(newBrandName);

						break;
					end;

					extraOptionNumber = extraOptionNumber + 1;
				end;
			end;
		end;
	end;

	if newVehicleBrandName ~= nil then
		local name = self:getName();

		if self.getCurrentHelper ~= nil and self.getIsAIActive ~= nil and self:getIsAIActive() then
			name = name .. " (" .. g_i18n:getText("ui_helper") .. " " .. self:getCurrentHelper().name .. ")";
		end;

		name = newVehicleBrandName .. " " .. name;

		return name;
	else
		return superFunc(self);
	end;
end;

function AddConfiguration:getIsColorConfiguration(searchedConfig)
	local isColorConfiguration = false;
	
	for _, newConfiguration in pairs(AddConfiguration.newConfigurations) do
		if searchedConfig == newConfiguration.name then
			isColorConfiguration = newConfiguration.isColorConfiguration;

			break;
		end;
	end;

	for _, defaultColorConfiguration in pairs(AddConfiguration.defaultColorConfigurations) do
		if searchedConfig == defaultColorConfiguration then
			isColorConfiguration = true;

			break;
		end;
	end;

	return isColorConfiguration;
end;

function AddConfiguration:loadExtraOptions(configurationKey, extraOptionNeedUpdate, extraOptionOnLoad, extraOptionOnPostLoad, extraOptionOnPreLoad)
	if extraOptionOnPreLoad then
		--## change agribumper color
		local specAgriBumper = self.spec_agribumper;

		if specAgriBumper ~= nil then
			local agribumperColor = getXMLString(self.xmlFile.handle, configurationKey .. ".colorChanges.agribumper#color");
			local agribumperMaterialId = getXMLString(self.xmlFile.handle, configurationKey .. ".colorChanges.agribumper#materialId");
			local agribumperShaderParameterName = getXMLString(self.xmlFile.handle, configurationKey .. ".colorChanges.agribumper#shaderParameterName");
			local agribumperGetColorFromConfig = getXMLString(self.xmlFile.handle, configurationKey .. ".colorChanges.agribumper#getColorFromConfig");

			if agribumperGetColorFromConfig ~= nil then
				if self.configurations[agribumperGetColorFromConfig] ~= nil then
					local colorName;
	
					if self:getIsColorConfiguration(agribumperGetColorFromConfig) then
						colorName = ConfigurationUtil.getColorByConfigId(self, agribumperGetColorFromConfig, self.configurations[agribumperGetColorFromConfig]);
					else
						printError("(colorChanges) :: The configuration '" .. agribumperGetColorFromConfig .. "' is not an color configuration! Stop coloring agribumper!", false, true);
					end;
					
					if colorName ~= nil then
						setXMLString(self.xmlFile.handle, "vehicle.agribumper.params#color", colorName);
					end;

					printDebug("(colorChanges) :: Change agribumper color successfully in '" .. self:getFullName() .. "'.", 1, true);
				end;
			elseif agribumperColor ~= nil then
				setXMLString(self.xmlFile.handle, "vehicle.agribumper.params#color", agribumperColor);
					
				printDebug("(colorChanges) :: Change agribumper color successfully in '" .. self:getFullName() .. "'.", 1, true);
			end;

			if agribumperMaterialId ~= nil then
				setXMLString(self.xmlFile.handle, "vehicle.agribumper.params#materialId", agribumperMaterialId);
			end;

			if agribumperShaderParameterName ~= nil then
				setXMLString(self.xmlFile.handle, "vehicle.agribumper.params#shaderParameterName", agribumperShaderParameterName);
			end;
		end;
	end;
	
	local extraOptionNumber = 0;

	while true do
		local extraOptionKey = string.format(configurationKey .. ".extraOption(%d)", extraOptionNumber);

	   	if not self.xmlFile:hasProperty(extraOptionKey) then
		   break;
	   	end;

		if extraOptionNeedUpdate then   
			local coverFillUnit = Utils.getNoNil(getXMLInt(self.xmlFile.handle, extraOptionKey .. "#fillUnit"), 1);
			local setCoverOpen = getXMLBool(self.xmlFile.handle, extraOptionKey .. "#setCoverOpen");
			   
			if self.spec_cover ~= nil and self.spec_cover.hasCovers and setCoverOpen ~= nil then
				self:setCoverState(coverFillUnit, setCoverOpen);
 
				printDebug("(loadExtraOptions) :: Open cover state is '" .. tostring(setCoverOpen) .. "'." , 1, true);
			end;

		elseif extraOptionOnPostLoad then
			local maxComponentMass = Utils.getNoNil(getXMLInt(self.xmlFile.handle, extraOptionKey .. "#maxMass"), 0);

			if maxComponentMass > 0 then
				self.maxComponentMass = maxComponentMass / 1000;
			end;
		elseif extraOptionOnLoad then
			local newCollisionMask = Utils.getNoNil(getXMLInt(self.xmlFile.handle, extraOptionKey .. "#newCollisionMask"), -1);

			if newCollisionMask > -1 then
				local componentIndices = {string.getVector(getXMLString(self.xmlFile.handle, extraOptionKey .. "#componentIndices"))};

				if #componentIndices > 0 then
					for _, componentIndice in pairs(componentIndices) do
						if self.components[componentIndice].node ~= nil then	
							setCollisionMask(self.components[componentIndice].node, newCollisionMask);

							printDebug("(loadExtraOptions) :: Set collision mask to '" .. tostring(newCollisionMask) .. "' for index '" .. componentIndice .. ">'." , 1, true);
						end;
					end;
				end;
			end;

			local rotationActive = string.getRadians(getXMLString(self.xmlFile.handle, extraOptionKey .. "#rotationActive"), 3);

			if rotationActive ~= nil then
				local node = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile.handle, extraOptionKey .. "#node"), self.i3dMappings);

				if node ~= nil then
					local configurationName = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. "#configurationName"), "");
					local activeConfigurations = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. "#activeConfigurations"), "");
					
					if configurationName ~= "" and activeConfigurations ~= "" then
						local storeItem = g_storeManager:getItemByXMLFilename(self.configFileName);
					
						if storeItem ~= nil and storeItem.configurations ~= nil and storeItem.configurations[configurationName] ~= nil then
							local activeConfigurations = string.split(activeConfigurations, " ");
							local configurations = storeItem.configurations[configurationName];
							local configuration = configurations[self.configurations[configurationName]];
						
							for _, activeConfiguration in pairs(activeConfigurations) do
								activeConfiguration = self:getL10nText(activeConfiguration);

								if configuration.name == activeConfiguration then	
									setRotation(node, rotationActive[1], rotationActive[2], rotationActive[3]);
									
									printDebug("(extraOptions) :: Set Rotation for node " .. getXMLString(self.xmlFile.handle, extraOptionKey .. "#node") .. " to " .. math.deg(rotationActive[1]) .. ", " .. math.deg(rotationActive[2]) .. ", " .. math.deg(rotationActive[3]) .. ".", 1, true);
								
									break;
								end;
							end;
						end;
					end;
				end;
			end;

			local specWheels = self.spec_wheels;

			if specWheels ~= nil then
				local fenderRotMax = getXMLFloat(self.xmlFile.handle, extraOptionKey .. "#fenderRotMax");
				local fenderRotMin = getXMLFloat(self.xmlFile.handle, extraOptionKey .. "#fenderRotMin");
				local tireIsInverted = getXMLBool(self.xmlFile.handle, extraOptionKey .. "#tireIsInverted");

				for _, wheel in pairs(specWheels.wheels) do
					if fenderRotMax ~= nil and fenderRotMin ~= nil then
						local oldFenderRotMax = getXMLFloat(self.xmlFile.handle, extraOptionKey .. "#oldFenderRotMax");
						local oldFenderRotMin = getXMLFloat(self.xmlFile.handle, extraOptionKey .. "#oldFenderRotMin");

						if oldFenderRotMax ~= nil and oldFenderRotMin ~= nil then	
							for _, fender in pairs(wheel.fenders) do
								local origRotMax = math.floor(math.deg(fender.rotMax));
								local origRotMin = math.floor(math.deg(fender.rotMin));

								if origRotMax == oldFenderRotMax then
									fender.rotMax = math.rad(fenderRotMax);

									printDebug("(loadExtraOptions) :: Set fender rot max to '" .. tostring(math.deg(fender.rotMax)) .. "' (old value '" .. oldFenderRotMax .. "') in " .. self:getFullName() .. "." , 1, true);
								end;

								if origRotMin == oldFenderRotMin then
									fender.rotMin = math.rad(fenderRotMin);

									printDebug("(loadExtraOptions) :: Set fender rot min to '" .. tostring(math.deg(fender.rotMin)) .. "' (old value '" .. oldFenderRotMin .. "') in " .. self:getFullName() .. "." , 1, true);
								end;
							end;
						end;
					end;

					if tireIsInverted ~= nil then
						local repr = getXMLString(self.xmlFile.handle, extraOptionKey .. "#wheelRepr");

						for i3dMappingName, i3dMapping in pairs(self.i3dMappings) do
							if i3dMappingName:upper() == repr:upper() then
								if wheel.repr == i3dMapping.nodeId then
									wheel.tireIsInverted = tireIsInverted;
								end;
							end;
						end;
					end;
				end;
			end;

			if self.spec_connectionHoses ~= nil then
				local specConnectionHoses = self.spec_connectionHoses;

				if self.xmlFile:hasProperty(extraOptionKey .. ".connectionHoses") then
					self:addHoseTargetNodes(self.xmlFile, extraOptionKey .. ".connectionHoses.target");
				end;

				local skipNodeNumber = 0;

				while true do
					local skipNodeKey = extraOptionKey .. ".connectionHoses.skipNode(" .. tostring(skipNodeNumber) .. ")";
				
					if not self.xmlFile:hasProperty(skipNodeKey) then
						break;
					end;
				
					local entry = {}
				
					if self:loadHoseSkipNode(self.xmlFile, skipNodeKey, entry) then
						table.insert(specConnectionHoses.hoseSkipNodes, entry);
					
						if specConnectionHoses.hoseSkipNodeByType[entry.type] == nil then
							specConnectionHoses.hoseSkipNodeByType[entry.type] = {};
						end
					
						table.insert(specConnectionHoses.hoseSkipNodeByType[entry.type], entry);
					end
				
					skipNodeNumber = skipNodeNumber + 1;
				end;

				local toolConnectorHoseNumber = 0;

				while true do
					local toolConnectorHoseKey = extraOptionKey .. ".connectionHoses.toolConnectorHose(" .. tostring(toolConnectorHoseNumber) .. ")";
				
					if not self.xmlFile:hasProperty(toolConnectorHoseKey) then
						break;
					end;
				
					local entry = {};
				
					if self:loadToolConnectorHoseNode(self.xmlFile, toolConnectorHoseKey, entry) then
						table.insert(specConnectionHoses.toolConnectorHoses, entry);
					
						specConnectionHoses.targetNodeToToolConnection[entry.startTargetNodeIndex] = entry;
						specConnectionHoses.targetNodeToToolConnection[entry.endTargetNodeIndex] = entry;
					end;
				
					toolConnectorHoseNumber = toolConnectorHoseNumber + 1;
				end;

				local hoseNumber = 0;

				while true do
					local hoseKey = extraOptionKey .. ".connectionHoses.hose(" .. tostring(hoseNumber) .. ")";
				
					if not self.xmlFile:hasProperty(hoseKey) then
						break;
					end;
				
					local entry = {};
				
					if self:loadHoseNode(self.xmlFile, hoseKey, entry, true) then
						table.insert(specConnectionHoses.hoseNodes, entry);
					
						entry.index = #specConnectionHoses.hoseNodes;
					
						for _, index in pairs(entry.inputAttacherJointIndices) do
							if specConnectionHoses.hoseNodesByInputAttacher[index] == nil then
								specConnectionHoses.hoseNodesByInputAttacher[index] = {};
							end;
						
							table.insert(specConnectionHoses.hoseNodesByInputAttacher[index], entry);
						end;
					end;
				
					hoseNumber = hoseNumber + 1;
				end;

				local localHoseNumber = 0;

				while true do
					local hoseKey = extraOptionKey .. ".connectionHoses.localHose(" .. tostring(localHoseNumber) .. ")";
				
					if not self.xmlFile:hasProperty(hoseKey) then
						break;
					end;
				
					local hose = {};
				
					if self:loadHoseNode(self.xmlFile, hoseKey .. ".hose", hose) then
						local target = {};
					
						if self:loadHoseTargetNode(self.xmlFile, hoseKey .. ".target", target) then
							table.insert(specConnectionHoses.localHoseNodes, {
								hose = hose,
								target = target
							});
						end;
					end;
				
					localHoseNumber = localHoseNumber + 1;
				end;

				for _, localHoseNode in ipairs(specConnectionHoses.localHoseNodes) do
					self:connectHose(localHoseNode.hose, self, localHoseNode.target, false);
				end;
			end;

			if self.spec_attachable ~= nil then
				local specAttachable = self.spec_attachable;

				if self.xmlFile:hasProperty(extraOptionKey .. ".attachable.inputAttacherJoints.inputAttacherJoint") then
					local inputAttacherJoint = {};

					if self:loadInputAttacherJoint(self.xmlFile, extraOptionKey .. ".attachable.inputAttacherJoints.inputAttacherJoint", inputAttacherJoint, extraOptionNumber - 1) then
						table.insert(specAttachable.inputAttacherJoints, inputAttacherJoint);
					
						inputAttacherJoint.jointInfo = g_currentMission:registerInputAttacherJoint(self, #specAttachable.inputAttacherJoints, inputAttacherJoint);
					end;
				end;
			end;

			if self.spec_attacherJoints ~= nil then
				local specAttacherJoints = self.spec_attacherJoints;
				
				if self.xmlFile:hasProperty(extraOptionKey .. ".attacherJoints.attacherJoint") then
					local attacherJointNumber = 0;

					while true do
						local attacherJointKey = extraOptionKey .. string.format(".attacherJoints.attacherJoint(%d)", attacherJointNumber);
					
						if not self.xmlFile:hasProperty(attacherJointKey) then
							break;
						end;
					
						local attacherJoint = {};
					
						if self:loadAttacherJointFromXML(attacherJoint, self.xmlFile, attacherJointKey, attacherJointNumber) then
							table.insert(specAttacherJoints.attacherJoints, attacherJoint);
						
							attacherJoint.index = #specAttacherJoints.attacherJoints;
						end;
					
						attacherJointNumber = attacherJointNumber + 1;
					end;
				end;
			end;
		elseif extraOptionOnPreLoad then
			local honkSoundTemplate = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. "#honkSoundTemplate"), "");
			local honkSoundFilename = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. "#honkSoundFilename"), "");
			local honkSoundLinkNode = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. "#honkSoundLinkNode"), "");
			local honkSoundOuterRadius = Utils.getNoNil(getXMLFloat(self.xmlFile.handle, extraOptionKey .. "#honkSoundOuterRadius"), "");
			local honkSoundInnerRadius = Utils.getNoNil(getXMLFloat(self.xmlFile.handle, extraOptionKey .. "#honkSoundInnerRadius"), "");
			local externalMotorSoundFile = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. "#externalMotorSoundFile"), "");
			local supportAnimationName = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. "#supportAnimationName"), "");
			local changeConfigurationName = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. "#changeConfigurationName"), "");
			local maxForwardSpeed = Utils.getNoNil(getXMLFloat(self.xmlFile.handle, extraOptionKey .. "#maxForwardSpeed"), "");
			local ikChain = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. "#ikChain"), "");
			local addCamera = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. "#addCameraNode"), "");
			local additionalCharacterNode = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. ".additionalCharacter#node"), "");
			local characterNode = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. ".character#node"), "");
			local newTypeDesc = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. "#newTypeDesc"), "");
			local deactivateSuspension = Utils.getNoNil(getXMLBool(self.xmlFile.handle, extraOptionKey .. "#deactivateSuspension"), false);
			local suspensionNode = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. "#suspensionNode"), "");

			if deactivateSuspension and suspensionNode ~= "" then
				local suspensionNumber = 0;

				while true do
					local suspensionKey = "vehicle.suspensions.suspension(" .. tostring(suspensionNumber) .. ")";

					if not self.xmlFile:hasProperty(suspensionKey) then
						break;
					end;

					local suspensionName = Utils.getNoNil(getXMLString(self.xmlFile.handle, suspensionKey .. "#node"), "");

					if suspensionName ~= "" then
						if suspensionNode == suspensionName then
							setXMLString(self.xmlFile.handle, suspensionKey .. "#minRotation", "0 0 0"); 
							setXMLString(self.xmlFile.handle, suspensionKey .. "#maxRotation", "0 0 0");

							printDebug("(extraOptions) :: Deactivated suspension for node " .. suspensionName .. ".", 1, true);
						end;
					end;

					suspensionNumber = suspensionNumber + 1;
				end;
			end;

			if newTypeDesc ~= "" then
				setXMLString(self.xmlFile.handle, "vehicle.base.typeDesc", newTypeDesc);

				printDebug("(extraOptions) :: Set 'typeDesc' to " .. newTypeDesc .. ".", 1, true);
			end;

			if characterNode ~= "" then
				setXMLString(self.xmlFile.handle, "vehicle.enterable.character#node", characterNode);

				printDebug("(extraOptions) :: Set character 'node' to " .. characterNode .. ".", 1, true);

				local foldMinLimit = Utils.getNoNil(getXMLFloat(self.xmlFile.handle, extraOptionKey .. ".character#foldMinLimit"), "");
				local foldMaxLimit = Utils.getNoNil(getXMLFloat(self.xmlFile.handle, extraOptionKey .. ".character#foldMaxLimit"), "");
				local cameraMinDistance = Utils.getNoNil(getXMLFloat(self.xmlFile.handle, extraOptionKey .. ".character#foldMaxLimit"), "");
				local spineRotation = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. ".character#spineRotation"), "");

				if foldMinLimit ~= "" then
					setXMLFloat(self.xmlFile.handle, "vehicle.enterable.character#foldMinLimit", foldMinLimit);

					printDebug("(extraOptions) :: Set character 'foldMinLimit' to " .. foldMinLimit .. ".", 1, true);
				end;

				if foldMaxLimit ~= "" then
					setXMLFloat(self.xmlFile.handle, "vehicle.enterable.character#foldMaxLimit", foldMaxLimit);

					printDebug("(extraOptions) :: Set character 'foldMaxLimit' to " .. foldMaxLimit .. ".", 1, true);
				end;

				if cameraMinDistance ~= "" then
					setXMLString(self.xmlFile.handle, "vehicle.enterable.character#cameraMinDistance", cameraMinDistance);

					printDebug("(extraOptions) :: Set character 'cameraMinDistance' to " .. cameraMinDistance .. ".", 1, true);
				end;

				if spineRotation ~= "" then
					setXMLString(self.xmlFile.handle, "vehicle.enterable.character#spineRotation", spineRotation);

					printDebug("(extraOptions) :: Set character 'spineRotation' to " .. spineRotation .. ".", 1, true);
				end;
				
				local targetNumber = 0;
				
				while true do
					local targetKey = extraOptionKey .. ".character.target(" .. tostring(targetNumber) .. ")";
					
					if not self.xmlFile:hasProperty(targetKey) then
						break;
					end;
					
					local ikChain = Utils.getNoNil(getXMLString(self.xmlFile.handle, targetKey .. "#ikChain"), "");
					local targetNode = Utils.getNoNil(getXMLString(self.xmlFile.handle, targetKey .. "#targetNode"), "");
					
					if ikChain ~= "" and targetNode ~= "" then
						setXMLString(self.xmlFile.handle, "vehicle.enterable.character.target(" .. tostring(targetNumber) .. ")#ikChain", ikChain);
						setXMLString(self.xmlFile.handle, "vehicle.enterable.character.target(" .. tostring(targetNumber) .. ")#targetNode", targetNode);
						
						printDebug("(extraOptions) :: Set character 'ikChain' to " .. ikChain .. ".", 1, true);
						printDebug("(extraOptions) :: Set character 'targetNode' to " .. targetNode .. ".", 1, true);
					else
						printError("(extraOptions) :: Missing 'ikChain' or 'targetNode' in '" .. targetKey .. "' Stop changeing character!", true, false);
					end;

					targetNumber = targetNumber + 1;
				end;
			end;

			if additionalCharacterNode ~= "" then
				setXMLString(self.xmlFile.handle, "vehicle.enterable.additionalCharacter#node", additionalCharacterNode);

				printDebug("(extraOptions) :: Set additional character 'node' to " .. additionalCharacterNode .. ".", 1, true);

				local foldMinLimit = Utils.getNoNil(getXMLFloat(self.xmlFile.handle, extraOptionKey .. ".additionalCharacter#foldMinLimit"), "");
				local foldMaxLimit = Utils.getNoNil(getXMLFloat(self.xmlFile.handle, extraOptionKey .. ".additionalCharacter#foldMaxLimit"), "");
				local cameraMinDistance = Utils.getNoNil(getXMLFloat(self.xmlFile.handle, extraOptionKey .. ".additionalCharacter#foldMaxLimit"), "");
				local spineRotation = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. ".additionalCharacter#spineRotation"), "");

				if foldMinLimit ~= "" then
					setXMLFloat(self.xmlFile.handle, "vehicle.enterable.additionalCharacter#foldMinLimit", foldMinLimit);

					printDebug("(extraOptions) :: Set additional character 'foldMinLimit' to " .. foldMinLimit .. ".", 1, true);
				end;

				if foldMaxLimit ~= "" then
					setXMLFloat(self.xmlFile.handle, "vehicle.enterable.additionalCharacter#foldMaxLimit", foldMaxLimit);

					printDebug("(extraOptions) :: Set additional character 'foldMaxLimit' to " .. foldMaxLimit .. ".", 1, true);
				end;

				if cameraMinDistance ~= "" then
					setXMLFloat(self.xmlFile.handle, "vehicle.enterable.additionalCharacter#cameraMinDistance", cameraMinDistance);

					printDebug("(extraOptions) :: Set additional character 'cameraMinDistance' to " .. cameraMinDistance .. ".", 1, true);
				end;

				if spineRotation ~= "" then
					setXMLString(self.xmlFile.handle, "vehicle.enterable.additionalCharacter#spineRotation", spineRotation);

					printDebug("(extraOptions) :: Set additional character 'spineRotation' to " .. spineRotation .. ".", 1, true);
				end;

				local targetNumber = 0;
				
				while true do
					local targetKey = extraOptionKey .. ".additionalCharacter.target(" .. tostring(targetNumber) .. ")";
					
					if not self.xmlFile:hasProperty(targetKey) then
						break;
					end;
					
					local ikChain = Utils.getNoNil(getXMLString(self.xmlFile.handle, targetKey .. "#ikChain"), "");
					local targetNode = Utils.getNoNil(getXMLString(self.xmlFile.handle, targetKey .. "#targetNode"), "");
					
					if ikChain ~= "" and targetNode ~= "" then
						setXMLString(self.xmlFile.handle, "vehicle.enterable.additionalCharacter.target(" .. tostring(targetNumber) .. ")#ikChain", ikChain);
						setXMLString(self.xmlFile.handle, "vehicle.enterable.additionalCharacter.target(" .. tostring(targetNumber) .. ")#targetNode", targetNode);
						
						printDebug("(extraOptions) :: Set additional character 'ikChain' to " .. ikChain .. ".", 1, true);
						printDebug("(extraOptions) :: Set additional character 'targetNode' to " .. targetNode .. ".", 1, true);
					else
						printError("(extraOptions) :: Missing 'ikChain' or 'targetNode' in '" .. targetKey .. "' Stop changeing additional character!", true, false);
					end;

					targetNumber = targetNumber + 1;
				end;
			end;

			if addCamera ~= "" then
				local transMin = Utils.getNoNil(getXMLFloat(self.xmlFile.handle, extraOptionKey .. "#cameraTransMin"), 0);
				local transMax = Utils.getNoNil(getXMLFloat(self.xmlFile.handle, extraOptionKey .. "#cameraTransMax"), 2);
				local rotMinX = Utils.getNoNil(getXMLFloat(self.xmlFile.handle, extraOptionKey .. "#rotMinX"), -1.1);
				local rotMaxX = Utils.getNoNil(getXMLFloat(self.xmlFile.handle, extraOptionKey .. "#rotMaxX"), 0.4);

				local useWorldXZRotation = Utils.getNoNil(getXMLBool(self.xmlFile.handle, extraOptionKey .. "#cameraUseWorldXZRotation"), false);
				local rotatable = Utils.getNoNil(getXMLBool(self.xmlFile.handle, extraOptionKey .. "#rotatable"), false);
				local limit = Utils.getNoNil(getXMLBool(self.xmlFile.handle, extraOptionKey .. "#limit"), true);
				local useDefaultPositionSmoothing = Utils.getNoNil(getXMLBool(self.xmlFile.handle, extraOptionKey .. "#useDefaultPositionSmoothing"), false);
				local isInside = Utils.getNoNil(getXMLBool(self.xmlFile.handle, extraOptionKey .. "#isInside"), false);
				local useMirror = Utils.getNoNil(getXMLBool(self.xmlFile.handle, extraOptionKey .. "#useMirror"), false);
				
				local cameraNumber = Utils.getNoNil(getXMLInt(self.xmlFile.handle, extraOptionKey .. "#cameraNumber"), "");

				if cameraNumber == "" then
					printError("(extraOptions) :: Missing 'cameraNumber' for '" .. addCamera .. "' in '" .. extraOptionKey .. "' Using default value '3' instead! This could mess up your cameras!", true, false);

					cameraNumber = 3;
				end;

				local floatTable = {
					transMax = transMax, 
					transMin = transMin, 
					rotMinX = rotMinX, 
					rotMaxX = rotMaxX,
				};

				for tag, value in pairs(floatTable) do
					setXMLFloat(self.xmlFile.handle, "vehicle.enterable.cameras.camera(" .. cameraNumber - 1 .. ")#" .. tag, value);

					printDebug("(extraOptions) :: Set camera tag '" .. tag .. "' to " .. tostring(value) .. ".", 1, true);
				end;

				local boolTable = {
					useWorldXZRotation = useWorldXZRotation, 
					rotatable = rotatable, 
					limit = limit, 
					useDefaultPositionSmoothing = useDefaultPositionSmoothing, 
					isInside = isInside, 
					useMirror = useMirror
				}; 

				for tag, value in pairs(boolTable) do
					setXMLBool(self.xmlFile.handle, "vehicle.enterable.cameras.camera(" .. cameraNumber - 1 .. ")#" .. tag, value);

					printDebug("(extraOptions) :: Set camera tag '" .. tag .. "' to " .. tostring(value) .. ".", 1, true);
				end;

				setXMLString(self.xmlFile.handle, "vehicle.enterable.cameras.camera(" .. cameraNumber - 1 .. ")#node", addCamera);
				
				printDebug("(extraOptions) :: Add camera '" .. addCamera .. "'.", 1, true);
			end;

			if ikChain ~= "" then
				local targetNode = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. "#targetNode"), "");

				if targetNode ~= "" then
					local ikChainNumber = 0;

					while true do
						local ikChainKey = "vehicle.enterable.characterNode.target(" .. tostring(ikChainNumber) .. ")";

						if not self.xmlFile:hasProperty(ikChainKey) then
							break;
						end;

						local ikChainOld = getXMLString(self.xmlFile.handle, ikChainKey .. "#ikChain");

						if ikChain == ikChainOld then
							local targetNodeOld = getXMLString(self.xmlFile.handle, ikChainKey .. "#targetNode");
							local characterTargetNodeModifier = getXMLString(self.xmlFile.handle, "vehicle.enterable.characterTargetNodeModifier#node");
							local stateNodeIndices = {string.getVector(getXMLString(self.xmlFile.handle, extraOptionKey .. "#stateNodeIndices"))};

							if #stateNodeIndices > 0 then
								printDebug("(extraOptions) :: targetNodeOld = '" .. targetNodeOld .. "' vs characterTargetNodeModifier = '" .. characterTargetNodeModifier .. "'.", 2, true);

								if characterTargetNodeModifier == targetNodeOld then
									local stateNode = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. "#stateNode"), "");
									local referenceNode = Utils.getNoNil(getXMLString(self.xmlFile.handle, extraOptionKey .. "#referenceNode"), "");

									if stateNode ~= "" and referenceNode ~= "" then
										setXMLString(self.xmlFile.handle, "vehicle.enterable.characterTargetNodeModifier#node", targetNode);

										printDebug("(extraOptions) :: Set characterTargetNodeModifier target node to '" .. targetNode .. "'.", 1, true);

										for _, stateNodeIndice in pairs(stateNodeIndices) do
											local stateNodeKey = "vehicle.enterable.characterTargetNodeModifier.state(" .. tostring(stateNodeIndice - 1) .. ")";

											if hasXMLProperty(self.xmlFile.handle, stateNodeKey) then	
												setXMLString(self.xmlFile.handle, stateNodeKey .. "#node", stateNode);
												setXMLString(self.xmlFile.handle, stateNodeKey .. "#referenceNode", referenceNode);

												printDebug("(extraOptions) :: Set state(" .. tostring(stateNodeIndice) .. ") node to '" .. stateNode .. "'.", 1, true);
												printDebug("(extraOptions) :: Set state(" .. tostring(stateNodeIndice) .. ") referenceNode to '" .. referenceNode .. "'.", 1, true);
											end;
										end;
									else
										if stateNode == "" then
											printError("(extraOptions) :: Missing 'stateNode' TAG for vehicle '" .. self:getFullName() .. "'! Stop change ikChain!", true, false);
										else
											printError("(extraOptions) :: Missing 'referenceNode' TAG for vehicle '" .. self:getFullName() .. "'! Stop change ikChain!", true, false);
										end;
									end;
								end;

								setXMLString(self.xmlFile.handle, ikChainKey .. "#targetNode", targetNode);

								printDebug("(extraOptions) :: Set characterNode target node to '" .. targetNode .. "'.", 1, true);

								break;
							else
								printError("(extraOptions) :: Missing 'stateNodeIndices' TAG for vehicle '" .. self:getFullName() .. "'! Stop change ikChain!", true, false);
							end;
						end;

						ikChainNumber = ikChainNumber + 1;
					end; 
				end;
			end;

			if maxForwardSpeed ~= "" then
				local motorIndices = {string.getVector(getXMLString(self.xmlFile.handle, extraOptionKey .. "#motorIndices"))};

				if #motorIndices > 0 then
					for _, motorIndice in pairs(motorIndices) do
						local motorKey = string.format("vehicle.motorized.motorConfigurations.motorConfiguration(" .. tostring(motorIndice - 1) .. ")");

						if self.xmlFile:hasProperty(motorKey) then
							setXMLFloat(self.xmlFile.handle, motorKey .. ".motor#maxForwardSpeed", maxForwardSpeed);

							printDebug("(extraOptions) :: Set motor config(" .. tostring(motorIndice) .. ") max forward speed to '" .. maxForwardSpeed .. "'.", 1, true);

							local minForwardGearRatio = Utils.getNoNil(getXMLFloat(self.xmlFile.handle, extraOptionKey .. "#minForwardGearRatio"), "");

							if minForwardGearRatio ~= "" then
								local gearRatioKey = motorKey .. ".transmission";

								setXMLFloat(self.xmlFile.handle, gearRatioKey .. "#minForwardGearRatio", minForwardGearRatio);

								printDebug("(extraOptions) :: Set motor config(" .. tostring(motorIndice) .. ") min forward gear ratio to '" .. minForwardGearRatio .. "'.", 1, true);
							end;
						end;
					end;
				else
					printError("(extraOptions) :: Missing 'motorIndices' TAG for vehicle '" .. self:getFullName() .. "'! Stop change max forward speed!", true, false);
				end;
			end;
			
			if supportAnimationName ~= "" then
				setXMLString(self.xmlFile.handle, "vehicle.attachable.supportArm#animationName", "notDefined"); --## deactivate the manual support animation of my SupportArm Script
				setXMLString(self.xmlFile.handle, "vehicle.attachable.support#animationName", supportAnimationName);

				printDebug("(extraOptions) :: Set support animation name to '" .. supportAnimationName .. "'.", 1, true);
			end;

			if externalMotorSoundFile ~= "" then
				self.externalSoundsFile = XMLFile.load("TempExternalSounds", Utils.getFilename(externalMotorSoundFile, self.baseDirectory), Vehicle.xmlSchemaSounds)

				printDebug("(extraOptions) :: Set external motor sound file to '" .. externalMotorSoundFile .. "'.", 1, true);
			end;

			if self.spec_honk ~= nil then
				if honkSoundOuterRadius ~= "" then
					setXMLFloat(self.xmlFile.handle, "vehicle.honk.sound#outerRadius", honkSoundOuterRadius);
				end;

				if honkSoundInnerRadius ~= "" then
					setXMLFloat(self.xmlFile.handle, "vehicle.honk.sound#innerRadius", honkSoundInnerRadius);
				end;

				if honkSoundTemplate ~= "" then	
					if self.xmlFile:hasProperty("vehicle.honk.sound#file") then	
						self.xmlFile:removeProperty("vehicle.honk.sound#file");
					end;

					setXMLString(self.xmlFile.handle, "vehicle.honk.sound#template", honkSoundTemplate);

					printDebug("(extraOptions) :: Set honk sound template to '" .. honkSoundTemplate .. "'.", 1, true);
				elseif honkSoundFilename ~= "" then
					if self.xmlFile:hasProperty("vehicle.honk.sound#template") then	
						self.xmlFile:removeProperty("vehicle.honk.sound#template");
					end;

					setXMLString(self.xmlFile.handle, "vehicle.honk.sound#file", honkSoundFilename);

					printDebug("(extraOptions) :: Set honk sound filename to '" .. AddConfiguration.currentModDirectory .. honkSoundFilename .. "'.", 1, true);
				end;

				if honkSoundLinkNode ~= "" then
					setXMLString(self.xmlFile.handle, "vehicle.honk.sound#linkNode", honkSoundLinkNode);
				end;
			end;

			if changeConfigurationName ~= "" then
				if self.configurations[changeConfigurationName] ~= nil then
					local configurationIndex = Utils.getNoNil(getXMLInt(self.xmlFile.handle, extraOptionKey .. "#configurationIndex"), 1);
					
					local storeItem = g_storeManager:getItemByXMLFilename(self.configFileName);
					local configuration = storeItem.configurations[changeConfigurationName][configurationIndex];

					configurationName = configuration.name;

					if configurationName == nil or configurationName == "" then
						configurationName = "index " .. configurationIndex;
					end;

					ConfigurationUtil.setConfiguration(self, changeConfigurationName, configurationIndex);
					
					printDebug("(extraOptions) :: Set '" .. changeConfigurationName .. "' configuration to '" .. configurationName .. "' for vehicle '" .. self:getFullName() .. "'.", 1, true);
				else
					printError("(extraOptions) :: Invalid '" .. changeConfigurationName .. "' configuration for vehicle '" .. self:getFullName() .. "'! Stop change this configuration!", true, false);
				end;
			end;
		end;

	   extraOptionNumber = extraOptionNumber + 1;
   	end;
end;

function AddConfiguration:initExtraOptions(configurationName, configurationId, secondConfigurationName, extraOptionNeedUpdate, extraOptionOnLoad, extraOptionOnPostLoad, extraOptionOnPreLoad)
	local configurationKey = self:getConfigurationKey(configurationName, configurationId, secondConfigurationName);

	if configurationKey ~= "noKeyFound" then
		self:loadExtraOptions(configurationKey, extraOptionNeedUpdate, extraOptionOnLoad, extraOptionOnPostLoad, extraOptionOnPreLoad);
	else
		printError("(" .. configurationName .. "Configurations) :: Failed to load configuartion key! Stop loading extra options from '" .. configurationName .. "Configurations'!", true, false);
	end;
end;

function AddConfiguration:setColor(configurationName, configurationId)
	local color = ConfigurationUtil.getColorByConfigId(self, configurationName, configurationId);

	if color ~= nil then
		local r, g, b, material = unpack(color);

		printDebug("(" .. configurationName .. "Configurations) :: r, g, b, materialId = " .. r .. ", " .. g .. ", " .. b .. ", " .. tostring(material), 1, true);

		local configNumber = 0;

		while true do
			local colorKey = string.format("vehicle.%sConfigurations.colorNode(%d)", configurationName, configNumber);

			if not self.xmlFile:hasProperty(colorKey) then
				break;
			end;

			local node = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile.handle, colorKey .. "#node"), self.i3dMappings);

			if node ~= nil then
				local shaderParameter = Utils.getNoNil(getXMLString(self.xmlFile.handle, colorKey .. "#shaderParameter"), "colorScale");
				local materialId = getXMLInt(self.xmlFile.handle, colorKey .. "#materialId");

				if getHasClassId(node, ClassIds.SHAPE) then
					if getHasShaderParameter(node, shaderParameter) then
						if materialId == nil then
							_, _, _, materialId = getShaderParameter(node, shaderParameter);
						end
		
						printDebug("(" .. configurationName .. "Configurations) :: shader parameter values (r, g, b, materialId) = " .. r .. ", " .. g .. ", " .. b .. ", " .. tostring(Utils.getNoNil(materialId, Utils.getNoNil(color[4], 0))), 1, true);
						
						if Utils.getNoNil(getXMLBool(self.xmlFile.handle, colorKey .. "#recursive"), false) then				
							I3DUtil.setShaderParameterRec(node, shaderParameter, r, g, b, Utils.getNoNil(materialId, Utils.getNoNil(color[4], 0)));
						else
							setShaderParameter(node, shaderParameter, r, g, b, Utils.getNoNil(materialId, Utils.getNoNil(color[4], 0)), true);
						end;
					else
						printError("(" .. configurationName .. "Configurations) :: Could not set vehicle color to '" .. getName(node) .. "' because the node has not the shader parameter '" .. shaderParameter .. "'! Stop try to coloring!", true, false);
					end;
				else
					printError("(" .. configurationName .. "Configurations) :: Could not set vehicle color to '" .. getName(node) .. "' because the node is not a shape! Stop try to coloring!", true, false);
				end;
			else
				printError("(" .. configurationName .. "Configurations) :: Could not find node! Stop try to coloring!", true, false);
			end;

			configNumber = configNumber + 1;
		end;

		configNumber = 0;

		while true do
			local materialKey = string.format("vehicle.%sConfigurations.material(%d)", configurationName, configNumber);

			if not self.xmlFile:hasProperty(materialKey) then
				break;
			end;

			local materialName = getXMLString(self.xmlFile.handle, materialKey .. "#name");

			if materialName ~= nil then
				local shaderParameterName = getXMLString(self.xmlFile.handle, materialKey .. "#shaderParameter");

				if shaderParameterName ~= nil then
					local colorStr = getXMLString(self.xmlFile.handle, materialKey .. "#color");
					local color;

					if colorStr ~= nil then
						color = g_brandColorManager:getBrandColorByName(colorStr);
					end;

					if color == nil then
						color = ConfigurationUtil.getColorByConfigId(self, configurationName, configurationId);
					end;

					if color ~= nil then
						local materialId = getXMLInt(self.xmlFile.handle, materialKey .. "#materialId");

						if self.setBaseMaterialColor ~= nil then
							self:setBaseMaterialColor(materialName, shaderParameterName, color, Utils.getNoNil(materialId, Utils.getNoNil(color[4], 0)));
						else
							printError("(" .. configurationName .. "Configurations) :: Missing function 'setBaseMaterialColor'!", true, false);
						end;
					else
						printError("(" .. configurationName .. "Configurations) :: Failed to load color '" .. tostring(colorStr) .. "' in '" .. self:getFullName() .. "'! Stop coloring '" .. materialName .. "'!", false, false);
					end;
				else
					printError("(" .. configurationName .. "Configurations) :: Missing shader parameter in '" .. self:getFullName() .. "'! Stop coloring '" .. materialName .. "'!", true, false);
				end;
			end;

			configNumber = configNumber + 1;
		end;
	end;
end;

function AddConfiguration:changeObjects(configurationName, configurationId, secondConfigurationName)
	secondConfigurationName = Utils.getNoNil(secondConfigurationName, configurationName);

	local configurationKey = self:getConfigurationKey(configurationName, configurationId, secondConfigurationName);
	
	if configurationKey ~= "noKeyFound" then
    	local configNumber = 0;

   		while true do
     		local materialKey = string.format(configurationKey .. ".material(%d)", configNumber);

			if not self.xmlFile:hasProperty(materialKey) then
            	break;
        	end;

			local baseMaterialNode = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile.handle, materialKey .. "#node"), self.i3dMappings);
        	local refMaterialNode = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile.handle, materialKey .. "#refNode"), self.i3dMappings);

			if baseMaterialNode ~= nil and refMaterialNode ~= nil then
            	local oldMaterial = getMaterial(baseMaterialNode, 0);
            	local newMaterial = getMaterial(refMaterialNode, 0);

				for _, component in pairs(self.components) do
               		ConfigurationUtil.replaceMaterialRec(self, component.node, oldMaterial, newMaterial);
            	end;
        	end;

			configNumber = configNumber + 1;
		end;

		--## change body color

		local colorNumber = 0;

		while true do
			local colorKey = configurationKey .. ".colorChanges.colorChange(" .. tostring(colorNumber) .. ")";

			if not self.xmlFile:hasProperty(colorKey) then
				break;
			end;

			local color = getXMLString(self.xmlFile.handle, colorKey .. "#color");
			local getColorFromConfig = getXMLString(self.xmlFile.handle, colorKey .. "#getColorFromConfig");
			local shaderParameterName = Utils.getNoNil(getXMLString(self.xmlFile.handle, colorKey .. "#shaderParameterName"), "colorMat0");
			local materialName = getXMLString(self.xmlFile.handle, colorKey .. "#materialName");
			local materialId = getXMLInt(self.xmlFile.handle, colorKey .. "#materialId");
			local colorName;
			local doStop = false;

			if materialName ~= nil then
				if getColorFromConfig ~= nil then
					if self.configurations[getColorFromConfig] ~= nil then
						if self:getIsColorConfiguration(getColorFromConfig) then
							colorName = ConfigurationUtil.getColorByConfigId(self, getColorFromConfig, self.configurations[getColorFromConfig]);
						else
							printError("(colorChanges) :: The configuration '" .. getColorFromConfig .. "' is not an color configuration! Stop coloring part '" .. materialName .. "'!", false, true);

							doStop = true;
						end;
					else
						printError("(colorChanges) :: Can't find the configuration '" .. getColorFromConfig .. "' in vehicle '" .. self:getFullName() .. "'! Stop coloring part '" .. materialName .. "'!", false, true);

						doStop = true;
					end;
				else
					colorName = g_brandColorManager:getBrandColorByName(color);
				end;

				if not doStop then
					if colorName == nil then
						colorName = ConfigurationUtil.getColorFromString(color);
					end;

					if colorName ~= nil then
						self:setBaseMaterialColor(materialName, shaderParameterName, colorName, Utils.getNoNil(materialId, Utils.getNoNil(colorName[4], 0)));
					
						printDebug("(colorChanges) :: Change color successfully in '" .. self:getFullName() .. "' for '" .. materialName .. "'.", 1, true);
					else
						printError("(colorChanges) :: Failed to load color '" .. tostring(color) .. "' in '" .. self:getFullName() .. "'! Stop coloring '" .. materialName .. "'!", false, false);
					end;
				end;
			else
				printError("(colorChanges) :: Missing 'materialName' in '" .. self:getFullName() .. "'! Stop coloring this vehicle body!", false, false);
			end;

			colorNumber = colorNumber + 1;
		end;

		ObjectChangeUtil.updateObjectChanges(self.xmlFile, "vehicle." .. configurationName .. "Configurations." .. secondConfigurationName .. "Configuration", configurationId, self.components, self);
	end;
end;

local function getRadians(str, superFunc, num)
	if str == nil then
		return nil;
	end;

	local results = str:getVectorN(num);

	if results ~= nil then
		for i = 1, #results do
			results[i] = math.rad(results[i] or 0);
		end;
	end;

	return results;
end;

string.getRadians = Utils.overwrittenFunction(string.getRadians, getRadians);

function AddConfiguration:onWheelHubI3DLoaded(superFunc, i3dNode, failedReason, args)
	local specWheels = getSpecByName(self, "wheels");
	local hub = args.hub;
	local linkNode = args.linkNode;
	local xmlFile = args.xmlFile;
	local key = args.key;
	local hubColorName, hubColorName2, hubColorName3, hubColorName4, hubColorName5, hubColorDarkName, hubColorNameFromConfig, materialId, materialId2, materialId3;
	
	if i3dNode ~= 0 then
		hub.node = I3DUtil.indexToObject(i3dNode, hub.nodeStr, self.i3dMappings);

		if hub.node ~= nil then
			link(linkNode, hub.node);
			delete(i3dNode);
		else
			Logging.xmlError(xmlFile, "Could not find hub node '%s' in '%s'", hub.nodeStr, hub.xmlFilename);

			return;
		end;

		for colorNumber = 0, 7 do
			local hubColorKey = string.format("vehicle.wheels.hubs.color%d", colorNumber);

			if self.xmlFile:hasProperty(hubColorKey) then
				local getHubColorFromConfig = Utils.getNoNil(getXMLString(self.xmlFile.handle, hubColorKey .. "#getHubColorFromConfig"), "");
				local colorFix = Utils.getNoNil(getXMLString(self.xmlFile.handle, hubColorKey .. "#colorFix"), "");
	
				if getHubColorFromConfig ~= "" then
					if self.configurations[getHubColorFromConfig] ~= nil then
						if self:getIsColorConfiguration(getHubColorFromConfig) then
							hubColorName5 = ConfigurationUtil.getColorByConfigId(self, getHubColorFromConfig, self.configurations[getHubColorFromConfig]);

							if colorFix ~= "" then
								hubColorName4 = g_brandColorManager:getBrandColorByName(colorFix);

								if hubColorName4 == nil then
									hubColorName4 = ConfigurationUtil.getColorFromString(colorFix);

									if hubColorName4 == nil then
										hubColorName4 = hubColorName5;
									end;
								end;
							end;

							break;
						else
							printError("(colorChanges) :: The configuration '" .. getHubColorFromConfig .. "' is not an color configuration! Stop coloring wheel hubs!", false, true);
						end;
					end;
				end;
			end;
		end;

		for colorNumber = 0, 7 do
			local color = XMLUtil.getXMLOverwrittenValue(xmlFile, key, string.format(".color%d", colorNumber), "", "global");
			local material = XMLUtil.getXMLOverwrittenValue(xmlFile, key, string.format(".color%d#material", colorNumber), "");

			if color == "global" then
				color = specWheels.hubsColors[colorNumber];
			else
				color = ConfigurationUtil.getColorFromString(color);

				if color ~= nil then
					color[4] = material;
				end;
			end;

			if self.getConfigurationKey ~= nil and self.getXMLPrefix ~= nil then
				for configurationName, _ in pairs(self.configurations) do
					local configurationKey = self:getConfigurationKey(self:getXMLPrefix(configurationName), self.configurations[configurationName], configurationName);
					local hubColorNumber = 0;

					while true do
						local hubColorKey = configurationKey .. ".colorChanges.hub(" .. tostring(hubColorNumber) .. ")";
						
						if not self.xmlFile:hasProperty(hubColorKey) then
							break;
						end;

						local shaderParameterName = Utils.getNoNil(getXMLString(self.xmlFile.handle, hubColorKey .. "#shaderParameterName"), "colorMat0");
						local hubColor = getXMLString(self.xmlFile.handle, hubColorKey .. "#color");
						local getHubColorFromConfig = Utils.getNoNil(getXMLString(self.xmlFile.handle, hubColorKey .. "#getHubColorFromConfig"), "");

						local hubColorDark = Utils.getNoNil(getXMLString(self.xmlFile.handle, hubColorKey .. "#colorDark"), "");

						if hubColorDark ~= "" then
							hubColorDarkName = g_brandColorManager:getBrandColorByName(hubColorDark);
							
							if hubColorDarkName == nil then
								hubColorDarkName = ConfigurationUtil.getColorFromString(hubColorDark);
							end;
						end;
						
						if getHubColorFromConfig ~= "" then
							if self.configurations[getHubColorFromConfig] ~= nil then
								local colorName;

								if self:getIsColorConfiguration(getHubColorFromConfig) then
									colorName = ConfigurationUtil.getColorByConfigId(self, getHubColorFromConfig, self.configurations[getHubColorFromConfig]);
								else
									printError("(colorChanges) :: The configuration '" .. getHubColorFromConfig .. "' is not an color configuration! Stop coloring wheel hubs!", false, true);
								end;
						
								if colorName ~= nil then
									hubColorNameFromConfig = colorName;

									hubColorName5 = nil;
								end;
							end;
						end;

						if shaderParameterName == "colorMat0" then
							if hubColor ~= nil then
								materialId = getXMLInt(self.xmlFile.handle, hubColorKey .. "#materialId");

								hubColorName = g_brandColorManager:getBrandColorByName(hubColor);
							
								if hubColorName == nil then
									hubColorName = ConfigurationUtil.getColorFromString(hubColor);
								end;

								hubColorName5 = nil;
							end;
						elseif shaderParameterName == "colorMat1" then
							if hubColor ~= nil then	
								materialId2 = getXMLInt(self.xmlFile.handle, hubColorKey .. "#materialId");

								hubColorName2 = g_brandColorManager:getBrandColorByName(hubColor);
									
								if hubColorName2 == nil then
									hubColorName2 = ConfigurationUtil.getColorFromString(hubColor);
								end;
							end;
						elseif shaderParameterName == "colorMat2" then
							if hubColor ~= nil then	
								materialId3 = getXMLInt(self.xmlFile.handle, hubColorKey .. "#materialId");

								hubColorName3 = g_brandColorManager:getBrandColorByName(hubColor);
									
								if hubColorName3 == nil then
									hubColorName3 = ConfigurationUtil.getColorFromString(hubColor);
								end;
							end;
						elseif shaderParameterName == "colorMat3" then
							if hubColor ~= nil then	
								materialId4 = getXMLInt(self.xmlFile.handle, hubColorKey .. "#materialId");

								hubColorName4 = g_brandColorManager:getBrandColorByName(hubColor);
									
								if hubColorName4 == nil then
									hubColorName4 = ConfigurationUtil.getColorFromString(hubColor);
								end;
							end;
						end;

						hubColorNumber = hubColorNumber + 1
					end;
				end;
			end;

			if color ~= nil and hub.colors[colorNumber] == nil then
				Logging.xmlWarning(xmlFile, "ColorShader 'color%d' is not supported by '%s'.", colorNumber, hub.xmlFilename);
			else
				color = hubColorName5 or hubColorNameFromConfig or hubColorName or color or hub.colors[colorNumber];
				color2 = hubColorName2;
				color3 = hubColorName3;
				color4 = hubColorName4;

				if hubColorDarkName ~= nil then
					local r, g, b, _ = unpack(color);
					local brightness = MathUtil.getBrightnessFromColor(r, g, b);
					
					if brightness >  0.2 then
						color4 = hubColorDarkName;
					end;
				end;

				if color2 == nil then
					color2 = color;
					materialId2 = materialId;
				end;
				
				if color3 == nil then
					color3 = color;
					materialId3 = materialId;
				end;
				
				if color4 == nil then
					color4 = color;
					materialId4 = materialId;
				end;

				local r, g, b, mat;

				if color ~= nil then
					if colorNumber ~= 1 and colorNumber ~= 2 and colorNumber ~= 5 then
						r, g, b, mat = unpack(color);

						mat = materialId or mat;

						if mat == nil then
							_, _, _, mat = I3DUtil.getShaderParameterRec(hub.node, string.format("colorMat%d", colorNumber));
						end;

						I3DUtil.setShaderParameterRec(hub.node, string.format("colorMat%d", colorNumber), r, g, b, mat, false);
					else
						if color2 ~= nil then
							r, g, b, mat = unpack(color2);

							mat = materialId2 or mat;

							if mat == nil then
								_, _, _, mat = I3DUtil.getShaderParameterRec(hub.node, "colorMat1");
							end;

							I3DUtil.setShaderParameterRec(hub.node, "colorMat1", r, g, b, mat, false);
						end;

						if color3 ~= nil then
							r, g, b, mat = unpack(color3);

							mat = materialId3 or mat;

							if mat == nil then
								_, _, _, mat = I3DUtil.getShaderParameterRec(hub.node, "colorMat2");
							end;

							I3DUtil.setShaderParameterRec(hub.node, "colorMat2", r, g, b, mat, false);
						end;
						
						if color4 ~= nil then
							r, g, b, mat = unpack(color4);

							mat = materialId3 or mat;

							if mat == nil then
								_, _, _, mat = I3DUtil.getShaderParameterRec(hub.node, "colorMat3");
							end;

							I3DUtil.setShaderParameterRec(hub.node, "colorMat3", r, g, b, mat, false);
						end;
					end;
				end;
			end;
		end;

		local offset = xmlFile:getValue(key .. "#offset");

		if offset ~= nil then
			if not hub.isLeft then
				offset = offset * -1;
			end;

			setTranslation(hub.node, offset, 0, 0);
		end;

		local scale = xmlFile:getValue(key .. "#scale", nil, true);

		if scale ~= nil then
			setScale(hub.node, scale[1], scale[2], scale[3]);
		end;

		table.insert(specWheels.hubs, hub);
	elseif not self.isDeleting and not self.isDeleted then
		Logging.xmlError(xmlFile, "Unable to load hub '%s'", hub.xmlFilename);
	end;
end;

function AddConfiguration:onWheelPartI3DLoaded(superFunc, i3dNode, failedReason, args)
	local specWheels = getSpecByName(self, "wheels");
	
	local wheel = args.wheel;
	local parentWheel = args.parentWheel;
	local linkNode = args.linkNode;
	local name = args.name;
	local filename = args.filename;
	local index = args.index;
	local offset = args.offset;
	local widthAndDiam = args.widthAndDiam;
	local scale = args.scale;
	local fileIdentifier = args.fileIdentifier;

	local rimColorName, rimColorName2, additionalColorName, materialId, innerRimMaterialId, additionalMaterialId;
	local shaderParameterName, additionalShaderParameterName = "colorMat0", "colorMat0";

	if i3dNode ~= 0 then
		wheel[fileIdentifier] = filename;
		wheel[name] = I3DUtil.indexToObject(i3dNode, index);

		if wheel[name] ~= nil then
			link(linkNode, wheel[name]);
			delete(i3dNode);

			if offset ~= 0 then
				local dir = 1;

				if not wheel.isLeft then
					dir = -1;
				end;

				setTranslation(wheel[name], offset * dir, 0, 0);
			end;

			if scale ~= nil then
				setScale(wheel[name], scale[1], scale[2], scale[3]);
			end;

			if widthAndDiam ~= nil then
				if getHasShaderParameter(wheel[name], "widthAndDiam") then
					I3DUtil.setShaderParameterRec(wheel[name], "widthAndDiam", widthAndDiam[1], widthAndDiam[2], 0, 0, false);
				else
					local scaleX = MathUtil.inchToM(widthAndDiam[1]);
					local scaleZY = MathUtil.inchToM(widthAndDiam[2]);

					setScale(wheel[name], scaleX, scaleZY, scaleZY);
				end;
			end;

			local rimConfigColor = ConfigurationUtil.getColorByConfigId(self, "rimColor", self.configurations.rimColor);

			if self.getConfigurationKey ~= nil and self.getXMLPrefix ~= nil then
				for configurationName, _ in pairs(self.configurations) do
					local configurationKey = self:getConfigurationKey(self:getXMLPrefix(configurationName), self.configurations[configurationName], configurationName);
					local rimColorNumber = 0;

					while true do
						local rimColorKey = configurationKey .. ".colorChanges.rim(" .. tostring(rimColorNumber) .. ")";
						
						if not self.xmlFile:hasProperty(rimColorKey) then
							break;
						end;
						
						local shaderParameterName = Utils.getNoNil(getXMLString(self.xmlFile.handle, rimColorKey .. "#shaderParameterName"), "colorMat0");
						local rimColor = getXMLString(self.xmlFile.handle, rimColorKey .. "#color");

						if shaderParameterName == "colorMat0" then
							local getColorFromConfig = getXMLString(self.xmlFile.handle, rimColorKey .. "#getColorFromConfig");
							
							if getColorFromConfig ~= nil then
								if self.configurations[getColorFromConfig] ~= nil then	
									if self:getIsColorConfiguration(getColorFromConfig) then
										rimColorName = ConfigurationUtil.getColorByConfigId(self, getColorFromConfig, self.configurations[getColorFromConfig]);
									else
										printError("(colorChanges) :: The configuration '" .. getColorFromConfig .. "' is not an color configuration! Stop coloring rims!", false, true);
									end;
								else
									printError("(colorChanges) :: Can't find the configuration '" .. getColorFromConfig .. "' in vehicle '" .. self:getFullName() .. "'! Stop coloring rims!", false, true);
								end;
							end;
							
							if rimColor ~= nil or rimColorName ~= nil then			
								materialId = getXMLInt(self.xmlFile.handle, rimColorKey .. "#materialId");

								if rimColor ~= nil then
									rimColorName = g_brandColorManager:getBrandColorByName(rimColor);
									
									if rimColorName == nil then
										rimColorName = ConfigurationUtil.getColorFromString(rimColor);
									end;
								end;
							end;
						elseif shaderParameterName == "colorMat3" then
							if rimColor ~= nil then
								rimColorName2 = g_brandColorManager:getBrandColorByName(rimColor);
									
								if rimColorName2 == nil then
									rimColorName2 = ConfigurationUtil.getColorFromString(rimColor);
								end;
							end;
						end;

						rimColorNumber = rimColorNumber + 1;
					end;

					local additionalColorKey = configurationKey .. ".colorChanges.additional";

					additionalShaderParameterName = Utils.getNoNil(getXMLString(self.xmlFile.handle, additionalColorKey .. "#shaderParameterName"), "colorMat0");

					local getRimColorAndIndexFromConfig = getXMLString(self.xmlFile.handle, additionalColorKey .. "#getRimColorAndIndexFromConfig");
					local getColorFromConfig = getXMLString(self.xmlFile.handle, additionalColorKey .. "#getColorFromConfig");
					local additionalColor = getXMLString(self.xmlFile.handle, additionalColorKey .. "#color");

					if additionalColor ~= nil then
						additionalMaterialId = getXMLInt(self.xmlFile.handle, additionalColorKey .. "#materialId");
					end;
						
					if getColorFromConfig ~= nil then
						if self.configurations[getColorFromConfig] ~= nil then	
							if self:getIsColorConfiguration(getColorFromConfig) then
								additionalColorName = ConfigurationUtil.getColorByConfigId(self, getColorFromConfig, self.configurations[getColorFromConfig]);
							else
								printError("(colorChanges) :: The configuration '" .. getColorFromConfig .. "' is not an color configuration! Stop coloring wheel weights!", false, true);
							end;
						else
							printError("(colorChanges) :: Can't find the configuration '" .. getColorFromConfig .. "' in vehicle '" .. self:getFullName() .. "'! Stop coloring wheel weights!", false, true);
						end;
					end;

					if getRimColorAndIndexFromConfig ~= nil then
						if self.configurations[getRimColorAndIndexFromConfig] ~= nil then
							local configurationKey = self:getConfigurationKey(self:getXMLPrefix(getRimColorAndIndexFromConfig), self.configurations[getRimColorAndIndexFromConfig], getRimColorAndIndexFromConfig);
						
							if configurationKey ~= "noKeyFound" and self.xmlFile:hasProperty(configurationKey .. ".colorChanges.rim#color") then
								additionalColor = getXMLString(self.xmlFile.handle, configurationKey .. ".colorChanges.rim#color");
								additionalMaterialId = getXMLInt(self.xmlFile.handle, configurationKey .. ".colorChanges.rim#materialId");
								additionalShaderParameterName = Utils.getNoNil(getXMLString(self.xmlFile.handle, configurationKey .. ".colorChanges.rim#shaderParameterName"), "colorMat0");
							end;
						end;
					end;

					if additionalColor ~= nil then
						additionalColorName = g_brandColorManager:getBrandColorByName(additionalColor);
							
						if additionalColorName == nil then
							additionalColorName = ConfigurationUtil.getColorFromString(additionalColor);
						end;
					end;
				end;
			end;

			local rimColor = rimColorName or wheel.color or rimConfigColor or specWheels.rimColor;
			local rimColor2 = rimColorName2 or rimColor;
			local additionalColor = additionalColorName or wheel.additionalColor or rimColor;
		
			if name == "wheelTire" then
				local zRot = 0;

				if wheel.tireIsInverted or parentWheel ~= nil and parentWheel.tireIsInverted then
					zRot = math.pi;
				end;

				setRotation(wheel.wheelTire, wheel.xRotOffset, 0, zRot);

				local x, y, z, _ = I3DUtil.getShaderParameterRec(wheel.wheelTire, "morphPosition");

				for _, shaderParameter in pairs({"morphPosition", "prevMorphPosition"}) do
					I3DUtil.setShaderParameterRec(wheel.wheelTire, shaderParameter, x, y, z, 0, false);
				end;
			elseif name == "wheelOuterRim" or name == "wheelInnerRim" then
				if rimColor ~= nil then
					local r, g, b, mat, _ = unpack(rimColor);
					local r2, g2, b2, _, _ = unpack(rimColor2);
					
					mat = materialId or mat or wheel.material;
					
					if wheel.wheelOuterRim ~= nil then
						if mat == nil then
							_, _, _, mat = I3DUtil.getShaderParameterRec(wheel.wheelOuterRim, shaderParameterName);
						end;

						I3DUtil.setShaderParameterRec(wheel.wheelOuterRim, "colorMat0", r, g, b, mat, false);
						I3DUtil.setShaderParameterRec(wheel.wheelOuterRim, "colorMat3", r2, g2, b2, mat, false);
					end;

					if wheel.wheelInnerRim ~= nil then
						I3DUtil.setShaderParameterRec(wheel.wheelInnerRim, "colorMat0", r, g, b, mat, false);
						I3DUtil.setShaderParameterRec(wheel.wheelInnerRim, "colorMat1", r, g, b, mat, false);
						I3DUtil.setShaderParameterRec(wheel.wheelInnerRim, "colorMat2", r, g, b, mat, false);
					end;
				end;

				if wheel.wheelInnerRim ~= nil then
					for hubColorNumber = 1, 7 do
						local color = specWheels.hubsColors[hubColorNumber];

						if color ~= nil then
							I3DUtil.setShaderParameterRec(wheel.wheelInnerRim, string.format("colorMat%d", hubColorNumber), color[1], color[2], color[3], color[4], false);
						end;
					end;
				end;
			elseif name == "wheelAdditional" then
				if wheel.wheelAdditional ~= nil and additionalColor ~= nil then
					local r, g, b, _ = unpack(additionalColor);

					if additionalColorName ~= nil then
						local r2, g2, b2, _ = unpack(additionalColorName);
					end;

					mat = additionalMaterialId or wheel.additionalMaterial;

					if mat == nil then
						_, _, _, mat = I3DUtil.getShaderParameterRec(wheel.wheelAdditional, additionalShaderParameterName);
					end;
					
					I3DUtil.setShaderParameterRec(wheel.wheelAdditional, additionalShaderParameterName, r, g, b, mat, false);
				end;
			end;
		else
			Logging.xmlWarning(self.xmlFile, "Failed to load node '%s' for file '%s'", index, filename);
		end;
	elseif not self.isDeleted and not self.isDeleting then
		Logging.xmlWarning(self.xmlFile, "Failed to load file '%s' wheel part '%s'", filename, name);
	end;
end;

function AddConfiguration:onWheelChockI3DLoaded(superFunc, i3dNode, failedReason, args)
	local wheel = args.wheel;
	local filename = args.filename;
	local xmlFile = args.xmlFile;
	local configKey = args.configKey;
	local chockKey = args.chockKey;

	if i3dNode ~= 0 then
		local _ = nil;
		local chockNode = getChildAt(i3dNode, 0);
		local posRefNode = I3DUtil.indexToObject(chockNode, getUserAttribute(chockNode, "posRefNode"), self.i3dMappings);

		if posRefNode ~= nil then
			local chock = {
				wheel = wheel,
				node = chockNode,
				filename = filename,
				scale = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, chockKey .. "#scale", "1 1 1", true)
			};

			setScale(chock.node, unpack(chock.scale));

			chock.parkingNode = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, chockKey .. "#parkingNode", nil, self.components, self.i3dMappings);
			chock.isInverted = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, chockKey .. "#isInverted", false);
			chock.isParked = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, chockKey .. "#isParked", false);

			_, chock.height, chock.zOffset = localToLocal(posRefNode, chock.node, 0, 0, 0);
			
			chock.height = chock.height / chock.scale[2];
			chock.zOffset = chock.zOffset / chock.scale[3];
			chock.offset = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, chockKey .. "#offset", "0 0 0", true);
			chock.parkedNode = I3DUtil.indexToObject(chockNode, getUserAttribute(chockNode, "parkedNode"), self.i3dMappings);
			chock.linkedNode = I3DUtil.indexToObject(chockNode, getUserAttribute(chockNode, "linkedNode"), self.i3dMappings);
			chock.color = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, chockKey .. "#color", nil, true);
			chock.materialId = nil;

			if self.getConfigurationKey ~= nil and self.getXMLPrefix ~= nil then
				for configurationName, _ in pairs(self.configurations) do
					local configurationKey = self:getConfigurationKey(self:getXMLPrefix(configurationName), self.configurations[configurationName], configurationName);
					local wheelChockColorStr = getXMLString(self.xmlFile.handle, configurationKey .. ".colorChanges.wheelChock#color");
					local getColorFromConfig = getXMLString(self.xmlFile.handle, configurationKey .. ".colorChanges.wheelChock#getColorFromConfig");
						
					if self.xmlFile:hasProperty(configurationKey .. ".colorChanges.wheelChock#materialId") then
						chock.materialId = getXMLInt(self.xmlFile.handle, configurationKey .. ".colorChanges.wheelChock#materialId");
					end;

					if getColorFromConfig ~= nil then
						if self.configurations[getColorFromConfig] ~= nil then	
							if self:getIsColorConfiguration(getColorFromConfig) then
								chock.color = ConfigurationUtil.getColorByConfigId(self, getColorFromConfig, self.configurations[getColorFromConfig]);
								chock.materialId = ConfigurationUtil.getMaterialByConfigId(self, getColorFromConfig, self.configurations[getColorFromConfig]);

								wheelChockColorStr = nil;
							else
								printError("(colorChanges) :: The configuration '" .. getColorFromConfig .. "' is not an color configuration! Stop coloring wheel chocks!", false, true);
							end;
						else
							printError("(colorChanges) :: Can't find the configuration '" .. getColorFromConfig .. "' in vehicle '" .. self:getFullName() .. "'! Stop coloring wheel chocks!", false, true);
						end;
					end;
	
					if wheelChockColorStr ~= nil then
						chockColor = g_brandColorManager:getBrandColorByName(wheelChockColorStr);
						
						if chockColor == nil then
							chockColor = string.getVectorN(wheelChockColorStr, 3);
						end;
	
						if chockColor ~= nil then
							chock.color = chockColor;

							printDebug("(colorChanges) :: Change wheel chock color successfully in '" .. self:getFullName() .. "' to '" .. wheelChockColorStr .. "'.", 1, true);
						else
							printError("(colorChanges) :: Failed to load color '" .. tostring(wheelChockColorStr) .. "' in '" .. self:getFullName() .. "'! Stop coloring wheel chock!", false, false);
						end;
					end;
				end;
			end;

			if chock.color ~= nil then
				if chock.materialId == nil then
					local _, _, _, defaultMaterial = getShaderParameter(chockNode, "colorMat0");

					chock.color[4] = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, chockKey .. "#material", defaultMaterial);
				else
					chock.color[4] = chock.materialId;
				end;

				I3DUtil.setShaderParameterRec(chockNode, "colorMat0", chock.color[1], chock.color[2], chock.color[3], chock.color[4]);
			end;

			chock.isInParkingPosition = false;

			self:updateWheelChockPosition(chock, chock.isParked);

			wheel.updateWheelChock = false;

			table.insert(wheel.wheelChocks, chock);
			table.insert(self.spec_wheels.wheelChocks, chock);
		else
			Logging.xmlWarning(xmlFile, "Missing 'posRefNode'-userattribute for wheel-chock '%s'!", chockKey);
		end;

		delete(i3dNode);
	end;
end;

function AddConfiguration:onAdditionalWheelConnectorI3DLoaded(superFunc, i3dNode, failedReason, args)
	local wheel = args.wheel;
	local connector = args.connector;
	local diameter = args.diameter;
	local baseWheelWidth = args.baseWheelWidth;
	local wheelDistance = args.wheelDistance;
	local offsetDir = args.offsetDir;
	local dualWheelWidth = args.dualWheelWidth;
	local filename = args.filename;
	local rimColorName, materialId;

	if i3dNode ~= 0 then
		local node = I3DUtil.indexToObject(i3dNode, connector.nodeStr, self.i3dMappings);

		if node ~= nil then
			connector.node = node;
			connector.linkNode = wheel.wheelTire;
			connector.filename = filename;

			link(wheel.driveNode, connector.node);

			if self.getConfigurationKey ~= nil and self.getXMLPrefix ~= nil then
				for configurationName, _ in pairs(self.configurations) do
					local configurationKey = self:getConfigurationKey(self:getXMLPrefix(configurationName), self.configurations[configurationName], configurationName);
					local rimColorKey = configurationKey .. ".colorChanges.rim";

					if self.xmlFile:hasProperty(rimColorKey) then
						local getColorFromConfig = getXMLString(self.xmlFile.handle, rimColorKey .. "#getColorFromConfig");
						local rimColor = getXMLString(self.xmlFile.handle, rimColorKey .. "#color");
							
						if getColorFromConfig ~= nil then
							if self.configurations[getColorFromConfig] ~= nil then	
								if self:getIsColorConfiguration(getColorFromConfig) then
									rimColorName = ConfigurationUtil.getColorByConfigId(self, getColorFromConfig, self.configurations[getColorFromConfig]);
								else
									printError("(colorChanges) :: The configuration '" .. getColorFromConfig .. "' is not an color configuration! Stop coloring rims!", false, true);
								end;
							end;
						end;
							
						if rimColor ~= nil or rimColorName ~= nil then			
							materialId = getXMLInt(self.xmlFile.handle, rimColorKey .. "#materialId");
								
							if rimColor ~= nil then
								rimColorName = g_brandColorManager:getBrandColorByName(rimColor);
								
								if rimColorName == nil then
									rimColorName = ConfigurationUtil.getColorFromString(rimColor);
								end;
							end;
						end;
					end;
				end;
			end;

			if not connector.useWidthAndDiam then
				if getHasShaderParameter(connector.node, "connectorPos") then
					I3DUtil.setShaderParameterRec(connector.node, "connectorPos", 0, baseWheelWidth, wheelDistance, dualWheelWidth, false);
				end;

				local x, _, z, w = I3DUtil.getShaderParameterRec(connector.node, "widthAndDiam");

				if connector.diameterFix ~= nil then
					diameter = connector.diameterFix;
				end;

				if connector.scaleFix ~= nil then
					setScale(connector.node, connector.scaleFix[1], connector.scaleFix[2], connector.scaleFix[3]);
				end;

				I3DUtil.setShaderParameterRec(connector.node, "widthAndDiam", x, diameter, z, w, false);
			else
				local connectorOffset = offsetDir * ((0.5 * baseWheelWidth + 0.5 * wheelDistance) * 0.0254 + connector.additionalOffset);
				local connectorDiameter = connector.diameter or diameter;

				setTranslation(connector.node, connectorOffset, 0, 0);
				I3DUtil.setShaderParameterRec(connector.node, "widthAndDiam", connector.width, connectorDiameter, 0, 0, false);
			end

			if connector.usePosAndScale and getHasShaderParameter(connector.node, "connectorPosAndScale") then
				local _, _, _, w = I3DUtil.getShaderParameterRec(connector.node, "connectorPosAndScale");

				I3DUtil.setShaderParameterRec(connector.node, "connectorPosAndScale", connector.startPos, connector.endPos, connector.scale, w, false);
			end;

			local color = rimColorName or self.spec_wheels.rimColor or connector.color;

			if color ~= nil and getHasShaderParameter(connector.node, "colorMat0") then
				local r, g, b, mat = unpack(color);

				if mat == nil then
					_, _, _, mat = I3DUtil.getShaderParameterRec(connector.node, "colorMat0");
				end;

				I3DUtil.setShaderParameterRec(connector.node, "colorMat0", r, g, b, materialId or mat, false);
			end;
		end;

		delete(i3dNode);
	end;
end;

function AddConfiguration:loadWheelsFromXML(superFunc, xmlFile, key, wheelConfigurationId)
	local specWheels = getSpecByName(self, "wheels");

	local wheelNumber = 0;
	local wheelIndiciesToRemove = nil;

	if self.getConfigurationKey ~= nil and self.getXMLPrefix ~= nil then
		for configurationName, _ in pairs(self.configurations) do
			local configurationKey = self:getConfigurationKey(self:getXMLPrefix(configurationName), self.configurations[configurationName], configurationName);
			local extraOptionNumber = 0;

			while true do
				local extraOptionKey = string.format(configurationKey .. ".extraOption(%d)", extraOptionNumber);
					
				if not xmlFile:hasProperty(extraOptionKey) then
					break;
				end;
		
				wheelIndiciesToRemove = {string.getVector(getXMLString(xmlFile.handle, extraOptionKey .. "#wheelIndiciesToRemove"))};

				extraOptionNumber = extraOptionNumber + 1;
			end;
		end;
	end;

	while true do
		local wheelKey = string.format(".wheels.wheel(%d)", wheelNumber);

		if not xmlFile:hasProperty(key .. wheelKey) then
			break;
		end;

		local stopAddWheel = false;

		if wheelIndiciesToRemove ~= nil and #wheelIndiciesToRemove > 0 then
			for _, wheelIndexToRemove in pairs(wheelIndiciesToRemove) do
				if wheelNumber + 1 == wheelIndexToRemove then
					stopAddWheel = true;

					break;
				end;
			end;
		end;

		if not stopAddWheel then
			local wheel = {
				xmlIndex = wheelNumber,
				updateIndex = wheelNumber % 4 + 1,
				configIndex = wheelConfigurationId
			};

			if self:loadWheelFromXML(xmlFile, key, wheelKey, wheel) then
				self:finalizeWheel(wheel);

				specWheels.maxUpdateIndex = math.max(specWheels.maxUpdateIndex, wheel.updateIndex);

				table.insert(specWheels.wheels, wheel);
			end;
		end;

		wheelNumber = wheelNumber + 1;
	end;
end;

function AddConfiguration:loadAttacherJointFromXML(superFunc, attacherJoint, xmlFile, baseName, index)
	local specAttacherJoints = getSpecByName(self, "attacherJoints");

	XMLUtil.checkDeprecatedXMLElements(xmlFile, baseName .. "#index", baseName .. "#node");
	XMLUtil.checkDeprecatedXMLElements(xmlFile, baseName .. "#indexVisual", baseName .. "#nodeVisual");
	XMLUtil.checkDeprecatedXMLElements(xmlFile, baseName .. "#ptoOutputNode", "vehicle.powerTakeOffs.output");
	XMLUtil.checkDeprecatedXMLElements(xmlFile, baseName .. "#lowerDistanceToGround", baseName .. ".distanceToGround#lower");
	XMLUtil.checkDeprecatedXMLElements(xmlFile, baseName .. "#upperDistanceToGround", baseName .. ".distanceToGround#upper");
	XMLUtil.checkDeprecatedXMLElements(xmlFile, baseName .. "#rotationNode", baseName .. ".rotationNode#node");
	XMLUtil.checkDeprecatedXMLElements(xmlFile, baseName .. "#upperRotation", baseName .. ".rotationNode#upperRotation");
	XMLUtil.checkDeprecatedXMLElements(xmlFile, baseName .. "#lowerRotation", baseName .. ".rotationNode#lowerRotation");
	XMLUtil.checkDeprecatedXMLElements(xmlFile, baseName .. "#startRotation", baseName .. ".rotationNode#startRotation");
	XMLUtil.checkDeprecatedXMLElements(xmlFile, baseName .. "#rotationNode2", baseName .. ".rotationNode2#node");
	XMLUtil.checkDeprecatedXMLElements(xmlFile, baseName .. "#upperRotation2", baseName .. ".rotationNode2#upperRotation");
	XMLUtil.checkDeprecatedXMLElements(xmlFile, baseName .. "#lowerRotation2", baseName .. ".rotationNode2#lowerRotation");
	XMLUtil.checkDeprecatedXMLElements(xmlFile, baseName .. "#transNode", baseName .. ".transNode#node");
	XMLUtil.checkDeprecatedXMLElements(xmlFile, baseName .. "#transNodeMinY", baseName .. ".transNode#minY");
	XMLUtil.checkDeprecatedXMLElements(xmlFile, baseName .. "#transNodeMaxY", baseName .. ".transNode#maxY");
	XMLUtil.checkDeprecatedXMLElements(xmlFile, baseName .. "#transNodeHeight", baseName .. ".transNode#height");

	local node = xmlFile:getValue(baseName .. "#node", nil, self.components, self.i3dMappings);

	if node == nil then
		Logging.xmlWarning(self.xmlFile, "Missing node for attacherJoint '%s'", baseName);

		return false;
	end;

	attacherJoint.jointTransform = node;
	attacherJoint.jointComponent = self:getParentComponent(attacherJoint.jointTransform);

	attacherJoint.jointTransformVisual = xmlFile:getValue(baseName .. "#nodeVisual", nil, self.components, self.i3dMappings);
	attacherJoint.supportsHardAttach = xmlFile:getValue(baseName .. "#supportsHardAttach", true);
	
	attacherJoint.jointOrigOffsetComponent = {localToLocal(attacherJoint.jointComponent, attacherJoint.jointTransform, 0, 0, 0)};
	attacherJoint.jointOrigDirOffsetComponent = {localDirectionToLocal(attacherJoint.jointComponent, attacherJoint.jointTransform, 0, 0, 1)};

	attacherJoint.jointTransformOrig = createTransformGroup("jointTransformOrig");

	link(getParent(node), attacherJoint.jointTransformOrig);
	setTranslation(attacherJoint.jointTransformOrig, getTranslation(node));
	setRotation(attacherJoint.jointTransformOrig, getRotation(node));

	local jointTypeStr = xmlFile:getValue(baseName .. "#jointType");
	local jointType = nil;

	if jointTypeStr ~= nil then
		jointType = AttacherJoints.jointTypeNameToInt[jointTypeStr];

		if jointType == nil then
			Logging.xmlWarning(self.xmlFile, "Invalid jointType '%s' for attacherJoint '%s'!", tostring(jointTypeStr), baseName);
		end;
	end;

	if jointType == nil then
		jointType = AttacherJoints.JOINTTYPE_IMPLEMENT;
	end;

	attacherJoint.jointType = jointType;

	local subTypeStr = xmlFile:getValue(baseName .. ".subType#name");
	
	attacherJoint.subTypes = string.split(subTypeStr, " ");

	if #attacherJoint.subTypes == 0 then
		attacherJoint.subTypes = nil;
	end;

	local brandRestrictionStr = xmlFile:getValue(baseName .. ".subType#brandRestriction");

	attacherJoint.brandRestrictions = string.split(brandRestrictionStr, " ");

	if #attacherJoint.brandRestrictions == 0 then
		attacherJoint.brandRestrictions = nil;
	else
		for attacherJointNumber = 1, #attacherJoint.brandRestrictions do
			local brand = g_brandManager:getBrandByName(attacherJoint.brandRestrictions[attacherJointNumber]);

			if brand ~= nil then
				attacherJoint.brandRestrictions[attacherJointNumber] = brand;
			else
				Logging.xmlError(xmlFile, "Unknown brand '%s' in '%s'", attacherJoint.brandRestrictions[attacherJointNumber], baseName .. ".subType#brandRestriction");

				attacherJoint.brandRestrictions = nil;

				break;
			end;
		end;
	end;

	local vehicleRestrictionStr = xmlFile:getValue(baseName .. ".subType#vehicleRestriction");

	attacherJoint.vehicleRestrictions = string.split(vehicleRestrictionStr, " ");

	if #attacherJoint.vehicleRestrictions == 0 then
		attacherJoint.vehicleRestrictions = nil;
	end;

	attacherJoint.allowsJointLimitMovement = xmlFile:getValue(baseName .. "#allowsJointLimitMovement", true);
	attacherJoint.allowsLowering = xmlFile:getValue(baseName .. "#allowsLowering", true);
	attacherJoint.isDefaultLowered = xmlFile:getValue(baseName .. "#isDefaultLowered", false);
	attacherJoint.allowDetachingWhileLifted = xmlFile:getValue(baseName .. "#allowDetachingWhileLifted", true);
	attacherJoint.allowFoldingWhileAttached = xmlFile:getValue(baseName .. "#allowFoldingWhileAttached", true);

	if jointType == AttacherJoints.JOINTTYPE_TRAILER or jointType == AttacherJoints.JOINTTYPE_TRAILERLOW then
		attacherJoint.allowsLowering = false;
	end;

	attacherJoint.canTurnOnImplement = xmlFile:getValue(baseName .. "#canTurnOnImplement", true);

	local rotationNode = xmlFile:getValue(baseName .. ".rotationNode#node", nil, self.components, self.i3dMappings);

	if rotationNode ~= nil then
		attacherJoint.rotationNode = rotationNode;
		attacherJoint.lowerRotation = xmlFile:getValue(baseName .. ".rotationNode#lowerRotation", "0 0 0", true);
		attacherJoint.upperRotation = xmlFile:getValue(baseName .. ".rotationNode#upperRotation", {getRotation(rotationNode)}, true);
		attacherJoint.rotX, attacherJoint.rotY, attacherJoint.rotZ = xmlFile:getValue(baseName .. ".rotationNode#startRotation", {getRotation(rotationNode)});

		local lowerValues = {
			attacherJoint.lowerRotation[1],
			attacherJoint.lowerRotation[2],
			attacherJoint.lowerRotation[3]
		};

		local upperValues = {
			attacherJoint.upperRotation[1],
			attacherJoint.upperRotation[2],
			attacherJoint.upperRotation[3]
		};

		for valueNumber = 1, 3 do
			local lowerValue = lowerValues[valueNumber];
			local upperValue = upperValues[valueNumber];

			if upperValue < lowerValue then
				upperValues[valueNumber] = lowerValue;
				lowerValues[valueNumber] = upperValue;
			end;
		end;

		attacherJoint.rotX = MathUtil.clamp(attacherJoint.rotX, lowerValues[1], upperValues[1]);
		attacherJoint.rotY = MathUtil.clamp(attacherJoint.rotY, lowerValues[2], upperValues[2]);
		attacherJoint.rotZ = MathUtil.clamp(attacherJoint.rotZ, lowerValues[3], upperValues[3]);
	end;

	local rotationNode2 = xmlFile:getValue(baseName .. ".rotationNode2#node", nil, self.components, self.i3dMappings);

	if rotationNode2 ~= nil then
		attacherJoint.rotationNode2 = rotationNode2;

		local defaultLowerRotation2 = {
			-attacherJoint.lowerRotation[1],
			-attacherJoint.lowerRotation[2],
			-attacherJoint.lowerRotation[3]
		};

		attacherJoint.lowerRotation2 = xmlFile:getValue(baseName .. ".rotationNode2#lowerRotation", defaultLowerRotation2, true);

		local defaultUpperRotation2 = {
			-attacherJoint.upperRotation[1],
			-attacherJoint.upperRotation[2],
			-attacherJoint.upperRotation[3]
		};

		attacherJoint.upperRotation2 = xmlFile:getValue(baseName .. ".rotationNode2#upperRotation", defaultUpperRotation2, true);
	end;

	attacherJoint.transNode = xmlFile:getValue(baseName .. ".transNode#node", nil, self.components, self.i3dMappings);

	if attacherJoint.transNode ~= nil then
		attacherJoint.transNodeOrgTrans = {getTranslation(attacherJoint.transNode)};

		attacherJoint.transNodeHeight = xmlFile:getValue(baseName .. ".transNode#height", 0.12);
		attacherJoint.transNodeMinY = xmlFile:getValue(baseName .. ".transNode#minY");
		attacherJoint.transNodeMaxY = xmlFile:getValue(baseName .. ".transNode#maxY");
		attacherJoint.transNodeDependentBottomArm = xmlFile:getValue(baseName .. ".transNode.dependentBottomArm#node", nil, self.components, self.i3dMappings);
		attacherJoint.transNodeDependentBottomArmThreshold = xmlFile:getValue(baseName .. ".transNode.dependentBottomArm#threshold", math.huge);
		attacherJoint.transNodeDependentBottomArmRotation = xmlFile:getValue(baseName .. ".transNode.dependentBottomArm#rotation", "0 0 0", true);
	end;

	if (attacherJoint.rotationNode ~= nil or attacherJoint.transNode ~= nil) and xmlFile:getValue(baseName .. ".distanceToGround#lower") == nil then
		Logging.xmlWarning(self.xmlFile, "Missing '.distanceToGround#lower' for attacherJoint '%s'. Use console command 'gsVehicleAnalyze' to get correct values!", baseName);
	end;

	attacherJoint.lowerDistanceToGround = xmlFile:getValue(baseName .. ".distanceToGround#lower", 0.7);

	if (attacherJoint.rotationNode ~= nil or attacherJoint.transNode ~= nil) and xmlFile:getValue(baseName .. ".distanceToGround#upper") == nil then
		Logging.xmlWarning(self.xmlFile, "Missing '.distanceToGround#upper' for attacherJoint '%s'. Use console command 'gsVehicleAnalyze' to get correct values!", baseName);
	end;

	attacherJoint.upperDistanceToGround = xmlFile:getValue(baseName .. ".distanceToGround#upper", 1);

	if attacherJoint.upperDistanceToGround < attacherJoint.lowerDistanceToGround then
		Logging.xmlWarning(self.xmlFile, "distanceToGround#lower may not be larger than distanceToGround#upper for attacherJoint '%s'. Switching values!", baseName);

		local copy = attacherJoint.lowerDistanceToGround;

		attacherJoint.lowerDistanceToGround = attacherJoint.upperDistanceToGround;
		attacherJoint.upperDistanceToGround = copy;
	end;

	attacherJoint.lowerRotationOffset = xmlFile:getValue(baseName .. "#lowerRotationOffset", 0);
	attacherJoint.upperRotationOffset = xmlFile:getValue(baseName .. "#upperRotationOffset", 0);
	attacherJoint.lockDownRotLimit = xmlFile:getValue(baseName .. "#lockDownRotLimit", false);
	attacherJoint.lockUpRotLimit = xmlFile:getValue(baseName .. "#lockUpRotLimit", false);
	attacherJoint.lockDownTransLimit = xmlFile:getValue(baseName .. "#lockDownTransLimit", true);
	attacherJoint.lockUpTransLimit = xmlFile:getValue(baseName .. "#lockUpTransLimit", false);

	local lowerRotLimitStr = "20 20 20";

	if jointType ~= AttacherJoints.JOINTTYPE_IMPLEMENT then
		lowerRotLimitStr = "0 0 0";
	end;

	local lx, ly, lz = xmlFile:getValue(baseName .. "#lowerRotLimit", lowerRotLimitStr);

	attacherJoint.lowerRotLimit = {
		math.abs(Utils.getNoNil(lx, 20)),
		math.abs(Utils.getNoNil(ly, 20)),
		math.abs(Utils.getNoNil(lz, 20))
	};

	local ux, uy, uz = xmlFile:getValue(baseName .. "#upperRotLimit");

	attacherJoint.upperRotLimit = {
		math.abs(Utils.getNoNil(Utils.getNoNil(ux, lx), 20)),
		math.abs(Utils.getNoNil(Utils.getNoNil(uy, ly), 20)),
		math.abs(Utils.getNoNil(Utils.getNoNil(uz, lz), 20))
	};

	local lowerTransLimitStr = "0.5 0.5 0.5";

	if jointType ~= AttacherJoints.JOINTTYPE_IMPLEMENT then
		lowerTransLimitStr = "0 0 0";
	end;

	lx, ly, lz = xmlFile:getValue(baseName .. "#lowerTransLimit", lowerTransLimitStr);

	attacherJoint.lowerTransLimit = {
		math.abs(Utils.getNoNil(lx, 0)),
		math.abs(Utils.getNoNil(ly, 0)),
		math.abs(Utils.getNoNil(lz, 0))
	};

	ux, uy, uz = xmlFile:getValue(baseName .. "#upperTransLimit");

	attacherJoint.upperTransLimit = {
		math.abs(Utils.getNoNil(Utils.getNoNil(ux, lx), 0)),
		math.abs(Utils.getNoNil(Utils.getNoNil(uy, ly), 0)),
		math.abs(Utils.getNoNil(Utils.getNoNil(uz, lz), 0))
	};

	attacherJoint.jointPositionOffset = xmlFile:getValue(baseName .. "#jointPositionOffset", "0 0 0", true);
	attacherJoint.rotLimitSpring = xmlFile:getValue(baseName .. "#rotLimitSpring", "0 0 0", true);
	attacherJoint.rotLimitDamping = xmlFile:getValue(baseName .. "#rotLimitDamping", "1 1 1", true);
	attacherJoint.rotLimitForceLimit = xmlFile:getValue(baseName .. "#rotLimitForceLimit", "-1 -1 -1", true);
	attacherJoint.transLimitSpring = xmlFile:getValue(baseName .. "#transLimitSpring", "0 0 0", true);
	attacherJoint.transLimitDamping = xmlFile:getValue(baseName .. "#transLimitDamping", "1 1 1", true);
	attacherJoint.transLimitForceLimit = xmlFile:getValue(baseName .. "#transLimitForceLimit", "-1 -1 -1", true);
	attacherJoint.moveDefaultTime = xmlFile:getValue(baseName .. "#moveTime", 0.5) * 1000
	attacherJoint.moveTime = attacherJoint.moveDefaultTime;
	attacherJoint.enableCollision = xmlFile:getValue(baseName .. "#enableCollision", false);

	local topArmFilename;

	if self.getConfigurationKey ~= nil and self.getXMLPrefix ~= nil then
		for configurationName, _ in pairs(self.configurations) do
			local configurationKey = self:getConfigurationKey(self:getXMLPrefix(configurationName), self.configurations[configurationName], configurationName);
			local extraOptionNumber = 0;

			while true do
				local extraOptionKey = string.format(configurationKey .. ".extraOption(%d)", extraOptionNumber);
					
				if not self.xmlFile:hasProperty(extraOptionKey) then
					break;
				end;

				local attacherJointIndices = {string.getVector(getXMLString(self.xmlFile.handle, extraOptionKey .. "#attacherJointIndices"))};
				
				if #attacherJointIndices > 0 then
					for _, attacherJointIndice in pairs(attacherJointIndices) do
						if index + 1 == attacherJointIndice then
							topArmFilename = getXMLString(self.xmlFile.handle, extraOptionKey .. "#topArmFilename");
						end;
					end;
				end;

				extraOptionNumber = extraOptionNumber + 1;
			end;
		end;
	end;

	topArmFilename = topArmFilename or xmlFile:getValue(baseName .. ".topArm#filename");

	if topArmFilename ~= nil then
		local baseNode = xmlFile:getValue(baseName .. ".topArm#baseNode", nil, self.components, self.i3dMappings);
		
		if baseNode ~= nil then
			topArmFilename = Utils.getFilename(topArmFilename, self.baseDirectory);

			local arguments = {
				xmlFile = xmlFile,
				baseName = baseName,
				attacherJoint = attacherJoint,
				baseNode = baseNode
			};

			attacherJoint.sharedLoadRequestIdTopArm = self:loadSubSharedI3DFile(topArmFilename, false, false, self.onTopArmI3DLoaded, self, arguments);
		end;
	else
		local topArmRotationNode = xmlFile:getValue(baseName .. ".topArm#rotationNode", nil, self.components, self.i3dMappings);
		local translationNode = xmlFile:getValue(baseName .. ".topArm#translationNode", nil, self.components, self.i3dMappings);
		local referenceNode = xmlFile:getValue(baseName .. ".topArm#referenceNode", nil, self.components, self.i3dMappings);

		if topArmRotationNode ~= nil then
			local topArm = {rotationNode = topArmRotationNode};

			topArm.rotX, topArm.rotY, topArm.rotZ = getRotation(topArmRotationNode);

			if translationNode ~= nil and referenceNode ~= nil then
				topArm.translationNode = translationNode;

				local x, y, z = getTranslation(translationNode);

				if math.abs(x) >= 0.0001 or math.abs(y) >= 0.0001 or math.abs(z) >= 0.0001 then
					Logging.xmlWarning(self.xmlFile, "TopArm translation of attacherJoint '%s' is not 0/0/0!", baseName);
				end;

				topArm.referenceDistance = calcDistanceFrom(referenceNode, translationNode);
			end;

			topArm.zScale = MathUtil.sign(xmlFile:getValue(baseName .. ".topArm#zScale", 1));
			topArm.toggleVisibility = xmlFile:getValue(baseName .. ".topArm#toggleVisibility", false);

			if topArm.toggleVisibility then
				setVisibility(topArm.rotationNode, false);
			end;

			topArm.rotationNodes = {};
			attacherJoint.topArm = topArm;
		end;
	end;

	local bottomArmRotationNode = xmlFile:getValue(baseName .. ".bottomArm#rotationNode", nil, self.components, self.i3dMappings);
	local translationNode = xmlFile:getValue(baseName .. ".bottomArm#translationNode", nil, self.components, self.i3dMappings);
	local referenceNode = xmlFile:getValue(baseName .. ".bottomArm#referenceNode", nil, self.components, self.i3dMappings);

	if bottomArmRotationNode ~= nil then
		local bottomArm = {
			rotationNode = bottomArmRotationNode,
			rotationNodeDir = createTransformGroup("rotationNodeDirTemp")
		};

		link(getParent(bottomArmRotationNode), bottomArm.rotationNodeDir);
		setTranslation(bottomArm.rotationNodeDir, getTranslation(bottomArmRotationNode));
		setRotation(bottomArm.rotationNodeDir, getRotation(bottomArmRotationNode));

		bottomArm.lastDirection = {0, 0, 0};

		bottomArm.rotX, bottomArm.rotY, bottomArm.rotZ = xmlFile:getValue(baseName .. ".bottomArm#startRotation", {getRotation(rotationNode)});

		function bottomArm.interpolatorGet()
			return getRotation(bottomArm.rotationNode);
		end;

		function bottomArm.interpolatorSet(x, y, z)
			setRotation(bottomArm.rotationNode, x, y, z);

			if self.setMovingToolDirty ~= nil then
				self:setMovingToolDirty(bottomArm.rotationNode);
			end;
		end;

		function bottomArm.interpolatorFinished(_)
			bottomArm.bottomArmInterpolating = false;
		end;

		bottomArm.interpolatorKey = rotationNode .. "rotation";
		bottomArm.bottomArmInterpolating = false;

		if translationNode ~= nil and referenceNode ~= nil then
			bottomArm.translationNode = translationNode;

			local x, y, z = getTranslation(translationNode);

			if math.abs(x) >= 0.0001 or math.abs(y) >= 0.0001 or math.abs(z) >= 0.0001 then
				Logging.xmlWarning(self.xmlFile, "BottomArm translation of attacherJoint '%s' is not 0/0/0!", baseName);
			end;

			bottomArm.referenceDistance = calcDistanceFrom(referenceNode, translationNode);
		end;

		bottomArm.zScale = MathUtil.sign(xmlFile:getValue(baseName .. ".bottomArm#zScale", 1));
		bottomArm.lockDirection = xmlFile:getValue(baseName .. ".bottomArm#lockDirection", true);
		bottomArm.resetSpeed = xmlFile:getValue(baseName .. ".bottomArm#resetSpeed", 45);
		bottomArm.toggleVisibility = xmlFile:getValue(baseName .. ".bottomArm#toggleVisibility", false);

		if bottomArm.toggleVisibility then
			setVisibility(bottomArm.rotationNode, false);
		end;

		if jointType == AttacherJoints.JOINTTYPE_IMPLEMENT then
			local toolbarI3dFilename = Utils.getFilename(xmlFile:getValue(baseName .. ".toolbar#filename", "$data/shared/assets/toolbar.i3d"), self.baseDirectory);

			local arguments = {
				bottomArm = bottomArm,
				referenceNode = referenceNode
			};

			bottomArm.sharedLoadRequestIdToolbar = self:loadSubSharedI3DFile(toolbarI3dFilename, false, false, self.onBottomArmToolbarI3DLoaded, self, arguments);
		end;

		attacherJoint.bottomArm = bottomArm;
	end;

	if self.isClient then
		attacherJoint.sampleAttach = g_soundManager:loadSampleFromXML(xmlFile, baseName, "attachSound", self.baseDirectory, self.components, 1, AudioGroup.VEHICLE, self.i3dMappings, self);
		attacherJoint.sampleDetach = g_soundManager:loadSampleFromXML(xmlFile, baseName, "detachSound", self.baseDirectory, self.components, 1, AudioGroup.VEHICLE, self.i3dMappings, self);
	end

	attacherJoint.steeringBarLeftNode = xmlFile:getValue(baseName .. ".steeringBars#leftNode", nil, self.components, self.i3dMappings);
	attacherJoint.steeringBarRightNode = xmlFile:getValue(baseName .. ".steeringBars#rightNode", nil, self.components, self.i3dMappings);
	attacherJoint.steeringBarForceUsage = xmlFile:getValue(baseName .. ".steeringBars#forceUsage", true);
	attacherJoint.visualNodes = xmlFile:getValue(baseName .. ".visuals#nodes", nil, self.components, self.i3dMappings, true);

	for i = 1, #attacherJoint.visualNodes do
		local visualNode = attacherJoint.visualNodes[i];

		if specAttacherJoints.visualNodeToAttacherJoints[visualNode] == nil then
			specAttacherJoints.visualNodeToAttacherJoints[visualNode] = {};
		end;

		table.insert(specAttacherJoints.visualNodeToAttacherJoints[visualNode], attacherJoint);
	end;

	attacherJoint.hideVisuals = xmlFile:getValue(baseName .. ".visuals#hide", nil, self.components, self.i3dMappings, true);

	for i = 1, #attacherJoint.hideVisuals do
		local hideNode = attacherJoint.hideVisuals[i];

		if specAttacherJoints.hideVisualNodeToAttacherJoints[hideNode] == nil then
			specAttacherJoints.hideVisualNodeToAttacherJoints[hideNode] = {};
		end;

		table.insert(specAttacherJoints.hideVisualNodeToAttacherJoints[hideNode], attacherJoint);
	end;

	attacherJoint.changeObjects = {};

	ObjectChangeUtil.loadObjectChangeFromXML(xmlFile, baseName, attacherJoint.changeObjects, self.components, self);
	ObjectChangeUtil.setObjectChanges(attacherJoint.changeObjects, false, self, self.setMovingToolDirty, true);

	attacherJoint.delayedObjectChanges = xmlFile:getValue(baseName .. "#delayedObjectChanges", true);
	attacherJoint.delayedObjectChangesOnAttach = xmlFile:getValue(baseName .. "#delayedObjectChangesOnAttach", false);

	attacherJoint.additionalAttachment = {attacherJointDirection = xmlFile:getValue(baseName .. ".additionalAttachment#attacherJointDirection")};

	attacherJoint.rootNode = xmlFile:getValue(baseName .. "#rootNode", self.components[1].node, self.components, self.i3dMappings);
	attacherJoint.rootNodeBackup = attacherJoint.rootNode;
	attacherJoint.jointIndex = 0;

	local comboTime = xmlFile:getValue(baseName .. "#comboTime");

	if comboTime ~= nil then
		table.insert(specAttacherJoints.attacherJointCombos.joints, {
			jointIndex = index + 1,
			time = MathUtil.clamp(comboTime, 0, 1) * specAttacherJoints.attacherJointCombos.duration
		});
	end;

	local schemaKey = baseName .. ".schema";

	if xmlFile:hasProperty(schemaKey) then
		local x, y = xmlFile:getValue(schemaKey .. "#position");
		local liftedOffsetX, liftedOffsetY = xmlFile:getValue(schemaKey .. "#liftedOffset", "0 5");

		self.schemaOverlay:addAttacherJoint(x, y, xmlFile:getValue(schemaKey .. "#rotation", 0), xmlFile:getValue(schemaKey .. "#invertX", false), liftedOffsetX, liftedOffsetY);
	else
		Logging.xmlWarning(self.xmlFile, "Missing schema overlay attacherJoint '%s'!", baseName);
	end;

	return true;
end;

function AddConfiguration:onTopArmI3DLoaded(superFunc, i3dNode, failedReason, args)
	if i3dNode ~= 0 then
		local xmlFile = args.xmlFile;
		local baseName = args.baseName;
		local attacherJoint = args.attacherJoint;
		local baseNode = args.baseNode;
		local rootNode = getChildAt(i3dNode, 0);

		link(baseNode, rootNode);
		delete(i3dNode);
		setTranslation(rootNode, 0, 0, 0);

		local translationNode = getChildAt(rootNode, 0);
		local referenceNode = getChildAt(translationNode, 0);

		local topArm = {
			rotationNode = rootNode,
			rotZ = 0,
			rotY = 0,
			rotX = 0,
			translationNode = translationNode
		};

		local _, _, referenceDistance = getTranslation(referenceNode);

		topArm.referenceDistance = referenceDistance;
		topArm.zScale = 1;

		local zScale = MathUtil.sign(xmlFile:getValue(baseName .. ".topArm#zScale", 1));

		if zScale < 0 then
			topArm.rotY = math.pi;

			setRotation(rootNode, topArm.rotX, topArm.rotY, topArm.rotZ);
		end;

		if getNumOfChildren(rootNode) > 1 then
			topArm.scaleNode = getChildAt(rootNode, 1);
			
			local scaleReferenceNode = getChildAt(topArm.scaleNode, 0);
			local _, _, scaleReferenceDistance = getTranslation(scaleReferenceNode);
			
			topArm.scaleReferenceDistance = scaleReferenceDistance;
		end;

		topArm.toggleVisibility = xmlFile:getValue(baseName .. ".topArm#toggleVisibility", false);

		if topArm.toggleVisibility then
			setVisibility(topArm.rotationNode, false);
		end;

		local colorValue = xmlFile:getValue(baseName .. ".topArm#color", nil, true);
		local colorValue2 = xmlFile:getValue(baseName .. ".topArm#color2", nil, true);
		local decalColor = xmlFile:getValue(baseName .. ".topArm#decalColor", nil, true);
		local useMainColor = xmlFile:getValue(baseName .. ".topArm#secondPartUseMainColor", true);
		local useBrandDecal = xmlFile:getValue(baseName .. ".topArm#useBrandDecal", true);
		local topArmColor, topArmColor2, topArmColor3, materialId, materialId2, materialId3;
		
		--## change top arm color
		if self.getConfigurationKey ~= nil and self.getXMLPrefix ~= nil then
			for configurationName, _ in pairs(self.configurations) do
				local configurationKey = self:getConfigurationKey(self:getXMLPrefix(configurationName), self.configurations[configurationName], configurationName);
				local attacherJointIndices = {string.getVector(getXMLString(self.xmlFile.handle, configurationKey .. ".colorChanges.topArm#attacherJointIndices"))};
				local topArmColorStr = getXMLString(self.xmlFile.handle, configurationKey .. ".colorChanges.topArm#color");
				local topArmColor2Str = getXMLString(self.xmlFile.handle, configurationKey .. ".colorChanges.topArm#color2");
				local topArmColor3Str = getXMLString(self.xmlFile.handle, configurationKey .. ".colorChanges.topArm#decalColor");
				local getColorFromConfig = getXMLString(self.xmlFile.handle, configurationKey .. ".colorChanges.topArm#getColorFromConfig");
				local getColor2FromConfig = getXMLString(self.xmlFile.handle, configurationKey .. ".colorChanges.topArm#getColor2FromConfig");
				local getColor3FromConfig = getXMLString(self.xmlFile.handle, configurationKey .. ".colorChanges.topArm#getDecalColorFromConfig");
				
				if topArmColorStr ~= nil or getColorFromConfig ~= nil then
					topArmColor = g_brandColorManager:getBrandColorByName(topArmColorStr);
					
					if topArmColor == nil then
						topArmColor = string.getVectorN(topArmColorStr, 3);
					end;

					if getColorFromConfig ~= nil then
						if self.configurations[getColorFromConfig] ~= nil then	
							if self:getIsColorConfiguration(getColorFromConfig) then
								topArmColor = ConfigurationUtil.getColorByConfigId(self, getColorFromConfig, self.configurations[getColorFromConfig]);
								materialId = ConfigurationUtil.getMaterialByConfigId(self, getColorFromConfig, self.configurations[getColorFromConfig]);
							else
								printError("(colorChanges) :: The configuration '" .. getColorFromConfig .. "' is not an color configuration! Stop coloring top arms!", false, true);
							end;
						end;
					end;
					
					if topArmColor ~= nil then
						topArmColor2 = topArmColor;

						if topArmColor2Str ~= nil then
							topArmColor2 = g_brandColorManager:getBrandColorByName(topArmColor2Str);

							if topArmColor2 == nil then
								topArmColor2 = string.getVectorN(topArmColor2Str, 3);
							end;
						end;

						if getColor2FromConfig ~= nil then
							if self.configurations[getColor2FromConfig] ~= nil then	
								if self:getIsColorConfiguration(getColor2FromConfig) then
									topArmColor2 = ConfigurationUtil.getColorByConfigId(self, getColor2FromConfig, self.configurations[getColor2FromConfig]);
									materialId2 = ConfigurationUtil.getMaterialByConfigId(self, getColor2FromConfig, self.configurations[getColor2FromConfig]);
								else
									printError("(colorChanges) :: The configuration '" .. getColor2FromConfig .. "' is not an color configuration! Stop coloring top arms!", false, true);
								end;
							end;
						end;

						if topArmColor3Str ~= nil then
							topArmColor3 = g_brandColorManager:getBrandColorByName(topArmColor3Str);

							if topArmColor3 == nil then
								topArmColor3 = string.getVectorN(topArmColor3Str, 3);
							end;
						end;

						if getColor3FromConfig ~= nil then
							if self.configurations[getColor3FromConfig] ~= nil then	
								if self:getIsColorConfiguration(getColor3FromConfig) then
									topArmColor3 = ConfigurationUtil.getColorByConfigId(self, getColor3FromConfig, self.configurations[getColor3FromConfig]);
									materialId3 = ConfigurationUtil.getMaterialByConfigId(self, getColor3FromConfig, self.configurations[getColor3FromConfig]);
								else
									printError("(colorChanges) :: The configuration '" .. getColor3FromConfig .. "' is not an color configuration! Stop coloring top arms!", false, true);
								end;
							end;
						end;
					end;

					if materialId == nil then
						materialId = getXMLInt(self.xmlFile.handle, configurationKey .. ".colorChanges.topArm#materialId");
					end;

					if materialId2 == nil then
						materialId2 = getXMLInt(self.xmlFile.handle, configurationKey .. ".colorChanges.topArm#materialId2");
					end;
					
					if materialId3 == nil then
						materialId3 = getXMLInt(self.xmlFile.handle, configurationKey .. ".colorChanges.topArm#materialId3");
					end;
				end;
			end;
		end;

		if not useBrandDecal then
			local brandDecal = I3DUtil.getChildByName(rootNode, "brandDecal");

			if brandDecal ~= nil then
				delete(brandDecal);
			else
				Logging.xmlDevWarning(xmlFile, "Unable to find brand decal to remove for '%s'", baseName .. ".topArm");
			end;
		end;

		colorValue = topArmColor or colorValue;

		local _, _, _, mat0 = I3DUtil.getShaderParameterRec(rootNode, "colorMat0");
		local _, _, _, mat1 = I3DUtil.getShaderParameterRec(rootNode, "colorMat1");

		if colorValue ~= nil then
			local material = xmlFile:getValue(baseName .. ".topArm#material");

			I3DUtil.setShaderParameterRec(rootNode, "colorMat0", colorValue[1], colorValue[2], colorValue[3], materialId or material or mat0);
		end;

		colorValue2 = topArmColor2 or colorValue2;

		if colorValue2 ~= nil then
			local material2 = xmlFile:getValue(baseName .. ".topArm#material2");

			I3DUtil.setShaderParameterRec(rootNode, "colorMat1", colorValue2[1], colorValue2[2], colorValue2[3], materialId2 or material2 or mat1);
		end;

		if useMainColor then
			if colorValue ~= nil then
				local material = xmlFile:getValue(baseName .. ".topArm#material");

				I3DUtil.setShaderParameterRec(rootNode, "colorMat3", colorValue[1], colorValue[2], colorValue[3], materialId or material or mat0);
			else
				local x, y, z, w = I3DUtil.getShaderParameterRec(rootNode, "colorMat0");

				I3DUtil.setShaderParameterRec(rootNode, "colorMat3", x, y, z, w);
			end;
		elseif colorValue2 ~= nil then
			local material2 = xmlFile:getValue(baseName .. ".topArm#material2");

			I3DUtil.setShaderParameterRec(rootNode, "colorMat3", colorValue2[1], colorValue2[2], colorValue2[3], materialId2 or material2 or mat1);
		else
			local x, y, z, w = I3DUtil.getShaderParameterRec(rootNode, "colorMat1");

			I3DUtil.setShaderParameterRec(rootNode, "colorMat3", x, y, z, w);
		end;

		decalColor = topArmColor3 or decalColor;

		if decalColor == nil and colorValue ~= nil then
			local brightness = MathUtil.getBrightnessFromColor(colorValue[1], colorValue[2], colorValue[3]);

			brightness = brightness > 0.075 and 1 or 0;

			decalColor = {1 - brightness, 1 - brightness, 1 - brightness};
		end;

		if decalColor ~= nil then
			I3DUtil.setShaderParameterRec(rootNode, "colorMat2", decalColor[1], decalColor[2], decalColor[3], materialId3 or 1);
		end;

		topArm.rotationNodes = {};

		xmlFile:iterate(baseName .. ".topArm.rotationNode", function (index, key)
			local nodePath = xmlFile:getValue(key .. "#node");

			if nodePath ~= nil then
				local node = I3DUtil.indexToObject(rootNode, nodePath);

				if node ~= nil then
					local rotation = xmlFile:getValue(key .. "#rotation", nil, true);

					if rotation ~= nil then
						local rotationNode = {
							node = node,
							activeRotation = {getRotation(node)},
							inactiveRotation = rotation
						};

						setRotation(node, rotationNode.inactiveRotation[1], rotationNode.inactiveRotation[2], rotationNode.inactiveRotation[3]);
						table.insert(topArm.rotationNodes, rotationNode);
					end;
				end;
			end;
		end);

		attacherJoint.topArm = topArm;
	end;
end;

function AddConfiguration:onDynamicallyPartI3DLoaded(superFunc, i3dNode, failedReason, args)
	local xmlFile = args.xmlFile;
	local partKey = args.partKey;
	local dynamicallyLoadedPart = args.dynamicallyLoadedPart;
	
	if i3dNode ~= 0 then
		local node = xmlFile:getValue(partKey .. "#node", "0|0", i3dNode);

		if node == nil then
			Logging.xmlWarning(xmlFile, "Failed to load dynamicallyLoadedPart '%s'. Unable to find node in loaded i3d", partKey);

			return false;
		end;

		local linkNode = xmlFile:getValue(partKey .. "#linkNode", "0>", self.components, self.i3dMappings);

		if linkNode == nil then
			Logging.xmlWarning(xmlFile, "Failed to load dynamicallyLoadedPart '%s'. Unable to find linkNode", partKey);

			return false;
		end;

		--## change dynamicallyLoadedPart color
		if self.getConfigurationKey ~= nil and self.getXMLPrefix ~= nil then
			for configurationName, _ in pairs(self.configurations) do
				local configurationKey = self:getConfigurationKey(self:getXMLPrefix(configurationName), self.configurations[configurationName], configurationName);
				local dynamicallyLoadedPartNumber = 0;

				while true do
					local dynamicallyLoadedPartKey = configurationKey .. ".colorChanges.dynamicallyLoadedParts.dynamicallyLoadedPart(" .. tostring(dynamicallyLoadedPartNumber) .. ")";

					if not self.xmlFile:hasProperty(dynamicallyLoadedPartKey) then
						break;
					end;

					local linkNodeCheck = getXMLString(self.xmlFile.handle, dynamicallyLoadedPartKey .. "#node");
					local linkNodeCheck2 = getXMLString(self.xmlFile.handle, partKey .. "#linkNode");

					if linkNodeCheck == linkNodeCheck2 then
						local dynamicallyLoadedPartColorStr = getXMLString(self.xmlFile.handle, dynamicallyLoadedPartKey .. "#color");
						local dynamicallyLoadedPartColor2Str = getXMLString(self.xmlFile.handle, dynamicallyLoadedPartKey .. "#color2");
							
						if dynamicallyLoadedPartColorStr ~= nil then
							local dynamicallyLoadedPartColor = g_brandColorManager:getBrandColorByName(dynamicallyLoadedPartColorStr);
								
							if dynamicallyLoadedPartColor == nil then
								dynamicallyLoadedPartColor = string.getVectorN(dynamicallyLoadedPartColorStr, 3);
							end;
								
							local dynamicallyLoadedPartColor2 = dynamicallyLoadedPartColor;
								
							if dynamicallyLoadedPartColor2Str ~= nil then
								dynamicallyLoadedPartColor2 = g_brandColorManager:getBrandColorByName(dynamicallyLoadedPartColor2Str);
									
								if dynamicallyLoadedPartColor2 == nil then
									dynamicallyLoadedPartColor2 = string.getVectorN(dynamicallyLoadedPartColor2Str, 3);
								end;
							end;
								
							local materialId = getXMLInt(self.xmlFile.handle, dynamicallyLoadedPartKey .. "#materialId");
							local materialIdBackUp = materialId;
							local materialId1 = getXMLInt(self.xmlFile.handle, dynamicallyLoadedPartKey .. "#materialId1");
							local materialId2 = getXMLInt(self.xmlFile.handle, dynamicallyLoadedPartKey .. "#materialId2");
								
							if dynamicallyLoadedPartColor ~= nil then
								if materialId1 ~= nil then
									materialId = materialId1;
								end;
									
								I3DUtil.setShaderParameterRec(node, "colorMat0", dynamicallyLoadedPartColor[1], dynamicallyLoadedPartColor[2], dynamicallyLoadedPartColor[3], Utils.getNoNil(materialId, Utils.getNoNil(dynamicallyLoadedPartColor[4], 0)));
									
								printDebug("(colorChanges) :: Change dynamicallyLoadedPart color (colorMat0) successfully in '" .. self:getFullName() .. "' to '" .. dynamicallyLoadedPartColorStr .. "' for node '" .. getName(node) .. "'.", 1, true);
							else
								printError("(colorChanges) :: Failed to load color (colorMat0) '" .. tostring(dynamicallyLoadedPartColorStr) .. "' in '" .. self:getFullName() .. "'! Stop coloring dynamicallyLoadedPart!", false, false);
							end;
								
							if dynamicallyLoadedPartColor2 ~= nil then
								materialId = materialIdBackUp;
									
								if materialId2 ~= nil then
									materialId = materialId2;
								end;
									
								I3DUtil.setShaderParameterRec(node, "colorMat1", dynamicallyLoadedPartColor2[1], dynamicallyLoadedPartColor2[2], dynamicallyLoadedPartColor2[3], Utils.getNoNil(materialId, Utils.getNoNil(dynamicallyLoadedPartColor2[4], 0)));
									
								printDebug("(colorChanges) :: Change dynamicallyLoadedPart color (colorMat1) successfully in '" .. self:getFullName() .. "' to '" .. Utils.getNoNil(dynamicallyLoadedPartColor2Str, dynamicallyLoadedPartColorStr) .. "' for node '" .. getName(node) .. "'.", 1, true);
							else
								printError("(colorChanges) :: Failed to load color (colorMat1) '" .. tostring(dynamicallyLoadedPartColor2Str) .. "' in '" .. self:getFullName() .. "'! Stop coloring dynamicallyLoadedPart!", false, false);
							end;
								
							materialId = materialIdBackUp;
						end;
							
						local decalMaterialHolderNode = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile.handle, dynamicallyLoadedPartKey .. "#decalMaterialHolderNode"), self.i3dMappings);
							
						if decalMaterialHolderNode ~= nil then
							local decalIndex = Utils.getNoNil(getXMLInt(self.xmlFile.handle, dynamicallyLoadedPartKey .. "#decalIndex"), 0);
							local getChildFromDynamicallyLoadedPart = Utils.getNoNil(getXMLBool(self.xmlFile.handle, dynamicallyLoadedPartKey .. "#getChildFromDynamicallyLoadedPart"), false);
									
							if getChildFromDynamicallyLoadedPart then
								node = getChildAt(node, 0);
							end;

							if getHasClassId(node, ClassIds.SHAPE) then
								if getHasClassId(getChildAt(node, decalIndex), ClassIds.SHAPE) then
									local newMaterial = getMaterial(decalMaterialHolderNode, 0);

									setMaterial(getChildAt(node, decalIndex), newMaterial, 0);
										
									printDebug("(colorChanges) :: Change dynamicallyLoadedPart decal color successfully in '" .. self:getFullName() .. "' for node '" .. getName(getChildAt(node, decalIndex)) .. "'.", 1, true);
								else
									printError("(colorChanges) :: The decal node  '" .. getName(getChildAt(node, decalIndex)) .. "' is not a shape in '" .. self:getFullName() .. "'! Stop change dynamicallyLoadedPart decal!", false, false);
								end;
							else
								printError("(colorChanges) :: The parent node  '" .. getName(node) .. "' is not a shape in '" .. self:getFullName() .. "'! Stop change dynamicallyLoadedPart decal!", false, false);
        					end;
						end;
					end;

					dynamicallyLoadedPartNumber = dynamicallyLoadedPartNumber + 1;
				end;
			end;
		end;

		local x, y, z = xmlFile:getValue(partKey .. "#position");

		if x ~= nil and y ~= nil and z ~= nil then
			setTranslation(node, x, y, z);
		end;

		local rotationNode = xmlFile:getValue(partKey .. "#rotationNode", node, i3dNode);
		local rotX, rotY, rotZ = xmlFile:getValue(partKey .. "#rotation");

		if rotX ~= nil and rotY ~= nil and rotZ ~= nil then
			setRotation(rotationNode, rotX, rotY, rotZ);
		end;

		local shaderParameterName = xmlFile:getValue(partKey .. "#shaderParameterName");
		local sx, sy, sz, sw = xmlFile:getValue(partKey .. "#shaderParameter");

		if shaderParameterName ~= nil and sx ~= nil and sy ~= nil and sz ~= nil and sw ~= nil then
			setShaderParameter(node, shaderParameterName, sx, sy, sz, sw, false);
		end;

		link(linkNode, node);
		delete(i3dNode);
		table.insert(self.spec_dynamicallyLoadedParts.parts, dynamicallyLoadedPart);
	end;
end;

function AddConfiguration:loadBeaconLightFromXML(superFunc, xmlFile, key)
	local specLights = getSpecByName(self, "lights");
	local beaconLight = {node = xmlFile:getValue(key .. "#node", nil, self.components, self.i3dMappings)};

	if beaconLight.node ~= nil then
		local lightXmlFilename = xmlFile:getValue(key .. "#filename");

		beaconLight.speed = xmlFile:getValue(key .. "#speed");
		beaconLight.realLightRange = xmlFile:getValue(key .. "#realLightRange", 1);
		beaconLight.intensity = xmlFile:getValue(key .. "#intensity");

		local beaconLightNumber = tonumber(string.sub(key, 41, 41));

		if self.getConfigurationKey ~= nil and self.getXMLPrefix ~= nil then
			for configurationName, _ in pairs(self.configurations) do
				local configurationKey = self:getConfigurationKey(self:getXMLPrefix(configurationName), self.configurations[configurationName], configurationName);
				local extraOptionNumber = 0;

				while true do
					local extraOptionKey = string.format(configurationKey .. ".extraOption(%d)", extraOptionNumber);
					
					if not self.xmlFile:hasProperty(extraOptionKey) then
						break;
					end;

					local beaconLightIndices = {string.getVector(getXMLString(self.xmlFile.handle, extraOptionKey .. "#beaconLightIndices"))};
					local beaconLightFilename = getXMLString(self.xmlFile.handle, extraOptionKey .. "#beaconLightFilename");

					if beaconLightFilename ~= nil then
						if #beaconLightIndices > 0 then
							for _, beaconLightIndice in pairs(beaconLightIndices) do
								if beaconLightNumber == beaconLightIndice then
									lightXmlFilename = beaconLightFilename;
								end;
							end;
						else
							lightXmlFilename = beaconLightFilename;
						end;
					end;

					extraOptionNumber = extraOptionNumber + 1;
				end;
			end;
		end;

		if lightXmlFilename ~= nil then
			lightXmlFilename = Utils.getFilename(lightXmlFilename, self.baseDirectory);
			local beaconLightXMLFile = XMLFile.load("beaconLightXML", lightXmlFilename, Lights.beaconLightXMLSchema);

			if beaconLightXMLFile ~= nil then
				specLights.xmlLoadingHandles[beaconLightXMLFile] = true;
				
				beaconLight.xmlFile = beaconLightXMLFile;

				local i3dFilename, beaconLightFilename = beaconLightXMLFile:getValue("beaconLight.filename"), nil;
				
				if i3dFilename ~= nil then
					beaconLight.filename = Utils.getFilename(i3dFilename, self.baseDirectory);

					local sharedLoadRequestId = self:loadSubSharedI3DFile(beaconLight.filename, false, false, self.onBeaconLightI3DLoaded, self, beaconLight);

					table.insert(specLights.sharedLoadRequestIds, sharedLoadRequestId);
				end;
			end;
		else
			beaconLight.lightShaderNode = beaconLight.node;
			beaconLight.realLightNode = xmlFile:getValue(key .. ".realLight#node", nil, self.components, self.i3dMappings);
			beaconLight.rotatorNode = xmlFile:getValue(key .. ".rotator#node", nil, self.components, self.i3dMappings);
			beaconLight.multiBlink = xmlFile:getValue(key .. "#multiBlink", false);
			beaconLight.device = BeaconLightManager.loadDeviceFromXML(xmlFile, key .. ".device");

			if beaconLight.realLightNode ~= nil then
				beaconLight.defaultColor = {getLightColor(beaconLight.realLightNode)};

				setVisibility(beaconLight.realLightNode, false);

				beaconLight.defaultLightRange = getLightRange(beaconLight.realLightNode);

				setLightRange(beaconLight.realLightNode, beaconLight.defaultLightRange * beaconLight.realLightRange);
			end;

			table.insert(specLights.beaconLights, beaconLight);
		end;
	end;
end;

function AddConfiguration:onPowerTakeOffI3DLoaded(superFunc, i3dNode, failedReason, args)
	local xmlFile = args.xmlFile;
	local powerTakeOff = args.powerTakeOff;
	local ptoMaterialId;

	if i3dNode ~= 0 then
		powerTakeOff.startNode = xmlFile:getValue("powerTakeOff.startNode#node", nil, i3dNode);
		powerTakeOff.size = xmlFile:getValue("powerTakeOff#size", 0.19);
		powerTakeOff.minLength = xmlFile:getValue("powerTakeOff#minLength", 0.6);
		powerTakeOff.maxAngle = xmlFile:getValue("powerTakeOff#maxAngle", 45);
		powerTakeOff.zOffset = xmlFile:getValue("powerTakeOff#zOffset", 0);
		powerTakeOff.animationNodes = g_animationManager:loadAnimations(xmlFile, "powerTakeOff.animationNodes", i3dNode, self);

		if xmlFile:getValue("powerTakeOff#isSingleJoint") then
			self:loadSingleJointPowerTakeOff(powerTakeOff, xmlFile, i3dNode);
		elseif xmlFile:getValue("powerTakeOff#isDoubleJoint") then
			self:loadDoubleJointPowerTakeOff(powerTakeOff, xmlFile, i3dNode);
		else
			self:loadBasicPowerTakeOff(powerTakeOff, xmlFile, i3dNode);
		end;
	
		if self.getConfigurationKey ~= nil and self.getXMLPrefix ~= nil then
			for configurationName, _ in pairs(self.configurations) do
				local configurationKey = self:getConfigurationKey(self:getXMLPrefix(configurationName), self.configurations[configurationName], configurationName);
				local ptoColorStr = getXMLString(self.xmlFile.handle, configurationKey .. ".colorChanges.pto#color");
				local ptoDecalColorStr = getXMLString(self.xmlFile.handle, configurationKey .. ".colorChanges.pto#decalColor");
					
				if self.xmlFile:hasProperty(configurationKey .. ".colorChanges.pto#materialId") then
					ptoMaterialId = getXMLInt(self.xmlFile.handle, configurationKey .. ".colorChanges.pto#materialId");
				end;

				if ptoColorStr ~= nil then
					local ptoColor = g_brandColorManager:getBrandColorByName(ptoColorStr);
					
					if ptoColor == nil then
						ptoColor = string.getVectorN(ptoColorStr, 3);
					end;

					if ptoColor ~= nil then
						powerTakeOff.color = ptoColor;

						printDebug("(colorChanges) :: Change pto color successfully in '" .. self:getFullName() .. "' to '" .. ptoColorStr .. "'.", 1, true);
					else
						printError("(colorChanges) :: Failed to load color '" .. tostring(ptoColorStr) .. "' in '" .. self:getFullName() .. "'! Stop coloring pto!", false, false);
					end;
				end;

				if ptoDecalColorStr ~= nil then
					local ptoDecalColor = g_brandColorManager:getBrandColorByName(ptoDecalColorStr);
					
					if ptoDecalColor == nil then
						ptoDecalColor = string.getVectorN(ptoDecalColorStr, 3);
					end;

					if ptoDecalColor ~= nil then
						powerTakeOff.decalColor = ptoDecalColor;

						printDebug("(colorChanges) :: Change pto decal color successfully in '" .. self:getFullName() .. "' to '" .. ptoDecalColorStr .. "'.", 1, true);
					else
						printError("(colorChanges) :: Failed to load color '" .. tostring(ptoDecalColorStr) .. "' in '" .. self:getFullName() .. "'! Stop coloring pto decal!", false, false);
					end;
				end;
			end;
		end;

		if powerTakeOff.color ~= nil and #powerTakeOff.color >= 3 then
			for _, colorMat in pairs({"colorMat0", "colorMat1", "colorMat2"}) do
				I3DUtil.setShaderParameterRec(powerTakeOff.startNode, colorMat, powerTakeOff.color[1], powerTakeOff.color[2], powerTakeOff.color[3], ptoMaterialId or powerTakeOff.color[4] or 0);
			end;
		end;

		if powerTakeOff.decalColor ~= nil and #powerTakeOff.decalColor >= 3 then
			local decalColorShaderParameter = xmlFile:getValue("powerTakeOff#decalColorShaderParameter");

			--if decalColorShaderParameter ~= nil then
				I3DUtil.setShaderParameterRec(powerTakeOff.startNode, "colorMat3", powerTakeOff.decalColor[1], powerTakeOff.decalColor[2], powerTakeOff.decalColor[3]);
			--end
		end;

		link(powerTakeOff.inputNode, powerTakeOff.startNode);

		powerTakeOff.i3dLoaded = true;

		if powerTakeOff.isLocal then
			self:placeLocalPowerTakeOff(powerTakeOff);
		else
			self:parkPowerTakeOff(powerTakeOff);
		end;

		self:updatePowerTakeOff(powerTakeOff, 0);

		delete(i3dNode);
	elseif not self.isDeleted and not self.isDeleting then
		Logging.xmlWarning(self.xmlFile, "Failed to find powerTakeOff in i3d file '%s'", powerTakeOff.filename);
	end;

	xmlFile:delete();
	
	powerTakeOff.xmlFile = nil;
end;

function AddConfiguration:onCrawlerI3DLoaded(superFunc, i3dNode, failedReason, args)
	local xmlFile = args.xmlFile;
	local crawler = args.crawler;

	local materialId, materialId1, crawlerColorName, crawlerColorName1;

	if i3dNode ~= 0 then
		local leftRightKey = crawler.isLeft and "leftNode" or "rightNode";

		crawler.loadedCrawler = xmlFile:getValue("crawler.file#" .. leftRightKey, nil, i3dNode);

		if crawler.loadedCrawler ~= nil then
			link(crawler.linkNode, crawler.loadedCrawler);

			crawler.scrollerNodes = {};

			local crawlerNumber = 0;

			while true do
				local crawlerKey = string.format("crawler.scrollerNodes.scrollerNode(%d)", crawlerNumber);

				if not xmlFile:hasProperty(crawlerKey) then
					break;
				end;

				local entry = {node = xmlFile:getValue(crawlerKey .. "#node", nil, crawler.loadedCrawler)};

				if entry.node ~= nil then
					entry.scrollSpeed = xmlFile:getValue(crawlerKey .. "#scrollSpeed", 1);
					entry.scrollLength = xmlFile:getValue(crawlerKey .. "#scrollLength", 1);
					entry.shaderParameterName = xmlFile:getValue(crawlerKey .. "#shaderParameterName", "offsetUV");
					entry.shaderParameterNamePrev = xmlFile:getValue(crawlerKey .. "#shaderParameterNamePrev");

					if entry.shaderParameterNamePrev ~= nil then
						if not getHasShaderParameter(entry.node, entry.shaderParameterNamePrev) then
							Logging.xmlWarning(xmlFile, "Node '%s' has no shader parameter '%s' (prev) for crawler node '%s'!", getName(entry.node), entry.shaderParameterNamePrev, key);

							return nil;
						end;
					else
						local prevName = "prev" .. entry.shaderParameterName:sub(1, 1):upper() .. entry.shaderParameterName:sub(2);

						if getHasShaderParameter(entry.node, prevName) then
							entry.shaderParameterNamePrev = prevName;
						end;
					end;

					entry.shaderParameterComponent = xmlFile:getValue(crawlerKey .. "#shaderParameterComponent", 1);
					entry.maxSpeed = xmlFile:getValue(crawlerKey .. "#maxSpeed", math.huge) / 1000;
					entry.scrollPosition = 0;

					if crawler.trackWidth ~= 1 and xmlFile:getValue(crawlerKey .. "#isTrackPart", true) then
						setScale(entry.node, crawler.trackWidth, 1, 1);
					end;

					table.insert(crawler.scrollerNodes, entry);
				end;

				crawlerNumber = crawlerNumber + 1;
			end;

			crawler.rotatingParts = {};
			crawlerNumber = 0;

			while true do
				local crawlerKey = string.format("crawler.rotatingParts.rotatingPart(%d)", crawlerNumber);

				if not xmlFile:hasProperty(crawlerKey) then
					break;
				end;

				local entry = {node = xmlFile:getValue(crawlerKey .. "#node", nil, crawler.loadedCrawler)};

				if entry.node ~= nil then
					entry.radius = xmlFile:getValue(crawlerKey .. "#radius");
					entry.speedScale = xmlFile:getValue(crawlerKey .. "#speedScale");

					if entry.speedScale == nil and entry.radius ~= nil then
						entry.speedScale = 1 / entry.radius;
					end;

					table.insert(crawler.rotatingParts, entry);
				end;

				crawlerNumber = crawlerNumber + 1;
			end;

			if self.getConfigurationKey ~= nil and self.getXMLPrefix ~= nil then
				for configurationName, _ in pairs(self.configurations) do
					local configurationKey = self:getConfigurationKey(self:getXMLPrefix(configurationName), self.configurations[configurationName], configurationName);
					local crawlerColorKey = configurationKey .. ".colorChanges.crawler";

					local crawlerColorNumber = 0;

					while true do
						local crawlerColorKey = configurationKey .. ".colorChanges.crawler(" .. tostring(crawlerColorNumber) .. ")";
						
						if not self.xmlFile:hasProperty(crawlerColorKey) then
							break;
						end;
						
						local getColorFromConfig = getXMLString(self.xmlFile.handle, crawlerColorKey .. "#getColorFromConfig");
						local crawlerColor = getXMLString(self.xmlFile.handle, crawlerColorKey .. "#color");
						local shaderParameterName = Utils.getNoNil(getXMLString(self.xmlFile.handle, crawlerColorKey .. "#shaderParameterName"), "colorMat0");
						
						if shaderParameterName == "colorMat0" then
							if getColorFromConfig ~= nil then
								if self.configurations[getColorFromConfig] ~= nil then	
									if self:getIsColorConfiguration(getColorFromConfig) then
										crawlerColorName = ConfigurationUtil.getColorByConfigId(self, getColorFromConfig, self.configurations[getColorFromConfig]);
									else
										printError("(colorChanges) :: The configuration '" .. getColorFromConfig .. "' is not an color configuration! Stop coloring crawlers!", false, true);
									end;
								end;
							end;

							if crawlerColor ~= nil then
								materialId = getXMLInt(self.xmlFile.handle, crawlerColorKey .. "#materialId");
								crawlerColorName = g_brandColorManager:getBrandColorByName(crawlerColor);
									
								if crawlerColorName == nil then
									crawlerColorName = ConfigurationUtil.getColorFromString(crawlerColor);
								end;
							end;
						elseif shaderParameterName == "colorMat1" then
							if getColorFromConfig ~= nil then
								if self.configurations[getColorFromConfig] ~= nil then	
									if self:getIsColorConfiguration(getColorFromConfig) then
										crawlerColorName1 = ConfigurationUtil.getColorByConfigId(self, getColorFromConfig, self.configurations[getColorFromConfig]);
									else
										printError("(colorChanges) :: The configuration '" .. getColorFromConfig .. "' is not an color configuration! Stop coloring crawlers!", false, true);
									end;
								end;
							end;

							if crawlerColor ~= nil then
								materialId1 = getXMLInt(self.xmlFile.handle, crawlerColorKey .. "#materialId");
								crawlerColorName1 = g_brandColorManager:getBrandColorByName(crawlerColor);
									
								if crawlerColorName1 == nil then
									crawlerColorName1 = ConfigurationUtil.getColorFromString(crawlerColor);
								end;
							end;
						end;

						crawlerColorNumber = crawlerColorNumber + 1;
					end;
				end;
			end;

			--## change crawler color
			for _, crawler in pairs(crawler.rotatingParts) do
				if crawler.node ~= nil then
					local parent = getParent(crawler.node);

					if crawlerColorName ~= nil then
						local r, g, b, mat = unpack(crawlerColorName)

						for num = 0, getNumOfChildren(parent) - 1 do
							local child = getChildAt(parent, num);

							if string.find(getName(child), "vis") then
								local childParent = child;

								for num = 0, getNumOfChildren(childParent) - 1 do
									local cildChild = getChildAt(childParent, num);

									if string.find(getName(cildChild), "decal") then
										I3DUtil.setShaderParameterRec(cildChild, "colorMat0", r, g, b, materialId or mat, true);
									end;
								end;
							end;
						end;

						I3DUtil.setShaderParameterRec(parent, "colorMat0", r, g, b, materialId or mat, true);
					end;
		
					if crawlerColorName1 ~= nil then
						local r, g, b, mat = unpack(crawlerColorName1);

						for num = 0, getNumOfChildren(crawler.node) - 1 do
							local child = getChildAt(crawler.node, num);

							if string.find(getName(child), "Hub") then
								I3DUtil.setShaderParameterRec(child, "colorMat2", r, g, b, materialId1 or mat, true);
							end;
						end;
						
						I3DUtil.setShaderParameterRec(parent, "colorMat1", r, g, b, materialId1 or mat, true);
					end;
				end;
			end;

			local function applyColor(name, color)
				crawlerNumber = 0;

				while true do
					local key = string.format("crawler.%s.%s(%d)", name .. "s", name, crawlerNumber);

					if not xmlFile:hasProperty(key) then
						break;
					end;

					local node = xmlFile:getValue(key .. "#node", nil, crawler.loadedCrawler);

					if node ~= nil then
						local shaderParameter = xmlFile:getValue(key .. "#shaderParameter");

						if getHasShaderParameter(node, shaderParameter) then
							local r, g, b, mat = unpack(color);

							if mat == nil then
								local _ = nil;
								_, _, _, mat = getShaderParameter(node, shaderParameter);
							end;

							I3DUtil.setShaderParameterRec(node, shaderParameter, r, g, b, materialId1 or materialId or mat, true);
						else
							Logging.xmlWarning(xmlFile, "Missing shaderParameter '%s' on object '%s' in %s", shaderParameter, getName(node), key);
						end;
					end;

					crawlerNumber = crawlerNumber + 1;
				end;
			end;

			crawler.hasDirtNodes = false;
			crawler.dirtNodes = {};

			crawlerNumber = 0;

			while true do
				local key = string.format("crawler.dirtNodes.dirtNode(%d)", crawlerNumber);

				if not xmlFile:hasProperty(key) then
					break;
				end;

				local node = xmlFile:getValue(key .. "#node", nil, crawler.loadedCrawler);

				if node ~= nil then
					crawler.dirtNodes[node] = node;
					crawler.hasDirtNodes = true;
				end;

				crawlerNumber = crawlerNumber + 1;
			end;

			local rimColor = crawlerColorName1 or Utils.getNoNil(ConfigurationUtil.getColorByConfigId(self, "rimColor", self.configurations.rimColor), self.spec_wheels.rimColor);

			if rimColor ~= nil then
				crawler.rimColorNodes = applyColor("rimColorNode", rimColor);
			end;

			crawler.objectChanges = {};

			ObjectChangeUtil.loadObjectChangeFromXML(xmlFile, "crawler", crawler.objectChanges, crawler.loadedCrawler, self);
			ObjectChangeUtil.setObjectChanges(crawler.objectChanges, true);

			crawlerNumber = 0;

			while true do
				local key = string.format("crawler.animations.animation(%d)", crawlerNumber);

				if not xmlFile:hasProperty(key) then
					break;
				end;

				if crawler.isLeft == xmlFile:getValue(key .. "#isLeft", false) then
					local animation = {};

					if self:loadAnimation(xmlFile, key, animation, crawler.loadedCrawler) then
						self.spec_animatedVehicle.animations[animation.name] = animation;
					end;
				end;

				crawlerNumber = crawlerNumber + 1;
			end;

			table.insert(self.spec_crawlers.crawlers, crawler);
		end;

		delete(i3dNode);
	elseif not self.isDeleted and not self.isDeleting then
		Logging.xmlWarning(xmlFile, "Failed to find crawler in i3d file '%s'", crawler.filename);
	end;

	xmlFile:delete();

	self.spec_crawlers.xmlLoadingHandles[xmlFile] = nil;
end;

function AddConfiguration:loadAdditionalWheelConnectorFromXML(superFunc, wheel, additionalWheel, xmlFile, configKey, wheelKey)
	local specLights = getSpecByName(self, "lights");

	local connectorFilename = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".connector#filename");
	
	if connectorFilename ~= nil and connectorFilename ~= "" then
		XMLUtil.checkDeprecatedXMLElements(xmlFile, configKey .. wheelKey .. ".connector#index", configKey .. wheelKey .. ".connector#node");

		local connector = {};

		if connectorFilename:endsWith(".xml") then
			local xmlFilename = Utils.getFilename(connectorFilename, self.baseDirectory);
			local connectorXmlFile = XMLFile.load("connectorXml", xmlFilename, Wheels.xmlSchemaConnector);

			if connectorXmlFile ~= nil then
				local nodeKey = "leftNode";

				if not wheel.isLeft then
					nodeKey = "rightNode";
				end;

				connector.filename = connectorXmlFile:getValue("connector.file#name");
				connector.nodeStr = connectorXmlFile:getValue("connector.file#" .. nodeKey);

				connectorXmlFile:delete()
			else
				Logging.xmlError(xmlFile, "Unable to load connector xml file '%s'!", connectorFilename);
			end;
		else
			connector.filename = connectorFilename;
			connector.nodeStr = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".connector#node");
		end;

		if connector.filename ~= nil and connector.filename ~= "" then
			connector.useWidthAndDiam = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".connector#useWidthAndDiam", false);
			connector.usePosAndScale = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".connector#usePosAndScale", false);
			connector.diameter = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".connector#diameter");
			connector.additionalOffset = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".connector#offset", 0);
			connector.width = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".connector#width");
			connector.startPos = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".connector#startPos");
			connector.endPos = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".connector#endPos");
			connector.scale = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".connector#uniformScale");
			connector.color = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".connector#color", nil, true) or ConfigurationUtil.getColorByConfigId(self, "rimColor", self.configurations.rimColor) or wheel.color or specLights.rimColor;
			connector.diameterFix = getXMLFloat(self.xmlFile.handle, configKey .. wheelKey .. ".connector#diameterFix");
			connector.scaleFix = string.getVectorN(getXMLString(self.xmlFile.handle, configKey .. wheelKey .. ".connector#scale"), 3);

			additionalWheel.connector = connector;
		end;
	end;
end;

function AddConfiguration:onLoadEnterable(superFunc, savegame)
	XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.mirrors.mirror(0)#index", "vehicle.enterable.mirrors.mirror(0)#node");
	XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.enterReferenceNode", "vehicle.enterable.enterReferenceNode");
	XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.enterReferenceNode#index", "vehicle.enterable.enterReferenceNode#node");
	XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.enterable.enterReferenceNode#index", "vehicle.enterable.enterReferenceNode#node");
	XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.exitPoint", "vehicle.enterable.exitPoint");
	XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.exitPoint#index", "vehicle.enterable.exitPoint#node");
	XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.enterable.exitPoint#index", "vehicle.enterable.exitPoint#node");
	XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.characterNode", "vehicle.enterable.characterNode");
	XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.characterNode#index", "vehicle.enterable.characterNode#node");
	XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.enterable.characterNode#index", "vehicle.enterable.characterNode#node");
	XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.nicknameRenderNode", "vehicle.enterable.nicknameRenderNode");
	XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.enterAnimation", "vehicle.enterable.enterAnimation");
	XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.cameras.camera1", "vehicle.enterable.cameras.camera");
	XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.enterable.cameras.camera1", "vehicle.enterable.cameras.camera");
	XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.indoorHud.time", "vehicle.enterable.dashboards.dashboard with valueType 'time'");
	XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.indoorHud.operatingTime", "vehicle.enterable.dashboards.dashboard with valueType 'operatingTime'");
	XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.enterable.nicknameRenderNode#index", "vehicle.enterable.nicknameRenderNode#node");

	local specEnterable = getSpecByName(self, "enterable");

	specEnterable.isEntered = false;
	specEnterable.isControlled = false;
	specEnterable.playerStyle = nil;
	specEnterable.canUseEnter = true;
	specEnterable.controllerFarmId = 0;
	specEnterable.controllerUserId = 0;
	specEnterable.disableCharacterOnLeave = true;
	
	specEnterable.isTabbable = self.xmlFile:getValue("vehicle.enterable#isTabbable", true);
	specEnterable.canBeEnteredFromMenu = self.xmlFile:getValue("vehicle.enterable#canBeEnteredFromMenu", specEnterable.isTabbable);
	specEnterable.forceSelectionOnEnter = self.xmlFile:getValue("vehicle.enterable.forceSelectionOnEnter", false);
	specEnterable.enterReferenceNode = self.xmlFile:getValue("vehicle.enterable.enterReferenceNode#node", nil, self.components, self.i3dMappings);
	specEnterable.exitPoint = self.xmlFile:getValue("vehicle.enterable.exitPoint#node", nil, self.components, self.i3dMappings);
	specEnterable.interactionRadius = self.xmlFile:getValue("vehicle.enterable.enterReferenceNode#interactionRadius", 6);
	
	specEnterable.vehicleCharacter = VehicleCharacter.new(self);

	local vehicleCharacterKey = "vehicle.enterable.characterNode";
	local vehicleCharacterModifierKey = "vehicle.enterable.characterTargetNodeModifier";

	if self.getConfigurationKey ~= nil and self.getXMLPrefix ~= nil then
		for configurationName, _ in pairs(self.configurations) do
			local extraOptionNumber = 0;
				
			while true do
				local configurationKey = self:getConfigurationKey(self:getXMLPrefix(configurationName), self.configurations[configurationName], configurationName) .. ".extraOption(" .. tostring(extraOptionNumber) .. ")";
				
				if not self.xmlFile:hasProperty(configurationKey) then
					break;
				end;

				if self.xmlFile:hasProperty(configurationKey .. ".characterNode") then
					vehicleCharacterKey = configurationKey .. ".characterNode";

					if self.xmlFile:hasProperty(configurationKey .. ".characterTargetNodeModifier") then
						vehicleCharacterModifierKey = configurationKey .. ".characterTargetNodeModifier";
					end;
					
					break;
				end;

				extraOptionNumber = extraOptionNumber + 1;
			end;
		end;
	end;

	if specEnterable.vehicleCharacter ~= nil and not specEnterable.vehicleCharacter:load(self.xmlFile, vehicleCharacterKey, self.i3dMappings) then
		specEnterable.vehicleCharacter = nil;
	end;

	self:loadAdditionalCharacterFromXML(self.xmlFile);

	specEnterable.nicknameRendering = {
		node = self.xmlFile:getValue("vehicle.enterable.nicknameRenderNode#node", nil, self.components, self.i3dMappings),
		offset = self.xmlFile:getValue("vehicle.enterable.nicknameRenderNode#offset", nil, true)
	};

	if specEnterable.nicknameRendering.node == nil then
		if specEnterable.vehicleCharacter ~= nil and specEnterable.vehicleCharacter.characterDistanceRefNode ~= nil then
			specEnterable.nicknameRendering.node = specEnterable.vehicleCharacter.characterDistanceRefNode;

			if specEnterable.nicknameRendering.offset == nil then
				specEnterable.nicknameRendering.offset = {0, 1.5, 0};
			end;
		else
			specEnterable.nicknameRendering.node = self.components[1].node;
		end;
	end;

	if specEnterable.nicknameRendering.offset == nil then
		specEnterable.nicknameRendering.offset = {0, 4, 0};
	end;

	specEnterable.enterAnimation = self.xmlFile:getValue("vehicle.enterable.enterAnimation#name");

	if specEnterable.enterAnimation ~= nil and not self:getAnimationExists(specEnterable.enterAnimation) then
		Logging.xmlWarning(self.xmlFile, "Unable to find enter animation '%s'", specEnterable.enterAnimation);
	end;

	self:loadCamerasFromXML(self.xmlFile, savegame);

	if specEnterable.numCameras == 0 then
		Logging.xmlError(self.xmlFile, "No cameras defined!");

		self:setLoadingState(VehicleLoadingUtil.VEHICLE_LOAD_ERROR);

		return;
	end;

	specEnterable.characterTargetNodeReferenceToState = {};
	specEnterable.characterTargetNodeModifiers = {};

	local modifierNumber = 0;

	while true do
		local modifierKey = string.format(vehicleCharacterModifierKey .. "(%d)", modifierNumber);

		if not self.xmlFile:hasProperty(modifierKey) then
			break;
		end;

		local modifier = {};

		if self:loadCharacterTargetNodeModifier(modifier, self.xmlFile, modifierKey) then
			table.insert(specEnterable.characterTargetNodeModifiers, modifier);
		end;

		modifierNumber = modifierNumber + 1;
	end;

	local allMirrors = {};

	if g_isDevelopmentVersion then
		I3DUtil.getNodesByShaderParam(self.rootNode, "reflectionScale", allMirrors);
	end;

	specEnterable.mirrors = {};

	local useMirrors = g_gameSettings:getValue("maxNumMirrors") > 0;
	
	mirrorNumber = 0;

	while true do
		local mirrorKey = string.format("vehicle.enterable.mirrors.mirror(%d)", mirrorNumber);

		if not self.xmlFile:hasProperty(mirrorKey) then
			break;
		end;

		local node = self.xmlFile:getValue(mirrorKey .. "#node", nil, self.components, self.i3dMappings);

		if node ~= nil then
			local prio = self.xmlFile:getValue(mirrorKey .. "#prio", 2);

			setReflectionMapObjectMasks(node, 32896, 2147483648.0, true);

			if getObjectMask(node) == 0 then
				setObjectMask(node, 16711807);
			end;

			if useMirrors then
				table.insert(specEnterable.mirrors, {
					cosAngle = 1,
					node = node,
					prio = prio,
					parentNode = getParent(node)
				});
			else
				setVisibility(node, false);
			end;

			allMirrors[node] = nil;
		end;

		mirrorNumber = mirrorNumber + 1;
	end;

	for node, _ in pairs(allMirrors) do
		Logging.xmlError(self.xmlFile, "Found Mesh '%s' with mirrorShader that is not entered in the vehicle XML", getName(node));
	end;

	self:setMirrorVisible(specEnterable.cameras[specEnterable.camIndex].useMirror);

	if self.loadDashboardsFromXML ~= nil then
		self:loadDashboardsFromXML(self.xmlFile, "vehicle.enterable.dashboards", {
			valueFunc = "getEnvironmentTime",
			valueTypeToLoad = "time",
			valueObject = g_currentMission.environment
		});

		self:loadDashboardsFromXML(self.xmlFile, "vehicle.enterable.dashboards", {
			valueFunc = "getFormattedOperatingTime",
			valueTypeToLoad = "operatingTime",
			valueObject = self
		});
	end;

	specEnterable.lastIsRaining = false;
	specEnterable.weatherObject = g_currentMission.environment.weather;

	if self.isClient then
		specEnterable.rainSamples = g_soundManager:loadSamplesFromXML(self.xmlFile, "vehicle.enterable.sounds", "rain", self.baseDirectory, self.components, 0, AudioGroup.VEHICLE, self.i3dMappings, self);
	end;

	specEnterable.reverbReferenceNode = self.xmlFile:getValue("vehicle.enterable.reverb#referenceNode", nil, self.components, self.i3dMappings);

	if specEnterable.reverbReferenceNode == nil then
		specEnterable.reverbReferenceNode = createTransformGroup("ReverebRefNode");

		link(self.rootNode, specEnterable.reverbReferenceNode);
		setTranslation(specEnterable.reverbReferenceNode, 0, 2, 0);
	end;

	specEnterable.dirtyFlag = self:getNextDirtyFlag();
	specEnterable.playerHotspot = PlayerHotspot.new();
	specEnterable.playerHotspot:setVehicle(self);

	g_currentMission:addInteractiveVehicle(self);
	g_currentMission:addEnterableVehicle(self);
end;

Enterable.onLoad = Utils.overwrittenFunction(Enterable.onLoad, AddConfiguration.onLoadEnterable);

function AddConfiguration:loadMotor(superFunc, xmlFile, motorId)
	local motorKey = nil;

	motorKey, motorId = ConfigurationUtil.getXMLConfigurationKey(xmlFile, motorId, "vehicle.motorized.motorConfigurations.motorConfiguration", "vehicle.motorized", "motor");

	local specMotorized = getSpecByName(self, "motorized");

	local fallbackConfigKey = "vehicle.motorized.motorConfigurations.motorConfiguration(0)";

	specMotorized.motorType = ConfigurationUtil.getConfigurationValue(xmlFile, motorKey, ".motor", "#type", "vehicle", fallbackConfigKey);
	specMotorized.motorStartAnimation = ConfigurationUtil.getConfigurationValue(xmlFile, motorKey, ".motor", "#startAnimationName", "vehicle", fallbackConfigKey);
	specMotorized.consumerConfigurationIndex = ConfigurationUtil.getConfigurationValue(xmlFile, motorKey, "#consumerConfigurationIndex", "", 1, fallbackConfigKey);

	local wheelKey, _ = ConfigurationUtil.getXMLConfigurationKey(xmlFile, self.configurations.wheel, "vehicle.wheels.wheelConfigurations.wheelConfiguration", "vehicle.wheels", "wheels");

	ObjectChangeUtil.updateObjectChanges(xmlFile, "vehicle.motorized.motorConfigurations.motorConfiguration", motorId, self.components, self);

	local motorMinRpm = ConfigurationUtil.getConfigurationValue(xmlFile, motorKey, ".motor", "#minRpm", 1000, fallbackConfigKey);
	local motorMaxRpm = ConfigurationUtil.getConfigurationValue(xmlFile, motorKey, ".motor", "#maxRpm", 1800, fallbackConfigKey);
	local minSpeed = ConfigurationUtil.getConfigurationValue(xmlFile, motorKey, ".motor", "#minSpeed", 1, fallbackConfigKey);
	local maxForwardSpeed = ConfigurationUtil.getConfigurationValue(xmlFile, motorKey, ".motor", "#maxForwardSpeed", nil, fallbackConfigKey);
	local maxBackwardSpeed = ConfigurationUtil.getConfigurationValue(xmlFile, motorKey, ".motor", "#maxBackwardSpeed", nil, fallbackConfigKey);

	if maxForwardSpeed ~= nil then
		maxForwardSpeed = maxForwardSpeed / 3.6;
	end;

	if maxBackwardSpeed ~= nil then
		maxBackwardSpeed = maxBackwardSpeed / 3.6;
	end;

	local maxWheelSpeed = ConfigurationUtil.getConfigurationValue(xmlFile, wheelKey, ".wheels", "#maxForwardSpeed", nil, nil);

	if maxWheelSpeed ~= nil then
		maxForwardSpeed = maxWheelSpeed / 3.6;
	end;

	local accelerationLimit = ConfigurationUtil.getConfigurationValue(xmlFile, motorKey, ".motor", "#accelerationLimit", 2, fallbackConfigKey);
	local brakeForce = ConfigurationUtil.getConfigurationValue(xmlFile, motorKey, ".motor", "#brakeForce", 10, fallbackConfigKey) * 2;
	local lowBrakeForceScale = ConfigurationUtil.getConfigurationValue(xmlFile, motorKey, ".motor", "#lowBrakeForceScale", 0.5, fallbackConfigKey);
	local lowBrakeForceSpeedLimit = ConfigurationUtil.getConfigurationValue(xmlFile, motorKey, ".motor", "#lowBrakeForceSpeedLimit", 1, fallbackConfigKey) / 3600;
	local torqueScale = ConfigurationUtil.getConfigurationValue(xmlFile, motorKey, ".motor", "#torqueScale", 1, fallbackConfigKey);
	local ptoMotorRpmRatio = ConfigurationUtil.getConfigurationValue(xmlFile, motorKey, ".motor", "#ptoMotorRpmRatio", 4, fallbackConfigKey);
	local transmissionKey = motorKey .. ".transmission";

	if self.getConfigurationKey ~= nil and self.getXMLPrefix ~= nil then
		for configurationName, _ in pairs(self.configurations) do
			local extraOptionNumber = 0;
				
			while true do
				local configurationKey = self:getConfigurationKey(self:getXMLPrefix(configurationName), self.configurations[configurationName], configurationName) .. ".extraOption(" .. tostring(extraOptionNumber) .. ")";
				
				if not self.xmlFile:hasProperty(configurationKey) then
					break;
				end;

				local newTransmissionName = Utils.getNoNil(getXMLString(self.xmlFile.handle, configurationKey .. ".transmission#name"), ""); 

				if newTransmissionName ~= "" then
					transmissionKey = configurationKey .. ".transmission";

					break;
				end;

				extraOptionNumber = extraOptionNumber + 1;
			end;
		end;
	end;

	if not xmlFile:hasProperty(transmissionKey) then
		transmissionKey = fallbackConfigKey .. ".transmission";
	end;

	local minForwardGearRatio = xmlFile:getValue(transmissionKey .. "#minForwardGearRatio");
	local maxForwardGearRatio = xmlFile:getValue(transmissionKey .. "#maxForwardGearRatio");
	local minBackwardGearRatio = xmlFile:getValue(transmissionKey .. "#minBackwardGearRatio");
	local maxBackwardGearRatio = xmlFile:getValue(transmissionKey .. "#maxBackwardGearRatio");
	local gearChangeTime = xmlFile:getValue(transmissionKey .. "#gearChangeTime");
	local autoGearChangeTime = xmlFile:getValue(transmissionKey .. "#autoGearChangeTime");
	local axleRatio = xmlFile:getValue(transmissionKey .. "#axleRatio", 1);
	local startGearThreshold = xmlFile:getValue(transmissionKey .. "#startGearThreshold", VehicleMotor.GEAR_START_THRESHOLD);

	if maxForwardGearRatio == nil or minForwardGearRatio == nil then
		minForwardGearRatio, maxForwardGearRatio = nil;
	else
		minForwardGearRatio = minForwardGearRatio * axleRatio;
		maxForwardGearRatio = maxForwardGearRatio * axleRatio;
	end;

	if minBackwardGearRatio == nil or maxBackwardGearRatio == nil then
		minBackwardGearRatio, maxBackwardGearRatio = nil;
	else
		minBackwardGearRatio = minBackwardGearRatio * axleRatio;
		maxBackwardGearRatio = maxBackwardGearRatio * axleRatio;
	end;

	local forwardGears = nil;

	if minForwardGearRatio == nil then
		forwardGears = self:loadGears(xmlFile, "forwardGear", transmissionKey, motorMaxRpm, axleRatio, 1);

		if forwardGears == nil then
			print("Warning: Missing forward gear ratios for motor in '" .. self.configFileName .. "'!");

			forwardGears = {{default = false, ratio = 1}};
		end;
	end;

	local backwardGears = nil;

	if minBackwardGearRatio == nil then
		backwardGears = self:loadGears(xmlFile, "backwardGear", transmissionKey, motorMaxRpm, axleRatio, -1);
	end;

	local gearGroups = self:loadGearGroups(xmlFile, transmissionKey .. ".groups", motorMaxRpm, axleRatio);
	local groupsType = xmlFile:getValue(transmissionKey .. ".groups#type", "default");
	local groupChangeTime = xmlFile:getValue(transmissionKey .. ".groups#changeTime", 0.5);
	local directionChangeUseGear = xmlFile:getValue(transmissionKey .. ".directionChange#useGear", false);
	local directionChangeGearIndex = xmlFile:getValue(transmissionKey .. ".directionChange#reverseGearIndex", 1);
	local directionChangeUseGroup = xmlFile:getValue(transmissionKey .. ".directionChange#useGroup", false);
	local directionChangeGroupIndex = xmlFile:getValue(transmissionKey .. ".directionChange#reverseGroupIndex", 1);
	local directionChangeTime = xmlFile:getValue(transmissionKey .. ".directionChange#changeTime", 0.5);
	local manualShiftGears = xmlFile:getValue(transmissionKey .. ".manualShift#gears", true);
	local manualShiftGroups = xmlFile:getValue(transmissionKey .. ".manualShift#groups", true);
	local torqueCurve = AnimCurve.new(linearInterpolator1);
	local torqueI = 0;
	local torqueBase = fallbackConfigKey .. ".motor.torque";

	if key ~= nil and xmlFile:hasProperty(key .. ".motor.torque(0)") then
		torqueBase = key .. ".motor.torque";
	end;

	while true do
		local torqueKey = string.format(torqueBase .. "(%d)", torqueI);
		local normRpm = xmlFile:getValue(torqueKey .. "#normRpm");
		local rpm = nil;

		if normRpm == nil then
			rpm = xmlFile:getValue(torqueKey .. "#rpm");
		else
			rpm = normRpm * motorMaxRpm;
		end;

		local torque = xmlFile:getValue(torqueKey .. "#torque");

		if torque == nil or rpm == nil then
			break
		end;

		torqueCurve:addKeyframe({
			torque * torqueScale,
			time = rpm
		});

		torqueI = torqueI + 1;
	end;

	specMotorized.motor = VehicleMotor.new(self, motorMinRpm, motorMaxRpm, maxForwardSpeed, maxBackwardSpeed, torqueCurve, brakeForce, forwardGears, backwardGears, minForwardGearRatio, maxForwardGearRatio, minBackwardGearRatio, maxBackwardGearRatio, ptoMotorRpmRatio, minSpeed);

	specMotorized.motor:setGearGroups(gearGroups, groupsType, groupChangeTime);
	specMotorized.motor:setDirectionChange(directionChangeUseGear, directionChangeGearIndex, directionChangeUseGroup, directionChangeGroupIndex, directionChangeTime);
	specMotorized.motor:setManualShift(manualShiftGears, manualShiftGroups);
	specMotorized.motor:setStartGearThreshold(startGearThreshold);

	local rotInertia = ConfigurationUtil.getConfigurationValue(xmlFile, key, ".motor", "#rotInertia", specMotorized.motor:getRotInertia(), fallbackConfigKey);
	local dampingRateScale = ConfigurationUtil.getConfigurationValue(xmlFile, key, ".motor", "#dampingRateScale", 1, fallbackConfigKey);

	specMotorized.motor:setRotInertia(rotInertia);
	specMotorized.motor:setDampingRateScale(dampingRateScale);
	specMotorized.motor:setLowBrakeForce(lowBrakeForceScale, lowBrakeForceSpeedLimit);
	specMotorized.motor:setAccelerationLimit(accelerationLimit);

	local motorRotationAccelerationLimit = ConfigurationUtil.getConfigurationValue(xmlFile, key, ".motor", "#rpmSpeedLimit", nil, fallbackConfigKey);

	if motorRotationAccelerationLimit ~= nil then
		motorRotationAccelerationLimit = motorRotationAccelerationLimit * math.pi / 30;

		specMotorized.motor:setMotorRotationAccelerationLimit(motorRotationAccelerationLimit);
	end;

	if gearChangeTime ~= nil then
		specMotorized.motor:setGearChangeTime(gearChangeTime);
	end;

	if autoGearChangeTime ~= nil then
		specMotorized.motor:setAutoGearChangeTime(autoGearChangeTime);
	end;
end;

Motorized.loadMotor = Utils.overwrittenFunction(Motorized.loadMotor, AddConfiguration.loadMotor);