AdditionalContractMapData = {};
AdditionalContractMapData.isLoaded = false;
AdditionalContractMapData.addConsoleCommand = true;
AdditionalContractMapData.CHANGE = 3;
AdditionalContractMapData.MAX_TRIES = 15;
AdditionalContractMapData.STOREPLACE_TRIGGERS = {};
AdditionalContractMapData.LOAD_TRIGGERS = {WORKSHOPS=true, SELLINGSTATIONS=true, PRODUCTIONPOINTS=false};
AdditionalContractMapData.ACCEPTS_ANIMALS = {NAMES={}, PLACEABLES={}, BYGIANTS={COW=true,SHEEP=true,HORSE=true,PIG=true,CHICKEN=false}};

local AdditionalContractMapData_mt = Class(AdditionalContractMapData);

function AdditionalContractMapData.new()	
	local self = {};
	setmetatable(self, AdditionalContractMapData_mt);	
	self.mapTriggers = {uniqueId={}, workshop={}, sellingStation={}, productionPoint={}};
	self.missionVehicles = {};
	self.hasMapTriggers = {unloadingStation=false,loadingStation=false};
	self.mapPositions = {loaded=false};
	self.mapTypes = {};
	return self;
end;

function AdditionalContractMapData:loadMap(args)	
	
end;

function AdditionalContractMapData:onStartMap(args)	
	if not self:isDataLoaded() then --mp		
		if g_server ~= nil then			
			g_additionalContractSplineManager:load(); --new			
		end; --only on server
		g_additionalContractMapData:loadTriggers();
		g_additionalContractMapData:hasTriggers();
		g_additionalContractMapData:loadStoreSpawnPlace();
		g_additionalContractMapData:loadMapPositions();
		g_additionalContractNpcManager:load();
		g_additionalContractMapData:loadAnimalTypes();
		if g_server ~= nil then				
			g_additionalContractPlaceableManager:load() --new
		end; --only on server
		AdditionalContractMapData.isLoaded = true;
	end;	
end;

function AdditionalContractMapData:isOnStartMap(args)
	
end;

function AdditionalContractMapData:isDataLoaded()
	return AdditionalContractMapData.isLoaded == true;
end;

---animal types---
function AdditionalContractMapData:loadAnimalTypes()
	local animalTypes = g_currentMission.animalSystem:getTypes();
	if animalTypes ~= nil and #animalTypes > 0 then			
		for a=1, #animalTypes do
			g_additionalContractMapData:insertAcceptsAnimalType(animalTypes[a]);			
		end;
		g_additionalContractMapData:searchAnimalPlaceablesOnFarmId(false);
	end;
end;

function AdditionalContractMapData:searchAnimalPlaceablesOnFarmId(dayChange)
	if #g_currentMission.husbandrySystem.placeables > 0 then
		local husbandryPlaceables = g_currentMission.husbandrySystem.placeables;
		if dayChange then g_additionalContractMapData:setAllAnimalsCanDeliveryOff();end;
		for _, placeable in ipairs(husbandryPlaceables) do
			if placeable:getOwnerFarmId() ~= AccessHandler.NOBODY and placeable:getNumOfAnimals() > 0 then				
				local animalType = g_currentMission.animalSystem:getTypeByIndex(placeable:getAnimalTypeIndex());
				if animalType ~= nil then
					g_additionalContractMapData:insertAcceptsAnimalType(animalType);
					g_additionalContractMapData:setAnimalCanDelivery(animalType.name, AdditionalContractMapData.ACCEPTS_ANIMALS.BYGIANTS[animalType.name] or false); --default by LS25);					
				end;
			end;
		end;
	end;	
end;

function AdditionalContractMapData:setAllAnimalsCanDeliveryOff()
	for _, data in pairs(AdditionalContractMapData.ACCEPTS_ANIMALS.NAMES) do
		if data ~= nil then data.canDelivery = false;end;
	end;
end;

function AdditionalContractMapData:insertAcceptsAnimalType(animalType)
	if animalType == nil or AdditionalContractMapData.ACCEPTS_ANIMALS.NAMES[animalType.name] ~= nil then return;end;
	local state = AdditionalContractMapData.ACCEPTS_ANIMALS.BYGIANTS[animalType.name] or false; --default by LS25
	AdditionalContractMapData.ACCEPTS_ANIMALS.NAMES[animalType.name] = {state=state, title=animalType.groupTitle, subNames={}, canTransport=state, canDelivery=false};
	if animalType.subTypes ~= nil and #animalType.subTypes > 0 then
		for s=1, #animalType.subTypes do
			local subAnimal = g_currentMission.animalSystem:getSubTypeByIndex(animalType.subTypes[s]);
			if AdditionalContractMapData.ACCEPTS_ANIMALS.NAMES[animalType.name].subNames[subAnimal.name] == nil then
				AdditionalContractMapData.ACCEPTS_ANIMALS.NAMES[animalType.name].subNames[subAnimal.name] = {state=state};						
			end;
		end;
	end;
end;

function AdditionalContractMapData:setAnimalCanTransport(animalTypeName, state)
	if animalTypeName ~= nil and AdditionalContractMapData.ACCEPTS_ANIMALS.NAMES[animalTypeName] ~= nil then
		AdditionalContractMapData.ACCEPTS_ANIMALS.NAMES[animalTypeName].canTransport = state or false;		
	end;	
end;

function AdditionalContractMapData:canTransportAnimal(animalTypeName)
	return AdditionalContractMapData.ACCEPTS_ANIMALS.NAMES[animalTypeName] ~= nil and AdditionalContractMapData.ACCEPTS_ANIMALS.NAMES[animalTypeName].canTransport;
end;

function AdditionalContractMapData:setAnimalCanDelivery(animalTypeName, state)
	if animalTypeName ~= nil and AdditionalContractMapData.ACCEPTS_ANIMALS.NAMES[animalTypeName] ~= nil then
		AdditionalContractMapData.ACCEPTS_ANIMALS.NAMES[animalTypeName].canDelivery = state or false;		
	end;	
end;

