--[[
    TFU6 package-wide delivery destination, accounting and multiplayer safety.

    Patched mission classes:
      * BaleCollectMission          (bale trigger required)
      * FruitCollectMission         (bulk unload trigger required)
      * FieldHeapMission            (bulk unload trigger required)
      * SupplyDeliveryBulkMission   (bulk unload trigger required)

    ChaffMission keeps its dedicated TFU5 crop-progress implementation and
    exposes the same delivery-safety interface from ChaffProgressSafety.lua.
]]

ContractDeliverySafety = ContractDeliverySafety or {};
ContractDeliverySafety.VERSION = 6;
ContractDeliverySafety.SYNC_INTERVAL_MS = 5000;
ContractDeliverySafety.DESTINATION_AUDIT_INTERVAL_MS = 5000;
ContractDeliverySafety.FINISH_VALUE = 0.995;

local function tfuClamp(value, minimum, maximum)
    if math.clamp ~= nil then return math.clamp(value, minimum, maximum);end;
    return math.max(minimum, math.min(maximum, value));
end;

local function tfuGetTimeMs()
    if type(g_time) == "number" then return g_time;end;
    if g_currentMission ~= nil and type(g_currentMission.time) == "number" then return g_currentMission.time;end;
    return 0;
end;

local function tfuConnectionIsServer(connection)
    if connection == nil then return nil;end;
    if connection.getIsServer ~= nil then
        local success, value = pcall(connection.getIsServer, connection);
        if success then return value == true;end;
    end;
    return connection.isServer == true;
end;

local function tfuBitIsSet(mask, flag)
    if mask == nil or flag == nil then return true;end;
    if bitAND ~= nil then return bitAND(mask, flag) ~= 0;end;
    return true;
end;

local function tfuGetMissionId(self)
    return tostring(self.uniqueId or self.activeMissionId or "unknown");
end;

local function tfuGetFieldId(self)
    if self.field ~= nil and self.field.getId ~= nil then
        local success, fieldId = pcall(self.field.getId, self.field);
        if success and fieldId ~= nil then return tostring(fieldId);end;
    end;
    return "none";
end;

local function tfuGetStationName(station)
    if station ~= nil and station.getName ~= nil then
        local success, value = pcall(station.getName, station);
        if success and value ~= nil then return tostring(value);end;
    end;
    local placeable = station ~= nil and station.owningPlaceable or nil;
    if placeable ~= nil and placeable.getName ~= nil then
        local success, value = pcall(placeable.getName, placeable);
        if success and value ~= nil then return tostring(value);end;
    end;
    return "unknown";
end;

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

local function tfuGetRequirements(config)
    if config.requireBales == true then return {requireBales=true};end;
    return {requireBulk=true};
end;

local function tfuIsStationSafe(station, fillTypeIndex, config)
    if g_additionalContractMapData ~= nil and g_additionalContractMapData.isMissionSafeSellingStation ~= nil then
        return g_additionalContractMapData:isMissionSafeSellingStation(station, fillTypeIndex, false, tfuGetRequirements(config));
    end;
    return false;
end;

local function tfuFindSafeStation(fillTypeIndex, config)
    if g_additionalContractMapData == nil or g_additionalContractMapData.getMissionSafeSellingStationWithHighestPrice == nil then return nil, 0;end;
    return g_additionalContractMapData:getMissionSafeSellingStationWithHighestPrice(fillTypeIndex, false, tfuGetRequirements(config));
end;

local function tfuGetSuccessFactor(config)
    local value = config.successFactor;
    if type(value) == "function" then
        local success, result = pcall(value);
        if success then value = result;else value = nil;end;
    end;
    if type(value) ~= "number" or value <= 0 then value = 0.95;end;
    return value;
end;

local function tfuDeliveryComplete(self, config)
    local expected = tonumber(self.expectedLiters) or 0;
    if expected <= 0 then return false;end;
    return (tonumber(self.depositedLiters) or 0) + 0.001 >= expected * tfuGetSuccessFactor(config);
end;

local function tfuMissionWasStarted(self)
    if self.getWasStarted ~= nil then
        local success, value = pcall(self.getWasStarted, self);
        if success then return value == true;end;
    end;
    return self.status == MissionStatus.RUNNING or self.status == MissionStatus.FINISHED;
end;

