----------------------------------------------------------------------------
-- @Author: ViperGTS96------------------------------------------------------
----------------------------------------------------------------------------
--------------------"The simplest design is the best design." --------------
----------------------------------------------------------------------------
----------------------------------------------------------------------------

mudSystem = {};
mudSystem.maxWheelLimit = 18;
mudSystem.psEmitRate = 0.4301/2; -- 0.4301 Base value
mudSystem.mudDensity = 0.151; --(115mud/76dirt)*0.1;
mudSystem.excludedToolTypes = { "PLANTERS", "SEEDERS", "WEEDERS", "TEDDERS", "WINDROWERS", "PLOWS", "SUBSOILERS", "CULTIVATORS", "DISCHARROWS", "POWERHARROWS", "SPADERS", "ROLLERS" };
mudSystem.overrideRain = false; --(debugging)

function mudSystem.prerequisitesPresent(specializations)
	return SpecializationUtil.hasSpecialization(Washable, specializations)
		and SpecializationUtil.hasSpecialization(Wheels, specializations)
		and SpecializationUtil.hasSpecialization(AnimatedVehicle, specializations);
end;

function mudSystem.registerEventListeners(vehicleType)
	SpecializationUtil.registerEventListener(vehicleType, "onLoadFinished", mudSystem);
	SpecializationUtil.registerEventListener(vehicleType, "onUpdateTick", mudSystem);
	SpecializationUtil.registerEventListener(vehicleType, "onDelete", mudSystem);
	SpecializationUtil.registerEventListener(vehicleType, "saveToXMLFile", mudSystem);
	SpecializationUtil.registerEventListener(vehicleType, "onReadStream", mudSystem);
	SpecializationUtil.registerEventListener(vehicleType, "onWriteStream", mudSystem);
end;

function mudSystem.registerFunctions(vehicleType)
	SpecializationUtil.registerFunction(vehicleType, "loadMudToWheel", mudSystem.loadMudToWheel);
	SpecializationUtil.registerFunction(vehicleType, "updateWheelMudPS", mudSystem.updateWheelMudPS);
	SpecializationUtil.registerFunction(vehicleType, "getIsInMud", mudSystem.getIsInMud);
	SpecializationUtil.registerFunction(vehicleType, "setMudEmittingState", mudSystem.setMudEmittingState);
	SpecializationUtil.registerFunction(vehicleType, "getIsOnMudFillType", mudSystem.getIsOnMudFillType);
	SpecializationUtil.registerFunction(vehicleType, "setMudSinkState", mudSystem.setMudSinkState);
	SpecializationUtil.registerFunction(vehicleType, "updateMudResistance", mudSystem_physics.updateMudResistance);
	SpecializationUtil.registerFunction(vehicleType, "deactivateMudPS", mudSystem.deactivateMudPS);
	SpecializationUtil.registerFunction(vehicleType, "zeroMudPS", mudSystem.zeroMudPS);
	SpecializationUtil.registerFunction(vehicleType, "onAttachableLoadFinished", mudSystem_physics.onAttachableLoadFinished);
end;

function mudSystem.registerOverwrittenFunctions(vehicleType)
	SpecializationUtil.registerOverwrittenFunction(vehicleType, "getIsSupportAnimationAllowed", mudSystem_physics.getIsSupportAnimationAllowed);
	SpecializationUtil.registerOverwrittenFunction(vehicleType, "updateWheelChockPosition", mudSystem_physics.updateWheelChockPosition);
	SpecializationUtil.registerOverwrittenFunction(vehicleType, "setNodeDirtAmount", mudSystem.setNodeDirtAmount);
	SpecializationUtil.registerOverwrittenFunction(vehicleType, "getDriveGroundParticleSystemsScale", mudSystem.getDriveGroundParticleSystemsScale);
	SpecializationUtil.registerOverwrittenFunction(vehicleType, "updateDirtAmount", mudSystem.updateDirtAmount);
end;