function AdditionalContractMapData:canDeliveryAnimal(animalTypeName)
	return AdditionalContractMapData.ACCEPTS_ANIMALS.NAMES[animalTypeName] ~= nil and AdditionalContractMapData.ACCEPTS_ANIMALS.NAMES[animalTypeName].canDelivery;
end;

function AdditionalContractMapData:hasDeliveryAnimals()
	for animalTypeName, data in pairs(AdditionalContractMapData.ACCEPTS_ANIMALS.NAMES) do
		if data ~= nil and data.state == true then
			if g_additionalContractMapData:canDeliveryAnimal(animalTypeName) and g_additionalContractMapData:canTransportAnimal(animalTypeName) then return true;end;
		end;
	end;
	return false;
end;

function AdditionalContractMapData:hasTransportAnimals()
	for animalTypeName, data in pairs(AdditionalContractMapData.ACCEPTS_ANIMALS.NAMES) do
		if data ~= nil and data.state == true then
			if g_additionalContractMapData:canTransportAnimal(animalTypeName) then return true;end;
		end;
	end;
	return false;
end;

function AdditionalContractMapData:getAcceptsAnimals(animalTypes, typVariant)	
	local acceptsAnimals = {};	
	for animalTypeName, data in pairs(AdditionalContractMapData.ACCEPTS_ANIMALS.NAMES) do
		if data ~= nil and data.state == true then			
			local accepts = true;
			if typVariant ~= nil then
				if typVariant == "DELIVERYANIMALS" then accepts = g_additionalContractMapData:canDeliveryAnimal(animalTypeName) and g_additionalContractMapData:canTransportAnimal(animalTypeName);
				elseif typVariant == "TRANSPORT" or typVariant == "RIDINGTRANSPORT" then accepts = g_additionalContractMapData:canTransportAnimal(animalTypeName);end;
			end;
			if accepts then
				local animalIndex = g_currentMission.animalSystem:getTypeIndexByName(animalTypeName);
				if animalIndex ~= nil and animalTypes[animalIndex] ~= nil then table.insert(acceptsAnimals, animalTypeName);end;
			end;
		end;
	end;
	return acceptsAnimals;
end;

function AdditionalContractMapData:isAcceptsAnimal(animalTypeName, typVariant)
	if AdditionalContractMapData.ACCEPTS_ANIMALS.NAMES[animalTypeName] == nil or not AdditionalContractMapData.ACCEPTS_ANIMALS.NAMES[animalTypeName].state then return false;end;
	if typVariant ~= nil and type(typVariant) == "string" then
		if typVariant == "DELIVERYANIMALS" then
			return g_additionalContractMapData:canDeliveryAnimal(animalTypeName) and g_additionalContractMapData:canTransportAnimal(animalTypeName);
		elseif typVariant == "TRANSPORT" or typVariant == "RIDINGTRANSPORT" then
			return g_additionalContractMapData:canTransportAnimal(animalTypeName);
		end;
	end;
	return true;
end;

function AdditionalContractMapData:isAcceptsAnimals(animalTypes) --table list, animalTypeName, optional typVariant
	for animalTypeName, typVariant in pairs(animalTypes) do
		animalTypes[animalTypeName] = g_additionalContractMapData:isAcceptsAnimal(animalTypeName, typVariant);			
	end;
	return animalTypes;
end;
---animal types--

function AdditionalContractMapData:loadMapPositions()	
	if g_currentMission ~= nil and not self.mapPositions.loaded then
		self.mapPositions = {loaded=false};
		local terrainNode = g_currentMission.terrainRootNode or g_currentMission.terrainRootNodeId or g_terrainNode;
		local terrainSize = Utils.getNoNil(g_currentMission.terrainSize, 2048);
		if terrainNode == nil then return;end;
		local distanceEdge = 20;
		self.mapPositions.minX = -terrainSize * 0.5 + distanceEdge;
		self.mapPositions.maxX =  terrainSize * 0.5 - distanceEdge;
		self.mapPositions.minZ = -terrainSize * 0.5 + distanceEdge;
		self.mapPositions.maxZ =  terrainSize * 0.5 - distanceEdge;
		if self.mapPositions.minX >= self.mapPositions.maxX or self.mapPositions.minZ >= self.mapPositions.maxZ then return;end;
		self.mapPositions.loaded = true;
	end;	
end;

function AdditionalContractMapData:loadStoreSpawnPlace()
	local storeSpawnPlaces = g_currentMission.storeSpawnPlaces;
	if storeSpawnPlaces ~= nil and #storeSpawnPlaces > 0 then
		for s=1, #storeSpawnPlaces do
			AdditionalContractMapData.STOREPLACE_TRIGGERS[s] = 
			{
				x=storeSpawnPlaces[s].startX; 
				y=storeSpawnPlaces[s].startY; 
				z=storeSpawnPlaces[s].startZ;
				rx=storeSpawnPlaces[s].rotX;
				ry=storeSpawnPlaces[s].rotY;
				rz=storeSpawnPlaces[s].rotZ;
				id=s;
				triggerTyp="storeSpawnPlace";
			};
		end;		
	end;
end;

