--
-- RoundBaleLoader
-- This is the specialization for automatic round bale loaders
--
-- @author  Stefan Geiger
-- @date  12/10/09
--
-- Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved.
--
---------------->>>><<<<--------------------------
--
-- @edited PeterJ - euroDZN
-- 20/05/2014
--
-- http://eurodzn.wordpress.com/
--
 
RoundBaleLoader = {};
  
function RoundBaleLoader.prerequisitesPresent(specializations)
	return SpecializationUtil.hasSpecialization(Fillable, specializations);
end;
  
RoundBaleLoader.GRAB_MOVE_UP = 1;
RoundBaleLoader.GRAB_MOVE_DOWN = 2;
RoundBaleLoader.GRAB_DROP_BALE = 3;
  
RoundBaleLoader.EMPTY_NONE = 1;
RoundBaleLoader.EMPTY_TO_WORK = 2;
RoundBaleLoader.EMPTY_ROTATE_PLATFORM = 3;
RoundBaleLoader.EMPTY_ROTATE1 = 4;
RoundBaleLoader.EMPTY_CLOSE_GRIPPERS = 5;
RoundBaleLoader.EMPTY_HIDE_PUSHER1 = 6;
RoundBaleLoader.EMPTY_HIDE_PUSHER2 = 7;
RoundBaleLoader.EMPTY_ROTATE2 = 8;
RoundBaleLoader.EMPTY_WAIT_TO_DROP = 9;
RoundBaleLoader.EMPTY_WAIT_TO_SINK = 10;
RoundBaleLoader.EMPTY_SINK = 11;
RoundBaleLoader.EMPTY_CANCEL = 12;
RoundBaleLoader.EMPTY_WAIT_TO_REDO = 13;

RoundBaleLoader.CHANGE_DROP_BALES = 1;
RoundBaleLoader.CHANGE_SINK = 2;
RoundBaleLoader.CHANGE_EMPTY_REDO = 3;
RoundBaleLoader.CHANGE_EMPTY_START = 4;
RoundBaleLoader.CHANGE_EMPTY_CANCEL = 5;
RoundBaleLoader.CHANGE_MOVE_TO_WORK = 6;
RoundBaleLoader.CHANGE_MOVE_TO_TRANSPORT = 7;
RoundBaleLoader.CHANGE_GRAB_BALE = 8;
RoundBaleLoader.CHANGE_GRAB_MOVE_UP = 9;
RoundBaleLoader.CHANGE_GRAB_DROP_BALE = 10;
RoundBaleLoader.CHANGE_GRAB_MOVE_DOWN = 11;
RoundBaleLoader.CHANGE_ROTATE_PLATFORM = 12;
RoundBaleLoader.CHANGE_EMPTY_ROTATE_PLATFORM = 13;
RoundBaleLoader.CHANGE_EMPTY_ROTATE1 = 14;
RoundBaleLoader.CHANGE_EMPTY_HIDE_PUSHER2 = 15;
RoundBaleLoader.CHANGE_EMPTY_ROTATE2 = 16;
RoundBaleLoader.CHANGE_EMPTY_WAIT_TO_DROP = 17;
RoundBaleLoader.CHANGE_EMPTY_STATE_NIL = 18;
RoundBaleLoader.CHANGE_EMPTY_WAIT_TO_REDO = 19;

RoundBaleLoader.CHANGE_BUTTON_EMPTY = 20;
RoundBaleLoader.CHANGE_BUTTON_EMPTY_ABORT = 21;
RoundBaleLoader.CHANGE_BUTTON_WORK_TRANSPORT = 22;

function RoundBaleLoader:load(xmlFile)

	self.doStateChange = RoundBaleLoader.doStateChange;
	self.balesToLoad = {}
	self.balesToMount = {};
	self.isInWorkPosition = false;
	self.grabberIsMoving = false;
	self.allowGrabbing = false;	
	self.synchronizeFillLevel = false;

	self.rotatePlatformDirection = 0;

	self.emptyState = RoundBaleLoader.EMPTY_NONE;
  
	self.itemsToSave ={}
	self.fillLevel = 0;
	self.fillLevelMax = Utils.getNoNil(getXMLInt(xmlFile, "vehicle.capacity"), 0);
	self.capacity = self.fillLevelMax;
 
	self.baleGrabber = {};
	self.baleGrabber.grabNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.baleGrabber#grabNode"));
	
	self.startBalePlace = {};
	self.startBalePlace.bales = {};
	self.startBalePlace.node = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.balePlaces#startBalePlace"));
	if self.startBalePlace.node ~= nil then
		if getNumOfChildren(self.startBalePlace.node) < 2 then
			self.startBalePlace.node = nil;
		else
			self.startBalePlace.origRot = {};
			self.startBalePlace.origTrans = {};
			for i=1, 2 do
				local node = getChildAt(self.startBalePlace.node, i-1);
				local x,y,z = getRotation(node);
				self.startBalePlace.origRot[i] = {x,y,z};
				local x,y,z = getTranslation(node);
				self.startBalePlace.origTrans[i] = {x,y,z};
			end;
		end;
	end;
	self.startBalePlace.count = 0;
  
	self.currentBalePlace = 1;
	self.balePlaces = {};
	local i=0;
	while true do
		local key = string.format("vehicle.balePlaces.balePlace(%d)", i);
		if not hasXMLProperty(xmlFile, key) then
			break;
		end;
		local node = Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#node"));
		if node ~= nil then
			local entry = {};
			entry.node = node;
			table.insert(self.balePlaces, entry);
		end;
		i = i + 1;
	end;
  
	self.workTransportButton = InputBinding.IMPLEMENT_EXTRA;
	self.emptyAbortButton = InputBinding.IMPLEMENT_EXTRA2;
	self.emptyButton = InputBinding.IMPLEMENT_EXTRA3;