function mudSystem:onLoadFinished(savegame)
	self.spec_mudSystem = {};
	local spec = self.spec_mudSystem;
    spec.enableMudSystem = Utils.getNoNil(getXMLBool(self.xmlFile.handle, "vehicle.mudSystem#enable"), true);
	spec.hasFlotationTires = Utils.getNoNil(getXMLBool(self.xmlFile.handle, "vehicle.mudSystem#hasFlotationTires"), false); --unused

	if #self.spec_wheels.wheels > mudSystem.maxWheelLimit and spec.enableMudSystem then 
		spec.enableMudSystem = false;
	else
		local vehicleName = string.lower(FSBaseMission:getVehicleName(self)); 
		local wrongTireType = string.find(vehicleName, "pickup");
		local fixForcePointRatio = string.find(vehicleName, "s22");
		if fixForcePointRatio then
			local modDescXML = loadXMLFile("modDescTemp", self.baseDirectory.."modDesc.xml", "modDescTemp");
			fixForcePointRatio = getXMLString(modDescXML, "modDesc.version") == "1.0.0.0";
			delete(modDescXML);
		end;
		for _, wheel in pairs(self.spec_wheels.wheels) do
			wheel.mudSystem_sinkOnly = mudSystem:getHasIssue(self, index, wheel);
			if wrongTireType then wheel.tireType = WheelsUtil.getTireType("street"); end;
			if fixForcePointRatio then wheel.fix_ForcePointRatio = wheel.forcePointRatio; end;
		end;
	end;

	if spec.enableMudSystem and g_currentMission.mudSystemPS ~= nil and g_currentMission.mudSystemPS.referenceShape ~= nil and g_currentMission.mudSystemPS.referencePS ~= nil then
		if self.spec_wheels ~= nil and #self.spec_wheels.wheels > 0 then
			local numWheels = #self.spec_wheels.wheels;
			for iWheel=1,numWheels do
				local wheel = self.spec_wheels.wheels[iWheel];
				if wheel.hasParticles and not wheel.mudSystem_sinkOnly then
					local refNode = wheel.node;
					self:loadMudToWheel(refNode, wheel);
					if wheel.additionalWheels ~= nil then
						for _,additionalWheel in pairs(wheel.additionalWheels) do
							self:loadMudToWheel(refNode, additionalWheel);
						end;
					end;
				end;
				if savegame ~= nil then
					wheel.isInMud = Utils.getNoNil(getXMLBool(savegame.xmlFile.handle, string.format("%s.mudSystem.wheel(%d)#isInMud", savegame.key, iWheel)), false);
					wheel.isInFieldMud = Utils.getNoNil(getXMLBool(savegame.xmlFile.handle, string.format("%s.mudSystem.wheel(%d)#isInFieldMud", savegame.key, iWheel)), false);
					wheel.radius = Utils.getNoNil(getXMLFloat(savegame.xmlFile.handle, string.format("%s.mudSystem.wheel(%d)#radius", savegame.key, iWheel)), wheel.radius);
					wheel.sink = Utils.getNoNil(getXMLFloat(savegame.xmlFile.handle, string.format("%s.mudSystem.wheel(%d)#sink", savegame.key, iWheel)), wheel.sink);
					wheel.sinkTarget = Utils.getNoNil(getXMLFloat(savegame.xmlFile.handle, string.format("%s.mudSystem.wheel(%d)#sinkTarget", savegame.key, iWheel)), wheel.sinkTarget);
					wheel.sinkLongStiffnessFactor = Utils.getNoNil(getXMLFloat(savegame.xmlFile.handle, string.format("%s.mudSystem.wheel(%d)#sinkLongStiffnessFactor", savegame.key, iWheel)), wheel.sinkLongStiffnessFactor);
					wheel.sinkLatStiffnessFactor = Utils.getNoNil(getXMLFloat(savegame.xmlFile.handle, string.format("%s.mudSystem.wheel(%d)#sinkLatStiffnessFactor", savegame.key, iWheel)), wheel.sinkLatStiffnessFactor);
					if self.isServer then	self:setWheelPositionDirty(wheel); end;
				end;
				self:onAttachableLoadFinished(savegame);
			end;
		end;
	end;

    self.dirtParticleSystemDirtyFlag = self:getNextDirtyFlag();
end;

function mudSystem:saveToXMLFile(xmlFile, key)
	local spec = self.spec_mudSystem;
	if spec.enableMudSystem ~= nil and spec.enableMudSystem then
		if self.spec_wheels ~= nil and #self.spec_wheels.wheels > 0 then
			local numWheels = #self.spec_wheels.wheels;
			for iWheel=1,numWheels do
				local wheel = self.spec_wheels.wheels[iWheel];
				setXMLBool(xmlFile.handle, string.format("%s.wheel(%d)#isInMud", key, iWheel), Utils.getNoNil(wheel.isInMud, false));
				setXMLBool(xmlFile.handle, string.format("%s.wheel(%d)#isInFieldMud", key, iWheel), Utils.getNoNil(wheel.isInFieldMud, false));
				setXMLFloat(xmlFile.handle, string.format("%s.wheel(%d)#radius", key, iWheel), wheel.radius);
				setXMLFloat(xmlFile.handle, string.format("%s.wheel(%d)#sink", key, iWheel), wheel.sink);
				setXMLFloat(xmlFile.handle, string.format("%s.wheel(%d)#sinkTarget", key, iWheel), wheel.sinkTarget);
				setXMLFloat(xmlFile.handle, string.format("%s.wheel(%d)#sinkLongStiffnessFactor", key, iWheel), wheel.sinkLongStiffnessFactor);
				setXMLFloat(xmlFile.handle, string.format("%s.wheel(%d)#sinkLatStiffnessFactor", key, iWheel), wheel.sinkLatStiffnessFactor);
			end;
		end;
	end;
end;