function AdditionalContractMapData:loadTriggers()
	--print("+++load triggers 1")
	if g_currentMission.placeableSystem.placeables ~= nil then		
		function getUnloadTriggerData(unloadTrigger)
			local x, y, z, rx, ry, rz, triggerId, typ;
			if unloadTrigger.triggerNode ~= nil then 
				typ = "default";
				triggerId = unloadTrigger.triggerNode;
				x, y, z = getWorldTranslation(triggerId);
				rx, ry, rz = getWorldRotation(triggerId);
			elseif unloadTrigger.exactFillRootNode ~= nil then 
				typ = "exactFillRootNode";
				triggerId = unloadTrigger.exactFillRootNode;
				x, y, z = getWorldTranslation(triggerId);
				rx, ry, rz = getWorldRotation(triggerId);
			elseif unloadTrigger.baleTrigger ~= nil and unloadTrigger.baleTrigger.triggerNode ~= nil then 
				typ = "baleTrigger";
				triggerId = unloadTrigger.baleTrigger.triggerNode;
				x, y, z = getWorldTranslation(triggerId);
				rx, ry, rz = getWorldRotation(triggerId);
			elseif unloadTrigger.woodTrigger ~= nil and unloadTrigger.woodTrigger.triggerNode ~= nil then 
				typ = "woodTrigger";
				triggerId = unloadTrigger.woodTrigger.triggerNode
				x, y, z = getWorldTranslation(triggerId);
				rx, ry, rz = getWorldRotation(triggerId);
			end;
			return x, y, z, rx, ry, rz, triggerId, typ;
		end;
		for placeableUniqueId, placeable in pairs(g_currentMission.placeableSystem.placeables) do --placeables
			--local fileName = placeable.configFileName
			
			--print("####configFileName: ".. tostring(fileName))
			if placeable.spec_workshop and placeable.spec_workshop.sellingPoint and AdditionalContractMapData.LOAD_TRIGGERS.WORKSHOPS then                
				if placeable.spec_workshop.sellingPoint.sellTriggerNode and placeable.spec_workshop.sellingPoint.owningPlaceable ~= nil then
                    local x, y, z = getWorldTranslation(placeable.spec_workshop.sellingPoint.sellTriggerNode);
					local rx, ry, rz = getWorldRotation(placeable.spec_workshop.sellingPoint.sellTriggerNode);
					
					--local object = g_currentMission.nodeToObject[placeable.spec_workshop.sellingPoint.sellTriggerNode];
					--local name = object:getName();
					--local uniqueId = object:getUniqueId();
					
					local name = placeable:getName(); --placeable.spec_workshop.sellingPoint.owningPlaceable:getName();
					local uniqueId = placeable:getUniqueId(); --placeable.spec_workshop.sellingPoint.owningPlaceable:getUniqueId();
					
					local workshop = {uniqueId=uniqueId, triggerId=placeable.spec_workshop.sellingPoint.sellTriggerNode, farmId=placeable.ownerFarmId, triggerTyp="workshop", name=name, x=x, y=y, z=z, rx=rx, ry=ry, rz=rz};			
					table.insert(self.mapTriggers.workshop, workshop);					
					self.mapTriggers.uniqueId[uniqueId] = workshop;
					--local object = NetworkUtil.getObject(placeableUniqueId)
					--local objectUniqueId = object:getUniqueId();
					--local objectId = NetworkUtil.getObjectId(object)
					--print("1. workshop uniqueId: ".. tostring(uniqueId).. "/name: ".. tostring(name))
					--print("2. workshop objectId: ".. tostring(objectId).. "/objectUniqueId: ".. tostring(objectUniqueId))
					--print("3. workshop x: ".. tostring(x).. "/z: ".. tostring(z))
				end;
            elseif placeable.spec_sellingStation and placeable.spec_sellingStation.sellingStation and AdditionalContractMapData.LOAD_TRIGGERS.SELLINGSTATIONS then				
				if placeable.spec_sellingStation.sellingStation.unloadTriggers and placeable.spec_sellingStation.sellingStation.owningPlaceable ~= nil then
					local sellingStationNodeId = placeable.spec_sellingStation.sellingStation.rootNode or placeable.spec_sellingStation.sellingStation.nodeId;
					local isTrainStation = placeable.spec_sellingStation.sellingStation.isTrainStation;
					local isPalletStation = placeable.spec_sellingStation.sellingStation.isPalletStation;
					local isSellingPoint = placeable.spec_sellingStation.sellingStation.isSellingPoint;
					local name = placeable.spec_sellingStation.sellingStation.owningPlaceable:getName();
					local stationName = placeable.spec_sellingStation.sellingStation.stationName;
					local uniqueId = placeable.spec_sellingStation.sellingStation.owningPlaceable:getUniqueId();
					local xS, yS, zS = getWorldTranslation(sellingStationNodeId);
					local rxS, ryS, rzS = getWorldRotation(sellingStationNodeId);
					local sellingStation = {uniqueId=uniqueId, x=xS, y=yS, z=zS, rx=rxS, ry=ryS, rz=rzS, isSellingPoint=isSellingPoint, farmId=placeable.ownerFarmId, isTrainStation=isTrainStation, isPalletStation=isPalletStation, triggerTyp="sellingStation", stationName=stationName, name=name, triggerId=sellingStationNodeId, triggers={}};
					table.insert(self.mapTriggers.sellingStation, sellingStation);
					self.mapTriggers.uniqueId[uniqueId] = sellingStation;
					for _, unloadTrigger in pairs(placeable.spec_sellingStation.sellingStation.unloadTriggers) do
						local x, y, z, rx, ry, rz, triggerId, typ = getUnloadTriggerData(unloadTrigger);						
						if triggerId ~= nil and x ~= nil then							
							local trigger = {triggerId=triggerId, typ=typ, x=x, y=y, z=z, rx=rx, ry=ry, rz=rz};
							table.insert(self.mapTriggers.uniqueId[uniqueId].triggers, trigger);							
						end;
					end;					
				end;
			elseif placeable.spec_productionPoint and placeable.spec_productionPoint.productionPoint and placeable.spec_productionPoint.productionPoint.unloadingStation and AdditionalContractMapData.LOAD_TRIGGERS.PRODUCTIONPOINTS then
				if placeable.spec_productionPoint.productionPoint.unloadingStation.unloadTriggers and placeable.spec_productionPoint.productionPoint.owningPlaceable ~= nil then
					local productionPointNodeId = placeable.spec_productionPoint.productionPoint.node or placeable.spec_productionPoint.productionPoint.nodeId;					
					local name = placeable.spec_productionPoint.productionPoint.owningPlaceable:getName();
					local uniqueId = placeable.spec_productionPoint.productionPoint.owningPlaceable:getUniqueId();
					local xP, yP, zP = getWorldTranslation(productionPointNodeId);
					local rxP, ryP, rzP = getWorldRotation(productionPointNodeId);
					local productionPoint = {uniqueId=uniqueId, x=xP, y=yP, z=zP, rx=rxP, ry=ryP, rz=rzP, farmId=placeable.ownerFarmId, triggerTyp="productionPoint", name=name, triggerId=productionPointNodeId, triggers={}};
					table.insert(self.mapTriggers.productionPoint, productionPoint);
					self.mapTriggers.uniqueId[uniqueId] = productionPoint;
					for _, unloadTrigger in pairs(placeable.spec_productionPoint.productionPoint.unloadingStation.unloadTriggers) do
						local x, y, z, rx, ry, rz, triggerId, typ = getUnloadTriggerData(unloadTrigger);						
						if triggerId ~= nil and x ~= nil then							
							local trigger = {triggerId=triggerId, typ=typ, x=x, y=y, z=z, rx=rx, ry=ry, rz=rz};													
							table.insert(self.mapTriggers.uniqueId[uniqueId].triggers, trigger);
						end;
					end;
				end;			
			end;			
		end;
		--print(DebugUtil.printTableRecursively(self.mapTriggers, "mapTriggers ", 0, 5))
	end;