local function tfuRaiseDeliveryDirty(self)
    if not self.isServer then return;end;
    local dirtyFlag = self.tfuDeliveryDirtyFlag or self.missionDirtyFlag;
    if dirtyFlag ~= nil and self.raiseDirtyFlags ~= nil then self:raiseDirtyFlags(dirtyFlag);end;
end;

local function tfuRemoveSellingStationHotspot(self)
    if self.sellingStationMapHotspot == nil then return;end;
    if self.mapHotspots ~= nil and table.removeElement ~= nil then table.removeElement(self.mapHotspots, self.sellingStationMapHotspot);end;
    if g_currentMission ~= nil and g_currentMission.removeMapHotspot ~= nil then
        pcall(g_currentMission.removeMapHotspot, g_currentMission, self.sellingStationMapHotspot);
    end;
    if self.sellingStationMapHotspot.delete ~= nil then self.sellingStationMapHotspot:delete();end;
    self.sellingStationMapHotspot = nil;
end;

local function tfuGetStationSaveIds(station)
    local placeable = station ~= nil and station.owningPlaceable or nil;
    if placeable == nil or g_currentMission == nil or g_currentMission.storageSystem == nil then return nil, nil;end;
    local uniqueId = nil;
    if placeable.getUniqueId ~= nil then
        local success, value = pcall(placeable.getUniqueId, placeable);
        if success then uniqueId = value;end;
    end;
    local index = nil;
    if g_currentMission.storageSystem.getPlaceableUnloadingStationIndex ~= nil then
        local success, value = pcall(g_currentMission.storageSystem.getPlaceableUnloadingStationIndex, g_currentMission.storageSystem, placeable, station);
        if success then index = value;end;
    end;
    return uniqueId, index;
end;

local function tfuSetSavedStationIds(self, station)
    local uniqueId, index = tfuGetStationSaveIds(station);
    if uniqueId == nil or index == nil then return false;end;
    self.sellingStationPlaceableUniqueId = uniqueId;
    self.unloadingStationIndex = index;
    return true;
end;

local function tfuUpdateStationSaveIds(self)
    if self.sellingStation ~= nil then tfuSetSavedStationIds(self, self.sellingStation);end;
end;

local function tfuResolveSavedStation(self)
    if self.sellingStation ~= nil then return self.sellingStation;end;
    if g_currentMission == nil or g_currentMission.placeableSystem == nil or g_currentMission.storageSystem == nil then return nil;end;
    if self.sellingStationPlaceableUniqueId == nil or self.unloadingStationIndex == nil then return nil;end;
    local placeable = g_currentMission.placeableSystem:getPlaceableByUniqueId(self.sellingStationPlaceableUniqueId);
    if placeable == nil then return nil;end;
    return g_currentMission.storageSystem:getPlaceableUnloadingStation(placeable, self.unloadingStationIndex);
end;

local function tfuReplaceSellingStation(self, newStation, config)
    if newStation == nil then return false;end;
    local oldStation = self.sellingStation;
    if oldStation == newStation then
        tfuUpdateStationSaveIds(self);
        self.tfuDestinationBlocked = false;
        return true;
    end;
    if oldStation ~= nil and oldStation.missions ~= nil then oldStation.missions[self] = nil;end;
    tfuRemoveSellingStationHotspot(self);
    self.sellingStation = nil;
    self.pendingSellingStationId = nil;
    if self.setSellingStation ~= nil then self:setSellingStation(newStation);end;
    if self.sellingStation ~= newStation then return false;end;
    tfuUpdateStationSaveIds(self);
    if self.isServer and self.status == MissionStatus.RUNNING and not tfuDeliveryComplete(self, config) then
        newStation.missions = newStation.missions or {};
        newStation.missions[self] = self;
    end;
    self.tfuDestinationBlocked = false;
    tfuRaiseDeliveryDirty(self);
    return true;
end;

local function tfuPreflightSavedDestination(self, config)
    if not self.isServer or self:getIsFinished() or self.fillTypeIndex == nil then return;end;
    local savedStation = tfuResolveSavedStation(self);
    if tfuIsStationSafe(savedStation, self.fillTypeIndex, config) then return;end;

    -- Unaccepted/CREATED missions are deleted rather than silently changing the
    -- price/reward target that was used when they were generated. New missions
    -- are generated safely by TFU6.
    if not tfuMissionWasStarted(self) then
        self.tfuDeleteForUnsafeDestination = true;
        return;
    end;

    local safeStation = tfuFindSafeStation(self.fillTypeIndex, config);
    if safeStation ~= nil and tfuSetSavedStationIds(self, safeStation) then
        Logging.info(
            "Additional Contracts | TFU saved destination migrated: type=%s mission=%s field=%s oldOwner=%s newOwner=%s newStation='%s'",
            config.name,
            tfuGetMissionId(self),
            tfuGetFieldId(self),
            tostring(tfuGetStationOwnerFarmId(savedStation)),
            tostring(tfuGetStationOwnerFarmId(safeStation)),
            tfuGetStationName(safeStation)
        );
    end;