function mudSystem:loadMudToWheel(refNode, wheel)
    wheel.mudSystem = {};
    local psEmitterShape = clone(g_currentMission.mudSystemPS.referenceShape, false, false, false);
    link(refNode, psEmitterShape);
    local x,y,z;
    if wheel.wheelTire == nil then
        x,y,z = localToLocal(wheel.driveNode, refNode, 0, 0, 0);
    else
        x,y,z = localToLocal(wheel.wheelTire, refNode, 0, 0, 0);
    end;
    setTranslation(psEmitterShape, x+wheel.xOffset,y,z);
    setRotation(psEmitterShape, 0, 0, 0);
    setScale(psEmitterShape, 2*wheel.width, 2*wheel.radius, 2*wheel.radius);
	
	wheel.mudSystemMainNode = createTransformGroup("mudNode");
	link(refNode, wheel.mudSystemMainNode);
	local rDX1,rDY1,rDZ1 = localToWorld(self.components[1].node, 0,0,1);
	local rDX2,rDY2,rDZ2 = localToWorld(self.components[1].node, 0,1,0);
	setTranslation(wheel.mudSystemMainNode, x+wheel.xOffset,y,z);
	setDirection(wheel.mudSystemMainNode,rDX1,rDY1,rDZ1,rDX2,rDY2,rDZ2);

    if g_currentMission.mudSystemPS.referencePS ~= nil then
		wheel.mudSystem.mudPS = {};
        local psClone = clone(g_currentMission.mudSystemPS.referencePS.shape, true, false, true);
        ParticleUtil.loadParticleSystemFromNode(psClone, wheel.mudSystem.mudPS, false, true, false);
        ParticleUtil.setEmitterShape(wheel.mudSystem.mudPS, psEmitterShape);
        wheel.mudSystem.mudPS.isActive = false;
        wheel.mudSystem.mudPS.particleSpeed = ParticleUtil.getParticleSystemSpeed(wheel.mudSystem.mudPS);
        wheel.mudSystem.mudPS.particleRandomSpeed = ParticleUtil.getParticleSystemSpeedRandom(wheel.mudSystem.mudPS);
		wheel.mudSystem.mudPS.resetPos = {getTranslation(wheel.mudSystem.mudPS.emitterShape)}
    end;
end;

function mudSystem:onDelete()
    if self.spec_mudSystem.enableMudSystem then
        for _,wheel in pairs(self.spec_wheels.wheels) do
            if wheel.mudSystem ~= nil and wheel.mudSystem.mudPS ~= nil then
				if entityExists(wheel.mudSystem.mudPS.shape) then
					delete(wheel.mudSystem.mudPS.shape);
				end;
				if entityExists(wheel.mudSystem.mudPS.emitterShape) then
					delete(wheel.mudSystem.mudPS.emitterShape);
				end;
				wheel.mudSystem = nil;
				if wheel.additionalWheels ~= nil then
					for _,additionalWheel in pairs(wheel.additionalWheels) do
						 if additionalWheel.mudSystem ~= nil and additionalWheel.mudSystem.mudPS ~= nil then
							if entityExists(additionalWheel.mudSystem.mudPS.shape) then
								delete(additionalWheel.mudSystem.mudPS.shape);
							end
							if entityExists(additionalWheel.mudSystem.mudPS.emitterShape) then
								delete(additionalWheel.mudSystem.mudPS.emitterShape);
							end
							additionalWheel.mudSystem = nil;
						end;
					end;
				end;
            end;
        end;
    end;
end;

function mudSystem:getHasIssue(self, index, wheel)
	
	local storeCat = g_storeManager:getItemByXMLFilename(self.configFileName)
	if storeCat ~= nil and storeCat.categoryName ~= nil then
		storeCat = storeCat.categoryName;
		for _, type in pairs(mudSystem.excludedToolTypes) do
			if type == storeCat then
                return true;
			end;
		end;
	end;

	if self.spec_plow ~= nil or self.spec_cultivator ~= nil or self.spec_roller then 
		return true; 
	end;

	return false;
end;

function mudSystem:getIsGroundWet(bypassWetness)
	local rainWeather = g_currentMission.environment.weather:getIsRaining();
	local rainFallScale = g_currentMission.environment.weather:getRainFallScale(true);
	rainWeather = rainWeather and rainFallScale > 0;
	local wetfield = g_currentMission.mudFieldSinkUpdater ~= nil and (g_currentMission.mudFieldSinkUpdater.fieldSinkAmount > 0);
	if mudSystem.overrideRain or (wetfield and (bypassWetness == nil)) then 
		rainWeather = true;
	end;
	return rainWeather;
end;

function mudSystem:getIsOnMudFillType(wheel, filltype)
	if g_currentMission.terrainRootNode ~= nil and g_currentMission.terrainDetailId ~= nil then
		if filltype ~= nil then
			if filltype == 0 then filltype = FillType.MUD end;
			local wheelNode = wheel.mudSystemMainNode
			local offsetY = 0;
			if wheelNode == nil then wheelNode = wheel.wheelTire; offsetY = -wheel.radius; end;
			if wheelNode == nil then wheelNode = wheel.driveNode; offsetY = -wheel.radius; end;
			if wheelNode == nil then wheelNode = wheel.repr; offsetY = -wheel.radius; end;
			local x1,y1,z1 = localToWorld(wheelNode, -(wheel.width/2),offsetY,0);
			local x2,y2,z2 = localToWorld(wheelNode, wheel.width/2,offsetY,0);
			y1 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x1,y1,z1);
			y2 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x2,y2,z2);
			local foundFillType = DensityMapHeightUtil.getFillTypeAtLine(x1,y1,z1, x2,y2,z2, 1.0);
			if foundFillType == filltype then
				return true;
			end;
		end;
	end;
	return false;
end;