end;

function RoundBaleLoader:delete()
	for i, balePlace in pairs(self.balePlaces) do
		if balePlace.bales ~= nil then
			for _, baleServerId in pairs(balePlace.bales) do
				local bale = networkGetObject(baleServerId);
				if bale ~= nil then
					if bale.nodeId ~= 0 then
						unlink(bale.nodeId);
					end;
				end;
			end;
		end;
	end;
	for i, baleServerId in ipairs(self.startBalePlace.bales) do
		local bale = networkGetObject(baleServerId);
		if bale ~= nil then
			if bale.nodeId ~= 0 then
				unlink(bale.nodeId);
			end;
		end;
	end;
end;

function RoundBaleLoader:readStream(streamId, connection)
	self.isInWorkPosition = streamReadBool(streamId);
	self.rotatePlatformDirection = streamReadIntN(streamId, 3);
	if self.isInWorkPosition then
		RoundBaleLoader.moveToWorkPosition(self);
	end;
	local emptyState = streamReadUIntN(streamId, 4);
	self.currentBalePlace = streamReadInt8(streamId);
	self.fillLevel = streamReadInt8(streamId);
	if streamReadBool(streamId) then
		local baleServerId = streamReadInt32(streamId);
		self.baleGrabber.currentBale = baleServerId;
		self.balesToMount[baleServerId] = {serverId=baleServerId, linkNode=self.baleGrabber.grabNode, trans={0,0,0}, rot={0,0,0} };
	end;
	self.startBalePlace.count = streamReadUIntN(streamId, 3);
	for i=1, self.startBalePlace.count do
		local baleServerId = streamReadInt32(streamId);
		local attachNode = getChildAt(self.startBalePlace.node, i-1)
		self.balesToMount[baleServerId] = {serverId=baleServerId, linkNode=attachNode, trans={0,0,0}, rot={0,0,0} };
		table.insert(self.startBalePlace.bales, baleServerId);
	end;
	for i=1, table.getn(self.balePlaces) do
		local balePlace = self.balePlaces[i];
		local numBales = streamReadUIntN(streamId, 3);
		if numBales > 0 then
			balePlace.bales = {};
			for baleI=1, numBales do
				local baleServerId = streamReadInt32(streamId, baleServerId);
				local x = streamReadFloat32(streamId);
				local y = streamReadFloat32(streamId);
				local z = streamReadFloat32(streamId);
				table.insert(balePlace.bales, baleServerId);
				self.balesToMount[baleServerId] = {serverId=baleServerId, linkNode=balePlace.node, trans={ x,y,z}, rot={0,0,0} };
			end;
		end;
	end;
	RoundBaleLoader.updateBalePlacesAnimations(self);
	if emptyState >= RoundBaleLoader.EMPTY_TO_WORK then
		self:doStateChange(RoundBaleLoader.CHANGE_EMPTY_START);
		AnimatedVehicle.updateAnimations(self, 99999999);
		if emptyState >= RoundBaleLoader.EMPTY_ROTATE_PLATFORM then
			self:doStateChange(RoundBaleLoader.CHANGE_EMPTY_ROTATE_PLATFORM);
			AnimatedVehicle.updateAnimations(self, 99999999);
			if emptyState >= RoundBaleLoader.EMPTY_ROTATE1 then
				self:doStateChange(RoundBaleLoader.CHANGE_EMPTY_ROTATE1);
				AnimatedVehicle.updateAnimations(self, 99999999);
				if emptyState >= RoundBaleLoader.EMPTY_HIDE_PUSHER2 then
					self:doStateChange(RoundBaleLoader.CHANGE_EMPTY_HIDE_PUSHER2);
					AnimatedVehicle.updateAnimations(self, 99999999);
					if emptyState >= RoundBaleLoader.EMPTY_ROTATE2 then
						self:doStateChange(RoundBaleLoader.CHANGE_EMPTY_ROTATE2);
						AnimatedVehicle.updateAnimations(self, 99999999);
						if emptyState >= RoundBaleLoader.EMPTY_WAIT_TO_DROP then
							self:doStateChange(RoundBaleLoader.CHANGE_EMPTY_WAIT_TO_DROP);
							AnimatedVehicle.updateAnimations(self, 99999999);
							if emptyState == RoundBaleLoader.EMPTY_CANCEL or emptyState == RoundBaleLoader.EMPTY_WAIT_TO_REDO then
								self:doStateChange(RoundBaleLoader.CHANGE_EMPTY_CANCEL);
								AnimatedVehicle.updateAnimations(self, 99999999);
								if emptyState == RoundBaleLoader.EMPTY_WAIT_TO_REDO then
									self:doStateChange(RoundBaleLoader.CHANGE_EMPTY_WAIT_TO_REDO);
									AnimatedVehicle.updateAnimations(self, 99999999);
								end;
							elseif emptyState == RoundBaleLoader.EMPTY_WAIT_TO_SINK or emptyState == RoundBaleLoader.EMPTY_SINK then
								self:doStateChange(RoundBaleLoader.CHANGE_DROP_BALES);
								AnimatedVehicle.updateAnimations(self, 99999999);
								if emptyState == RoundBaleLoader.EMPTY_SINK then
									self:doStateChange(RoundBaleLoader.CHANGE_SINK);
									AnimatedVehicle.updateAnimations(self, 99999999);
								end;
							end;
						end;
					end;
				end;
			end;
		end;
	end;
	self.emptyState = emptyState;
