AnimalDealerMission = {};
AnimalDealerMission.NAME = "animalDealerMission";
AnimalDealerMission.UI_SETTINGS = true;
AnimalDealerMission.MISSIONS_XML = {};
AnimalDealerMission.TARGET_TRIGGER_DISTANCE = 300; --dropoff trigger
AnimalDealerMission.MAX_NUM_INSTANCE = 1;
AnimalDealerMission.DEFAULT_NUM_INSTANCE = 1;
AnimalDealerMission.BALANCE = {false,0};
AnimalDealerMission.RANDOM = false;
AnimalDealerMission.MAX_NUM_SEASON = {0,0,1}; --0=off or max,is,nextDay + ingame day
AnimalDealerMission.ACCEPTS_VARIANT = {RIDING=false,RIDINGTRANSPORT=false,TRANSPORT=false,DELIVERYFILLTYPES=false,DELIVERYANIMALS=false}; --default all false (check map before set true)
AnimalDealerMission.FILLTYPES_CATEGORY = {ANIMALDEALER_BULK={INDEX=0,DEFAULT="STRAW"},fillTypes={}};
AnimalDealerMission.MODSUPPORT = {RealisticLivestock=false,RealisticLivestockRM={RIDING=false,RIDINGTRANSPORT=false,TRANSPORT=false,DELIVERYANIMALS=false}};

source(AdditionalContracts.modDir.. "missions/animalDealerMission/AnimalDealerChangeSettingsEvent.lua");
source(AdditionalContracts.modDir.. "missions/animalDealerMission/AnimalDealerLoadSettingsEvent.lua");
source(AdditionalContracts.modDir.. "missions/animalDealerMission/DeliveryAnimalsEvent.lua");

AnimalDealerMission.uiData = {
	settings = {
		animalDealerMission_minMax = AnimalDealerMission.DEFAULT_NUM_INSTANCE;
	};
	controls = {
		{ typName="animalDealerMission", name="animalDealerMission_minMax", autoBind=true, min=0, max=AnimalDealerMission.MAX_NUM_INSTANCE, step=1, unit="/ ".. tostring(AnimalDealerMission.MAX_NUM_INSTANCE), nillable=false };
	};
};

AnimalDealerMission.data = {	
	ownTable = {VEHICLE_USE_COST=60, SUCCESS_FACTOR=0.99, classOverlay="car", typOverlay="lowAnimalsTrailer"};	
	reward = {dismiss=500, min=2000, PER_METER=2, PER_OBJECT=2000, NUM_OBJECTS_PER_DRIVE=2};
	jobTypName = g_i18n:getText("animals_dealer");
};

AnimalDealerMission.metadata = {
	interface = "FS25 ...", --new
	title = "Animal Dealer Contracts",
	notes = "Dieser Mod generiert Viehhändler Aufträge.",
	author = "(by HappyLooser)",		
	build = 5,
	datum = "04.03.2026",
	update = "14.04.2026",
	debugPrint = false, 
	discord = "HappyLooser Modding",
	info = " Link Freigabe,Änderungen,Kopien oder Code Benutzung ist ohne meine Zustimmung nicht erlaubt",
	"##Orginal Link Freigabe: https://www.farming-simulator.com/mods.php"
};

local function debugPrint(addConsole, text, ...)
	if AnimalDealerMission.metadata.debugPrint or addConsole ~= nil then
		if addConsole ~= nil then 
			print(text,...);
		else
			Logging.info(text,...);
		end;
	end;
end;

local AnimalDealerMission_mt = Class(AnimalDealerMission, AbstractMission);
InitObjectClass(AnimalDealerMission, "AnimalDealerMission");

function AnimalDealerMission.registerMetaXMLPaths(schema, key)
	AnimalDealerMission:superClass().registerMetaXMLPaths(schema, key);
	schema:register(XMLValueType.INT, key .. "#nextDay", "Earliest day a new mission can spawn");	
end;

function AnimalDealerMission.loadMetaDataFromXMLFile(xmlFile, key)
	if AnimalDealerMission:canMaxNumSeason() then
		g_missionManager:getMissionTypeDataByName(AnimalDealerMission.NAME).nextMissionDay = xmlFile:getValue(key .. "#nextDay");		
	end;
end;

function AnimalDealerMission.saveMetaDataToXMLFile(xmlFile, key)
	local data = g_missionManager:getMissionTypeDataByName(AnimalDealerMission.NAME);	
	if data.nextMissionDay ~= nil then
		xmlFile:setValue(key .. "#nextDay", data.nextMissionDay);
	end;	
end;

--function AnimalDealerMission.registerXMLPaths(schema, key)
--	AnimalDealerMission:superClass().registerXMLPaths(schema, key);	
--	schema:register(XMLValueType.INT, key .. "#penalty", "Penalty by dismiss");
	
--end

function AnimalDealerMission.registerSavegameXMLPaths(schema, key)
	AnimalDealerMission:superClass().registerSavegameXMLPaths(schema, key);
	local animalDealerKey = string.format("%s.animalDealer", key);	
	schema:register(XMLValueType.STRING, animalDealerKey .. "#typVariant", "typVariant by ACCEPTS_VARIANT");
	schema:register(XMLValueType.STRING, animalDealerKey .. "#missionConfig", "missionConfig name");
	schema:register(XMLValueType.INT, animalDealerKey .. "#objectConfig", "objectConfig index");	
	schema:register(XMLValueType.STRING, animalDealerKey .. "#dropoffTriggerTyp", "dropoff trigger typ");	
	schema:register(XMLValueType.FLOAT, animalDealerKey .. "#dropoffTriggerX", "dropoff trigger position x");
	schema:register(XMLValueType.FLOAT, animalDealerKey .. "#dropoffTriggerZ", "dropoff trigger position z");	
	schema:register(XMLValueType.INT, animalDealerKey .. "#numObjects", "Num objects spawned or delivery");	
	--RIDINGTRANSPORT,RIDING,TRANSPORT--
	schema:register(XMLValueType.FLOAT, animalDealerKey .. "#pickupTriggerX", "pickup trigger position x");
	schema:register(XMLValueType.FLOAT, animalDealerKey .. "#pickupTriggerZ", "pickup trigger position z");	
	schema:register(XMLValueType.STRING, animalDealerKey .. ".object(?)#uniqueId", "UniqueId of created object");
	schema:register(XMLValueType.STRING, animalDealerKey .. ".object(?)#name", "Name of created object");
	schema:register(XMLValueType.BOOL, animalDealerKey .. ".object(?)#state", "State of created object");
	--RIDINGTRANSPORT,RIDING,TRANSPORT--
	--DELIVERYANIMALS--
	local animalDealerKey1 = string.format("%s.animalData", key);	
	schema:register(XMLValueType.INT, animalDealerKey1 .. "#numDeliveryAnimals", "Num finished delivery animals");
	schema:register(XMLValueType.INT, animalDealerKey1 .. "#animalTypeIndex", "Animal type index, 0 for all"); --old
	schema:register(XMLValueType.INT, animalDealerKey1 .. "#subTypeIndex", "Animal sub type index"); --old
	schema:register(XMLValueType.STRING, animalDealerKey1 .. "#animalTypeName", "Animal type name");
	schema:register(XMLValueType.STRING, animalDealerKey1 .. "#subTypeName", "Animal sub type name");
	schema:register(XMLValueType.INT, animalDealerKey1 .. "#minHealth", "Condition min health ...");
	schema:register(XMLValueType.INT, animalDealerKey1 .. "#minAge", "Condition min ages ...");
	schema:register(XMLValueType.INT, animalDealerKey1 .. "#minFitness", "Condition min fitness only for horses ...");
	--DELIVERYANIMALS--
	--DELIVERYFILLTYPES--
	local animalDealerKey2 = string.format("%s.fillTypeData", key);
	schema:register(XMLValueType.FLOAT, animalDealerKey2 .. "#expectedLiters", "Expected liters total");
	schema:register(XMLValueType.FLOAT, animalDealerKey2 .. "#depositedLiters", "Deposited liters total");
	schema:register(XMLValueType.STRING, animalDealerKey2 .. ".fillType(?)#name", "Name of the fill type");
	schema:register(XMLValueType.FLOAT, animalDealerKey2 .. ".fillType(?)#expectedLiters", "Expected liters");	
	--DELIVERYFILLTYPES--
end;

function AnimalDealerMission:loadFromXMLFile(xmlFile, key)
	local animalDealerKey = string.format("%s.animalDealer", key);
	if not xmlFile:hasProperty(animalDealerKey .. "#typVariant") or not xmlFile:hasProperty(animalDealerKey .. "#missionConfig") then return false;end;
	local typVariant = xmlFile:getValue(animalDealerKey .. "#typVariant");
	if AnimalDealerMission.ACCEPTS_VARIANT[typVariant] == nil then return false;end;
	self.typVariant = typVariant;
	local missionConfig = xmlFile:getValue(animalDealerKey .. "#missionConfig");	
	self.missionConfig = self:getDealerMissionConfigByName(missionConfig);
	if self.missionConfig == nil then
		return false;
	end;
	local objectConfig = xmlFile:getValue(animalDealerKey .. "#objectConfig");
	self.objectConfig = self:getDealerMissionObjectConfigByIndex(objectConfig);
	if self.objectConfig == nil then
		return false;
	end;
	self.numObjects = xmlFile:getValue(animalDealerKey .. "#numObjects") or 0;
	
	local pickupTriggerX = xmlFile:getValue(animalDealerKey .. "#pickupTriggerX");
	local pickupTriggerZ = xmlFile:getValue(animalDealerKey .. "#pickupTriggerZ");	
	local dropoffTriggerTyp = xmlFile:getValue(animalDealerKey .. "#dropoffTriggerTyp");
	local dropoffTriggerX = xmlFile:getValue(animalDealerKey .. "#dropoffTriggerX");
	local dropoffTriggerZ = xmlFile:getValue(animalDealerKey .. "#dropoffTriggerZ");
	
	self.pendingLoadingData = {
		["dropoff"] = {triggerTyp=dropoffTriggerTyp,x=dropoffTriggerX, z=dropoffTriggerZ};
		["pickup"] = {x=pickupTriggerX, z=pickupTriggerZ};
		["objects"] = {};
		["animalData"] = {};
		["fillTypeData"] = {};
	};
	
	if typVariant == "DELIVERYANIMALS" then 
		local animalDealerKey1 = string.format("%s.animalData", key);
		self.numDeliveryAnimals = xmlFile:getValue(animalDealerKey1 .. "#numDeliveryAnimals") or 0;
		local oldAnimalXmlKey = xmlFile:hasProperty(animalDealerKey1 .. "#animalTypeIndex");
		if xmlFile:hasProperty(animalDealerKey1 .. "#animalTypeIndex") then
			local animalData = {			
				animalTypeIndex = xmlFile:getValue(animalDealerKey1 .. "#animalTypeIndex") or 0;			
				subTypeIndex = xmlFile:getValue(animalDealerKey1 .. "#subTypeIndex") or 0;			
				minHealth = xmlFile:getValue(animalDealerKey1 .. "#minHealth") or 0;
				minAge = xmlFile:getValue(animalDealerKey1 .. "#minAge") or 0;
				minFitness = xmlFile:getValue(animalDealerKey1 .. "#minFitness") or 0;				
			};
			table.insert(self.pendingLoadingData.animalData, animalData);
		else
			local animalData = {			
				animalTypeName = xmlFile:getValue(animalDealerKey1 .. "#animalTypeName");			
				subTypeName = xmlFile:getValue(animalDealerKey1 .. "#subTypeName");			
				minHealth = xmlFile:getValue(animalDealerKey1 .. "#minHealth") or 0;
				minAge = xmlFile:getValue(animalDealerKey1 .. "#minAge") or 0;
				minFitness = xmlFile:getValue(animalDealerKey1 .. "#minFitness") or 0;				
			};
			table.insert(self.pendingLoadingData.animalData, animalData);
		end;				
	elseif self.typVariant == "DELIVERYFILLTYPES" then
		local animalDealerKey2 = string.format("%s.fillTypeData", key);
		self.expectedLiters = xmlFile:getValue(animalDealerKey2 .. "#expectedLiters") or 0;
		self.depositedLiters = xmlFile:getValue(animalDealerKey2 .. "#depositedLiters") or 0;
		local int = 0;
		while true do
			local fillTypeKey = string.format("%s.fillType(%d)", animalDealerKey2, int);
			if not xmlFile:hasProperty(fillTypeKey .. "#name") then
				break;
			end;			
			local fillTypeData = {
				fillType = xmlFile:getValue(fillTypeKey.."#name");
				expectedLiters = xmlFile:getValue(fillTypeKey.."#expectedLiters");				
			};
			table.insert(self.pendingLoadingData.fillTypeData, fillTypeData);			
			int = int + 1;	
		end;
		if self.pendingLoadingData.fillTypeData ~= nil then self.fillTypeData = g_additionalContractUtils:getConvertFillTypeDataOnSavegame(self.pendingLoadingData.fillTypeData);self.pendingLoadingData.fillTypeData = nil;end;
	else
		--if self.status == MissionStatus.RUNNING then
			local int = 0;
			while true do
				local objectKey = string.format("%s.object(%d)", animalDealerKey, int);
				if not xmlFile:hasProperty(objectKey .. "#name") then
					break;
				end;			
				local uniqueId = xmlFile:getValue(objectKey .. "#uniqueId");
				local name = xmlFile:getValue(objectKey .. "#name");
				local state = xmlFile:getValue(objectKey .. "#state");
				local objectTable = {
					uniqueId = uniqueId;
					name = name;
					state = state or false;
				};
				table.insert(self.pendingLoadingData.objects, objectTable);			
				int = int + 1;			
			end;		
		--end	
	end;
	if not AnimalDealerMission:superClass().loadFromXMLFile(self, xmlFile, key) then
		return false;
	end;	
	return true;