end;

function AdditionalContractMapData:insertTypeObject(typeName, typeObject, alReadyExists, objectTyp)
	if typeName == nil or typeObject == nil then return false;end;
	if self.mapTypes[typeName] == nil then self.mapTypes[typeName] = {};end;
	if alReadyExists == nil or alReadyExists then
		if not self:isTypeObjectAlReadyExists(typeName, typeObject) then table.insert(self.mapTypes[typeName], typeObject);else return false;end;
	else
		table.insert(self.mapTypes[typeName], typeObject);
	end;
	if objectTyp ~= nil then
		if objectTyp == "trigger" then
			local triggerId = typeObject.triggerId or typeObject.triggerNode;
			if triggerId ~= nil then
				local x, y, z = getWorldTranslation(triggerId);
				local rx, ry, rz = getWorldRotation(triggerId);
				local trigger = {uniqueId=g_additionalContractUtils:generateTypId(1,2), triggerId=triggerId, farmId=typeObject.ownerFarmId or AccessHandler.EVERYONE, triggerTyp=typeName, name=typeObject.title or "Unknown", x=x, y=y, z=z, rx=rx, ry=ry, rz=rz};
				if self.mapTriggers[typeName] == nil then self.mapTriggers[typeName] = {};self.hasMapTriggers[typeName] = true;end;
				table.insert(self.mapTriggers[typeName], trigger);
			end;
		end;
	end;	
	return true;
end;

function AdditionalContractMapData:removeTypeObject(typeName, typeObject, node)
	if typeName == nil or self.mapTypes[typeName] == nil then return false;end;
	if typeObject == nil and node == nil then return false;end;
	for _, mapTypeObject in pairs(self.mapTypes[typeName]) do
		if typeObject ~= nil and mapTypeObject == typeObject then table.remove(self.mapTypes[typeName], mapTypeObject);return true;end;
		local nodeId = mapTypeObject.rootNode or mapTypeObject.nodeId;
		local triggerId = mapTypeObject.triggerId or mapTypeObject.triggerNode;
		if node ~= nil and ((nodeId ~= nil and nodeId == node) or (triggerId ~= nil and triggerId == node)) then table.remove(self.mapTypes[typeName], mapTypeObject);return true;end;		
	end;
	return true;
end;

function AdditionalContractMapData:isTypeObjectAlReadyExists(typeName, typeObject, node)
	if typeName == nil or self.mapTypes[typeName] == nil or #self.mapTypes[typeName] <= 0 then return false;end;
	if typeObject == nil and node == nil then return false;end;	
	for _, mapTypeObject in pairs(self.mapTypes[typeName]) do
		if typeObject ~= nil and mapTypeObject == typeObject then return true;end;
		local nodeId = mapTypeObject.rootNode or mapTypeObject.nodeId;
		local triggerId = mapTypeObject.triggerId or mapTypeObject.triggerNode;
		if node ~= nil and ((nodeId ~= nil and nodeId == node) or (triggerId ~= nil and triggerId == node)) then return true;end;		
	end;
	return false;
end;

function AdditionalContractMapData:hasTypeObject(typeName)
	if typeName == nil or self.mapTypes[typeName] == nil then return false;end;
	return #self.mapTypes[typeName] > 0;
end;

function AdditionalContractMapData:getTypeObjects(typeName)
	if typeName == nil or self.mapTypes[typeName] == nil or #self.mapTypes[typeName] <= 0 then return nil;end;
	return self.mapTypes[typeName];
end;

function AdditionalContractMapData:getTypeObject(typeName, node, triggerId, isEnabled)
	if typeName == nil or self.mapTypes[typeName] == nil or #self.mapTypes[typeName] <= 0 or (node == nil and triggerId == nil) then return nil;end;
	for _, mapTypeObject in pairs(self.mapTypes[typeName]) do
		local oTriggerId = mapTypeObject.triggerId or mapTypeObject.triggerNode;		
		local nodeId = mapTypeObject.rootNode or mapTypeObject.nodeId;
		if node ~= nil and nodeId ~= nil and node == nodeId then 
			if isEnabled ~= nil and mapTypeObject.isEnabled ~= nil then
				if mapTypeObject.isEnabled then return mapTypeObject;end;
			else
				return mapTypeObject;
			end;
		end;
		if triggerId ~= nil and oTriggerId ~= nil and triggerId == oTriggerId then 
			if isEnabled ~= nil and mapTypeObject.isEnabled ~= nil then
				if mapTypeObject.isEnabled then return mapTypeObject;end;
			else
				return mapTypeObject;
			end;
		end;
	end;	
	return nil;
end;

function AdditionalContractMapData:addMissionAtTypeObject(typeName, triggerId, mission)
	if typeName == nil or self.mapTypes[typeName] == nil or #self.mapTypes[typeName] <= 0 or triggerId == nil or mission == nil then return false;end;
	for key, mapTypeObject in pairs(self.mapTypes[typeName]) do
		local oTriggerId = mapTypeObject.triggerId or mapTypeObject.triggerNode;
		if oTriggerId ~= nil and triggerId == oTriggerId and mapTypeObject.setMission ~= nil then
			self.mapTypes[typeName][key].setMission(mission);
			return true;
		end;
	end;
	return false;
end;