end;

local function tfuEnsureSafeDestination(self, config, reason)
    if not self.isServer or self:getIsFinished() then return true;end;
    if self.fillTypeIndex == nil then return false;end;
    if tfuIsStationSafe(self.sellingStation, self.fillTypeIndex, config) then
        self.tfuDestinationBlocked = false;
        tfuUpdateStationSaveIds(self);
        if self.status == MissionStatus.RUNNING and not tfuDeliveryComplete(self, config) then
            self.sellingStation.missions = self.sellingStation.missions or {};
            self.sellingStation.missions[self] = self;
        end;
        return true;
    end;

    local oldStation = self.sellingStation;
    local oldOwner = tfuGetStationOwnerFarmId(oldStation);
    local safeStation = tfuFindSafeStation(self.fillTypeIndex, config);
    if safeStation ~= nil and tfuReplaceSellingStation(self, safeStation, config) then
        Logging.info(
            "Additional Contracts | TFU delivery destination migrated: type=%s mission=%s farm=%s field=%s reason=%s oldOwner=%s oldStation='%s' newOwner=%s newStation='%s'",
            config.name,
            tfuGetMissionId(self),
            tostring(self.farmId),
            tfuGetFieldId(self),
            tostring(reason or "audit"),
            tostring(oldOwner),
            tfuGetStationName(oldStation),
            tostring(tfuGetStationOwnerFarmId(safeStation)),
            tfuGetStationName(safeStation)
        );
        return true;
    end;

    if oldStation ~= nil then
        oldStation.missions = oldStation.missions or {};
        if tfuMissionWasStarted(self) then
            -- Keep the blocked mission registered at its old station so the
            -- SellingStation interceptor can return zero and preserve goods.
            oldStation.missions[self] = self;
        else
            oldStation.missions[self] = nil;
        end;
    end;
    self.tfuDestinationBlocked = true;
    tfuRaiseDeliveryDirty(self);
    if not tfuMissionWasStarted(self) and not self.tfuMarkedForDeletion then
        self.tfuMarkedForDeletion = true;
        if g_missionManager ~= nil and g_missionManager.markMissionForDeletion ~= nil then
            pcall(g_missionManager.markMissionForDeletion, g_missionManager, self);
        end;
    end;
    local now = tfuGetTimeMs();
    if now >= (self.tfuNextDestinationErrorTime or 0) then
        self.tfuNextDestinationErrorTime = now + 30000;
        Logging.error(
            "Additional Contracts | TFU delivery blocked: type=%s mission=%s farm=%s field=%s reason=%s oldOwner=%s station='%s'; no neutral compatible mission destination exists",
            config.name,
            tfuGetMissionId(self),
            tostring(self.farmId),
            tfuGetFieldId(self),
            tostring(reason or "audit"),
            tostring(oldOwner),
            tfuGetStationName(oldStation)
        );
    end;
    return false;
end;

local function tfuGetAuthoritativeCompletion(self, config)
    local success, completion = pcall(config.previousGetCompletion, self);
    if not success or type(completion) ~= "number" then completion = tonumber(self.completion) or 0;end;
    return tfuClamp(completion, 0, 1);
end;

local function tfuWriteDeliveryState(streamId, self, includeStation)
    local writeStation = includeStation == true and self.sellingStation ~= nil;
    streamWriteBool(streamId, writeStation);
    if writeStation then NetworkUtil.writeNodeObject(streamId, self.sellingStation);end;
    streamWriteFloat32(streamId, tonumber(self.expectedLiters) or 0);
    streamWriteFloat32(streamId, tonumber(self.depositedLiters) or 0);
    streamWriteFloat32(streamId, tonumber(self.completion or self.tfuSyncedCompletion) or 0);
    streamWriteFloat32(streamId, tonumber(self.fieldPercentageDone or self.tfuSyncedFieldCompletion) or 0);
    streamWriteBool(streamId, self.tfuDestinationBlocked == true);
end;