end;

function AnimalDealerMission:saveToXMLFile(xmlFile, key)	
	local animalDealerKey = string.format("%s.animalDealer", key);
	xmlFile:setValue(animalDealerKey .. "#typVariant", self.typVariant);
	xmlFile:setValue(animalDealerKey .. "#missionConfig", self.missionConfig.name);
	xmlFile:setValue(animalDealerKey .. "#objectConfig", self.objectConfig.index);	
	xmlFile:setValue(animalDealerKey .. "#dropoffTriggerTyp", self.dropoff.triggerTyp);	
	xmlFile:setValue(animalDealerKey .. "#dropoffTriggerX", self.dropoff.x);
	xmlFile:setValue(animalDealerKey .. "#dropoffTriggerZ", self.dropoff.z);
	xmlFile:setValue(animalDealerKey .. "#numObjects", self.numObjects);	
	
	if self.typVariant == "DELIVERYANIMALS" then
		local animalDealerKey1 = string.format("%s.animalData", key);
		xmlFile:setValue(animalDealerKey1 .. "#numDeliveryAnimals", self.numDeliveryAnimals);
		local animalTypeName = "ALL";
		if self.animalData.animalTypeIndex ~= 0 then animalTypeName = g_currentMission.animalSystem:getTypeByIndex(self.animalData.animalTypeIndex).name;end;
		xmlFile:setValue(animalDealerKey1 .. "#animalTypeName", animalTypeName);
		local subTypeName = "ALL";
		if self.animalData.subTypeIndex ~= 0 then subTypeName = g_currentMission.animalSystem:getSubTypeByIndex(self.animalData.subTypeIndex).name;end;
		xmlFile:setValue(animalDealerKey1 .. "#subTypeName", subTypeName);		
		xmlFile:setValue(animalDealerKey1 .. "#minHealth", self.animalData.minHealth);
		xmlFile:setValue(animalDealerKey1 .. "#minAge", self.animalData.minAge);
		xmlFile:setValue(animalDealerKey1 .. "#minFitness", self.animalData.minFitness);
	elseif self.typVariant == "DELIVERYFILLTYPES" then
		local animalDealerKey2 = string.format("%s.fillTypeData", key);
		xmlFile:setValue(animalDealerKey2 .. "#expectedLiters", self.expectedLiters);
		xmlFile:setValue(animalDealerKey2 .. "#depositedLiters", self.depositedLiters);
		local int = 0;
		for fillTypeIndex, value in pairs(self.fillTypeData.acceptsFillType) do
			local fillTypeKey = string.format("%s.fillType(%d)", animalDealerKey2, int);
			xmlFile:setValue(fillTypeKey .. "#name", g_fillTypeManager:getFillTypeNameByIndex(fillTypeIndex));
			xmlFile:setValue(fillTypeKey .. "#expectedLiters", value.expectedLiters);			
			int = int + 1;
		end;
	else
		xmlFile:setValue(animalDealerKey .. "#pickupTriggerX", self.pickup.x);
		xmlFile:setValue(animalDealerKey .. "#pickupTriggerZ", self.pickup.z);	
		local int = 0;
		for _, values in pairs(self.objects) do
			local uniqueId = nil;
			if values.object ~= nil then uniqueId = values.object:getUniqueId();end;	
			local name = values.name;
			local state = values.state;
			
			--local uniqueId = object:getUniqueId();		
			local objectKey = string.format("%s.object(%d)", animalDealerKey, int);
			if uniqueId ~= nil then xmlFile:setValue(objectKey .. "#uniqueId", uniqueId);end;
			xmlFile:setValue(objectKey .. "#name", name);
			xmlFile:setValue(objectKey .. "#state", state);
			int = int + 1;
		end;
	end;	
	AnimalDealerMission:superClass().saveToXMLFile(self, xmlFile, key);	
end;

function AnimalDealerMission:onSavegameLoaded()
	if self.pendingLoadingData ~= nil then
		local dropoff = g_additionalContractMapData:getTriggerByPosition(self.pendingLoadingData.dropoff.triggerTyp, self.pendingLoadingData.dropoff.x, self.pendingLoadingData.dropoff.z);
		if dropoff ~= nil then 
			self.dropoff = dropoff;
			if g_server ~= nil then					
				self:addMissionAtTrigger();				
			end;
		end;		
		if self.typVariant == "DELIVERYANIMALS" then
			self.animalData = g_additionalContractUtils:getConvertAnimalDataOnSavegame(self.pendingLoadingData.animalData[1]);
			if self.animalData == nil then
				Logging.warning("Unable to retrieve animalData for animal dealer contract (typ: DELIVERYANIMALS)");
				g_missionManager:markMissionForDeletion(self);
				return;
			end;
		elseif self.typVariant == "DELIVERYFILLTYPES" then
			if self.pendingLoadingData.fillTypeData ~= nil then self.fillTypeData = g_additionalContractUtils:getConvertFillTypeDataOnSavegame(self.pendingLoadingData.fillTypeData);self.pendingLoadingData.fillTypeData = nil;end;
			if self.fillTypeData == nil then
				Logging.warning("Unable to retrieve fillTypeData for animal dealer contract (typ: DELIVERYFILLTYPES)");
				g_missionManager:markMissionForDeletion(self);
				return;
			end;
			if not self:getIsFinished() and self:getWasStarted() then
				self:setSellingStation(true);
			end;
		else
			self.pickup = {x=self.pendingLoadingData.pickup.x, z=self.pendingLoadingData.pickup.z}; --on server/client	
			for _, values in ipairs(self.pendingLoadingData.objects) do
				local vehicle = g_currentMission.vehicleSystem:getVehicleByUniqueId(values.uniqueId);
				local name = values.name;
				local state = values.state;
				if state == true then self.numFinished = self.numFinished + 1;end;			
				local objectTable = {
					object = vehicle;
					name = name;
					state = state;
				};
				table.insert(self.objects, objectTable);
			end;		
			if self.numFinished > self.numObjects then self.numFinished = self.numObjects;end;
		end;
		self:setTypDescription();
		self:addTypVariant(self.typVariant, self:getWasRunning());
	end;	
	
	AnimalDealerMission:superClass().onSavegameLoaded(self)
end;

function AnimalDealerMission:loadMapData(xmlFile, key, baseDirectory)
	
end;

function AnimalDealerMission:unloadMapData()
	
end;

function AnimalDealerMission.new(isServer, isClient)	
	local title = AnimalDealerMission.data.jobTypName;
	local description = g_i18n:getText("category_animals");
	local self = AbstractMission.new(isServer, isClient, title, description, AnimalDealerMission_mt);
	
	if AnimalDealerMission.data.typ ~= nil then AnimalDealerMission.data.typ = nil;end; --mdMod
	self.jobTypName = self.data.jobTypName;
	
	self.loadingObjects = AdditionalContractsLoading.new();	
	self.customEnvironment = AnimalDealerMission.NAME; --amt mod
	self.pendingSellingStationId = nil;
	self.sellingStation = nil;	
	self.objects = {};	
	self.objectsAtTrigger = {};
	self.numFinished = 0;
	self.numObjects = 0;
	self.numDeliveryAnimals = 0;	
	self.penalty = 0;
	self.mapHotspots = {};
	self.typVariant = nil;
	self.animalData = {};
	self.fillTypeData = {};
	self.fillTypeIndex = nil;
	self.depositedLiters = 0;
	self.expectedLiters = 0;
	self.lastSellChange = -1;
	-- The Animal Dealer fill-type variant uses its own private mission-only
	-- station. Mark it for the same credit-before-consume router; all other
	-- variants are ignored because acceptsMissionFillType returns false.
	self.tfuIsDeliveryMission = true;
	self.tfuDestinationBlocked = false;
	self.farmlandId = FarmlandManager.NO_OWNER_FARM_ID;	--GUI does not do a nil check so just use 0	
	return self;
end;