function mudSystem:onUpdateTick(dt)
    if self.spec_mudSystem.enableMudSystem then
		local spec = self.spec_mudSystem;
		local vehicleActive = self:getIsActive();

		if self.isServer then
			if vehicleActive then
				spec.onDeactivateCalled = false;
				for i,wheel in pairs(self.spec_wheels.wheels) do
					local updateSink_A = wheel.isInMud;
					local updateSink_B = wheel.isInFieldMud;
					wheel.isInMud, wheel.isInFieldMud = self:getIsInMud(nil, wheel);
					if updateSink_A ~= wheel.isInMud or updateSink_B ~= wheel.isInFieldMud then
						self:setMudSinkState(i,-1, wheel.isInMud, wheel.isInFieldMud);
					end;
					if wheel.mudSystem ~= nil and wheel.mudSystem.mudPS ~= nil and not wheel.mudSystem_sinkOnly then
						local ps = wheel.mudSystem.mudPS;
						local updateState = ps.isActive;
						local activePS = wheel.isInMud;
						ps.isActive = activePS;
						ParticleUtil.setEmittingState(ps, ps.isActive);
						if updateState ~= ps.isActive then
							self:setMudEmittingState(i,-1, ps.isActive);
						end;
					end;
					if wheel.additionalWheels ~= nil then
						for a,additionalWheel in pairs(wheel.additionalWheels) do
							updateSink_A = additionalWheel.isInMud;
							updateSink_B = additionalWheel.isInFieldMud;
							additionalWheel.isInMud, additionalWheel.isInFieldMud  = self:getIsInMud(wheel, additionalWheel);
							if updateSink_A ~= additionalWheel.isInMud or updateSink_B ~= additionalWheel.isInFieldMud then
								self:setMudSinkState(i,a, additionalWheel.isInMud, additionalWheel.isInFieldMud);
							end;
							if additionalWheel.mudSystem ~= nil and additionalWheel.mudSystem.mudPS ~= nil and not wheel.mudSystem_sinkOnly then
								ps = additionalWheel.mudSystem.mudPS;
								updateState = ps.isActive;
								activePS = additionalWheel.isInMud;
								ps.isActive = activePS;
								ParticleUtil.setEmittingState(ps, ps.isActive);
								if updateState ~= ps.isActive then
									self:setMudEmittingState(i,a, ps.isActive);
								end;
							end;
						end;
					end;
				end;
			elseif not spec.onDeactivateCalled then
				self:deactivateMudPS();
				spec.onDeactivateCalled = true;
			end;
		end;

		if self.isClient then
			if vehicleActive then
				spec.zeroPSCalled = false;
				for i,wheel in pairs(self.spec_wheels.wheels) do
					if wheel.mudSystem ~= nil and wheel.mudSystem.mudPS ~= nil then
						if wheel.netInfo.xDriveLast_MudSystem == nil then
							wheel.netInfo.xDriveLast_MudSystem = wheel.netInfo.xDrive;
						end;
						local xDriveDiff = wheel.netInfo.xDrive - wheel.netInfo.xDriveLast_MudSystem;
						if xDriveDiff > math.pi then
							wheel.netInfo.xDriveLast_MudSystem = wheel.netInfo.xDriveLast_MudSystem + 2*math.pi;
						elseif xDriveDiff < -math.pi then
							wheel.netInfo.xDriveLast_MudSystem = wheel.netInfo.xDriveLast_MudSystem - 2*math.pi;
						end;
						xDriveDiff = wheel.netInfo.xDrive - wheel.netInfo.xDriveLast_MudSystem;
						wheel.netInfo.xDriveLast_MudSystem = wheel.netInfo.xDrive;
						local wheelRotSpeed = math.deg(xDriveDiff) / (0.001 * dt);
						local maxWheelRotSpeed = 1080;
						local wheelRotFactor = math.abs(wheelRotSpeed) / maxWheelRotSpeed;
						wheelRotFactor = wheelRotFactor * wheel.radius;
						self:updateWheelMudPS(wheel, wheelRotFactor, wheel.steeringAngle);
						if wheel.additionalWheels ~= nil then
							for a,additionalWheel in pairs(wheel.additionalWheels) do
								self:updateWheelMudPS(additionalWheel, wheelRotFactor, wheel.steeringAngle, wheel);
							end;
						end;
					end;
				end;
			elseif spec.zeroPSCalled ~= nil and not spec.zeroPSCalled then
				self:zeroMudPS();
				spec.zeroPSCalled = true;
            end;
		end;

    end;
end;

function mudSystem:deactivateMudPS()
    for i,wheel in pairs(self.spec_wheels.wheels) do
		if wheel.mudSystem ~= nil and wheel.mudSystem.mudPS ~= nil and not wheel.mudSystem_sinkOnly then
			if wheel.mudSystem.mudPS.isActive then
				ParticleUtil.setEmittingState(wheel.mudSystem.mudPS, false);
				wheel.mudSystem.mudPS.isActive = false;
				self:setMudEmittingState(i,-1, false);
			end;
            if wheel.additionalWheels ~= nil then
                for a,additionalWheel in pairs(wheel.additionalWheels) do
					if additionalWheel.mudSystem ~= nil and additionalWheel.mudSystem.mudPS.isActive then
						ParticleUtil.setEmittingState(additionalWheel.mudSystem.mudPS, false);
						additionalWheel.mudSystem.mudPS.isActive = false;
						self:setMudEmittingState(i,a, false);
					end;
                end;
            end;
        end;
    end;