end;

function RoundBaleLoader:writeStream(streamId, connection)
	streamWriteBool(streamId, self.isInWorkPosition);
	streamWriteIntN(streamId, self.rotatePlatformDirection, 3);
	streamWriteUIntN(streamId, self.emptyState, 4);
	streamWriteInt8(streamId, self.currentBalePlace);
	streamWriteInt8(streamId, self.fillLevel);
	if streamWriteBool(streamId, self.baleGrabber.currentBale ~= nil) then
		streamWriteInt32(streamId, self.baleGrabber.currentBale);
	end;
	streamWriteUIntN(streamId, self.startBalePlace.count, 3);
	for i=1, self.startBalePlace.count do
		local baleServerId = self.startBalePlace.bales[i];
		streamWriteInt32(streamId, baleServerId);
	end;
	for i=1, table.getn(self.balePlaces) do
		local balePlace = self.balePlaces[i];
		local numBales = 0;
		if balePlace.bales ~= nil then
			numBales = table.getn(balePlace.bales);
		end;
		streamWriteUIntN(streamId, numBales, 3);
		if balePlace.bales ~= nil then
			for baleI=1, numBales do
				local baleServerId = balePlace.bales[baleI];
				local bale = networkGetObject(baleServerId);
				local nodeId = bale.nodeId;
				local x,y,z = getTranslation(nodeId);
				streamWriteInt32(streamId, baleServerId);
				streamWriteFloat32(streamId, x);
				streamWriteFloat32(streamId, y);
				streamWriteFloat32(streamId, z);
			end;
			end;
	end;
end;
  
function RoundBaleLoader.updateBalePlacesAnimations(self)
	if self.currentBalePlace ~= 0 then
		local multiplicator = 1/((table.getn(self.balePlaces))-1);
		self:playAnimation("moveBalePlaces", 20, 0, true);
		self:setAnimationStopTime("moveBalePlaces", math.min(multiplicator*(self.currentBalePlace-1), 1));
		AnimatedVehicle.updateAnimations(self, 99999999);
	end;
	if self.startBalePlace.count >= 1 then
  		self:playAnimation("balesToOtherRow", 20, nil, true);
		AnimatedVehicle.updateAnimations(self, 99999999);
		if self.startBalePlace.count >= 2 then
			RoundBaleLoader.rotatePlatform(self);
		end;
	end;
end;

function RoundBaleLoader:mouseEvent(posX, posY, isDown, isUp, button)
end;

function RoundBaleLoader:draw()

	if self:getIsActiveForInput(true) then
		if self.emptyState == RoundBaleLoader.EMPTY_NONE then
			if self.grabberMoveState == nil and not self:getIsAnimationPlaying("baleGrabberDropBale") then
				if self.isInWorkPosition then
					g_currentMission:addHelpButtonText(g_i18n:getText("BALELOADER_TRANSPORT"), self.workTransportButton);
				else
					g_currentMission:addHelpButtonText(g_i18n:getText("BALELOADER_WORK"), self.workTransportButton);
				end;
			end;
			if RoundBaleLoader.getAllowsStartUnloading(self) then
				g_currentMission:addHelpButtonText(g_i18n:getText("BALELOADER_UNLOAD"), self.emptyButton);
			end;
		else
			if self.emptyState >= RoundBaleLoader.EMPTY_TO_WORK and self.emptyState <= RoundBaleLoader.EMPTY_ROTATE2 then
					g_currentMission:addExtraPrintText(g_i18n:getText("BALELOADER_UP"));
			elseif self.emptyState == RoundBaleLoader.EMPTY_CANCEL or self.emptyState == RoundBaleLoader.EMPTY_SINK then
				g_currentMission:addExtraPrintText(g_i18n:getText("BALELOADER_DOWN"));
			elseif self.emptyState == RoundBaleLoader.EMPTY_WAIT_TO_DROP then
				g_currentMission:addHelpButtonText(g_i18n:getText("BALELOADER_READY"), self.emptyButton);
				g_currentMission:addHelpButtonText(g_i18n:getText("BALELOADER_ABORT"), self.emptyAbortButton);
			elseif self.emptyState == RoundBaleLoader.EMPTY_WAIT_TO_SINK then
				g_currentMission:addHelpButtonText(g_i18n:getText("BALELOADER_SINK"), self.emptyButton);
			elseif self.emptyState == RoundBaleLoader.EMPTY_WAIT_TO_REDO then
				g_currentMission:addHelpButtonText(g_i18n:getText("BALELOADER_UNLOAD"), self.emptyButton);
			end;
		end;
	end