local function tfuReadDeliveryState(streamId, self, config)
    if streamReadBool(streamId) then
        local station = NetworkUtil.readNodeObject(streamId);
        if station ~= nil and station ~= self.sellingStation then tfuReplaceSellingStation(self, station, config);end;
    end;
    self.expectedLiters = math.max(streamReadFloat32(streamId), 0);
    self.depositedLiters = math.max(streamReadFloat32(streamId), 0);
    self.tfuSyncedCompletion = tfuClamp(streamReadFloat32(streamId), 0, 1);
    self.tfuSyncedFieldCompletion = tfuClamp(streamReadFloat32(streamId), 0, 1);
    self.tfuDestinationBlocked = streamReadBool(streamId);
    self.completion = self.tfuSyncedCompletion;
    self.fieldPercentageDone = self.tfuSyncedFieldCompletion;
end;

local function tfuLogDeliveryProgress(self, config, fillDelta, force)
    local expected = math.max(tonumber(self.expectedLiters) or 0, 0);
    local deposited = math.max(tonumber(self.depositedLiters) or 0, 0);
    local completion = tonumber(self.completion) or 0;
    local bucket = math.floor(tfuClamp(completion, 0, 1) * 20 + 0.0001);
    if force or self.tfuLastDeliveryLogBucket ~= bucket then
        self.tfuLastDeliveryLogBucket = bucket;
        Logging.info(
            "Additional Contracts | TFU delivery credited: type=%s mission=%s farm=%s field=%s stationOwner=%s station='%s' delta=%.1f deposited=%.1f/%.1f required=%.1f completion=%.2f%%",
            config.name,
            tfuGetMissionId(self),
            tostring(self.farmId),
            tfuGetFieldId(self),
            tostring(tfuGetStationOwnerFarmId(self.sellingStation)),
            tfuGetStationName(self.sellingStation),
            fillDelta or 0,
            deposited,
            expected,
            expected * tfuGetSuccessFactor(config),
            completion * 100
        );
    end;
end;