end;

function mudSystem:zeroMudPS()
    for _,wheel in pairs(self.spec_wheels.wheels) do
		if wheel.mudSystem ~= nil and wheel.mudSystem.mudPS ~= nil and not wheel.mudSystem_sinkOnly then
			ParticleUtil.setEmitCountScale(wheel.mudSystem.mudPS, 0);
			ParticleUtil.setParticleSystemSpeed(wheel.mudSystem.mudPS, 0);
			ParticleUtil.setParticleSystemSpeedRandom(wheel.mudSystem.mudPS, 0);
            if wheel.additionalWheels ~= nil then
                for _,additionalWheel in pairs(wheel.additionalWheels) do
					ParticleUtil.setEmitCountScale(additionalWheel.mudSystem.mudPS, 0);
					ParticleUtil.setParticleSystemSpeed(additionalWheel.mudSystem.mudPS, 0);
					ParticleUtil.setParticleSystemSpeedRandom(additionalWheel.mudSystem.mudPS, 0);
                end;
            end;
        end;
    end;
end;

function mudSystem:getIsInMud(parent, wheel, force)
	local isActive = false;
	local isFieldMud = false;
	local spec = self.spec_wheels;
	local hasGroundContact = (wheel.hasGroundContact ~= nil and wheel.hasGroundContact);
	local hasSnowContact = wheel.hasSnowContact ~= nil and wheel.hasSnowContact;
	if parent ~= nil then
		hasGroundContact = parent.hasGroundContact ~= nil and parent.hasGroundContact;
	end;
	if hasGroundContact or (force ~= nil) then 
		local wheelNode = wheel.mudSystemMainNode;
		local offsetY = 0;
		if wheelNode == nil then wheelNode = wheel.wheelTire; offsetY = -wheel.radius; end;
		if wheelNode == nil then wheelNode = wheel.driveNode; offsetY = -wheel.radius; end;
		if wheelNode == nil then wheelNode = wheel.repr; offsetY = -wheel.radius; end;
		local x,y,z = getWorldTranslation(wheelNode);
		if offsetY > 0 then
			x,y,z = localToWorld(wheelNode, 0,offsetY,0);
		end;
		local yT = getDensityHeightAtWorldPos(g_currentMission.terrainDetailHeightId, x,y,z);
		if (y-0.1 > yT) then
			if self:getIsOnMudFillType(wheel, 0) then
				isActive = true;
			elseif mudSystem:getIsGroundWet() then
				if wheel.densityType ~= nil and force == nil then
					isActive = wheel.densityType ~= 0 and wheel.densityType ~= spec.tireTrackGroundGrassValue and wheel.densityType ~= spec.tireTrackGroundGrassCutValue and not hasSnowContact;
					isFieldMud = true;
				elseif g_currentMission.terrainDetailId ~= nil then
					local densityType = getDensityAtWorldPos(g_currentMission.terrainDetailId,x,yT,z);
					if wheel.hasSnowContact == nil and parent ~= nil then
						hasSnowContact =  parent.hasSnowContact ~= nil and parent.hasSnowContact;
					end;
					isActive = densityType ~= 0 and densityType ~= spec.tireTrackGroundGrassValue and densityType ~= spec.tireTrackGroundGrassCutValue and not hasSnowContact;
					isFieldMud = true;
					if wheel.densityType ~= nil and force ~= nil then
						wheel.densityType = densityType;
						wheel.densityTypeForcedUpdate = true;
					end;
				end;
			end;
		end;
	end;
	
	return isActive, isActive and isFieldMud;
end;

function mudSystem:createBodyTrack(self,index,r,g,b,a,ux,uy,uz,dirt,dir)
	if self.spec_attachable == nil and self.spec_mudSystem ~= nil then
		local wheels = self.spec_wheels.wheels;
		if self.spec_mudSystem.MudTrackIndex == nil and #wheels > 1 then
			local x1,y1,z1 = getWorldTranslation(wheels[1].driveNode);
			local oppositeWheelIndex = wheels[1].oppositeWheelIndex;
			if oppositeWheelIndex == nil then oppositeWheelIndex = 2; end;
			if wheels[oppositeWheelIndex] ~= nil and wheels[oppositeWheelIndex].isLeft == wheels[1].isLeft then
				oppositeWheelIndex = 3; 
			end;
			if wheels[oppositeWheelIndex] ~= nil and wheels[oppositeWheelIndex].isLeft ~= wheels[1].isLeft then
				local x2,y2,z2 = getWorldTranslation(wheels[oppositeWheelIndex].driveNode);
				local width = x1-x2;
				local width2 = y1-y2;
				local width3 = z1-z2;
				if width < 0 then width = width * (-1); end;
				if width2 < 0 then width2 = width2 * (-1); end;
				if width3 < 0 then width3 = width3 * (-1); end;
				width = (width + width2) + width3;
				width = (width - wheels[1].width);
				self.spec_mudSystem.MudTrackIndex = g_currentMission.tireTrackSystem:createTrack(width*0.8, 4);
			end;
		end;
		if self.spec_mudSystem.MudTrackIndex ~= nil then
			local x,y,z = getWorldTranslation(self.components[1].node);
			y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x,y,z)+0.001;
			g_currentMission.tireTrackSystem:addTrackPoint(self.spec_mudSystem.MudTrackIndex,x,y,z,ux,uy,uz,r,g,b,dirt,a,dir);
		end;
	end;