end

function RoundBaleLoader:keyEvent(unicode, sym, modifier, isDown)
end;
function RoundBaleLoader:update(dt)
	if self.firstTimeRun then
		for k,v in pairs(self.balesToLoad) do
			local baleObject = Bale:new(self.isServer, self.isClient);
			local x,y,z = unpack(v.translation);
			local rx,ry,rz = unpack(v.rotation);
			baleObject:load(v.filename, x,y,z,rx,ry,rz, v.fillLevel);
			baleObject:mount(self, v.parentNode, x,y,z, rx,ry,rz)
			baleObject:register();
			table.insert(v.bales, networkGetObjectId(baleObject));
			self.balesToLoad[k] = nil;
		end;
		for k, baleToMount in pairs(self.balesToMount) do
			local bale = networkGetObject(baleToMount.serverId);
			if bale ~= nil then
				local x,y,z = unpack(baleToMount.trans);
				local rx,ry,rz = unpack(baleToMount.rot);
				bale:mount(self, baleToMount.linkNode, x,y,z, rx,ry,rz);
				self.balesToMount[k] = nil;
			end;
		end;
	end;
	if self:getIsActive() then
		if self:getIsActiveForInput() and self.isClient then
			if InputBinding.hasEvent(self.emptyButton)  then
				g_client:getServerConnection():sendEvent(BaleLoaderStateEvent:new(self, RoundBaleLoader.CHANGE_BUTTON_EMPTY));
			elseif InputBinding.hasEvent(self.emptyAbortButton) then
				g_client:getServerConnection():sendEvent(BaleLoaderStateEvent:new(self, RoundBaleLoader.CHANGE_BUTTON_EMPTY_ABORT));
			elseif InputBinding.hasEvent(self.workTransportButton) then
				if not self:getIsAnimationPlaying("baleGrabberDropBale") then
					g_client:getServerConnection():sendEvent(BaleLoaderStateEvent:new(self, RoundBaleLoader.CHANGE_BUTTON_WORK_TRANSPORT));
				end;
			end;
		end;
		if self.grabberIsMoving then
			if not self:getIsAnimationPlaying("baleGrabberTransportToWork") and not self:getIsAnimationPlaying("baleGrabberDropBale") then
				self.grabberIsMoving = false;
			end;
		end;
		self.allowGrabbing = false;
		if self.isInWorkPosition and not self.grabberIsMoving and self.grabberMoveState == nil and self.startBalePlace.count < 2 and not self:getIsAnimationPlaying("balePusher") and self.emptyState == RoundBaleLoader.EMPTY_NONE and self.fillLevel < self.fillLevelMax then
			self.allowGrabbing = true;
		end;
		if self.isServer then
			if self.allowGrabbing then
				if self.baleGrabber.grabNode ~= nil and self.baleGrabber.currentBale == nil then
					local nearestBale = RoundBaleLoader.getBaleInRange(self, self.baleGrabber.grabNode);
					if nearestBale ~= nil then
						g_server:broadcastEvent(BaleLoaderStateEvent:new(self, RoundBaleLoader.CHANGE_GRAB_BALE, networkGetObjectId(nearestBale)), true, nil, self);
					end;
				end;
			end;
			if self.grabberMoveState ~= nil then
				if self.grabberMoveState == RoundBaleLoader.GRAB_MOVE_UP then
					g_server:broadcastEvent(BaleLoaderStateEvent:new(self, RoundBaleLoader.CHANGE_GRAB_MOVE_UP), true, nil, self);
				elseif self.grabberMoveState == RoundBaleLoader.GRAB_DROP_BALE then
					if not self:getIsAnimationPlaying("baleGrabberDropBale") then
						g_server:broadcastEvent(BaleLoaderStateEvent:new(self, RoundBaleLoader.CHANGE_GRAB_DROP_BALE), true, nil, self);
					end;
				elseif self.grabberMoveState == RoundBaleLoader.GRAB_MOVE_DOWN then
					g_server:broadcastEvent(BaleLoaderStateEvent:new(self, RoundBaleLoader.CHANGE_GRAB_MOVE_DOWN), true, nil, self);
				end;
			end;
			if self.rotatePlatformDirection ~= 0 then
				g_server:broadcastEvent(BaleLoaderStateEvent:new(self, RoundBaleLoader.CHANGE_ROTATE_PLATFORM), true, nil, self);
			end;
			if self.emptyState ~= RoundBaleLoader.EMPTY_NONE then
				if self.emptyState == RoundBaleLoader.EMPTY_TO_WORK then
					if not self:getIsAnimationPlaying("baleGrabberTransportToWork") then
						g_server:broadcastEvent(BaleLoaderStateEvent:new(self, RoundBaleLoader.CHANGE_EMPTY_ROTATE_PLATFORM), true, nil, self);
					end;
				elseif self.emptyState == RoundBaleLoader.EMPTY_ROTATE_PLATFORM then
					g_server:broadcastEvent(BaleLoaderStateEvent:new(self, RoundBaleLoader.CHANGE_EMPTY_ROTATE1), true, nil, self);
				elseif self.emptyState == RoundBaleLoader.EMPTY_ROTATE1 then
					if not self:getIsAnimationPlaying("emptyRotate") and not self:getIsAnimationPlaying("moveBalePlaces") then
						g_server:broadcastEvent(BaleLoaderStateEvent:new(self, RoundBaleLoader.CHANGE_EMPTY_HIDE_PUSHER2), true, nil, self);
					end;
				elseif self.emptyState == RoundBaleLoader.EMPTY_HIDE_PUSHER2 then
					if not self:getIsAnimationPlaying("moveBalePlacesToEmpty") then
						g_server:broadcastEvent(BaleLoaderStateEvent:new(self, RoundBaleLoader.CHANGE_EMPTY_ROTATE2), true, nil, self);
					end;
				elseif self.emptyState == RoundBaleLoader.EMPTY_ROTATE2 then
					if not self:getIsAnimationPlaying("emptyRotate") then
						g_server:broadcastEvent(BaleLoaderStateEvent:new(self, RoundBaleLoader.CHANGE_EMPTY_WAIT_TO_DROP), true, nil, self);
					end;
				elseif self.emptyState == RoundBaleLoader.EMPTY_SINK then
					if not self:getIsAnimationPlaying("emptyRotate") then
						g_server:broadcastEvent(BaleLoaderStateEvent:new(self, RoundBaleLoader.CHANGE_EMPTY_STATE_NIL), true, nil, self);
					end;
				elseif self.emptyState == RoundBaleLoader.EMPTY_CANCEL then
					if not self:getIsAnimationPlaying("emptyRotate") then
						g_server:broadcastEvent(BaleLoaderStateEvent:new(self, RoundBaleLoader.CHANGE_EMPTY_WAIT_TO_REDO), true, nil, self);
					end;
				end;
			end;
		end;
		---*** stop moving forward ***---
		if self.attacherVehicle ~= nil and self.attacherVehicle.motor ~= nil and self.attacherVehicleMoving == true then
			local wheelRotX,_,_ = getRotation(self.wheels[1].driveNode);
			if wheelRotX > (self.wheelRotX + 3) then
				self.attacherVehicle.motor:setSpeedLevel(0, false);
				self.attacherVehicleMoving = false;
			end;
		end;
	end;