local function tfuPatchDeliveryMission(classObject, config)
    if classObject == nil or classObject.TFU_DELIVERY_SAFETY_PATCHED == true then return;end;
    classObject.TFU_DELIVERY_SAFETY_PATCHED = true;
    config.classObject = classObject;
    config.previousGetCompletion = classObject.getCompletion;

    local previousNew = classObject.new;
    function classObject.new(isServer, isClient, customMt)
        local self = previousNew(isServer, isClient, customMt);
        self.tfuIsDeliveryMission = true;
        self.tfuDeliveryDirtyFlag = self.getNextDirtyFlag ~= nil and self:getNextDirtyFlag() or self.missionDirtyFlag;
        self.tfuSyncedCompletion = nil;
        self.tfuSyncedFieldCompletion = nil;
        self.tfuDestinationBlocked = false;
        self.tfuDeleteForUnsafeDestination = false;
        self.tfuNextDeliverySyncTime = 0;
        self.tfuNextDestinationAuditTime = 0;
        self.tfuNextDestinationErrorTime = 0;
        self.tfuLastDeliveryLogBucket = -1;
        if config.syntheticDeposit == true and self.depositedLiters == 10 then self.depositedLiters = 0;end;
        return self;
    end;

    function classObject:getTFUDeliveryRequirements()
        return tfuGetRequirements(config);
    end;

    function classObject:getDeliveryRequirements()
        return tfuGetRequirements(config);
    end;

    function classObject:isTFUDestinationSafe(station, fillTypeIndex)
        return tfuIsStationSafe(station or self.sellingStation, fillTypeIndex or self.fillTypeIndex, config);
    end;

    function classObject:isDeliveryDestinationSafe(station, fillTypeIndex)
        return tfuIsStationSafe(station or self.sellingStation, fillTypeIndex or self.fillTypeIndex, config);
    end;

    function classObject:getDeliverySuccessFactor()
        return tfuGetSuccessFactor(config);
    end;

    function classObject:acceptsMissionFillType(fillTypeIndex)
        return self.fillTypeIndex ~= nil and self.fillTypeIndex == fillTypeIndex;
    end;

    local previousWriteStream = classObject.writeStream;
    function classObject:writeStream(streamId, connection)
        previousWriteStream(self, streamId, connection);
        tfuWriteDeliveryState(streamId, self, true);
    end;

    local previousReadStream = classObject.readStream;
    function classObject:readStream(streamId, connection)
        previousReadStream(self, streamId, connection);
        tfuReadDeliveryState(streamId, self, config);
    end;

    local classSuper = classObject:superClass();
    local previousWriteUpdateStream = classObject.writeUpdateStream or (classSuper ~= nil and classSuper.writeUpdateStream or nil);
    function classObject:writeUpdateStream(streamId, connection, dirtyMask)
        if previousWriteUpdateStream ~= nil then previousWriteUpdateStream(self, streamId, connection, dirtyMask);end;
        if tfuConnectionIsServer(connection) ~= true then
            local writeState = tfuBitIsSet(dirtyMask, self.tfuDeliveryDirtyFlag or self.missionDirtyFlag);
            streamWriteBool(streamId, writeState);
            if writeState then tfuWriteDeliveryState(streamId, self, true);end;
        end;
    end;

    local previousReadUpdateStream = classObject.readUpdateStream or (classSuper ~= nil and classSuper.readUpdateStream or nil);
    function classObject:readUpdateStream(streamId, timestamp, connection)
        if previousReadUpdateStream ~= nil then previousReadUpdateStream(self, streamId, timestamp, connection);end;
        if tfuConnectionIsServer(connection) ~= false and streamReadBool(streamId) then
            tfuReadDeliveryState(streamId, self, config);
        end;
    end;

    function classObject:getCompletion()
        if not self.isServer and type(self.tfuSyncedCompletion) == "number" then return self.tfuSyncedCompletion;end;
        return config.previousGetCompletion(self);
    end;

    local previousOnSavegameLoaded = classObject.onSavegameLoaded;
    function classObject:onSavegameLoaded()
        tfuPreflightSavedDestination(self, config);
        previousOnSavegameLoaded(self);
        if not self.isServer or self:getIsFinished() then return;end;
        if config.syntheticDeposit == true and self.depositedLiters == 10 then self.depositedLiters = 0;end;
        if self.tfuDeleteForUnsafeDestination == true then
            if self.sellingStation ~= nil and self.sellingStation.missions ~= nil then self.sellingStation.missions[self] = nil;end;
            Logging.warning("Additional Contracts | Removed unstarted unsafe delivery mission: type=%s mission=%s field=%s", config.name, tfuGetMissionId(self), tfuGetFieldId(self));
            g_missionManager:markMissionForDeletion(self);
            return;
        end;
        tfuEnsureSafeDestination(self, config, "savegameLoad");
        self.completion = tfuGetAuthoritativeCompletion(self, config);
        self.tfuSyncedCompletion = self.completion;
        self.tfuSyncedFieldCompletion = tfuClamp(tonumber(self.fieldPercentageDone) or 0, 0, 1);
        tfuRaiseDeliveryDirty(self);
    end;

    local previousStart = classObject.start;
    function classObject:start(spawnVehicles)
        if self.isServer and not tfuEnsureSafeDestination(self, config, "missionStart") then return false;end;
        local success = previousStart(self, spawnVehicles);
        if success and self.isServer then
            self.completion = tfuGetAuthoritativeCompletion(self, config);
            self.tfuSyncedCompletion = self.completion;
            self.tfuSyncedFieldCompletion = tfuClamp(tonumber(self.fieldPercentageDone) or 0, 0, 1);
            tfuRaiseDeliveryDirty(self);
        end;
        return success;
    end;

    if classObject.finishedPreparing ~= nil then
        local previousFinishedPreparing = classObject.finishedPreparing;
        function classObject:finishedPreparing()
            previousFinishedPreparing(self);
            if self.isServer and not self:getIsFinished() then
                self.completion = tfuGetAuthoritativeCompletion(self, config);
                self.tfuSyncedCompletion = self.completion;
                self.tfuSyncedFieldCompletion = tfuClamp(tonumber(self.fieldPercentageDone) or 0, 0, 1);
                tfuRaiseDeliveryDirty(self);
            end;
        end;
    end;

    function classObject:fillSold(fillDelta, fillTypeIndex)
        if type(fillDelta) ~= "number" or fillDelta <= 0 then return 0;end;
        if fillTypeIndex ~= nil and self.fillTypeIndex ~= nil and fillTypeIndex ~= self.fillTypeIndex then return 0;end;
        if self.status ~= MissionStatus.RUNNING or self.tfuDestinationBlocked == true then return 0;end;
        if self.isServer and not tfuIsStationSafe(self.sellingStation, self.fillTypeIndex, config) then
            tfuEnsureSafeDestination(self, config, "deliveryAttempt");
            if not tfuIsStationSafe(self.sellingStation, self.fillTypeIndex, config) then return 0;end;
        end;

        local expected = math.max(tonumber(self.expectedLiters) or 0, 0);
        if expected <= 0 then return 0;end;
        local before = math.max(tonumber(self.depositedLiters) or 0, 0);
        self.depositedLiters = math.min(before + fillDelta, expected);
        local credited = self.depositedLiters - before;
        if credited <= 0 then return 0;end;

        local required = expected * tfuGetSuccessFactor(config);
        if self.sellingStation ~= nil then
            self.sellingStation.missions = self.sellingStation.missions or {};
            if self.depositedLiters + 0.001 >= required then
                self.sellingStation.missions[self] = nil;
            else
                self.sellingStation.missions[self] = self;
            end;
        end;
        self.lastSellChange = 30;
        if self.isServer then
            self.completion = tfuGetAuthoritativeCompletion(self, config);
            self.tfuSyncedCompletion = self.completion;
            self.tfuSyncedFieldCompletion = tfuClamp(tonumber(self.fieldPercentageDone) or 0, 0, 1);
            tfuRaiseDeliveryDirty(self);
            tfuLogDeliveryProgress(self, config, credited, self.depositedLiters + 0.001 >= required);
        end;
        return credited;
    end;

    local previousUpdate = classObject.update;
    function classObject:update(dt)
        previousUpdate(self, dt);
        if not self.isServer or self.status ~= MissionStatus.RUNNING then return;end;

        local now = tfuGetTimeMs();
        if now >= (self.tfuNextDestinationAuditTime or 0) then
            self.tfuNextDestinationAuditTime = now + ContractDeliverySafety.DESTINATION_AUDIT_INTERVAL_MS;
            tfuEnsureSafeDestination(self, config, "runtimeAudit");
        end;

        self.completion = tfuGetAuthoritativeCompletion(self, config);
        self.tfuSyncedCompletion = self.completion;
        self.tfuSyncedFieldCompletion = tfuClamp(tonumber(self.fieldPercentageDone) or 0, 0, 1);

        if now >= (self.tfuNextDeliverySyncTime or 0) then
            self.tfuNextDeliverySyncTime = now + ContractDeliverySafety.SYNC_INTERVAL_MS;
            tfuRaiseDeliveryDirty(self);
        end;

        if self.completion >= ContractDeliverySafety.FINISH_VALUE and self.status == MissionStatus.RUNNING then
            Logging.info(
                "Additional Contracts | TFU delivery mission completed: type=%s mission=%s farm=%s field=%s deposited=%.1f/%.1f completion=%.2f%%",
                config.name,
                tfuGetMissionId(self),
                tostring(self.farmId),
                tfuGetFieldId(self),
                tonumber(self.depositedLiters) or 0,
                tonumber(self.expectedLiters) or 0,
                self.completion * 100
            );
            self:finish(MissionFinishState.SUCCESS);
        end;
    end;

    Logging.info("Additional Contracts | Installed TFU delivery safety/tracking for %s", config.name);