function AnimalDealerMission:init(typVariant, addConsole, animalData)
		
	local typVariantMission = g_additionalContractUtils:getRandomXmlMissionByTyp(AnimalDealerMission.MISSIONS_XML, typVariant);
	if typVariantMission == nil then debugPrint(addConsole, "typVariantMission nil");return false;end;
	local object = table.getRandomElement(typVariantMission.objects);
	if object == nil then debugPrint(addConsole, "object nil");return false;end;
	local dropoffTrigger = g_additionalContractMapData:getRandomTriggerNoFarmId("animalDealer");
	if dropoffTrigger == nil then debugPrint(addConsole, "dropoffTrigger nil");return false;end;
	local typeObject = g_additionalContractMapData:getTypeObject("animalDealer", nil, dropoffTrigger.triggerId);
	if typeObject == nil then debugPrint(addConsole, "typeObject nil");return false;end;
		
	if typeObject.mission ~= nil then debugPrint(addConsole, "typeObject.mission not nil(has active mission)");return false;end; --not multiple
	if typVariant == "DELIVERYANIMALS" then --not multiple animals
		self.animalData = g_additionalContractUtils:getConvertAnimalData(typeObject, object.animalData[1], typVariant);
		if self.animalData == nil then debugPrint(addConsole, "convertAnimalData nil or not accepts typ %s", object.animalData[1].typ);return false;end;
	elseif typVariant == "DELIVERYFILLTYPES" then --multiple fillTypes
		local numFillTypes = g_additionalContractUtils:getIntegerToVector(object.minMax);
		self.fillTypeData = g_additionalContractUtils:getConvertFillTypeData(numFillTypes, object.fillTypeData, typVariant);
		if self.fillTypeData == nil then debugPrint(addConsole, "convertFillTypeData nil");return false;end;
	end;
	local distance = AnimalDealerMission.TARGET_TRIGGER_DISTANCE;
	if object.targetTriggerDistance ~= nil then distance = object.targetTriggerDistance;end;
	local pickupTrigger = nil;
	if typVariant == "DELIVERYANIMALS" or typVariant == "DELIVERYFILLTYPES" then
		pickupTrigger = {};
	else
		pickupTrigger = g_additionalContractMapData:getRandomMapPositionByMinDistance(dropoffTrigger.x, dropoffTrigger.z, distance, true, object.size[1]);
		if pickupTrigger == nil then debugPrint(addConsole, "pickupTrigger nil");return false;end;
	end;
	if typVariant == "DELIVERYFILLTYPES" then
		self.numObjects = self.fillTypeData.numFillTypes;
		self.expectedLiters = self.fillTypeData.expectedLiters;
		self.depositedLiters = 0; --TFU6: no synthetic delivery credit
		self.fillTypeData.expectedLiters = nil;
	else
		self.numObjects = g_additionalContractUtils:getIntegerToVector(object.minMax);
	end;
	self.pickup = pickupTrigger;
	self.dropoff = dropoffTrigger;	
	if not self:addMissionAtTrigger() then debugPrint(addConsole, "addMissionAtTrigger false");return false;end;	
	self.missionConfig = typVariantMission;
	self.objectConfig = object;		
	self.typVariant = tostring(typVariant);	
	self:setTypDescription();	
	self.reward = self:calculateReward(object.rewardScale);
	self:setMinReward();
	--self.penalty = self:calculatePenalty();	
	return AnimalDealerMission:superClass().init(self);	
end;

function AnimalDealerMission:prepare(spawnVehicles)	
	if self.isServer then
		self:loadPlaceables();
	end
	AnimalDealerMission:superClass().prepare(self, spawnVehicles);
end

function AnimalDealerMission:start(spawnVehicles)	
	if not self:loadObjects() then
		debugPrint(nil, "loadObjects false");
		return false;
	end;
	if not AnimalDealerMission:superClass().start(self, spawnVehicles) then
		return false;
	end;
	--if not self:setSellingStation(false) then return false;end;	
	self:addTypVariant(self.typVariant);
	return true;	
end;

function AnimalDealerMission:update(dt)
	AnimalDealerMission:superClass().update(self, dt);	
	if self.status == MissionStatus.RUNNING and g_localPlayer ~= nil and g_localPlayer.farmId == self.farmId and not self:hasHotspots() then		
		self:createHotspots();		
	end;
	if self.status == MissionStatus.RUNNING and self.typVariant == "DELIVERYFILLTYPES" and self.sellingStation == nil then
		if g_localPlayer ~= nil and g_localPlayer.farmId == self.farmId and self.pendingSellingStationId ~= nil then
			local sellingStation = NetworkUtil.getObject(self.pendingSellingStationId);
			self:setSellingStation(false, sellingStation);
		elseif self.isServer and self.sellingStation == nil then
			self:setSellingStation(false);
		end;
	end;
end;

function AnimalDealerMission:writeStream(streamId, connection) --server
	streamWriteUInt8(streamId, self.numObjects);
	streamWriteUInt8(streamId, self.missionConfig.id);
	streamWriteUInt8(streamId, self.objectConfig.index);
	streamWriteString(streamId, tostring(self.typVariant));	
	----	
	streamWriteString(streamId, tostring(self.dropoff.triggerTyp));	
	streamWriteFloat32(streamId, self.dropoff.x);
	streamWriteFloat32(streamId, self.dropoff.z);
	
	--streamWriteString(streamId, tostring(self.dropoff.uniqueId));
	
	--NetworkUtil.writeNodeObject(streamId, g_currentMission.placeableSystem:getPlaceableByUniqueId(self.dropoff.uniqueId));
	if self.typVariant == "DELIVERYANIMALS" then
		streamWriteUInt16(streamId, self.numDeliveryAnimals);
		streamWriteUIntN(streamId, self.animalData.animalTypeIndex, AnimalSystem.SEND_NUM_BITS);
		streamWriteUIntN(streamId, self.animalData.subTypeIndex, AnimalSystem.SEND_NUM_BITS);
		streamWriteUInt16(streamId, self.animalData.minHealth);
		streamWriteUInt16(streamId, self.animalData.minAge);
		streamWriteUInt16(streamId, self.animalData.minFitness);
	elseif self.typVariant == "DELIVERYFILLTYPES" then
		local sellingStation = g_additionalContractPlaceableManager:getPlaceableByUniqueId("AnimalDealerMission_SellingStation_1");
		if streamWriteBool(streamId, sellingStation ~= nil) then		
			NetworkUtil.writeNodeObject(streamId, sellingStation);
		end;
		streamWriteFloat32(streamId, self.expectedLiters);
		if streamWriteBool(streamId, self.depositedLiters > 0) then
			streamWriteFloat32(streamId, self.depositedLiters);
		end;
		for fillTypeIndex, fillData in pairs(self.fillTypeData.acceptsFillType) do			
			streamWriteUIntN(streamId, fillTypeIndex, FillTypeManager.SEND_NUM_BITS);
			streamWriteFloat32(streamId, fillData.expectedLiters);			
		end;
	else
		streamWriteFloat32(streamId, self.pickup.x);
		streamWriteFloat32(streamId, self.pickup.z);
	end;
	AnimalDealerMission:superClass().writeStream(self, streamId, connection);
end;

function AnimalDealerMission:readStream(streamId, connection) --client
	self.numObjects = streamReadUInt8(streamId);	
	self.missionConfig = self:getDealerMissionConfigById(streamReadUInt8(streamId));
	self.objectConfig = self:getDealerMissionObjectConfigByIndex(streamReadUInt8(streamId));
	self.typVariant = streamReadString(streamId);
	self:setTypDescription();	
	----
	local dropoffTriggerTyp = streamReadString(streamId);
	local dropoffTriggerX = streamReadFloat32(streamId);		
	local dropoffTriggerZ = streamReadFloat32(streamId);
	
	--local dropoffTriggerUniqueId = streamReadString(streamId);
	
	--local dropoffNodeObjectId = NetworkUtil.readNodeObjectId(streamId);
	local pickupTriggerX = nil;		
	local pickupTriggerZ = nil;	
	if self.typVariant == "DELIVERYANIMALS" then
		self.numDeliveryAnimals = streamReadUInt16(streamId);
		local animalData = {
			animalTypeIndex = streamReadUIntN(streamId, AnimalSystem.SEND_NUM_BITS);
			subTypeIndex = streamReadUIntN(streamId, AnimalSystem.SEND_NUM_BITS);
			minHealth = streamReadUInt16(streamId);
			minAge = streamReadUInt16(streamId);
			minFitness = streamReadUInt16(streamId);
		};
		self.animalData = g_additionalContractUtils:getConvertAnimalDataOnSavegame(animalData);
	elseif self.typVariant == "DELIVERYFILLTYPES" then
		if streamReadBool(streamId) then
			self.pendingSellingStationId = NetworkUtil.readNodeObjectId(streamId);
		end;
		self.expectedLiters = streamReadFloat32(streamId);
		if streamReadBool(streamId) then
			self.depositedLiters = streamReadFloat32(streamId);
		end;
		for f=1, self.numObjects do
			local fillTypeIndex = streamReadUIntN(streamId, FillTypeManager.SEND_NUM_BITS);
			local expectedLiters = streamReadFloat32(streamId);		
			if self.fillTypeData.acceptsFillType == nil then self.fillTypeData.acceptsFillType = {};end;
			self.fillTypeData.acceptsFillType[fillTypeIndex] = {expectedLiters=expectedLiters};
		end;
	else
		pickupTriggerX = streamReadFloat32(streamId);		
		pickupTriggerZ = streamReadFloat32(streamId);
	end;	
	self.pendingLoadingData = {
		["dropoff"] = {triggerTyp=dropoffTriggerTyp, x=dropoffTriggerX, z=dropoffTriggerZ};
		["pickup"] = {x=pickupTriggerX, z=pickupTriggerZ};		
	};
		
	AnimalDealerMission:superClass().readStream(self, streamId, connection);
end;

function AnimalDealerMission:writeUpdateStream(streamId, connection, dirtyMask)
    AnimalDealerMission:superClass().writeUpdateStream(self, streamId, connection, dirtyMask);
    if self.typVariant == "DELIVERYANIMALS" and streamWriteBool(streamId, self.numDeliveryAnimals > 0) then
        streamWriteUInt8(streamId, self.numDeliveryAnimals);
    elseif self.typVariant == "DELIVERYFILLTYPES" and streamWriteBool(streamId, self.depositedLiters > 0) then
        streamWriteFloat32(streamId, self.depositedLiters);
		for fillTypeIndex, fillData in pairs(self.fillTypeData.acceptsFillType) do			
			streamWriteUIntN(streamId, fillTypeIndex, FillTypeManager.SEND_NUM_BITS);
			streamWriteFloat32(streamId, fillData.expectedLiters);			
		end;
    end;
end;

function AnimalDealerMission:readUpdateStream(streamId, timestamp, connection)
    AnimalDealerMission:superClass().readUpdateStream(self, streamId, timestamp, connection);
    if self.typVariant == "DELIVERYANIMALS" and streamReadBool(streamId) then
        self.numDeliveryAnimals = streamReadUInt8(streamId);
    elseif self.typVariant == "DELIVERYFILLTYPES" and streamReadBool(streamId) then
        self.depositedLiters = streamReadFloat32(streamId);		
		for f=1, self.numObjects do
			local fillTypeIndex = streamReadUIntN(streamId, FillTypeManager.SEND_NUM_BITS);
			local expectedLiters = streamReadFloat32(streamId);
			if self.fillTypeData.acceptsFillType == nil then self.fillTypeData.acceptsFillType = {};end;
			self.fillTypeData.acceptsFillType[fillTypeIndex] = {expectedLiters=expectedLiters};
		end;
    end;
end;

function AnimalDealerMission:objectEnteredTrigger(trigger, triggerId, otherId)	
	if self.isServer and self.status == MissionStatus.RUNNING then
		if self.typVariant == "RIDINGTRANSPORT" or self.typVariant == "RIDING" or self.typVariant == "TRANSPORT" then
			local object = g_currentMission.nodeToObject[otherId];
			if object ~= nil and object.spec_rideable ~= nil then				
				local name = "";
				if object.getName ~= nil then name = object:getName();end;
				if name ~= nil and name:len() > 3 then 				
					for key, values in pairs(self.objects) do
						if values.name == name and values.state == false then
							self.numFinished = self.numFinished + 1;
							values.state = true;
							--if values.object == nil or values.object ~= object then values.object = object;end;
							self:deleteObject(object);
							values.object = nil;							
						end;
					end;					
				end;				
			end;
		end;
	end;