function AdditionalContractMapData:removeMissionAtTypeObject(typeName, triggerId, mission)
	if typeName == nil or self.mapTypes[typeName] == nil or #self.mapTypes[typeName] <= 0 or triggerId == nil or mission == nil then return;end;
	for key, mapTypeObject in pairs(self.mapTypes[typeName]) do
		local oTriggerId = mapTypeObject.triggerId or mapTypeObject.triggerNode;
		if oTriggerId ~= nil and triggerId == oTriggerId and mapTypeObject.setMission ~= nil and mapTypeObject.mission ~= nil and mapTypeObject.mission == mission then
			self.mapTypes[typeName][key].setMission(nil);
			return;
		end;
	end;
	return;
end;

function AdditionalContractMapData:getVirtualTrigger(typId, prefix)
	local fakeId = typId or g_additionalContractUtils:generateTypId(1,2);
	if prefix ~= nil and not string.find(fakeId, prefix) then fakeId = prefix.. fakeId;end;
	local virtualTrigger = {uniqueId=fakeId, x=0, y=0, z=0, rx=0, ry=0, rz=0, triggerTyp="virtual", name="virtual", triggerId=fakeId, farmId=AccessHandler.EVERYONE};	
	return virtualTrigger;
end;

function AdditionalContractMapData:hasMapTrigger(triggerTyp)
	if triggerTyp == nil or self.hasMapTriggers[triggerTyp] == nil then return false;end;
	return self.hasMapTriggers[triggerTyp];
end;

function AdditionalContractMapData:hasTrigger(triggerTyp)
	if triggerTyp == nil or self.mapTriggers[triggerTyp] == nil then return false,0;end;
	return #self.mapTriggers[triggerTyp] > 0, #self.mapTriggers[triggerTyp];
end;

function AdditionalContractMapData:insertTrigger(triggerTyp)

end;

function AdditionalContractMapData:getTrigger(triggerTyp, triggerPos)
	if triggerTyp == nil or self.mapTriggers[triggerTyp] == nil or #self.mapTriggers[triggerTyp] <= 0 then return nil;end;
	if triggerPos ~= nil then return self.mapTriggers[triggerTyp][triggerPos];end;
	return self:getRandomTrigger(triggerTyp);
end;

function AdditionalContractMapData:getTriggers(triggerTyp)
	if triggerTyp == nil or self.mapTriggers[triggerTyp] == nil or #self.mapTriggers[triggerTyp] <= 0 then return nil;end;
	return self.mapTriggers[triggerTyp];
end;