end;

function mudSystem:updateWheelMudPS(wheel, wheelRotFactor, steeringAngle, parentWheel)
    if wheel.mudSystem ~= nil and wheel.mudSystem.mudPS ~= nil then
		local ps = wheel.mudSystem.mudPS;
        if ps.isActive then
			local psEmitRate = wheel.radiusOriginal; if psEmitRate == nil then psEmitRate = parentWheel.radiusOriginal; end;
			psEmitRate = 1 + (1.0-math.min(psEmitRate, 1.0));
			if wheel.tireType == WheelsUtil.getTireType("crawler") then psEmitRate = psEmitRate*1.5; end;
			local sinkAmount = 1;
			if wheel.isInFieldMud then sinkAmount = g_currentMission.mudFieldSinkUpdater.fieldSinkAmount; end;
			local sizeScale = wheel.width * psEmitRate;
			local speedEmitScale = wheelRotFactor * sizeScale;
            local speedEmitScaleMultiplier = math.pow(2*speedEmitScale, 2);
            local emitScale = math.min(1.5 *(speedEmitScale + speedEmitScaleMultiplier),5);
            ParticleUtil.setEmitCountScale(ps, emitScale*sinkAmount);
			local tspeed = self:getLastSpeed(true);
			if not isAttachment and tspeed < 5 then
				local wheelSpeed = math.min(((25*wheelRotFactor)*0.9)* 3.6, 25);
				tspeed = math.max(tspeed,wheelSpeed);
				tspeed = math.max(tspeed,1);
			end;
			tspeed = math.min(tspeed,25);
			psEmitRate = mudSystem.psEmitRate*psEmitRate;
            local speedFactor = (psEmitRate * wheelRotFactor * tspeed);
            local speed = ps.particleSpeed * speedFactor;
            speed = math.min(speed, 0.004);
			if wheel.tireType == WheelsUtil.getTireType("street") then speed = speed*0.6; end;
            ParticleUtil.setParticleSystemSpeed(ps, speed*sinkAmount);
            ParticleUtil.setParticleSystemSpeedRandom(ps, (ps.particleRandomSpeed * speedFactor)*sinkAmount);
            local x,y,z;
            if wheel.wheelTire == nil then
                x,y,z = localToLocal(wheel.driveNode, getParent(ps.emitterShape), wheel.xOffset, 0, 0);
            else
                x,y,z = localToLocal(wheel.wheelTire, getParent(ps.emitterShape), 0, 0, 0);
            end;
            setTranslation(ps.emitterShape, x,y,z);
            if self.movingDirection < 0 then
                setRotation(ps.emitterShape, 0,math.pi+steeringAngle,0);
            else
                setRotation(ps.emitterShape, 0,steeringAngle,0);
            end;
		else
			ParticleUtil.setEmitCountScale(wheel.mudSystem.mudPS, 0);
			ParticleUtil.setParticleSystemSpeed(wheel.mudSystem.mudPS, 0);
			ParticleUtil.setParticleSystemSpeedRandom(wheel.mudSystem.mudPS, 0);
        end;
    end;
end;

 function mudSystem:setMudEmittingState(i,a, state, noEventSend)
	if g_currentMission.missionDynamicInfo.isMultiplayer then
		mudSystemEvent.sendEvent(self, i,a, state, noEventSend);
		local ps;
		if a < 0 then ps = self.spec_wheels.wheels[i].mudSystem.mudPS;
		else ps = self.spec_wheels.wheels[i].additionalWheels[a].mudSystem.mudPS; end;
		ParticleUtil.setEmittingState(ps, state);
		ps.isActive = state;
	end;
end;

 function mudSystem:setMudSinkState(i,a, stateA, stateB, noEventSend)
	if g_currentMission.missionDynamicInfo.isMultiplayer then
		mudSystemSinkEvent.sendEvent(self, i,a, stateA, stateB, noEventSend);
		if a < 0 then 
			self.spec_wheels.wheels[i].isInMud = stateA;
			self.spec_wheels.wheels[i].isInFieldMud = stateB;
		else 
			self.spec_wheels.wheels[i].additionalWheels[a].isInMud = stateA;
			self.spec_wheels.wheels[i].additionalWheels[a].isInFieldMud = stateB;
		end;
	end;
end;

mudSystemEvent = {};
mudSystemEvent_mt = Class(mudSystemEvent, Event);
InitEventClass(mudSystemEvent,"mudSystemEvent");

function mudSystemEvent:emptyNew()
    local self = Event.new(mudSystemEvent_mt);
    self.className = "mudSystemEvent";
    return self;
end;
function mudSystemEvent:new(vehicle,wheel,additionalWheel,state)
	local self = mudSystemEvent.emptyNew()
    self.vehicle = vehicle;
    self.wheel = wheel;
    self.additionalWheel = additionalWheel;
    self.state = state;
    return self;