end;

function RoundBaleLoader.getBaleInRange(self, refNode)
	local px, py, pz = getWorldTranslation(refNode);
	local nearestDistance = 1.5;
	local nearestBale = nil;
  
	for index, item in pairs(g_currentMission.itemsToSave) do
		if item.item:isa(Bale) then
			if getUserAttribute(item.item.nodeId, "isRoundbale") == true then
				local bale = item.item;
				local vx, vy, vz = getWorldTranslation(bale.nodeId);
				local distance = Utils.vector3Length(px-vx, py-vy, pz-vz);
				if distance < nearestDistance then
					nearestBale = item.item;
				end;
			end;
		end;
	end;
	return nearestBale;
end;

function RoundBaleLoader:onDetach()
end;

function RoundBaleLoader:onAttach()
end;  
  
function RoundBaleLoader:loadFromAttributesAndNodes(xmlFile, key, resetVehicles)
	self.currentBalePlace = 1;
	self.startBalePlace.count = 0;
	local numBales = 0
	local i=0;
	while true do
		local baleKey = key..string.format(".bale(%d)", i);
		if not hasXMLProperty(xmlFile, baleKey) then
			break;
		end;
		local filename = getXMLString(xmlFile, baleKey.."#filename");
		if filename ~= nil then
			filename = Utils.convertFromNetworkFilename(filename);
			local x,y,z = Utils.getVectorFromString(getXMLString(xmlFile, baleKey.."#position"));
			local xRot,yRot,zRot = Utils.getVectorFromString(getXMLString(xmlFile, baleKey.."#rotation"));
			local fillLevel = getXMLFloat(xmlFile, baleKey.."#fillLevel");
			local balePlace = getXMLInt(xmlFile, baleKey.."#balePlace");
			local helper = getXMLInt(xmlFile, baleKey.."#helper");
			if balePlace == nil or (balePlace > 0 and (x == nil or y == nil or z == nil or xRot == nil or yRot == nil or zRot == nil)) or (balePlace < 1 and helper == nil) then
				print("Warning: Corrupt savegame, bale "..filename.." could not be loaded");
			else
				local translation;
				local rotation;
				if balePlace > 0 then
					translation = {x,y,z};
					rotation={xRot, yRot, zRot};
				else
					translation = {0,0,0};
					rotation={0,0,0};
				end;
				local parentNode = nil;
				local bales = nil;
				if balePlace < 1 then
					if helper <= getNumOfChildren(self.startBalePlace.node) then
						parentNode = getChildAt(self.startBalePlace.node, helper-1);
						if self.startBalePlace.bales == nil then
							self.startBalePlace.bales = {};
						end;
						bales = self.startBalePlace.bales;
						self.startBalePlace.count = self.startBalePlace.count+1;
					end;
				elseif balePlace <= table.getn(self.balePlaces) then
					self.currentBalePlace = math.max(self.currentBalePlace, balePlace+1);
					parentNode = self.balePlaces[balePlace].node;
					if self.balePlaces[balePlace].bales == nil then
						self.balePlaces[balePlace].bales = {};
					end;
					bales = self.balePlaces[balePlace].bales;
				end;
				if parentNode ~= nil then
					numBales = numBales+1;
					table.insert(self.balesToLoad, {parentNode=parentNode, filename=filename, bales=bales, translation=translation, rotation=rotation, fillLevel=fillLevel});
				end;
			end;
		end;
		i = i +1;
	end;
	RoundBaleLoader.updateBalePlacesAnimations(self);
	self.fillLevel = numBales;
	return BaseMission.VEHICLE_LOAD_OK;