end;

-- Each class keeps its original field progress and reward calculation. TFU6
-- standardizes only destination ownership/capability, delivery accounting,
-- multiplayer synchronization and exact mission-specific delivery thresholds.
tfuPatchDeliveryMission(BaleCollectMission, {
    name="baleCollectMission",
    requireBales=true,
    syntheticDeposit=true,
    successFactor=function() return BaleCollectMission.data.ownTable.SUCCESS_FACTOR;end
});

tfuPatchDeliveryMission(FruitCollectMission, {
    name="fruitCollectMission",
    requireBales=false,
    successFactor=function() return FruitCollectMission.data.ownTable.SUCCESS_FACTOR;end
});

tfuPatchDeliveryMission(FieldHeapMission, {
    name="fieldHeapMission",
    requireBales=false,
    successFactor=function() return FieldHeapMission.data.ownTable.SUCCESS_FACTOR;end
});

tfuPatchDeliveryMission(SupplyDeliveryBulkMission, {
    name="supplyDeliveryBulkMission",
    requireBales=false,
    syntheticDeposit=true,
    successFactor=function() return SupplyDeliveryBulkMission.data.ownTable.SUCCESS_FACTOR;end
});

Logging.info("Additional Contracts | Installed package-wide neutral sell-point and delivery tracking safety v%s", tostring(ContractDeliverySafety.VERSION));