end;
function mudSystemEvent:readStream(streamId,connection)
    self.vehicle = NetworkUtil.readNodeObject(streamId);
    self.wheel = streamReadInt8(streamId);
    self.additionalWheel = streamReadInt8(streamId);
    self.state = streamReadBool(streamId);
    self:run(connection);
end;
function mudSystemEvent:writeStream(streamId,connection)
    NetworkUtil.writeNodeObject(streamId,self.vehicle);
    streamWriteInt8(streamId,self.wheel);
    streamWriteInt8(streamId,self.additionalWheel);
    streamWriteBool(streamId,self.state);
end;
function mudSystemEvent:run(connection)
    self.vehicle:setMudEmittingState(self.wheel, self.additionalWheel, self.state, true);
    if not connection:getIsServer() then
        g_server:broadcastEvent(mudSystemEvent:new(self.vehicle,self.wheel,self.additionalWheel,self.state),nil,connection,self.vehicle);
    end;
end;
function mudSystemEvent.sendEvent(vehicle,wheel,additionalWheel,state,noEventSend)
    if noEventSend == nil or noEventSend == false then
        if g_server ~= nil then
            g_server:broadcastEvent(mudSystemEvent:new(vehicle,wheel,additionalWheel,state),nil,nil,vehicle);
        else
            g_client:getServerConnection():sendEvent(mudSystemEvent:new(vehicle,wheel,additionalWheel,state));
        end;
    end;
end;

----------

mudSystemSinkEvent = {};
mudSystemSinkEvent_mt = Class(mudSystemSinkEvent, Event);
InitEventClass(mudSystemSinkEvent,"mudSystemSinkEvent");

function mudSystemSinkEvent:emptyNew()
    local self = Event.new(mudSystemSinkEvent_mt);
    self.className = "mudSystemSinkEvent";
    return self;
end;
function mudSystemSinkEvent:new(vehicle,wheel,additionalWheel,stateA,stateB)
	local self = mudSystemSinkEvent.emptyNew()
    self.vehicle = vehicle;
    self.wheel = wheel;
    self.additionalWheel = additionalWheel;
    self.stateA = stateA;
    self.stateB = stateB;
    return self;
end;
function mudSystemSinkEvent:readStream(streamId,connection)
    self.vehicle = NetworkUtil.readNodeObject(streamId);
    self.wheel = streamReadInt8(streamId);
    self.additionalWheel = streamReadInt8(streamId);
    self.stateA = streamReadBool(streamId);
    self.stateB = streamReadBool(streamId);
    self:run(connection);
end;
function mudSystemSinkEvent:writeStream(streamId,connection)
    NetworkUtil.writeNodeObject(streamId,self.vehicle);
    streamWriteInt8(streamId,self.wheel);
    streamWriteInt8(streamId,self.additionalWheel);
    streamWriteBool(streamId,self.stateA);
    streamWriteBool(streamId,self.stateB);
end;
function mudSystemSinkEvent:run(connection)
    self.vehicle:setMudSinkState(self.wheel, self.additionalWheel, self.stateA, self.stateB, true);
    if not connection:getIsServer() then
        g_server:broadcastEvent(mudSystemSinkEvent:new(self.vehicle,self.wheel,self.additionalWheel,self.stateA, self.stateB),nil,connection,self.vehicle);
    end;
end;
function mudSystemSinkEvent.sendEvent(vehicle,wheel,additionalWheel,stateA,stateB,noEventSend)
    if noEventSend == nil or noEventSend == false then
        if g_server ~= nil then
            g_server:broadcastEvent(mudSystemSinkEvent:new(vehicle,wheel,additionalWheel,stateA,stateB),nil,nil,vehicle);
        else
            g_client:getServerConnection():sendEvent(mudSystemSinkEvent:new(vehicle,wheel,additionalWheel,stateA,stateB));
        end;
    end;
end;

function mudSystem:onReadStream(streamId, connection)
	if connection.isServer and self.spec_mudSystem ~= nil then
		local spec = self.spec_mudSystem;
		if spec.enableMudSystem and self.spec_wheels ~= nil then
			for _,wheel in pairs(self.spec_wheels.wheels) do
				wheel.radius = streamReadFloat32(streamId);
				wheel.sink = streamReadFloat32(streamId);
				wheel.sinkTarget = streamReadFloat32(streamId);
				wheel.sinkLongStiffnessFactor = streamReadFloat32(streamId);
				wheel.sinkLatStiffnessFactor = streamReadFloat32(streamId);
				wheel.isInMud = streamReadBool(streamId);
				wheel.isInFieldMud = streamReadBool(streamId);
				self:setWheelPositionDirty(wheel);
			end;
		end;
	end;
end;

function mudSystem:onWriteStream(streamId, connection)
	if not connection.isServer and self.spec_mudSystem ~= nil then
		local spec = self.spec_mudSystem;
		if spec.enableMudSystem and self.spec_wheels ~= nil then
			for _,wheel in pairs(self.spec_wheels.wheels) do
				streamWriteFloat32(streamId, wheel.radius);
				streamWriteFloat32(streamId, wheel.sink);
				streamWriteFloat32(streamId, wheel.sinkTarget);
				streamWriteFloat32(streamId, wheel.sinkLongStiffnessFactor);
				streamWriteFloat32(streamId, wheel.sinkLatStiffnessFactor);
				streamWriteBool(streamId, Utils.getNoNil(wheel.isInMud, false));
				streamWriteBool(streamId, Utils.getNoNil(wheel.isInFieldMud, false));
			end;
		end;
	end;