end

function RoundBaleLoader:getSaveAttributesAndNodes(nodeIdent)
	local attributes = "";
	local nodes = "";
	local baleNum = 0;
	for i, balePlace in pairs(self.balePlaces) do
		if balePlace.bales ~= nil then
			for _, baleServerId in pairs(balePlace.bales) do
				local bale = networkGetObject(baleServerId);
				if bale ~= nil then
					local nodeId = bale.nodeId;
					local x,y,z = getTranslation(nodeId);
					local rx,ry,rz = getRotation(nodeId);
					if baleNum>0 then
						nodes = nodes.."\n";
					end;
					nodes = nodes..nodeIdent..'<bale filename="'..Utils.encodeToHTML(Utils.convertToNetworkFilename(bale.i3dFilename))..'" fillLevel="'..bale:getFillLevel()..'" position="'..x..' '..y..' '..z..'" rotation="'..rx..' '..ry..' '..rz..'" balePlace="'..i..'" />';
					baleNum = baleNum+1;
				end;
			end;
		end;
	end;
	for i, baleServerId in ipairs(self.startBalePlace.bales) do
		local bale = networkGetObject(baleServerId);
		if bale ~= nil then
			if baleNum>0 then
				nodes = nodes.."\n";
			end;
			nodes = nodes..nodeIdent..'<bale filename="'..Utils.encodeToHTML(Utils.convertToNetworkFilename(bale.i3dFilename))..'" fillLevel="'..bale:getFillLevel()..'" balePlace="0" helper="'..i..'"/>';
			baleNum = baleNum+1;
		end;
	end;
	return attributes,nodes;
end