end;

--function AnimalDealerMission:objectLeaveTrigger(trigger, triggerId, otherId)
	
--end;

function AnimalDealerMission:finish(finishState)
	AnimalDealerMission:superClass().finish(self, finishState);	
	if g_currentMission:getIsServer() then				
		if finishState then
			g_farmManager:getFarmById(self.farmId).stats:updateMissionDone();
		end;		
	end;
	if self.sellingStation ~= nil then
		local sellingStation = nil;
		--if self.pendingSellingStationId ~= nil then sellingStation = NetworkUtil.getObject(self.pendingSellingStationId);end;
		self:removeSellingStation(sellingStation);
	end
	if g_currentMission:getFarmId() == self.farmId then
		if finishState == MissionFinishState.SUCCESS then
			g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_OK, self.jobTypName.. "-".. g_i18n:getText("contract_field_finished"));
		elseif finishState == MissionFinishState.FAILED or finishState == MissionFinishState.CANCELED then 
			g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_CRITICAL, self.jobTypName.. "-".. g_i18n:getText("contract_failed"));
		elseif finishState == MissionFinishState.TIMED_OUT then
			g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_CRITICAL, self.jobTypName.. "-".. g_i18n:getText("contract_field_timedOut"));
		end;
	end;
end;

function AnimalDealerMission:dismiss()
	AnimalDealerMission:superClass().dismiss(self);
end;

function AnimalDealerMission:delete()	
	self:removeSellingStation();	
	for _, values in pairs(self.objects) do
		self:deleteObject(values.object);
	end;
	self.animalData = {};
	self.fillTypeData = {};
	self.objects = {};		
	self:destroyHotspots();
	self:removeMissionAtTrigger();	
	self:removeTypVariant(self.typVariant);
	AnimalDealerMission:superClass().delete(self);
end;

function AnimalDealerMission:deleteObject(object)
	if object ~= nil and object.rootNode ~= nil and entityExists(object.rootNode) then
		g_currentMission.nodeToObject[object.rootNode]:delete();			
	end;
end;

function AnimalDealerMission:validateObjects()
	-- if self.typVariant == "RIDING" then 
		-- for key, values in pairs(self.objects) do
			-- if values.object == nil or (values.object.rootNode ~= nil and not entityExists(values.object.rootNode)) then return false;end;
		-- end;
	-- end;
	return true;
end;

function AnimalDealerMission:validate()
    return AnimalDealerMission:superClass().validate(self) and self:validateObjects() and self:getDropoffTrigger() and g_additionalContractMapData:getTypeObject("animalDealer", nil, self:getDropoffTrigger().triggerId);	
end;