end;

function mudSystem:getDriveGroundParticleSystemsScale(superFunc, particleSystem, speed)
	local wheel = particleSystem.wheel;

	if wheel ~= nil then
		if particleSystem.onlyActiveOnGroundContact and wheel.contact ~= Wheels.WHEEL_GROUND_CONTACT and not wheel.hasSnowContact then
			return 0;
		end;
		-----------
		if (wheel.isInMud ~= nil and wheel.isInMud) or (wheel.isInFieldMud ~= nil and wheel.isInFieldMud)  then
			return 0;
		end;
		----------
		if not Wheels.GROUND_PARTICLES[wheel.lastTerrainAttribute] and not wheel.hasSnowContact then
			return 0;
		end;

		local grassValue = g_currentMission.fieldGroundSystem:getFieldGroundValue(FieldGroundType.GRASS);

		if wheel.densityType == grassValue then
			return 0;
		end;
	end;

	local minSpeed = particleSystem.minSpeed;
	local direction = particleSystem.direction;

	if minSpeed < speed and (direction == 0 or direction > 0 == (self.movingDirection > 0)) then
		local maxSpeed = particleSystem.maxSpeed;
		local alpha = math.min((speed - minSpeed) / (maxSpeed - minSpeed), 1);
		local scale = MathUtil.lerp(particleSystem.minScale, particleSystem.maxScale, alpha);

		return scale;
	end;

	return 0;
end;

function mudSystem:setNodeDirtAmount(superFunc, nodeData, dirtAmount, force)
	local isInMud = false;
	local vehicle = self;
	local hasWheels = vehicle.spec_wheels ~= nil and vehicle.spec_wheels.wheels ~= nil and #vehicle.spec_wheels.wheels > 0;
	if not hasWheels then
		if self.spec_attachable ~= nil and self.spec_attachable.attacherVehicle ~= nil then
			rootVehicle = self.spec_attachable.attacherVehicle:getRootVehicle();
			if self ~= rootVehicle then
				vehicle = rootVehicle;
			end;
		end;
	end;
	if hasWheels or vehicle ~= self then
		for _, wheel in pairs(vehicle.spec_wheels.wheels) do
			if (wheel.isInMud ~= nil and wheel.isInMud) or (wheel.isInFieldMud ~= nil and wheel.isInFieldMud) then
				isInMud = true;
				dirtAmount = dirtAmount + (0.01*Washable.getIntervalMultiplier());
				break;
			end;
		end;
	end;
	----------------------------------------^
	local spec = self.spec_washable;
	nodeData.dirtAmount = MathUtil.clamp(dirtAmount, 0, 1);
	local diff = nodeData.dirtAmountSent - nodeData.dirtAmount;

	if Washable.SEND_THRESHOLD < math.abs(diff) or force or isInMud then ----<
		for i = 1, #nodeData.nodes do
			local node = nodeData.nodes[i];
			local x, _, z, w = getShaderParameter(node, "RDT");

			setShaderParameter(node, "RDT", x, nodeData.dirtAmount, 0, w, false);
		end;

		if self.isServer then
			self:raiseDirtyFlags(spec.dirtyFlag);

			nodeData.dirtAmountSent = nodeData.dirtAmount;
		end;
	end;
end;

function mudSystem:updateDirtAmount(superFunc, nodeData, dt, allowsWashingByRain, rainScale, timeSinceLastRain, temperature)
	local spec = self.spec_washable;
	local change = 0;

	local isInMud = false;
	local vehicle = self;
	local hasWheels = vehicle.spec_wheels ~= nil and vehicle.spec_wheels.wheels ~= nil and #vehicle.spec_wheels.wheels > 0;
	if not hasWheels then
		if self.spec_attachable ~= nil and self.spec_attachable.attacherVehicle ~= nil then
			rootVehicle = self.spec_attachable.attacherVehicle:getRootVehicle();
			if self ~= rootVehicle then
				vehicle = rootVehicle;
			end;
		end;
	end;
	if hasWheels or vehicle ~= self then
		for _, wheel in pairs(vehicle.spec_wheels.wheels) do
			if (wheel.isInMud ~= nil and wheel.isInMud) or (wheel.isInFieldMud ~= nil and wheel.isInFieldMud) then
				isInMud = true;
				break;
			end;
		end;
	end;

	if (g_currentMission.realDirtFixEnabled ~= nil and not g_currentMission.realDirtFixEnabled) or not isInMud then
		if allowsWashingByRain and rainScale > 0.1 and timeSinceLastRain < 30 and temperature > 0 and nodeData.dirtAmount > 0.5 then
			change = -(dt / spec.washDuration);
		end;
	end;

	local dirtMultiplier = spec.lastDirtMultiplier;

	if dirtMultiplier ~= 0 then
		change = dt * spec.dirtDuration * dirtMultiplier;
	end;

	return change;
end;