function RoundBaleLoader:doStateChange(id, nearestBaleServerId)
	if id == RoundBaleLoader.CHANGE_DROP_BALES then
		self.currentBalePlace = 1;
		for _, balePlace in pairs(self.balePlaces) do
			if balePlace.bales ~= nil then
				for _, baleServerId in pairs(balePlace.bales) do
					local bale = networkGetObject(baleServerId);
					if bale ~= nil then
						bale:unmount();
					end;
					self.balesToMount[baleServerId] = nil;
				end;
				balePlace.bales = nil;
			end;
		end;
		self.fillLevel = 0;
		self.emptyState = RoundBaleLoader.EMPTY_WAIT_TO_SINK;
		---*** move forward ***---
		if self.attacherVehicle ~= nil and self.attacherVehicle.motor ~= nil then
			self.attacherVehicle.motor:setSpeedLevel(4, true);
			self.attacherVehicleMoving = true;
			self.wheelRotX,_,_ = getRotation(self.wheels[1].driveNode);
		end;
	elseif id == RoundBaleLoader.CHANGE_SINK then
		self:playAnimation("emptyRotate", -1.5, nil, true);
		self:playAnimation("moveBalePlaces", -5, nil, true);
		if not self.isInWorkPosition then
			self:playAnimation("baleGrabberTransportToWork", -1, nil, true);
		end;
		self.emptyState = RoundBaleLoader.EMPTY_SINK;
	elseif id == RoundBaleLoader.CHANGE_EMPTY_REDO then
		self:playAnimation("emptyRotate", 1, nil, true);
		self.emptyState = RoundBaleLoader.EMPTY_ROTATE2;
	elseif id == RoundBaleLoader.CHANGE_EMPTY_START then
		RoundBaleLoader.moveToWorkPosition(self);
		self.emptyState = RoundBaleLoader.EMPTY_TO_WORK;
	elseif id == RoundBaleLoader.CHANGE_EMPTY_CANCEL then
		self:playAnimation("emptyRotate", -1, nil, true);
		self.emptyState = RoundBaleLoader.EMPTY_CANCEL;
	elseif id == RoundBaleLoader.CHANGE_MOVE_TO_TRANSPORT then
		if self.isInWorkPosition then
			self.grabberIsMoving = true;
			self.isInWorkPosition = false;
			RoundBaleLoader.moveToTransportPosition(self);
		end;
	elseif id == RoundBaleLoader.CHANGE_MOVE_TO_WORK then
		if not self.isInWorkPosition then
			self.grabberIsMoving = true;
			self.isInWorkPosition = true;
			RoundBaleLoader.moveToWorkPosition(self);
		end;
	elseif id == RoundBaleLoader.CHANGE_GRAB_BALE then
		local bale = networkGetObject(nearestBaleServerId);
		self.baleGrabber.currentBale = nearestBaleServerId;
		if bale ~= nil then
			bale:mount(self, self.baleGrabber.grabNode, 0,0,0, 0,0,0);
			self.balesToMount[nearestBaleServerId] = nil;
		else
			self.balesToMount[nearestBaleServerId] = {serverId=nearestBaleServerId, linkNode=self.baleGrabber.grabNode, trans={0,0,0}, rot={0,0,0} };
		end;
		self.grabberMoveState = RoundBaleLoader.GRAB_MOVE_UP;
		self.fillLevel = self.fillLevel + 1;
	elseif id == RoundBaleLoader.CHANGE_GRAB_MOVE_UP then
		self:playAnimation("baleGrabberDropBale", 1, nil, true);
		self.grabberMoveState = RoundBaleLoader.GRAB_DROP_BALE;
	elseif id == RoundBaleLoader.CHANGE_GRAB_DROP_BALE then
		if self.startBalePlace.count < 2 and self.startBalePlace.node ~= nil then
			local attachNode = getChildAt(self.startBalePlace.node, self.startBalePlace.count)
			local bale = networkGetObject(self.baleGrabber.currentBale);
			if bale ~= nil then
				bale:mount(self, attachNode, 0,0,0, 0,0,0);
				self.balesToMount[self.baleGrabber.currentBale] = nil;
			else
				self.balesToMount[self.baleGrabber.currentBale] = {serverId=self.baleGrabber.currentBale, linkNode=attachNode, trans={0,0,0}, rot={0,0,0} };
			end;
			self.startBalePlace.count = self.startBalePlace.count + 1;
			table.insert(self.startBalePlace.bales, self.baleGrabber.currentBale);
			self.baleGrabber.currentBale = nil;
            if self.startBalePlace.count == 1 then
				self:playAnimation("balesToOtherRow", 1, nil, true);
			elseif self.startBalePlace.count == 2 then
				RoundBaleLoader.rotatePlatform(self);
			end;
			self:playAnimation("baleGrabberDropBale", -1, nil, true);
			self.grabberMoveState = RoundBaleLoader.GRAB_MOVE_DOWN;
			self.grabberIsMoving = true;
		end;
	elseif id == RoundBaleLoader.CHANGE_GRAB_MOVE_DOWN then
		self.grabberMoveState = nil;
	elseif id == RoundBaleLoader.CHANGE_ROTATE_PLATFORM then
		if self.rotatePlatformDirection > 0 then
			local balePlace = self.balePlaces[self.currentBalePlace];
			self.currentBalePlace = self.currentBalePlace + 1;
			for i=1, table.getn(self.startBalePlace.bales) do
				local node = getChildAt(self.startBalePlace.node, i-1);
				local x,y,z = getTranslation(node);
				local baleServerId = self.startBalePlace.bales[i];
				local bale = networkGetObject(baleServerId);
				if bale ~= nil then
					bale:mount(self, balePlace.node, x,y,z, 0,0,0);
					self.balesToMount[baleServerId] = nil;
				else
					self.balesToMount[baleServerId] = {serverId=baleServerId, linkNode=balePlace.node, trans={ x,y,z}, rot={0,0,0} };
				end;
			end;
			balePlace.bales = self.startBalePlace.bales;
			self.startBalePlace.bales = {};
			self.startBalePlace.count = 0;
			for i=1, 2 do
				local node = getChildAt(self.startBalePlace.node, i-1);
				setRotation(node, unpack(self.startBalePlace.origRot[i]));
				setTranslation(node, unpack(self.startBalePlace.origTrans[i]));
			end;
			if self.emptyState == RoundBaleLoader.EMPTY_NONE then
				self.rotatePlatformDirection = -1;
			else
				self.rotatePlatformDirection = 0;
			end;
			self:playAnimation("balesToOtherRow", -3, nil, true);
		else
			self.rotatePlatformDirection = 0;
		end;
	elseif id == RoundBaleLoader.CHANGE_EMPTY_ROTATE_PLATFORM then
		self.emptyState = RoundBaleLoader.EMPTY_ROTATE_PLATFORM;
		if self.startBalePlace.count ~= 0 then
			RoundBaleLoader.rotatePlatform(self)
		end;
	elseif id == RoundBaleLoader.CHANGE_EMPTY_ROTATE1 then
		self:playAnimation("emptyRotate", 1, nil, true);
		self:setAnimationStopTime("emptyRotate", 0.2);
		self.emptyState = RoundBaleLoader.EMPTY_ROTATE1;
	elseif id == RoundBaleLoader.CHANGE_EMPTY_HIDE_PUSHER2 then
		local balePlacesTime = self:getRealAnimationTime("moveBalePlaces");
		self:playAnimation("moveBalePlacesToEmpty", 1, balePlacesTime/self:getAnimationDuration("moveBalePlacesToEmpty"), true);
		self.emptyState = RoundBaleLoader.EMPTY_HIDE_PUSHER2;
	elseif id == RoundBaleLoader.CHANGE_EMPTY_ROTATE2 then
		self:playAnimation("emptyRotate", 1, self:getAnimationTime("emptyRotate"), true);
		self.emptyState = RoundBaleLoader.EMPTY_ROTATE2;
	elseif id == RoundBaleLoader.CHANGE_EMPTY_WAIT_TO_DROP then
		self.emptyState = RoundBaleLoader.EMPTY_WAIT_TO_DROP;
	elseif id == RoundBaleLoader.CHANGE_EMPTY_STATE_NIL then
		self.emptyState = RoundBaleLoader.EMPTY_NONE;
	elseif id == RoundBaleLoader.CHANGE_EMPTY_WAIT_TO_REDO then
		self.emptyState = RoundBaleLoader.EMPTY_WAIT_TO_REDO;
	elseif id == RoundBaleLoader.CHANGE_BUTTON_EMPTY then
		assert(self.isServer);
		if self.emptyState ~= RoundBaleLoader.EMPTY_NONE then
			if self.emptyState == RoundBaleLoader.EMPTY_WAIT_TO_DROP then
				g_server:broadcastEvent(BaleLoaderStateEvent:new(self, RoundBaleLoader.CHANGE_DROP_BALES), true, nil, self);
			elseif self.emptyState == RoundBaleLoader.EMPTY_WAIT_TO_SINK then
				g_server:broadcastEvent(BaleLoaderStateEvent:new(self, RoundBaleLoader.CHANGE_SINK), true, nil, self);
			elseif self.emptyState == RoundBaleLoader.EMPTY_WAIT_TO_REDO then
				g_server:broadcastEvent(BaleLoaderStateEvent:new(self, RoundBaleLoader.CHANGE_EMPTY_REDO), true, nil, self);
			end;
		else
			if RoundBaleLoader.getAllowsStartUnloading(self) then
				g_server:broadcastEvent(BaleLoaderStateEvent:new(self, RoundBaleLoader.CHANGE_EMPTY_START), true, nil, self);
			end;
		end;
	elseif id == RoundBaleLoader.CHANGE_BUTTON_EMPTY_ABORT then
		assert(self.isServer);
		if self.emptyState ~= RoundBaleLoader.EMPTY_NONE then
			if self.emptyState == RoundBaleLoader.EMPTY_WAIT_TO_DROP then
				g_server:broadcastEvent(BaleLoaderStateEvent:new(self, RoundBaleLoader.CHANGE_EMPTY_CANCEL), true, nil, self);
			end;
		end;
	elseif id == RoundBaleLoader.CHANGE_BUTTON_WORK_TRANSPORT then
		assert(self.isServer);
		if self.emptyState == RoundBaleLoader.EMPTY_NONE and self.grabberMoveState == nil then
			if self.isInWorkPosition then
				g_server:broadcastEvent(BaleLoaderStateEvent:new(self, RoundBaleLoader.CHANGE_MOVE_TO_TRANSPORT), true, nil, self);
			else
				g_server:broadcastEvent(BaleLoaderStateEvent:new(self, RoundBaleLoader.CHANGE_MOVE_TO_WORK), true, nil, self);
			end;
		end;
	end;