function AdditionalContractMapData:getRandomTrigger(triggerTyp)
	if self.mapTriggers[triggerTyp] == nil or #self.mapTriggers[triggerTyp] <= 0 then return nil;end;
	return self.mapTriggers[triggerTyp][math.random(1, #self.mapTriggers[triggerTyp])];
end;

function AdditionalContractMapData:getRandomTriggerEveryoneOrFarmId(triggerTyp, playerFarmId, isSellingPoint, isTrainStation, isPalletStation)
	if self.mapTriggers[triggerTyp] == nil or #self.mapTriggers[triggerTyp] <= 0 then return nil;end;
	local everyoneOrFarmIdTriggers = {};
	if playerFarmId == nil and (isSellingPoint or isSellingPoint == nil) and (isTrainStation or isTrainStation == nil) and (isPalletStation or isPalletStation == nil) then 
		return self:getRandomTrigger(triggerTyp);
	else			
		for _, trigger in pairs(self.mapTriggers[triggerTyp]) do
			if (trigger.farmId == AccessHandler.EVERYONE or trigger.farmId == playerFarmId) and (isSellingPoint == nil or trigger.isSellingPoint == isSellingPoint) and (isTrainStation == nil or trigger.isTrainStation == isTrainStation) and (isPalletStation == nil or trigger.isPalletStation == isPalletStation) then
				table.insert(everyoneOrFarmIdTriggers, trigger);
			end;
		end;
	end;
	if #everyoneOrFarmIdTriggers <= 0 then return nil;end;
	return everyoneOrFarmIdTriggers[math.random(1, #everyoneOrFarmIdTriggers)];
end;

function AdditionalContractMapData:getRandomTriggerNoFarmId(triggerTyp, isSellingPoint, isTrainStation, isPalletStation)
	if self.mapTriggers[triggerTyp] == nil or #self.mapTriggers[triggerTyp] <= 0 then return nil;end;
	local noFarmIdTriggers = {};	
	for _, trigger in pairs(self.mapTriggers[triggerTyp]) do
		if trigger.farmId == AccessHandler.EVERYONE and (isSellingPoint == nil or trigger.isSellingPoint == isSellingPoint) and (isTrainStation == nil or trigger.isTrainStation == isTrainStation) and (isPalletStation == nil or trigger.isPalletStation == isPalletStation) then --or not trigger.farmId == AccessHandler.NOBODY then
			table.insert(noFarmIdTriggers, trigger);
		end;
	end;
	if #noFarmIdTriggers <= 0 then return nil;end;
	return noFarmIdTriggers[math.random(1, #noFarmIdTriggers)];
end;

function AdditionalContractMapData:getTriggerByNetwork(pendingLoadingData)
	if pendingLoadingData.nodeObjectId ~= nil then
		local object = NetworkUtil.getObject(pendingLoadingData.nodeObjectId);
		if object ~= nil then return self:getTriggerByUniqueId(object:getUniqueId());end;	
	end;
	return nil;
end;

function AdditionalContractMapData:getTriggerByUniqueId(uniqueId)	
	return self.mapTriggers.uniqueId[uniqueId];	
end;

function AdditionalContractMapData:getTriggerByName(triggerTyp, triggerName)	
	for _, trigger in pairs(self.mapTriggers[triggerTyp]) do
		if trigger.name == triggerName then
			return trigger;
		end;
	end;
	return nil;	
end;

function AdditionalContractMapData:getAllTriggerByName(triggerTyp, triggerName)	
	local triggers = {};
	for _, trigger in pairs(self.mapTriggers[triggerTyp]) do
		if trigger.name == triggerName then
			table.insert(triggers, trigger);			
		end;
	end;
	return triggers;	
end;

function AdditionalContractMapData:getTriggerByPosition(triggerTyp, x, z)	
	for _, trigger in pairs(self.mapTriggers[triggerTyp]) do
		if trigger.x == x and trigger.z == z then
			return trigger;
		end;
	end;
	return nil;	
end;

function AdditionalContractMapData:hasTriggerFlag(triggerNode, flag)
	return CollisionFlag.getHasMaskFlagSet(triggerNode, CollisionFlag[flag]);
end;

function AdditionalContractMapData:hasTriggers()
	self.hasMapTriggers.unloadingStation = false;
	self.hasMapTriggers.loadingStation = false;
	if g_currentMission == nil or g_currentMission.storageSystem == nil then return;end;
	for _, unloadingStation in pairs(g_currentMission.storageSystem:getUnloadingStations()) do
		if self:isMissionSafeSellingStation(unloadingStation, nil, false) then
			self.hasMapTriggers.unloadingStation = true;
			break;
		end;
	end;
	for _, loadingStation in pairs(g_currentMission.storageSystem:getLoadingStations()) do
		local placeable = loadingStation ~= nil and loadingStation.owningPlaceable or nil;
		local ownerFarmId = nil;
		if placeable ~= nil and placeable.getOwnerFarmId ~= nil then
			local ok, value = pcall(placeable.getOwnerFarmId, placeable);
			if ok then ownerFarmId = value;end;
		end;
		if ownerFarmId == nil and placeable ~= nil then ownerFarmId = placeable.ownerFarmId;end;
		if placeable ~= nil and loadingStation.isSellingPoint and loadingStation.allowMissions and self:isNeutralFarmId(ownerFarmId) then
			self.hasMapTriggers.loadingStation = true;
			break;
		end;
	end;
end;

function AdditionalContractMapData:hasMinFields(minFields)
	local fields = g_fieldManager:getFields();
	if fields ~= nil and #fields >= minFields then return true;end;
	return false;
end;

function AdditionalContractMapData:getRandomField(lastField, change)
	local fields = g_fieldManager:getFields();
	if fields ~= nil and #fields > 0 then 
		local change = change or 3;
		for i = math.random(1, #fields), #fields do
			if change <= 0 then break;end;
			local field = g_fieldManager.fields[i];
			if field ~= nil and field.getId ~= nil then
				local fieldId = field:getId();
				if fieldId ~= nil and fieldId ~= lastField then
					return self:getFieldById(fieldId);
				end;
			end;
			change = change - 1;
		end;
	end;
	return nil;
end;

function AdditionalContractMapData:getRandomFieldForMission(lastField, change)
	local field = g_fieldManager:getFieldForMission();
	if field ~= nil and field.getId ~= nil then 
		local fieldId = field:getId();
		if fieldId ~= nil and fieldId ~= lastField then			
			local x, z = field:getCenterOfFieldWorldPosition()
			local farmlandId = g_farmlandManager:getFarmlandIdAtWorldPosition(x, z);
			if farmlandId ~= nil and farmlandId > 0 then
				return {triggerId=fieldId, triggerTyp="field", farmlandId=farmlandId, x=x, y=0, z=z};
			end;			
		end;		
	end;
	if change ~= nil then return self:getRandomField(lastField, change);end;
	return nil;
end;

function AdditionalContractMapData:getFieldById(fieldId)
	local field = g_fieldManager:getFieldById(fieldId);
	if field ~= nil then				
		local x, z = field:getCenterOfFieldWorldPosition()
		local farmlandId = g_farmlandManager:getFarmlandIdAtWorldPosition(x, z);
		if farmlandId ~= nil and farmlandId > 0 then
			return {triggerId=fieldId, triggerTyp="field", farmlandId=farmlandId, x=x, y=0, z=z};
		end;			
	end;
	return nil;
end;

function AdditionalContractMapData:getStorePlaceTrigger(place, change)
	if AdditionalContractMapData.STOREPLACE_TRIGGERS ~= nil and #AdditionalContractMapData.STOREPLACE_TRIGGERS > 0 then
		if place ~= nil then
			if AdditionalContractMapData.STOREPLACE_TRIGGERS[place] ~= nil then
				return AdditionalContractMapData.STOREPLACE_TRIGGERS[place];			
			elseif change ~= nil and change then
				return self:getStorePlaceTrigger();
			end;
		else
			return AdditionalContractMapData.STOREPLACE_TRIGGERS[1]
		end;
	end;
	return nil;
end;

function AdditionalContractMapData:getStorePlaceTriggerByPosition(x, z)
	if AdditionalContractMapData.STOREPLACE_TRIGGERS ~= nil and #AdditionalContractMapData.STOREPLACE_TRIGGERS > 0 then
		for s=1, #AdditionalContractMapData.STOREPLACE_TRIGGERS do
			if AdditionalContractMapData.STOREPLACE_TRIGGERS[s].x == x and AdditionalContractMapData.STOREPLACE_TRIGGERS[s].z == z then
				return AdditionalContractMapData.STOREPLACE_TRIGGERS[s];			
			end;
		end;
	end;
	return nil;
end;

function AdditionalContractMapData:update(dt)
	
end;

function AdditionalContractMapData:isNeutralFarmId(farmId)
	-- Farm ownership and access-permission constants are different domains.
	-- Never compare a farmId with AccessHandler.EVERYONE/FARM because a real
	-- player farm can have the same numeric value. Unknown ownership fails
	-- closed; normal map/NPC selling points report owner farm 0 (or lower).
	local numericFarmId = tonumber(farmId);
	return numericFarmId ~= nil and numericFarmId <= 0;
end;

function AdditionalContractMapData:getSellingStationOwnerFarmId(unloadingStation)
	if unloadingStation == nil then return nil;end;
	local placeable = unloadingStation.owningPlaceable;
	if placeable == nil then return nil;end;
	if placeable.getOwnerFarmId ~= nil then
		local success, farmId = pcall(placeable.getOwnerFarmId, placeable);
		if success and farmId ~= nil then return farmId;end;
	end;
	if placeable.ownerFarmId ~= nil then return placeable.ownerFarmId;end;
	if placeable.farmId ~= nil then return placeable.farmId;end;
	return nil;
end;

function AdditionalContractMapData:isInternalMissionSellingStation(unloadingStation)
	if unloadingStation == nil then return false;end;
	local placeable = unloadingStation.owningPlaceable;
	if unloadingStation.tfuMissionInternal == true or (placeable ~= nil and placeable.tfuMissionInternal == true) then return true;end;
	if placeable ~= nil and placeable.getUniqueId ~= nil then
		local success, uniqueId = pcall(placeable.getUniqueId, placeable);
		if success and uniqueId == "AnimalDealerMission_SellingStation_1" then return true;end;
	end;
	return false;
end;

function AdditionalContractMapData:getTriggerClassObject(trigger)
	if trigger == nil or ClassUtil == nil or ClassUtil.getClassObjectByObject == nil then return nil;end;
	local success, classObject = pcall(ClassUtil.getClassObjectByObject, trigger);
	if success then return classObject;end;
	return nil;
end;

function AdditionalContractMapData:isTriggerClass(trigger, expectedClass)
	if trigger == nil or expectedClass == nil then return false;end;
	local classObject = self:getTriggerClassObject(trigger);
	if classObject == expectedClass then return true;end;
	if trigger.isa ~= nil then
		local success, value = pcall(trigger.isa, trigger, expectedClass);
		if success and value == true then return true;end;
	end;
	return false;
end;

function AdditionalContractMapData:triggerSupportsFillType(trigger, fillTypeIndex)
	if trigger == nil then return false;end;
	if fillTypeIndex == nil then return true;end;
	if trigger.getIsFillTypeSupported ~= nil then
		local success, value = pcall(trigger.getIsFillTypeSupported, trigger, fillTypeIndex);
		if success and type(value) == "boolean" then return value;end;
	end;
	local fillTypes = trigger.fillTypes;
	if fillTypes == nil or next(fillTypes) == nil then return true;end;
	return fillTypes[fillTypeIndex] == true;
end;

-- Mirrors GIANTS UnloadingStation:validateUnloadTriggers semantics:
-- * bulk requires a regular UnloadTrigger
-- * bales require a BaleUnloadTrigger, or a regular UnloadTrigger without an
--   exactFillRootNode
-- The check is performed for the exact mission fill type.
function AdditionalContractMapData:getMissionStationCapabilities(unloadingStation, fillTypeIndex)
	local hasBaleTrigger = false;
	local hasBulkTrigger = false;
	if unloadingStation == nil then return false, false;end;

	for _, trigger in pairs(unloadingStation.unloadTriggers or {}) do
		if self:triggerSupportsFillType(trigger, fillTypeIndex) then
			local isBaleTrigger = self:isTriggerClass(trigger, BaleUnloadTrigger);
			local isPalletTrigger = PalletUnloadTrigger ~= nil and self:isTriggerClass(trigger, PalletUnloadTrigger);
			local isRegularUnloadTrigger = self:isTriggerClass(trigger, UnloadTrigger);
			local hasConversion = trigger.fillTypeConversions ~= nil and next(trigger.fillTypeConversions) ~= nil;
			local classDetected = isBaleTrigger or isPalletTrigger or isRegularUnloadTrigger;

			if hasConversion then
				-- A converting production trigger can consume a different product than
				-- the mission records. It is not a safe contract destination.
			elseif isBaleTrigger then
				hasBaleTrigger = true;
			elseif isPalletTrigger then
				-- Pallet-only unload triggers are neither bulk nor bale targets.
			elseif isRegularUnloadTrigger then
				hasBulkTrigger = true;
				if trigger.exactFillRootNode == nil then hasBaleTrigger = true;end;
			elseif not classDetected then
				-- Compatibility fallback for custom unload-trigger wrappers that do not
				-- expose their class object through ClassUtil.
				if trigger.baleTrigger ~= nil then
					hasBaleTrigger = true;
				elseif trigger.exactFillRootNode ~= nil then
					hasBulkTrigger = true;
				elseif trigger.triggerNode ~= nil then
					hasBulkTrigger = true;
					hasBaleTrigger = true;
				end;
			end;
		end;
	end;

	-- Last-resort metadata fallback for map triggers already indexed by this
	-- mod. Station-level acceptedFillTypes was checked separately, so this only
	-- supplies the trigger shape/class information.
	if not hasBaleTrigger or not hasBulkTrigger then
		local placeable = unloadingStation.owningPlaceable;
		if placeable ~= nil and placeable.getUniqueId ~= nil and self.mapTriggers ~= nil and self.mapTriggers.uniqueId ~= nil then
			local ok, uniqueId = pcall(placeable.getUniqueId, placeable);
			local mapTrigger = ok and self.mapTriggers.uniqueId[uniqueId] or nil;
			for _, trigger in pairs(mapTrigger ~= nil and mapTrigger.triggers or {}) do
				if trigger.typ == "baleTrigger" then
					hasBaleTrigger = true;
				elseif trigger.typ == "default" then
					hasBulkTrigger = true;
					hasBaleTrigger = true;
				elseif trigger.typ == "exactFillRootNode" then
					hasBulkTrigger = true;
				end;
			end;
		end;
	end;
	return hasBaleTrigger, hasBulkTrigger;
end;

function AdditionalContractMapData:isMissionSafeSellingStation(unloadingStation, fillTypeIndex, force, requirements)
	if unloadingStation == nil or unloadingStation.owningPlaceable == nil or unloadingStation.isSellingPoint ~= true then return false;end;
	if unloadingStation.isRegistered == false then return false;end;
	local isInternal = self:isInternalMissionSellingStation(unloadingStation);
	if not (unloadingStation.allowMissions == true or force == true or isInternal) then return false;end;
	if fillTypeIndex ~= nil then
		if unloadingStation.acceptedFillTypes == nil or unloadingStation.acceptedFillTypes[fillTypeIndex] ~= true then return false;end;
	end;
	local ownerFarmId = self:getSellingStationOwnerFarmId(unloadingStation);
	if not self:isNeutralFarmId(ownerFarmId) and not isInternal then return false;end;
	if requirements ~= nil then
		local hasBaleTrigger, hasBulkTrigger = self:getMissionStationCapabilities(unloadingStation, fillTypeIndex);
		if requirements.requireBales == true and not hasBaleTrigger then return false;end;
		if requirements.requireBulk == true and not hasBulkTrigger then return false;end;
	end;
	return true;
end;

function AdditionalContractMapData:getMissionSafeSellingStationWithHighestPrice(fillTypeIndex, force, requirements)
	local highestPrice = -math.huge;
	local sellPoint = nil;
	local forceState = force == true;
	if g_currentMission == nil or g_currentMission.storageSystem == nil then return nil, 0;end;
	for _, unloadingStation in pairs(g_currentMission.storageSystem:getUnloadingStations()) do
		if self:isMissionSafeSellingStation(unloadingStation, fillTypeIndex, forceState, requirements) then
			local price = 0;
			if unloadingStation.getEffectiveFillTypePrice ~= nil then
				local success, value = pcall(unloadingStation.getEffectiveFillTypePrice, unloadingStation, fillTypeIndex);
				if success and type(value) == "number" then price = value;end;
			end;
			if sellPoint == nil or price > highestPrice then
				sellPoint = unloadingStation;
				highestPrice = price;
			end;
		end;
	end;
	if sellPoint == nil then
		local fallbackPrice = 0;
		if forceState and g_currentMission.economyManager ~= nil and g_currentMission.economyManager.getPricePerLiter ~= nil then
			fallbackPrice = g_currentMission.economyManager:getPricePerLiter(fillTypeIndex) or 0;
		end;
		return nil, fallbackPrice;
	end;
	return sellPoint, math.max(highestPrice, 0);
end;

-- Backwards-compatible public method. All Additional Contracts callers receive
-- only a neutral/NPC-owned (or explicitly internal mission-only) destination.
function AdditionalContractMapData:getSellingStationWithHighestPrice(fillTypeIndex, force, requirements)
	return self:getMissionSafeSellingStationWithHighestPrice(fillTypeIndex, force, requirements);
end;

function AdditionalContractMapData:hasPricePerLiter(fillTypeIndex)
	local pricePerLiter = g_currentMission.economyManager:getPricePerLiter(fillTypeIndex);
	return pricePerLiter ~= nil and pricePerLiter ~= 0, pricePerLiter;
end;

function AdditionalContractMapData:isAvailableForSellingStation(sellingStation)
	return sellingStation ~= nil;
end;

function AdditionalContractMapData:getRandomMapPositionByMinDistance(x, z, distance, nonBlockingPosition, size, testHeight, collisionMask)
	if self.mapPositions == nil or not self.mapPositions.loaded then return nil;end;
	local randomMapPosition = {};
	for tries=1, AdditionalContractMapData.MAX_TRIES do
		local position = self:getRandomMapPosition(3, nonBlockingPosition, size, testHeight, collisionMask);
		if position ~= nil and g_additionalContractUtils:getObjectDistance( {x=x, z=z}, {x=position.x, z=position.z} ) >= distance then			
			table.insert(randomMapPosition, position);
		end;
	end;
	if #randomMapPosition > 0 then return randomMapPosition[math.random(1, #randomMapPosition)];else return nil;end;
end;

function AdditionalContractMapData:getRandomMapPositionByMaxDistance(x, z, distance, nonBlockingPosition, size, testHeight, collisionMask)
	if self.mapPositions == nil or not self.mapPositions.loaded then return nil;end;
	local randomMapPosition = {};
	for tries=1, AdditionalContractMapData.MAX_TRIES do
		local position = self:getRandomMapPosition(3, nonBlockingPosition, size, testHeight, collisionMask);
		if position ~= nil and g_additionalContractUtils:getObjectDistance( {x=x, z=z}, {x=position.x, z=position.z} ) <= distance then			
			table.insert(randomMapPosition, position);
		end;
	end;
	if #randomMapPosition > 0 then return randomMapPosition[math.random(1, #randomMapPosition)];else return nil;end;
end;

function AdditionalContractMapData:getRandomMapPosition(maxTries, nonBlockingPosition, size, testHeight, collisionMask) 
	if self.mapPositions == nil or not self.mapPositions.loaded then return nil;end;
	local maxTries = maxTries or AdditionalContractMapData.MAX_TRIES;
	local x, y, z = nil;
	local currentTries = 0;
	local minX = self.mapPositions.minX;
	local maxX = self.mapPositions.maxX;
	local minZ = self.mapPositions.minZ;
	local maxZ = self.mapPositions.maxZ;
	while (x == nil or z == nil) and currentTries < maxTries do
		x = MathUtil.lerp(minX, maxX, math.random());
		z = MathUtil.lerp(minZ, maxZ, math.random());
		if x ~= nil and z ~= nil and nonBlockingPosition then
			x, z = g_additionalContractPointManager:getNonBlockingPosition(x, z, nil ,nil, size, testHeight, collisionMask);
		end;
		currentTries = currentTries + 1;
	end;
	if x ~= nil and z ~= nil then
		terrainY = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 0, z) + 1;
		y = g_additionalContractPointManager:getTerrainLimitedYPosition(x, terrainY, z, 0.01, collisionMask);
		return {x=x,y=y,z=z};
	end;
	return nil;
end;
g_additionalContractMapData = AdditionalContractMapData.new();

function orgAdcOnSavegameLoaded(self)	
	if g_server ~= nil then		
		g_additionalContractSplineManager:load(); --new		
	end; --only on server
	g_additionalContractMapData:loadStoreSpawnPlace();
	g_additionalContractMapData:loadTriggers();
	g_additionalContractMapData:hasTriggers();
	g_additionalContractMapData:loadMapPositions();
	g_additionalContractNpcManager:load();
	g_additionalContractMapData:loadAnimalTypes();
	if g_server ~= nil then	
		g_additionalContractPlaceableManager:load() --new		
	end;
	AdditionalContractMapData.isLoaded = true;
end;
AutoSaveManager.onSavegameLoaded = Utils.appendedFunction(AutoSaveManager.onSavegameLoaded, orgAdcOnSavegameLoaded);