function AnimalDealerMission.tryGenerateMission(addConsole, variantTyp, animalData, setMaxNumSeason)
	if AnimalDealerMission.canRun(addConsole) then
		debugPrint(addConsole, "AnimalDealerMission.tryGenerateMission start");		
		local data = g_missionManager:getMissionTypeDataByName(AnimalDealerMission.NAME);
		local randomVariant = {};
		local typVariant = nil;
		if variantTyp ~= nil and variantTyp ~= "RANDOM" then
			if data.activeTypVariant[variantTyp] == nil then				
				if not AnimalDealerMission.ACCEPTS_VARIANT[variantTyp] then 
					if addConsole then printError("Error: typVariant (".. tostring(variantTyp).. ") is not posibble at map");end;
					return;
				end;
				table.insert(randomVariant, variantTyp);
			end;
		else
			for variant, accepts in pairs(AnimalDealerMission.ACCEPTS_VARIANT) do
				if accepts and data.activeTypVariant[variant] == nil then table.insert(randomVariant, variant);end;		
			end;
		end;	
		if #randomVariant > 0 then			
			typVariant = randomVariant[math.random(1, #randomVariant)];
		else
			if addConsole then printError("Error: Not found free typVariant");end;
			return;
		end;
		
		if typVariant == nil then
			if addConsole then printError("Error: typVariant is nil");end;
			return;
		end;		
		debugPrint(addConsole, "AnimalDealerMission.tryGenerateMission end and generate AnimalDealerMission.new() with typVariant ", typVariant);
		local mission = AnimalDealerMission.new(true, g_client ~= nil);		
		if mission:init(typVariant, addConsole, animalData) then			
			debugPrint(addConsole, "AnimalDealerMission:init() is true");
			mission:setEndDate(mission:getEndDate());
			data.activeTypVariant[mission.typVariant] = false;
			if (addConsole == nil or (addConsole and setMaxNumSeason)) and mission:canMaxNumSeason() then 
				mission:setMaxNumSeason();				
				if mission:isMaxNumSeason() then
					data.nextMissionDay = g_currentMission.environment.currentMonotonicDay + mission:getNextMissionDay();
					mission:setMaxNumSeason(0);
				end;
			end;
			return mission;
		end;		
		mission:delete();
	else
		if addConsole then printError("Error: Mission type not active or disabled");end;
	end;
	return nil;
end;

function AnimalDealerMission:getEndDate()
	local day = g_currentMission.environment.currentMonotonicDay;
	local hour = g_currentMission.environment:getMonotonicHour();
	if hour > 5 then day = day + 1;end;
	return day, MathUtil.hoursToMs(math.random(9, 16));
end;

function AnimalDealerMission:loadPlaceables()
	if self.typVariant == "DELIVERYFILLTYPES" then
		local xmlFilename = Utils.getFilename("missions/animalDealerMission/placeables.xml", AdditionalContracts.modDir);
		g_additionalContractPlaceableManager:load(xmlFilename, AnimalDealerMission.loadingPlaceables, self.farmId);
	end;
end;

function AnimalDealerMission:loadObjects()
	if self.typVariant == "RIDINGTRANSPORT" or self.typVariant == "RIDING" or self.typVariant == "TRANSPORT" then
		local trigger = self:getPickupTrigger();
		local objectConfig = self.objectConfig;	
		if objectConfig == nil then
			debugPrint(nil, "objectConfig nil");
			return false;
		end;
		local canSpawn, objectsSpawnPosition = g_additionalContractSpawnManager:getObjectsSpawnPosition(trigger, nil, self.numObjects, objectConfig);
		if not canSpawn or #objectsSpawnPosition <= 0 then debugPrint(nil, "canSpawn is false/objectsSpawnPosition <= 0");return false;end;
		
		for o=1, #objectsSpawnPosition do		
			self.loadingObjects:spawnObject(self, objectsSpawnPosition[o], trigger, objectConfig, nil, o, true);		
		end;		
	end;	
	return true;
end;

function AnimalDealerMission:setSellingStation(onSavegame, sellingStation)
	if self.typVariant == "DELIVERYFILLTYPES" then
		local sellingStation = sellingStation or g_additionalContractPlaceableManager:getPlaceableByUniqueId("AnimalDealerMission_SellingStation_1");
		if not g_currentMission:getIsServer() then debugPrint(nil, "sellingStation %s", sellingStation);end;
		if sellingStation ~= nil then
			local unloadingStation = g_currentMission.storageSystem:getPlaceableUnloadingStation(sellingStation, 1);
			if unloadingStation ~= nil then				
				-- TFU6: this dynamically spawned station belongs exclusively to the
				-- AnimalDealer mission and is therefore exempt from the neutral public
				-- sell-point rule. It is never a normal player production/silo target.
				unloadingStation.tfuMissionInternal = true;
				if unloadingStation.owningPlaceable ~= nil then unloadingStation.owningPlaceable.tfuMissionInternal = true;end;
				self.sellingStation = unloadingStation;
				unloadingStation.missions[self] = self;
				self.pendingSellingStationId = nil;
				return true;
			end;
		end;
		return false;
	end;
	return true;
end;

function AnimalDealerMission:removeSellingStation(sellingStation)	
	if self.typVariant == "DELIVERYFILLTYPES" or self.sellingStation ~= nil then		
		local sellingStation = nil;
		if self.typVariant == "DELIVERYFILLTYPES" and g_currentMission:getIsServer() then
			sellingStation = g_additionalContractPlaceableManager:getPlaceableByUniqueId("AnimalDealerMission_SellingStation_1");						
		end;
		if self.sellingStation ~= nil then
			self.sellingStation.missions[self] = nil;
			self.sellingStation = nil;
			self.pendingSellingStationId = nil;
		end;
		if sellingStation ~= nil then 
			g_additionalContractPlaceableManager:removePlaceable(sellingStation);
			sellingStation:delete();
		end;
	end;
end;

function AnimalDealerMission:setDeliveryAnimals(cluster, animalTypeIndex, numAnimals)	
	if self.status == MissionStatus.RUNNING and cluster ~= nil and self.typVariant ~= nil and self.typVariant == "DELIVERYANIMALS" and self.animalData ~= nil then
		if self.numDeliveryAnimals >= self.numObjects then return;end;
		local allTypes = self.animalData.animalTypeTitle == "ALL";
		if allTypes or self.animalData.animalTypeIndex == animalTypeIndex then
			local isHorse = animalTypeIndex == AnimalType.HORSE;
			if self.animalData.subTypeIndex ~= nil and self.animalData.subTypeIndex ~= 0 then
				local subType = g_currentMission.animalSystem:getSubTypeByIndex(cluster:getSubTypeIndex());
				if subType == nil or subType.typeIndex == nil or self.animalData.subTypeIndex ~= subType.typeIndex then return;end;				
			end;
			function isConditionTrue()				
				if self.animalData.minHealth > 0 and cluster.health ~= nil and cluster.health < self.animalData.minHealth then
					return false;
				end;
				if self.animalData.minAge > 0 and cluster.getAge ~= nil and cluster:getAge() < self.animalData.minAge then
					return false;
				end;
				if self.animalData.minFitness > 0 and isHorse and cluster.fitness ~= nil and cluster.fitness < self.animalData.minFitness then
					return false;
				end;				
				return true;
			end;			
			if isConditionTrue() then 
				local deliveryAnimals = math.clamp(numAnimals, 0, self.numObjects - self.numDeliveryAnimals);				
				self.numDeliveryAnimals = self.numDeliveryAnimals + deliveryAnimals;				
				g_client:getServerConnection():sendEvent(DeliveryAnimalsEvent.new(self, deliveryAnimals));
			end;			
		end;
	end;	
	return;
end;

function AnimalDealerMission:addMissionAtTrigger()	
	local dropoffTrigger = self:getDropoffTrigger();
	if dropoffTrigger ~= nil then return g_additionalContractMapData:addMissionAtTypeObject("animalDealer", dropoffTrigger.triggerId, self);end;
	return false;
end;

function AnimalDealerMission:removeMissionAtTrigger()	
	local dropoffTrigger = self:getDropoffTrigger();
	if dropoffTrigger ~= nil then g_additionalContractMapData:removeMissionAtTypeObject("animalDealer", dropoffTrigger.triggerId, self);end;	
end;

function AnimalDealerMission:addTypVariant(typVariant, state)
	local data = g_missionManager:getMissionTypeDataByName(AnimalDealerMission.NAME);
	if data ~= nil then
		if data.activeTypVariant == nil then data.activeTypVariant = {};end;
		data.activeTypVariant[typVariant] = state or true;
	end;
end;

function AnimalDealerMission:removeTypVariant(typVariant)
	local data = g_missionManager:getMissionTypeDataByName(AnimalDealerMission.NAME);
	if data ~= nil then
		if data.activeTypVariant == nil or data.activeTypVariant[typVariant] == nil then return;end;
		data.activeTypVariant[typVariant] = nil;
	end;
end;

function AnimalDealerMission.canRun(addConsole)	
	if not AnimalDealerMission:getOnOff() then return false;end;
	if not g_additionalContractMapData:hasTrigger("animalDealer") or #AnimalDealerMission.MISSIONS_XML == 0 then 		
		local infoMsg = g_i18n:getText("ui_construction_couldNotLoadItem").. "/".. g_i18n:getText("animals_dealer");
		g_additionalContractTypes:unregisterTyp(AnimalDealerMission.NAME, true, infoMsg);
		return false;
	end;
	if addConsole then return true;end;
	local data = g_missionManager:getMissionTypeDataByName(AnimalDealerMission.NAME);
	if data.numInstances >= data.maxNumInstances or data.numInstances >= g_additionalContractTypes.settings[AnimalDealerMission.NAME.. "_minMax"] then
		return false;
	else
		return not AnimalDealerMission:canMaxNumSeason() or data.nextMissionDay == nil or g_currentMission.environment.currentMonotonicDay >= data.nextMissionDay;		
	end;
end;

---hotspot---
function AnimalDealerMission:getMapHotspots()
	return self.mapHotspots;
end;

function AnimalDealerMission:hasHotspots()
	if self.typVariant == "RIDINGTRANSPORT" or self.typVariant == "RIDING" or self.typVariant == "TRANSPORT" then
		return self.pickupHotspot ~= nil and self.dropoffHotspot ~= nil and g_additionalContractMapData:isDataLoaded();
	else
		return self.dropoffHotspot ~= nil and g_additionalContractMapData:isDataLoaded();
	end;
end;

function AnimalDealerMission:createHotspots()
	if self:getDropoffTrigger() == nil and self.pendingLoadingData ~= nil and self.pendingLoadingData.dropoff ~= nil then self:setDropoffTrigger();end;
	if self:getDropoffTrigger() == nil then return;end; --mp waiting login
	if self.dropoffHotspot == nil then self.dropoffHotspot = self:createHotspot(self:getDropoffTrigger(), "dropoff", self.objectConfig.dropoffHotspotTyp, false, "chartreuse4");end;
	
	if self:getPickupTrigger() == nil and self.pendingLoadingData ~= nil and self.pendingLoadingData.pickup ~= nil then self:setPickupTrigger();end;
	if self:getPickupTrigger() == nil then return;end; --mp waiting login
	if self.pickupHotspot == nil then self.pickupHotspot = self:createHotspot(self:getPickupTrigger(), "pickup", self.objectConfig.pickupHotspotTyp, false, "chartreuse4");end;
end;

function AnimalDealerMission:createHotspot(trigger, typ, hotspotTyp, blinking, color, fillTypeName)
	if trigger == nil or not g_additionalContractUtils:isLoadedHotspotOverlays() then return nil;end; --MP
	local x, _, z;
	if trigger.triggerId ~= nil then x, _, z = getWorldTranslation(trigger.triggerId);elseif trigger.x ~= nil and trigger.z ~= nil then x = trigger.x;z = trigger.z;end;
	if x == nil then return nil;end;
	local mapHotspot = UniversalMissionHotspot.new(nil, self.farmId, typ, hotspotTyp, blinking, color, fillTypeName);
	mapHotspot:setWorldPosition(x, z);
	g_currentMission:addMapHotspot(mapHotspot);		
	local contractsFrameMapHotspot = UniversalCircleMissionHotspot.new(nil, "chartreuse4"); --fake for InGameMenuContractsFrame
	contractsFrameMapHotspot:setWorldPosition(x, z);
	table.addElement(self.mapHotspots, contractsFrameMapHotspot);
	return mapHotspot;
end;

function AnimalDealerMission:destroyHotspots()
	if self.pickupHotspot ~= nil then
		g_currentMission:removeMapHotspot(self.pickupHotspot);
		self.pickupHotspot:delete();
		self.pickupHotspot = nil;
	end;
	if self.dropoffHotspot ~= nil then
		g_currentMission:removeMapHotspot(self.dropoffHotspot);
		self.dropoffHotspot:delete();
		self.dropoffHotspot = nil;
	end;
end;
---hotspot---

function AnimalDealerMission:getCompletion()
	if self.typVariant == "DELIVERYANIMALS" then
		return self.numDeliveryAnimals / self.numObjects;
	elseif self.typVariant == "DELIVERYFILLTYPES" then
		local completion;
		if self.expectedLiters > 0 then
			local sellCompletion = self.depositedLiters / self.expectedLiters / AnimalDealerMission.data.ownTable.SUCCESS_FACTOR;
			completion = math.min(sellCompletion, 1);
		else
			completion = 1;
		end;	
		return completion;
	else
		return self.numFinished / self.numObjects;
	end;
end;

function AnimalDealerMission:acceptsMissionFillType(fillTypeIndex)
	if self.typVariant == "DELIVERYFILLTYPES" and self.fillTypeData ~= nil and fillTypeIndex ~= nil then
		if self.status == MissionStatus.RUNNING then
			local fillData = self.fillTypeData.acceptsFillType[fillTypeIndex];
			return fillData ~= nil and (tonumber(fillData.expectedLiters) or 0) > 0;
		end;
	end;
	return false;
end;

function AnimalDealerMission:getDeliveryRequirements()
	return {requireBulk=true};
end;

function AnimalDealerMission:getTFUDeliveryRequirements()
	return {requireBulk=true};
end;

function AnimalDealerMission:isDeliveryDestinationSafe(station, fillTypeIndex)
	local targetStation = station or self.sellingStation;
	if targetStation == nil or targetStation.tfuMissionInternal ~= true then return false;end;
	if g_additionalContractMapData ~= nil and g_additionalContractMapData.isMissionSafeSellingStation ~= nil then
		return g_additionalContractMapData:isMissionSafeSellingStation(targetStation, fillTypeIndex, false, {requireBulk=true});
	end;
	return true;
end;

function AnimalDealerMission:isTFUDestinationSafe(station, fillTypeIndex)
	return self:isDeliveryDestinationSafe(station, fillTypeIndex);
end;

function AnimalDealerMission:getDeliverySuccessFactor()
	return AnimalDealerMission.data.ownTable.SUCCESS_FACTOR;
end;

function AnimalDealerMission:fillSold(fillDelta, fillTypeIndex)
	if self.typVariant ~= "DELIVERYFILLTYPES" or self.status ~= MissionStatus.RUNNING then return 0;end;
	if type(fillDelta) ~= "number" or fillDelta <= 0 or not self:isDeliveryDestinationSafe(self.sellingStation, fillTypeIndex) then return 0;end;
	local fillData = self.fillTypeData ~= nil and self.fillTypeData.acceptsFillType ~= nil and self.fillTypeData.acceptsFillType[fillTypeIndex] or nil;
	if fillData == nil then return 0;end;
	local typeRemaining = math.max(tonumber(fillData.expectedLiters) or 0, 0);
	local totalRemaining = math.max((tonumber(self.expectedLiters) or 0) - (tonumber(self.depositedLiters) or 0), 0);
	local credited = math.min(fillDelta, typeRemaining, totalRemaining);
	if credited <= 0 then return 0;end;
	fillData.expectedLiters = typeRemaining - credited;
	self.depositedLiters = (tonumber(self.depositedLiters) or 0) + credited;
	local requiredLiters = (tonumber(self.expectedLiters) or 0) * AnimalDealerMission.data.ownTable.SUCCESS_FACTOR;
	if self.sellingStation ~= nil and requiredLiters <= self.depositedLiters + 0.001 then
		self.sellingStation.missions[self] = nil;
	end;
	self.lastSellChange = 30;
	return credited;
end;

function AnimalDealerMission:getNPC()	
	local npc, subData = g_additionalContractNpcManager:getNPCByName("ADC_HELPER", "animaldealer");
	if npc == nil then 
		npc = g_npcManager:getNPCByIndex(1);
	elseif subData ~= nil then
		local variantTitle = g_i18n:getText(subData.title);
		if self.typVariant == "RIDING" then variantTitle = g_i18n:getText("action_rideAnimal").. "..." elseif self.typVariant == "RIDINGTRANSPORT" then variantTitle = g_i18n:getText("ui_helpLine").. "...";elseif self.typVariant == "TRANSPORT" or self.typVariant == "DELIVERYANIMALS" then variantTitle = g_i18n:getText("category_animalTransport");elseif self.typVariant == "DELIVERYFILLTYPES" then variantTitle = g_i18n:getText("ai_jobTitleDeliver").. "...";end;
		npc.title = variantTitle;
		local imageFilename = Utils.getFilename(subData.imageFilename, AdditionalContracts.modDir);
		if imageFilename ~= nil then npc.imageFilename = imageFilename;end;
	end;
	return npc;
end;

function AnimalDealerMission:getDealerMissionConfigById(id)
	for _, dealerMission in pairs(AnimalDealerMission.MISSIONS_XML) do
		if dealerMission.id == id then
			return dealerMission;
		end;
	end;
	return nil;
end;

function AnimalDealerMission:getDealerMissionConfigByName(name)
	for _, dealerMission in pairs(AnimalDealerMission.MISSIONS_XML) do
		if dealerMission.name == name then
			return dealerMission;
		end;
	end;
	return nil;
end;

function AnimalDealerMission:getDealerMissionObjectConfigByIndex(index)
	for _, object in pairs(self.missionConfig.objects) do
		if object.index == index then
			return object;
		end;
	end;	
end;

function AnimalDealerMission:getOwnData()
	return self.data;
end;

function AnimalDealerMission:getNumObjects(returnForce)
	return Utils.getNoNil(self.numObjects, returnForce or 0);
end;

function AnimalDealerMission:getFarmlandId()
   return self.farmlandId or FarmlandManager.NO_OWNER_FARM_ID; --GUI does not do a nil check so just use 0 / Error: Running LUA method 'update'. dataS/scripts/gui/InGameMenuContractsFrame.lua:572: attempt to compare nil < nil
end;

function AnimalDealerMission:setPickupTrigger()
	self.pickup = {x=self.pendingLoadingData.pickup.x, z=self.pendingLoadingData.pickup.z};
	self.pendingLoadingData.pickup = nil;
end;

function AnimalDealerMission:getPickupTrigger()	
	return self.pickup;	
end;

function AnimalDealerMission:setDropoffTrigger()	
	self.dropoff = g_additionalContractMapData:getTriggerByPosition(self.pendingLoadingData.dropoff.triggerTyp, self.pendingLoadingData.dropoff.x, self.pendingLoadingData.dropoff.z);	
	if self.dropoff ~= nil then self.pendingLoadingData.dropoff = nil;end;
end;

function AnimalDealerMission:getDropoffTrigger()	
	return self.dropoff;	
end;

function AnimalDealerMission:calculateStealingCost()	
	if self.finishState ~= MissionFinishState.SUCCESS and self.isServer then
		if self.typVariant == "RIDINGTRANSPORT" or self.typVariant == "RIDING" then
			local penalty = AnimalDealerMission.data.reward.dismiss;
			local missingObjects = 0;			
			for _, values in pairs(self.objects) do
				if values.state or (values.object ~= nil and values.object.rootNode ~= nil and entityExists(values.object.rootNode)) then
					
				else
					missingObjects = missingObjects + 1;
				end;
			end;
			if missingObjects > 0 then				
				penalty = penalty + (AnimalDealerMission.data.reward.PER_OBJECT * missingObjects);							
			end;
			self.stealingCost = penalty;
		end;
	end;
	return self.stealingCost or 0;
end;

function AnimalDealerMission:getStealingCosts()	
	if self.stealingCost == 0 then
        self:calculateStealingCost();
    end;
    return self.stealingCost or 0;	
end;

function AnimalDealerMission:hasLeasableVehicles() --no leasing vehicles
    if self.typVariant ~= nil and self.typVariant == "RIDING" then return false;end;
	return AnimalDealerMission:superClass().hasLeasableVehicles(self);
end;

function AnimalDealerMission:getVehicleSize()	
	if self.objectConfig == nil or self.objectConfig.leasingSize == nil or self.objectConfig.leasingSize == "NONE" then return nil;end;
	return self.objectConfig.leasingSize;	
end;

function AnimalDealerMission:getVehicleVariant()	
	if self.objectConfig == nil or self.objectConfig.leasingVariant == "NONE" then return nil;end;
	local leasingSize = self:getVehicleSize();	
	if leasingSize == nil or leasingSize == "NONE" then return nil;end;	
	local leasingVariant = self.objectConfig.leasingVariant;
	--local _, _, _, vehicleGroup = self:getVehicleGroupFromIdentifier(self.vehicleGroupIdentifier);
	if self.typVariant ~= nil and self.typVariant == "DELIVERYFILLTYPES" then
		if not string.find(leasingVariant, "DELIVERYFILLTYPES_") then		
			--if vehicleGroup ~= nil and vehicleGroup.variant ~= nil and string.find(vehicleGroup.variant, "DELIVERYFILLTYPES_") then return vehicleGroup.variant;end; --onSavegame
			local loadingType = self:getLoadingTypeVariant() or "_MIX";
			leasingVariant = leasingVariant.. loadingType;
		end;	
	end;
	return leasingVariant;	
end;

function AnimalDealerMission:getLoadingTypeVariant()
	if self.typVariant == "DELIVERYFILLTYPES" then
		local bulk = false;local liquid = false;
		local totalNum = #self.fillTypeData.acceptsFillType;
		for fillTypeIndex, fillData in pairs(self.fillTypeData.acceptsFillType) do
			if fillData.loadingType ~= nil and fillData.loadingType ~= "UNKNOWN" then
				if fillData.loadingType:upper() == "BULK" and not bulk then bulk = true;totalNum = totalNum-1;
				elseif fillData.loadingType:upper() == "LIQUID" and not liquid then liquid = true;totalNum = totalNum-1;end;
				if bulk and liquid then return "_MIX";end;
			else
				local fillType = g_fillTypeManager:getFillTypeByIndex(fillTypeIndex);
				if fillType ~= nil then
					if fillType.name == "WATER" or fillType.name == "DIGESTATE" or string.find(fillType.name, "LIQUID") then liquid = true;totalNum = totalNum-1;
					elseif fillType.isPalletType or fillType.isBaleType or fillType.palletFilename ~= nil then bulk = true;totalNum = totalNum-1;
					elseif fillType.isBulkType then bulk = true;totalNum = totalNum-1;end;
				end;
			end;
		end;
		if bulk and not liquid and totalNum <= 0 then return "_BULK";elseif liquid and not bulk and totalNum <= 0 then return "_LIQUID";end;
		return "_MIX";
	end;
	return nil;
end;

function AnimalDealerMission:getVehicleCosts()
	if self.vehiclesToLoad == nil then
		return 0;
	end;
	local numVehicles = #self.vehiclesToLoad;
	local difficultyMultiplier = 0.7 + 0.3 * g_currentMission.missionInfo.economicDifficulty;
	return numVehicles * AnimalDealerMission.data.ownTable.VEHICLE_USE_COST * difficultyMultiplier;
end;

function AnimalDealerMission:calculateReward(rewardScale)	
	if self.typVariant == "RIDINGTRANSPORT" or self.typVariant == "RIDING" then
		local pickupTrigger = self:getPickupTrigger();
		local dropoffTrigger = self:getDropoffTrigger();
		local distance = g_additionalContractUtils:getObjectDistance( {x=dropoffTrigger.x, z=dropoffTrigger.z}, {x=pickupTrigger.x, z=pickupTrigger.z} );
		local driveReward = self.numObjects / AnimalDealerMission.data.reward.NUM_OBJECTS_PER_DRIVE;
		return (math.ceil(driveReward) * AnimalDealerMission.data.reward.PER_METER * distance + self.numObjects * AnimalDealerMission.data.reward.PER_OBJECT) * rewardScale;	
	elseif self.typVariant == "DELIVERYFILLTYPES" then
		local rewardTotal = 0;		
		for fillTypeIndex, fillData in pairs(self.fillTypeData.acceptsFillType) do
			local _, highestPrice = g_additionalContractMapData:getSellingStationWithHighestPrice(fillTypeIndex, true);			
			if highestPrice == nil or highestPrice == 0 then highestPrice = 0.5;end;			
			local fillReward = fillData.expectedLiters * (highestPrice * fillData.rewardScale);
			rewardTotal = rewardTotal + fillReward;
		end;
		return (rewardTotal * rewardScale) + (self.numObjects * AnimalDealerMission.data.reward.PER_OBJECT);
	end;
	return self.numObjects * AnimalDealerMission.data.reward.PER_OBJECT * rewardScale;	
end;

function AnimalDealerMission:getReward()
	return self.reward;
end;

function AnimalDealerMission:setMinReward()
	if self.reward < AnimalDealerMission.data.reward.min then self.reward = AnimalDealerMission.data.reward.min;end;	
end;

function AnimalDealerMission:calculatePenalty()
	if self.typVariant == "RIDINGTRANSPORT" or self.typVariant == "RIDING" then
		return self:getNumObjects(1) * AnimalDealerMission.data.reward.dismiss;
	end;
	return AnimalDealerMission.data.reward.dismiss;
end;

function AnimalDealerMission:setTypDescription()
	if self.objectConfig.typDescription ~= nil then		
		local typDescription = g_i18n:getText(self.objectConfig.typDescription);		
		if self.numObjects > 1 then 
			local typDescriptionMultiple = g_i18n:getText(self.objectConfig.typDescription.. "_multiple");
			if typDescriptionMultiple ~= nil and not string.find(typDescriptionMultiple:lower(), "missing") then typDescription = typDescriptionMultiple;end;
		end;
		self.description = typDescription;		
	end;
	if self.typVariant == "DELIVERYFILLTYPES" then AnimalDealerMission.data.typ = "TRANSPORT";end; --mdMod
end;

function AnimalDealerMission:getMissionTypeName()
    return AnimalDealerMission.NAME;
end;

function AnimalDealerMission:getDetails()
	local details = AnimalDealerMission:superClass().getDetails(self);	
	if self.missionConfig.dropoffTriggers ~= nil then
		local name = g_i18n:getText(self.missionConfig.dropoffTriggers[1].title);		
		if self.typVariant == "DELIVERYFILLTYPES" then name = name.. "-".. g_i18n:getText("category_storages");end;
		local sellingStation = {
			["title"] = g_i18n:getText("contract_title");
			["value"] = tostring(name);
		};	
		table.insert(details, sellingStation);
	end;
	local variantTitle = g_i18n:getText("category_misc");
	if self.typVariant == "RIDINGTRANSPORT" then
		variantTitle = g_i18n:getText("contract_transport_title").. "/".. g_i18n:getText("action_rideAnimal");
	elseif self.typVariant == "RIDING" then
		variantTitle = g_i18n:getText("action_rideAnimal");
	elseif self.typVariant == "TRANSPORT" then
		variantTitle = g_i18n:getText("contract_transport_title");
	elseif self.typVariant == "DELIVERYANIMALS" then
		variantTitle = g_i18n:getText("ai_jobTitleDeliver").. "/".. g_i18n:getText("ui_sellItem");
	elseif self.typVariant == "DELIVERYFILLTYPES" then
		variantTitle = g_i18n:getText("ai_jobTitleDeliver");
	end;	
	local variant = {
		["title"] = g_i18n:getText("infohud_type"); --infohud_type
		["value"] = tostring(variantTitle);
	};
	table.insert(details, variant);
	if self.objectConfig.category ~= nil then
		--typeDesc_livestockTrailer,action_rideAnimal ui_numAnimals
		local num = self:getNumObjects(1);
		local typTitle = "?";
		local isHorse = self.animalData.animalTypeIndex == AnimalType.HORSE;
		if self.typVariant == "RIDING" or self.typVariant == "RIDINGTRANSPORT" or isHorse then
			if num > 1 then typTitle = g_i18n:getText("animal_group_horses") else typTitle = g_i18n:getText("fillType_horse");end;
		elseif self.typVariant == "DELIVERYANIMALS" then			
			if self.animalData.subTypeName ~= nil then
				typTitle = g_i18n:getText("fillType_".. tostring(self.animalData.subTypeName:lower()));
			elseif self.animalData.animalTypeTitle ~= nil then
				if self.animalData.animalTypeTitle == "ALL" then
					typTitle = g_i18n:getText("category_animals");					
				else
					typTitle = self.animalData.animalTypeTitle;
				end;			
			else
				typTitle = g_i18n:getText("category_misc");
			end;
			num = num - self.numDeliveryAnimals;
		end;		
		local category = {
			["title"] = g_i18n:getText("ui_total"); --g_i18n:getText(self.objectConfig.category);
			["value"] = tostring(num).. "x ".. tostring(typTitle);
		};
		table.insert(details, category);
		if self.typVariant == "DELIVERYANIMALS" then			
			local conditionHealth = nil;
			local conditionAge = nil;
			local conditionFitness = nil;
			local condition = {
				["title"] = g_i18n:getText("ui_other");
				["value"] = "";
			};
			local setCondition = 0;
			if self.animalData.minHealth > 0 then
				setCondition = setCondition + 1;
				conditionHealth = {
					["title"] = g_i18n:getText("infohud_health");
					["value"] = "> ".. string.format("%.f%%", self.animalData.minHealth-1);
				};
			end;
			if self.animalData.minAge > 0 then
				setCondition = setCondition + 1;
				conditionAge = {
					["title"] = g_i18n:getText("infohud_age");
					["value"] = "> ".. g_i18n:formatNumMonth(self.animalData.minAge-1);
				};			
			end;
			if self.animalData.minFitness > 0 then
				setCondition = setCondition + 1;
				conditionFitness = {
					["title"] = g_i18n:getText("infohud_fitness");
					["value"] = "> ".. string.format("%.f%%", self.animalData.minFitness-1);
				};			
			end;
			if setCondition > 0 then
				condition["value"] = g_i18n:getText("ui_condition").. " ".. tostring(setCondition).. "x";
			else
				condition["value"] = g_i18n:getText("ui_none");
			end;
			table.insert(details, condition);
			if conditionHealth ~= nil then
				table.insert(details, conditionHealth);
			end;
			if conditionAge ~= nil then
				table.insert(details, conditionAge);
			end;
			if conditionFitness ~= nil then
				table.insert(details, conditionFitness);
			end;
		end;
	elseif self.typVariant == "DELIVERYFILLTYPES" then
		local num = self:getNumObjects(1);
		local category = {
			["title"] = g_i18n:getText("finance_purchaseConsumable");
			["value"] = tostring(num).. "x";
		};
		table.insert(details, category);
		-- if num > 1 then
			-- local deliveryTotal = {
				-- ["title"] = tostring(g_i18n:getText("ui_total"));
				-- ["value"] = tostring(g_i18n:formatNumber(self.expectedLiters - self.depositedLiters));
			-- };
			-- table.insert(details, deliveryTotal);
		-- end;
		for fillTypeIndex, fillData in pairs(self.fillTypeData.acceptsFillType) do			
			local typTitle = g_fillTypeManager:getFillTypeTitleByIndex(fillTypeIndex);
			local expectedLiters = math.max(0, fillData.expectedLiters);
			local value = "";
			if expectedLiters <= 0 then value = g_i18n:getText("contract_transport_finished");else value = g_i18n:formatNumber(expectedLiters);end;
			local deliveryDetails = {
				["title"] = tostring(typTitle);
				["value"] = tostring(value);
			};
			table.insert(details, deliveryDetails);
		end;		
	end;
	
	if self.finishState ~= MissionFinishState.SUCCESS and self.status ~= MissionStatus.RUNNING then
		if AnimalDealerMission.data.reward.dismiss > 0 and self.typVariant == "RIDINGTRANSPORT" or self.typVariant == "RIDING" then
			local penalty = {
				["title"] = g_i18n:getText("contract_details_penalty");
				["value"] = g_i18n:formatMoney(AnimalDealerMission.data.reward.dismiss+AnimalDealerMission.data.reward.PER_OBJECT, 0, true, true);
			};
			table.insert(details, penalty);
		end;
	end;
	return details;
end;

function AnimalDealerMission:getFinishedDetails()
	local finishedDetails = AnimalDealerMission:superClass().getFinishedDetails(self);
	if self.typVariant == "RIDINGTRANSPORT" or self.typVariant == "RIDING" then
		if self.finishState ~= MissionFinishState.SUCCESS then			
			for _, v in pairs(finishedDetails) do
				if v ~= nil and v.title ~= nil and v.title == g_i18n:getText("contract_stealing") then
					v.title = v.title.. " + ".. g_i18n:getText("contract_details_penalty");					
				end;
			end;			
		end;		
	end;
	return finishedDetails;
end;

function AnimalDealerMission:setMaxNumInstance(maxNumInstance)
	self.uiData.settings[AnimalDealerMission.NAME.. "_minMax"] = maxNumInstance;
	g_additionalContractTypes:replaceUISettings(AnimalDealerMission.NAME.. "_minMax", maxNumInstance);
	local data = g_missionManager:getMissionTypeDataByName(AnimalDealerMission.NAME);
	if data ~= nil then data.maxNumInstances = maxNumInstance;end;	
end;

function AnimalDealerMission:getMaxNumInstance()
	local data = g_missionManager:getMissionTypeDataByName(AnimalDealerMission.NAME);
	if data == nil then 
		return g_additionalContractTypes.settings[AnimalDealerMission.NAME.. "_minMax"];
	end;
	return data.maxNumInstances;
end;

function AnimalDealerMission:getTriggerAcceptsFillTypes(triggerTyp)
	local fillTypes = nil;
	local mission = AnimalDealerAbstractFunction:getActiveMissionOnVariant("DELIVERYFILLTYPES")
	if mission ~= nil and mission.fillTypeData ~= nil and mission.fillTypeData.acceptsFillType ~= nil then		
		for fillTypeIndex, _ in pairs(mission.fillTypeData.acceptsFillType) do			
			if fillTypes ~= nil then
				fillTypes = fillTypes.. " ".. g_fillTypeManager:getFillTypeNameByIndex(fillTypeIndex);
			else
				fillTypes = g_fillTypeManager:getFillTypeNameByIndex(fillTypeIndex);
			end;
		end;
	end;	
	return fillTypes;
end;

---md mod---
function AnimalDealerMission:getTriggerInfo(index, isPickup)
	local triggers = self.missionConfig.dropoffTriggers;
	if isPickup then
		triggers = self.missionConfig.pickupTriggers;
	end;
	for _, info in ipairs(triggers) do
		if info.index == index then
			return info;
		end;
	end;
	return {};
end;

function AnimalDealerMission:getTriggerTitle(index, isPickup)
	if self.typVariant == "DELIVERYFILLTYPES" then 
		local name = g_i18n:getText(self.missionConfig.dropoffTriggers[1].title);
		return name.. "-".. g_i18n:getText("category_storages");
	end;
	return "";
	--local info = self:getTriggerInfo(index, isPickup);
	--return info == nil and "" or g_i18n:convertText(Utils.getNoNil(info.title, ""));
end;

--function AnimalDealerMission:getActiveTriggers() --md mod
--	return self.pickup, self.dropoff, self:getTriggerTitle(self.dropoff, false);
--end;

function AnimalDealerMission:getTriggerHotspot(isPickup)
	if isPickup then
		return nil; --self.pickup;
	end;
	return self.dropoff;
end;
---md mod---

---amt mod---
function AnimalDealerMission:canDeleteActiveMissionTime()
	return false;
end;
---amt mod---

function AnimalDealerMission:getOnOff()
	return g_additionalContractTypes.settings[AnimalDealerMission.NAME.. "_minMax"] > 0;
end;

function AnimalDealerMission:setOnOff(state)
	
end;

function AnimalDealerMission:canMaxNumSeason()
	return AnimalDealerMission.MAX_NUM_SEASON[1] > 0;
end;

function AnimalDealerMission:isMaxNumSeason(canMaxNumSeason)
	if canMaxNumSeason ~= nil and canMaxNumSeason then return AnimalDealerMission:canMaxNumSeason() and AnimalDealerMission.MAX_NUM_SEASON[2] >= AnimalDealerMission.MAX_NUM_SEASON[1];end;
	return AnimalDealerMission.MAX_NUM_SEASON[2] >= AnimalDealerMission.MAX_NUM_SEASON[1];
end;

function AnimalDealerMission:setMaxNumSeason(state)
	AnimalDealerMission.MAX_NUM_SEASON[2] = state or AnimalDealerMission.MAX_NUM_SEASON[2] + 1;	
end;

function AnimalDealerMission:getNextMissionDay()
	return AnimalDealerMission.MAX_NUM_SEASON[3];
end;

function AnimalDealerMission:canBalance()
	return AnimalDealerMission.BALANCE[1];
end;

function AnimalDealerMission:getBalance()
	return AnimalDealerMission.BALANCE[2];
end;

function AnimalDealerMission:setBalance(state)
	AnimalDealerMission.BALANCE[2] = state or AnimalDealerMission.BALANCE[2] + 1;
end;

function AnimalDealerMission:canRandom()
	return AnimalDealerMission.RANDOM;
end;

function AnimalDealerMission:setRandom(state)
	AnimalDealerMission.RANDOM = state or not AnimalDealerMission.RANDOM;
end;

function AnimalDealerMission:onStartMap(args)
	if g_server ~= nil and AdditionalContractTypes.addConsoleCommand and not g_addTestCommands then
		addConsoleCommand("ACMissionGenerate-AD", "Force generating a new mission for animal dealer", "consoleGenerateMission", AnimalDealerAbstractFunction, "typVariant RANDOM|RIDING|RIDINGTRANSPORT|TRANSPORT|DELIVERYANIMALS|DELIVERYFILLTYPES; optional animalName optional numAnimals optional setMaxNumSeason higher [true]"); --; optional animalName COW..|RANDOM [ALL]; optional subTypeName HOLSTEIN.. [ALL]; optional numAnimals [2]; optional setMaxNumSeason higher [true]");			
	end;	
	if g_server ~= nil then 
		AnimalDealerMission:startCheck();	
	end;
end;

function AnimalDealerMission:isOnStartMap(args)
	if not args.isDetiServer and g_currentMission.hlUtils.modLoaded["FS25_MissionsDisplay"] ~= nil then
		if g_currentMission.hlUtils.globalFunction["FS25_MissionsDisplay"].getBuildVersion == nil or g_currentMission.hlUtils.globalFunction["FS25_MissionsDisplay"].getBuildVersion() <= 28 then
			local isAlReadyType = false;
			local isUnknownAlReadyType = false;
			local mdMod = getfenv(0)["FS25_MissionsDisplay"];
			if mdMod ~= nil and mdMod.Missions_Display ~= nil and mdMod.Missions_Display.values ~= nil and mdMod.Missions_Display.values.missionsTypes ~= nil then
				local missionData = g_missionManager:getMissionType(AnimalDealerMission.NAME);
				for missionId, missionsType in pairs(mdMod.Missions_Display.values.missionsTypes) do
					if missionsType.name == "animalDealerMission" and missionsType.modName == "FS25_AdditionalContracts_TFU" then isAlReadyType = true;break;end;
					if missionData ~= nil and missionData.typeId ~= nil and missionData.typeId == missionsType.typId then isUnknownAlReadyType = true;break;end;					
				end;				
				if isUnknownAlReadyType or not isAlReadyType then								
					local isActive = function(args)return missionData.classObject:getOnOff(args);end;
					local changeMaxNumInstance = function(args)return missionData.classObject:getMaxNumInstance(args);end;
					local animalDealerMissionData = {name="animalDealerMission",jobTypName=AnimalDealerMission:getJobTypName(),typId=missionData.typeId,isActive=isActive,changeMaxNumInstance=changeMaxNumInstance, overlay="animalDealer", modName="FS25_AdditionalContracts_TFU", view=true, total=0};
					if isUnknownAlReadyType then
						animalDealerMissionData.view = mdMod.Missions_Display.values.missionsTypes[missionData.typeId].view;				
					end;
					mdMod.Missions_Display.values.missionsTypes[missionData.typeId] = animalDealerMissionData;
				end;
			end;			
		end;
	end;
	
	--if not args.isDetiServer and g_currentMission.hlUtils ~= nil and g_currentMission.hlUtils.colorIntern ~= nil then
	--	g_currentMission.hlUtils.colorIntern.chocolate4 = {139/255,69/255,19/255,1.00};		
	--end;
end;

function AnimalDealerMission:startCheck()	
		
	local placeable = g_additionalContractPlaceableManager:getPlaceableByUniqueId("AnimalDealerMission_SellingStation_1");
			
	if placeable == nil then		
		local animalDealerTriggers = g_additionalContractMapData:getTriggers("animalDealer");
		AnimalDealerMission.ACCEPTS_VARIANT["DELIVERYFILLTYPES"] = animalDealerTriggers ~= nil and #animalDealerTriggers > 0;
	else
		g_additionalContractPlaceableManager:addPlaceable(placeable);
		AnimalDealerMission.ACCEPTS_VARIANT["DELIVERYFILLTYPES"] = true;
	end;		
	
	local hasCorruptMod = g_additionalContractUtils:hasOtherMods("realisticLivestock");
	if not hasCorruptMod then 
		AnimalDealerMission.MODSUPPORT.RealisticLivestock = true;
		function setAllTrue()
			AnimalDealerMission.MODSUPPORT.RealisticLivestockRM.RIDING = true;AnimalDealerMission.MODSUPPORT.RealisticLivestockRM.RIDINGTRANSPORT = true;AnimalDealerMission.MODSUPPORT.RealisticLivestockRM.DELIVERYANIMALS = true;
			--AnimalDealerMission.MODSUPPORT.RealisticLivestockRM.TRANSPORT = false;
		end;
		if g_additionalContractUtils:hasOtherMods("realisticLivestockRM") then
			if g_additionalContractUtils:hasModSupport("FS25_RealisticLivestockRM") then 
				setAllTrue();
			else
				AnimalDealerMission.MODSUPPORT.RealisticLivestockRM.RIDING = g_additionalContractUtils:hasModSupport("FS25_RealisticLivestockRM", "RIDING");
				AnimalDealerMission.MODSUPPORT.RealisticLivestockRM.RIDINGTRANSPORT = g_additionalContractUtils:hasModSupport("FS25_RealisticLivestockRM", "RIDINGTRANSPORT");
				--AnimalDealerMission.MODSUPPORT.RealisticLivestockRM.TRANSPORT = g_additionalContractUtils:hasModSupport("FS25_RealisticLivestockRM", "TRANSPORT");
				AnimalDealerMission.MODSUPPORT.RealisticLivestockRM.DELIVERYANIMALS = g_additionalContractUtils:hasModSupport("FS25_RealisticLivestockRM", "DELIVERYANIMALS");
				if not AnimalDealerMission.MODSUPPORT.RealisticLivestockRM.RIDING or not AnimalDealerMission.MODSUPPORT.RealisticLivestockRM.RIDINGTRANSPORT or not AnimalDealerMission.MODSUPPORT.RealisticLivestockRM.DELIVERYANIMALS then
					Logging.info("** AnimalDealerMission-Contracts supported variants riding:%s | riding transport:%s | transport:%s | delivery animals:%s (by RealsitickLivestockRM Mod) **", 
						AnimalDealerMission.MODSUPPORT.RealisticLivestockRM.RIDING,
						AnimalDealerMission.MODSUPPORT.RealisticLivestockRM.RIDINGTRANSPORT,
						AnimalDealerMission.MODSUPPORT.RealisticLivestockRM.TRANSPORT,
						AnimalDealerMission.MODSUPPORT.RealisticLivestockRM.DELIVERYANIMALS
					);
				end;
			end;
		else
			setAllTrue();
		end;
	else
		Logging.info("** AnimalDealerMission-Contracts supported variants riding:false | riding transport:false | transport:false | delivery animals:false (by RealsitickLivestock Mod) **")
	end;
	if not hasCorruptMod then
		local horseState = g_additionalContractMapData:isAcceptsAnimal("HORSE");
		AnimalDealerMission.ACCEPTS_VARIANT["RIDING"] = horseState and AnimalDealerMission.MODSUPPORT.RealisticLivestockRM.RIDING;
		AnimalDealerMission.ACCEPTS_VARIANT["RIDINGTRANSPORT"] = horseState and AnimalDealerMission.MODSUPPORT.RealisticLivestockRM.RIDINGTRANSPORT;
		AnimalDealerMission.ACCEPTS_VARIANT["DELIVERYANIMALS"] = AnimalDealerMission.MODSUPPORT.RealisticLivestockRM.DELIVERYANIMALS and g_additionalContractMapData:hasDeliveryAnimals();
		--AnimalDealerMission.ACCEPTS_VARIANT["TRANSPORT"] = true and AnimalDealerMission.MODSUPPORT.RealisticLivestockRM.TRANSPORT;
	end;
	AnimalDealerMission:onDayChanged(true);
	
end;

function AnimalDealerMission.loadingPlaceables(placeables, placeableLoadState, farmId)	
	if placeables ~= nil and #placeables > 0 and placeableLoadState == PlaceableLoadingState.OK then		
		for _, placeable in ipairs(placeables) do			
			placeable:setOwnerFarmId(farmId or AccessHandler.NOBODY);					
			AnimalDealerMission:setPlaceableOnLoad(placeable);
			break; --not multiple
		end;
	end;
end;

function AnimalDealerMission:setPlaceableOnLoad(placeable)
	local animalDealerTriggers = g_additionalContractMapData:getTriggers("animalDealer");
	if animalDealerTriggers ~= nil and #animalDealerTriggers > 0 then
		if placeable.spec_sellingStation.sellingStation.stationName ~= nil then 
			local stationName = placeable.spec_sellingStation.sellingStation.stationName.. "-"..  g_i18n:getText("category_storages");
			placeable.spec_sellingStation.sellingStation.stationName = stationName;
		end;		
		placeable:setPose(animalDealerTriggers[1].x, animalDealerTriggers[1].y, animalDealerTriggers[1].z, animalDealerTriggers[1].rx, animalDealerTriggers[1].ry, animalDealerTriggers[1].rz);
		local x, y, z = placeable:getPosition();
		AnimalDealerMission:setPlaceableMarkerTrigger(placeable, x, z);
		--placeable:setPose(nil, y-30, nil);						
	end;
end;

function AnimalDealerMission:setPlaceableMarkerTrigger(placeable, x, z)
	if g_terrainNode ~= nil and placeable ~= nil and placeable.spec_triggerMarkers ~= nil then
		local x, z = x, z;
		if x == nil or z == nil then x, _, z = placeable:getPosition();end;
		if x ~= nil and z ~= nil then
			for _, marker in ipairs(placeable.spec_triggerMarkers.triggerMarkers) do
				if marker.adjustToGround then					
					local mY = getTerrainHeightAtWorldPos(g_terrainNode, x, 0, z) + marker.groundOffset;
					setWorldTranslation(marker.node, x, mY, z);					
				end;
			end;
		end;
	end;
end;

function AnimalDealerMission:loadInit()	
	if AnimalDealerMission.UI_SETTINGS then		
		local isReady = false;
		for key, value in pairs(AnimalDealerMission.uiData.settings) do			
			isReady = g_additionalContractTypes:setUISettings(key, value);
			if not isReady then break;end; 
		end;
		if isReady then
			for _, control in ipairs(AnimalDealerMission.uiData.controls) do
				g_additionalContractTypes:setUIControls(control);			
			end;
		else
			for key, value in pairs(AnimalDealerMission.uiData.settings) do
				g_additionalContractTypes:delUISettings(key);				 
			end;
		end;		
	end;
	local data = g_missionManager:getMissionTypeDataByName(AnimalDealerMission.NAME);
	if data ~= nil then 
		data.activeTypVariant = {};		
	end;	
	local xmlFile = Utils.getFilename("missions/animalDealerMission/missions.xml", AdditionalContracts.modDir);
	if xmlFile ~= nil and xmlFile ~= "" then AnimalDealerMission.MISSIONS_XML = g_additionalContractUtils:loadMissionsXml(xmlFile);end;	
	xmlFile = Utils.getFilename("missionVehicles/animalDealerMissionVehicles.xml", AdditionalContracts.modDir)
	if xmlFile ~= nil and xmlFile ~= "" then g_missionManager:addPendingMissionVehiclesFile(xmlFile, AdditionalContracts.modDir);end;	
end;

function AnimalDealerMission:loadSettingsEvent(mission, connection, x, y, z, viewDistanceCoeff)
	if g_currentMission ~= nil and g_currentMission.missionDynamicInfo ~= nil and g_currentMission.missionDynamicInfo.isMultiplayer then
		g_client:getServerConnection():sendEvent(AnimalDealerLoadSettingsEvent.new());
	end;
end;

function AnimalDealerMission:changeSettingsEvent(settingsId, state)
	if g_currentMission ~= nil and g_currentMission.missionDynamicInfo ~= nil and g_currentMission.missionDynamicInfo.isMultiplayer then
		g_client:getServerConnection():sendEvent(AnimalDealerChangeSettingsEvent.new(settingsId, state));
	else
		self:onChangeSettings(settingsId, state);
	end;
end;

function AnimalDealerMission:onChangeSettings(settingsId, state)
	if settingsId == "animalDealerMission_minMax" then		
		self:setMaxNumInstance(state);		
	end;	
end;

function AnimalDealerMission:loadSettingsXML(xmlFile, prefix) 	
	if not xmlFile:hasProperty(prefix.. ".".. AnimalDealerMission.NAME.. ".minMax") then return;end;	
	local minMax = xmlFile:getInt(prefix.. ".".. AnimalDealerMission.NAME.. ".minMax");
	local control = g_additionalContractTypes:getUIControls(AnimalDealerMission.NAME.. "_minMax");
	if minMax > control.max then minMax = control.max;elseif minMax < control.min then minMax = control.min;end;	
	self:setMaxNumInstance(minMax);	
end;

function AnimalDealerMission:saveSettingsXML(xmlFile, prefix)    
	xmlFile:setInt(prefix.. ".".. AnimalDealerMission.NAME.. ".minMax", g_additionalContractTypes.settings[AnimalDealerMission.NAME.. "_minMax"]);
end;

function AnimalDealerMission:getJobTypName()
	return self.data.jobTypName;
end;

function AnimalDealerMission:getMissionTypes()
	local missionData = g_missionManager:getMissionType(AnimalDealerMission.NAME);
	local isActive = function(args)return missionData.classObject:getOnOff(args);end;
	local changeMaxNumInstance = function(args)return missionData.classObject:getMaxNumInstance(args);end;
	local animalDealerMissionData = {name="animalDealerMission",jobTypName=AnimalDealerMission:getJobTypName(),typId=missionData.typeId,isActive=isActive,changeMaxNumInstance=changeMaxNumInstance, overlay="animalDealer", modName="FS25_AdditionalContracts_TFU", view=true};
	return animalDealerMissionData;
end;

function AnimalDealerMission:getClassTypOverlay(args)
	local classOverlay = "car";
	local typOverlay = "lowAnimalsTrailer";
	if self.objectConfig.classOverlay ~= nil then classOverlay = self.objectConfig.classOverlay;end;
	if self.objectConfig.typOverlay ~= nil then typOverlay = self.objectConfig.typOverlay;end;	
	return classOverlay, typOverlay;
end;

function AnimalDealerMission:onDayChanged(firstStart)
	if not AnimalDealerMission:getOnOff() or g_missionManager:getMissionType(AnimalDealerMission.NAME) == nil then return;end;
	local timeScale = g_currentMission:getEffectiveTimeScale();
	if (timeScale > 0 and timeScale <= 60) or firstStart ~= nil then		
		g_additionalContractMapData:searchAnimalPlaceablesOnFarmId(true);
	end;
	if firstStart == nil then
		local animalDealerTriggers = g_additionalContractMapData:getTriggers("animalDealer");
		AnimalDealerMission.ACCEPTS_VARIANT["DELIVERYFILLTYPES"] = animalDealerTriggers ~= nil and #animalDealerTriggers > 0;
		if AnimalDealerMission.MODSUPPORT.RealisticLivestock and AnimalDealerMission.MODSUPPORT.RealisticLivestockRM.DELIVERYANIMALS then
			AnimalDealerMission.ACCEPTS_VARIANT["DELIVERYANIMALS"] = g_additionalContractMapData:hasDeliveryAnimals();	
		end;
		--if AnimalDealerMission.MODSUPPORT.RealisticLivestock and AnimalDealerMission.MODSUPPORT.RealisticLivestockRM.TRANSPORT then
		--	AnimalDealerMission.ACCEPTS_VARIANT["TRANSPORT"] = true;	
		--end;
	end;	
end;

g_additionalContractTypes:registerTyp(AnimalDealerMission, AnimalDealerMission.NAME, true);