end;

function RoundBaleLoader.getAllowsStartUnloading(self)
	return self.fillLevel > 0 and self.rotatePlatformDirection == 0 and not self.grabberIsMoving and self.grabberMoveState == nil and self.emptyState == RoundBaleLoader.EMPTY_NONE;
end;

function RoundBaleLoader.rotatePlatform(self)
	self.rotatePlatformDirection = 1;
	if self.currentBalePlace ~= 0 then
		local multiplicator = 1/((table.getn(self.balePlaces))-1);
		self:playAnimation("moveBalePlaces", 1, multiplicator*(self.currentBalePlace-1), true);
		self:setAnimationStopTime("moveBalePlaces", math.min(multiplicator*self.currentBalePlace, 1));
		if self.currentBalePlace < table.getn(self.balePlaces) then
			self:playAnimation("balePusher", 1, nil, true);
		end;
	end;
end;

function RoundBaleLoader.moveToWorkPosition(self)
	self:playAnimation("baleGrabberTransportToWork", 1, Utils.clamp(self:getAnimationTime("baleGrabberTransportToWork"), 0, 1), true);
end;

function RoundBaleLoader.moveToTransportPosition(self)
	self:playAnimation("baleGrabberTransportToWork", -1, Utils.clamp(self:getAnimationTime("baleGrabberTransportToWork"), 0, 1), true);
end;