--[[
    TFU contract settings safety (TFU7)

    The per-contract maximum is authoritative on the server. A value of 0
    disables generation immediately and removes only unaccepted CREATED offers.
    PREPARING, RUNNING and FINISHED contracts are preserved.
]]

ContractSettingsSafety = ContractSettingsSafety or {}

ContractSettingsSafety.VERSION = "TFU7"
ContractSettingsSafety.AUDIT_INTERVAL_MS = 2000
ContractSettingsSafety.TYPE_ORDER = {
    "baleCollectMission",
    "chaffMission",
    "mulcherMission",
    "rollerMission",
    "fruitCollectMission",
    "fieldHeapMission",
    "animalDealerMission",
    "universalMission"
}
ContractSettingsSafety.SETTING_ORDER = {
    "additionalContract_successFactor",
    "baleCollectMission_minMax",
    "chaffMission_minMax",
    "mulcherMission_minMax",
    "rollerMission_minMax",
    "fruitCollectMission_minMax",
    "fieldHeapMission_minMax",
    "animalDealerMission_minMax",
    "universalMission_minMax"
}
ContractSettingsSafety.TYPE_BY_SETTING = {}
ContractSettingsSafety.SETTING_INDEX = {}
ContractSettingsSafety.originalSetters = {}
ContractSettingsSafety.originalCanRun = {}
ContractSettingsSafety.originalGenerators = {}
ContractSettingsSafety.authoritative = {}
ContractSettingsSafety.pendingDelete = setmetatable({}, {__mode = "k"})
ContractSettingsSafety.auditTimer = 0
ContractSettingsSafety.startupComplete = false
ContractSettingsSafety.installed = false

for index, settingId in ipairs(ContractSettingsSafety.SETTING_ORDER) do
    ContractSettingsSafety.SETTING_INDEX[settingId] = index
end
for _, typName in ipairs(ContractSettingsSafety.TYPE_ORDER) do
    ContractSettingsSafety.TYPE_BY_SETTING[typName .. "_minMax"] = typName
end

local function tfuRound(value)
    value = tonumber(value) or 0
    if value >= 0 then
        return math.floor(value + 0.5)
    end
    return math.ceil(value - 0.5)
end

local function tfuConnectionIsServer(connection)
    return connection ~= nil and connection.getIsServer ~= nil and connection:getIsServer()
end

function ContractSettingsSafety:getClass(typName)
    if g_additionalContractTypes == nil or typName == nil or g_additionalContractTypes.getTyp == nil then
        return nil
    end
    return g_additionalContractTypes:getTyp(typName)
end

function ContractSettingsSafety:isManagedType(typName)
    return self:getClass(typName) ~= nil
end

function ContractSettingsSafety:isKnownSetting(settingId)
    if settingId == "additionalContract_successFactor" then return true end
    local typName = self.TYPE_BY_SETTING[settingId]
    return typName ~= nil and self:isManagedType(typName)
end

function ContractSettingsSafety:getControl(settingId)
    if g_additionalContractTypes == nil or settingId == nil or g_additionalContractTypes.getUIControls == nil then
        return nil
    end
    return g_additionalContractTypes:getUIControls(settingId)
end

function ContractSettingsSafety:normalizeSetting(settingId, value)
    if not self:isKnownSetting(settingId) then return nil end

    local normalized = tonumber(value)
    if normalized == nil and g_additionalContractTypes ~= nil and g_additionalContractTypes.settings ~= nil then
        normalized = tonumber(g_additionalContractTypes.settings[settingId])
    end

    if normalized == nil then
        if settingId == "additionalContract_successFactor" then
            normalized = AdditionalContractTypes.SUCCESS_FACTOR[1]
        else
            local classObject = self:getClass(self.TYPE_BY_SETTING[settingId])
            normalized = classObject ~= nil and tonumber(classObject.DEFAULT_NUM_INSTANCE) or 0
        end
    end
    normalized = tfuRound(normalized)

    local minimum
    local maximum
    local control = self:getControl(settingId)
    if control ~= nil then
        minimum = tonumber(control.min)
        maximum = tonumber(control.max)
    elseif settingId == "additionalContract_successFactor" then
        minimum = tonumber(AdditionalContractTypes.SUCCESS_FACTOR[2]) or 0
        maximum = tonumber(AdditionalContractTypes.SUCCESS_FACTOR[3]) or 100
    else
        local classObject = self:getClass(self.TYPE_BY_SETTING[settingId])
        minimum = 0
        maximum = classObject ~= nil and tonumber(classObject.MAX_NUM_INSTANCE) or 255
    end

    if minimum ~= nil then normalized = math.max(minimum, normalized) end
    if maximum ~= nil then normalized = math.min(maximum, normalized) end
    return normalized
end

function ContractSettingsSafety:captureCurrentSettings(overwrite)
    if g_additionalContractTypes == nil or g_additionalContractTypes.settings == nil then return end
    for _, settingId in ipairs(self.SETTING_ORDER) do
        if self:isKnownSetting(settingId) and (overwrite or self.authoritative[settingId] == nil) then
            local value = self:normalizeSetting(settingId, g_additionalContractTypes.settings[settingId])
            if value ~= nil then self.authoritative[settingId] = value end
        end
    end
end

function ContractSettingsSafety:getAuthoritativeValue(settingId)
    if not self:isKnownSetting(settingId) then return nil end
    local value = self.authoritative[settingId]
    if value == nil then
        value = self:normalizeSetting(settingId, nil)
        self.authoritative[settingId] = value
    end
    return value
end

function ContractSettingsSafety:getLimit(typName)
    if not self:isManagedType(typName) then return nil end
    return self:getAuthoritativeValue(typName .. "_minMax")
end

function ContractSettingsSafety:isEnabled(typName)
    local limit = self:getLimit(typName)
    return limit ~= nil and limit > 0
end

function ContractSettingsSafety:getMissionRootType(mission)
    if mission == nil then return nil end

    local candidates = {}
    if type(mission.type) == "table" then table.insert(candidates, mission.type.name) end
    if type(mission.missionType) == "table" then table.insert(candidates, mission.missionType.name) end
    table.insert(candidates, mission.typeName)

    if mission.getMissionTypeName ~= nil then
        local ok, typName = pcall(mission.getMissionTypeName, mission)
        if ok then table.insert(candidates, typName) end
    end

    for _, typName in ipairs(candidates) do
        if typName ~= nil and self:isManagedType(typName) then return typName end
    end
    return nil
end

function ContractSettingsSafety:getMissions()
    if g_missionManager == nil then return {} end
    if g_missionManager.getMissions ~= nil then
        local ok, missions = pcall(g_missionManager.getMissions, g_missionManager)
        if ok and missions ~= nil then return missions end
    end
    return g_missionManager.missions or {}
end

function ContractSettingsSafety:getMissionUniqueId(mission)
    if mission ~= nil and mission.getUniqueId ~= nil then
        local ok, value = pcall(mission.getUniqueId, mission)
        if ok and value ~= nil then return tostring(value) end
    end
    return "pending"
end

function ContractSettingsSafety:isCreatedMission(mission)
    if mission == nil then return false end
    if MissionStatus ~= nil and MissionStatus.CREATED ~= nil then
        return mission.status == MissionStatus.CREATED
    end
    if mission.getWasStarted ~= nil then
        local ok, started = pcall(mission.getWasStarted, mission)
        if ok then return not started end
    end
    return mission.farmId == nil
end

function ContractSettingsSafety:markCreatedForDeletion(mission, typName, reason)
    if mission == nil or self.pendingDelete[mission] or mission.tfuSettingsDeletionPending == true then return false end
    if not self:isCreatedMission(mission) then return false end
    if g_missionManager == nil then return false end

    self.pendingDelete[mission] = true
    mission.tfuSettingsDeletionPending = true

    if g_missionManager.markMissionForDeletion ~= nil then
        g_missionManager:markMissionForDeletion(mission)
    elseif g_missionManager.missionsToDelete ~= nil then
        if table.addElement ~= nil then
            table.addElement(g_missionManager.missionsToDelete, mission)
        else
            table.insert(g_missionManager.missionsToDelete, mission)
        end
    else
        self.pendingDelete[mission] = nil
        mission.tfuSettingsDeletionPending = nil
        return false
    end

    Logging.info(
        "Additional Contracts | TFU unaccepted offer removed: type=%s mission=%s reason=%s",
        tostring(typName),
        self:getMissionUniqueId(mission),
        tostring(reason or "settings")
    )
    return true
end

function ContractSettingsSafety:getTypeInstanceCount(typName)
    local count = 0
    for _, mission in pairs(self:getMissions()) do
        if mission ~= nil and not self.pendingDelete[mission] and self:getMissionRootType(mission) == typName then
            count = count + 1
        end
    end
    return count
end

function ContractSettingsSafety:reconcileType(typName, reason)
    if g_server == nil or g_missionManager == nil or not self:isManagedType(typName) then return 0 end

    local limit = self:getLimit(typName) or 0
    local protectedCount = 0
    local created = {}

    for _, mission in pairs(self:getMissions()) do
        if mission ~= nil and not self.pendingDelete[mission] and self:getMissionRootType(mission) == typName then
            if self:isCreatedMission(mission) then
                table.insert(created, mission)
            else
                -- Never remove PREPARING, RUNNING or FINISHED mission objects.
                protectedCount = protectedCount + 1
            end
        end
    end

    table.sort(created, function(a, b)
        local aTime = tonumber(a.generationTime) or 0
        local bTime = tonumber(b.generationTime) or 0
        if aTime == bTime then
            return self:getMissionUniqueId(a) < self:getMissionUniqueId(b)
        end
        return aTime < bTime
    end)

    local allowedCreated = math.max(0, limit - protectedCount)
    local removed = 0
    for index = allowedCreated + 1, #created do
        if self:markCreatedForDeletion(created[index], typName, reason) then
            removed = removed + 1
        end
    end

    if removed > 0 and g_missionManager.generationTimer ~= nil then
        g_missionManager.generationTimer = 0
    end
    return removed
end

function ContractSettingsSafety:forceLocalValue(settingId, value)
    if g_additionalContractTypes == nil or g_additionalContractTypes.settings == nil then return nil end
    local normalized = self:normalizeSetting(settingId, value)
    if normalized == nil then return nil end

    if settingId == "additionalContract_successFactor" then
        g_additionalContractTypes:setSuccessFactor(normalized)
        g_additionalContractTypes.settings[settingId] = normalized
        if AdditionalContractTypes.uiData ~= nil and AdditionalContractTypes.uiData.settings ~= nil then
            AdditionalContractTypes.uiData.settings[settingId] = normalized
        end
        return normalized
    end

    local typName = self.TYPE_BY_SETTING[settingId]
    local classObject = self:getClass(typName)
    if classObject == nil then return nil end

    local originalSetter = self.originalSetters[typName]
    if originalSetter ~= nil then
        originalSetter(classObject, normalized)
    end

    g_additionalContractTypes.settings[settingId] = normalized
    if classObject.uiData ~= nil and classObject.uiData.settings ~= nil then
        classObject.uiData.settings[settingId] = normalized
    end

    if g_missionManager ~= nil and g_missionManager.getMissionTypeDataByName ~= nil then
        local data = g_missionManager:getMissionTypeDataByName(typName)
        if data ~= nil then data.maxNumInstances = normalized end
    end
    return normalized
end

function ContractSettingsSafety:saveNow(reason)
    if g_server == nil or g_currentMission == nil or g_additionalContractTypes == nil then return false end
    if g_currentMission.missionInfo == nil or g_currentMission.missionInfo.savegameDirectory == nil then return false end

    local ok, err = pcall(g_additionalContractTypes.saveSettingsXML, g_additionalContractTypes, g_currentMission)
    if not ok then
        Logging.warning("Additional Contracts | TFU settings save failed: %s", tostring(err))
        return false
    end
    Logging.info("Additional Contracts | TFU settings saved (%s)", tostring(reason or "settingChange"))
    return true
end

function ContractSettingsSafety:applySetting(settingId, value, reason, persist)
    if not self:isKnownSetting(settingId) then return nil end

    local normalized = self:normalizeSetting(settingId, value)
    if normalized == nil then return nil end
    local previous = self:getAuthoritativeValue(settingId)

    self.authoritative[settingId] = normalized
    self:forceLocalValue(settingId, normalized)

    local typName = self.TYPE_BY_SETTING[settingId]
    if g_server ~= nil and self.startupComplete then
        if typName ~= nil then self:reconcileType(typName, reason or "settingChange") end

        if previous ~= normalized then
            Logging.info(
                "Additional Contracts | TFU setting applied: %s=%d%s",
                tostring(settingId),
                normalized,
                typName ~= nil and normalized == 0 and " (disabled)" or ""
            )
        end

        if persist ~= false then self:saveNow(reason) end
    end
    return normalized
end

function ContractSettingsSafety:enforceType(typName, reason)
    if not self:isManagedType(typName) then return end
    local settingId = typName .. "_minMax"
    local limit = self:getAuthoritativeValue(settingId)
    if limit == nil then return end

    local current = g_additionalContractTypes.settings ~= nil and tonumber(g_additionalContractTypes.settings[settingId]) or nil
    local data = g_missionManager ~= nil and g_missionManager.getMissionTypeDataByName ~= nil
        and g_missionManager:getMissionTypeDataByName(typName) or nil

    if current ~= limit or (data ~= nil and data.maxNumInstances ~= limit) then
        self:forceLocalValue(settingId, limit)
        Logging.info("Additional Contracts | TFU restored authoritative setting: %s=%d", settingId, limit)
    end
    self:reconcileType(typName, reason)
end

function ContractSettingsSafety:enforceAll(reason)
    if g_server == nil then return end

    local successFactor = self:getAuthoritativeValue("additionalContract_successFactor")
    if successFactor ~= nil then
        local current = tonumber(g_additionalContractTypes.settings["additionalContract_successFactor"])
        if current ~= successFactor then
            self:forceLocalValue("additionalContract_successFactor", successFactor)
        end
    end

    for _, typName in ipairs(self.TYPE_ORDER) do
        self:enforceType(typName, reason)
    end
end

function ContractSettingsSafety:getSyncValues()
    local values = {}
    for index, settingId in ipairs(self.SETTING_ORDER) do
        local value = self:getAuthoritativeValue(settingId) or 0
        values[index] = value
        values[settingId] = value -- named mirror for diagnostics and compatibility
    end
    return values
end

function ContractSettingsSafety:applySyncValues(values)
    if values == nil then return end
    for index, settingId in ipairs(self.SETTING_ORDER) do
        local value = values[index]
        if value ~= nil and self:isKnownSetting(settingId) then
            local normalized = self:normalizeSetting(settingId, value)
            if normalized ~= nil then
                self.authoritative[settingId] = normalized
                self:forceLocalValue(settingId, normalized)
            end
        end
    end
end

function ContractSettingsSafety:getSnapshotText()
    local values = {}
    for _, settingId in ipairs(self.SETTING_ORDER) do
        if self:isKnownSetting(settingId) then
            table.insert(values, string.format("%s=%d", settingId, self:getAuthoritativeValue(settingId) or 0))
        end
    end
    return table.concat(values, ", ")
end

function ContractSettingsSafety:onStartMap()
    -- XML loading has finished. Capture the loaded values, including explicit 0.
    self:captureCurrentSettings(true)
    self.startupComplete = true
    self:enforceAll("startup")
    self:saveNow("startup")
    Logging.info("Additional Contracts | TFU hard-disable/settings fix active: %s", self:getSnapshotText())
end

function ContractSettingsSafety:update(dt)
    if g_server == nil or not self.startupComplete then return end
    self.auditTimer = self.auditTimer - (tonumber(dt) or 0)
    if self.auditTimer <= 0 then
        self.auditTimer = self.AUDIT_INTERVAL_MS
        self:enforceAll("runtimeAudit")
    end
end

function ContractSettingsSafety:isConnectionAdmin(connection)
    if connection == nil or tfuConnectionIsServer(connection) then return true end
    if g_currentMission == nil or g_currentMission.userManager == nil then return false end
    if g_currentMission.userManager.getUserByConnection == nil then return false end

    local user = g_currentMission.userManager:getUserByConnection(connection)
    return user ~= nil and user.getIsMasterUser ~= nil and user:getIsMasterUser()
end

-- -------------------------------------------------------------------------
-- Server-authoritative settings networking
-- -------------------------------------------------------------------------
TFUContractSettingChangeEvent = {}
local TFUContractSettingChangeEvent_mt = Class(TFUContractSettingChangeEvent, Event)
InitEventClass(TFUContractSettingChangeEvent, "TFUContractSettingChangeEvent")

function TFUContractSettingChangeEvent.emptyNew()
    return Event.new(TFUContractSettingChangeEvent_mt)
end

function TFUContractSettingChangeEvent.new(index, value)
    local self = TFUContractSettingChangeEvent.emptyNew()
    if type(index) == "string" then
        index = ContractSettingsSafety.SETTING_INDEX[index] or 0
    end
    self.index = math.max(0, math.min(255, tfuRound(index)))
    self.value = math.max(0, math.min(255, tfuRound(value)))
    return self
end

function TFUContractSettingChangeEvent:writeStream(streamId, connection)
    streamWriteUInt8(streamId, self.index)
    streamWriteUInt8(streamId, self.value)
end

function TFUContractSettingChangeEvent:readStream(streamId, connection)
    self.index = streamReadUInt8(streamId)
    self.value = streamReadUInt8(streamId)
    self:run(connection)
end

function TFUContractSettingChangeEvent:run(connection)
    local settingId = ContractSettingsSafety.SETTING_ORDER[self.index]
    if settingId == nil then return end

    if g_server ~= nil and not tfuConnectionIsServer(connection) then
        if not ContractSettingsSafety:isConnectionAdmin(connection) then
            Logging.warning("Additional Contracts | Rejected non-admin contract setting request: %s", tostring(settingId))
            if connection ~= nil and connection.sendEvent ~= nil then
                connection:sendEvent(TFUContractSettingsSyncEvent.new(ContractSettingsSafety:getSyncValues()))
            end
            return
        end

        local normalized = ContractSettingsSafety:applySetting(settingId, self.value, "multiplayerAdmin", true)
        if normalized ~= nil then
            g_server:broadcastEvent(TFUContractSettingChangeEvent.new(self.index, normalized), false)
        end
    elseif g_server == nil and tfuConnectionIsServer(connection) then
        local normalized = ContractSettingsSafety:normalizeSetting(settingId, self.value)
        if normalized ~= nil then
            ContractSettingsSafety.authoritative[settingId] = normalized
            ContractSettingsSafety:forceLocalValue(settingId, normalized)
        end
    end
end

TFUContractSettingsSyncEvent = {}
local TFUContractSettingsSyncEvent_mt = Class(TFUContractSettingsSyncEvent, Event)
InitEventClass(TFUContractSettingsSyncEvent, "TFUContractSettingsSyncEvent")

function TFUContractSettingsSyncEvent.emptyNew()
    return Event.new(TFUContractSettingsSyncEvent_mt)
end

function TFUContractSettingsSyncEvent.new(values)
    local self = TFUContractSettingsSyncEvent.emptyNew()
    self.values = values or ContractSettingsSafety:getSyncValues()
    return self
end

function TFUContractSettingsSyncEvent:writeStream(streamId, connection)
    for index = 1, #ContractSettingsSafety.SETTING_ORDER do
        streamWriteUInt8(streamId, self.values[index] or 0)
    end
end

function TFUContractSettingsSyncEvent:readStream(streamId, connection)
    self.values = {}
    for index = 1, #ContractSettingsSafety.SETTING_ORDER do
        self.values[index] = streamReadUInt8(streamId)
    end
    self:run(connection)
end

function TFUContractSettingsSyncEvent:run(connection)
    if g_server == nil and tfuConnectionIsServer(connection) then
        ContractSettingsSafety:applySyncValues(self.values)
    end
end

TFUContractSettingsRequestEvent = {}
local TFUContractSettingsRequestEvent_mt = Class(TFUContractSettingsRequestEvent, Event)
InitEventClass(TFUContractSettingsRequestEvent, "TFUContractSettingsRequestEvent")

function TFUContractSettingsRequestEvent.emptyNew()
    return Event.new(TFUContractSettingsRequestEvent_mt)
end

function TFUContractSettingsRequestEvent.new()
    return TFUContractSettingsRequestEvent.emptyNew()
end

function TFUContractSettingsRequestEvent:writeStream(streamId, connection)
end

function TFUContractSettingsRequestEvent:readStream(streamId, connection)
    self:run(connection)
end

function TFUContractSettingsRequestEvent:run(connection)
    if g_server ~= nil and not tfuConnectionIsServer(connection) and connection ~= nil and connection.sendEvent ~= nil then
        connection:sendEvent(TFUContractSettingsSyncEvent.new(ContractSettingsSafety:getSyncValues()))
    end
end

function ContractSettingsSafety:sendSnapshot(connection)
    if g_server ~= nil and connection ~= nil and not tfuConnectionIsServer(connection) and connection.sendEvent ~= nil then
        connection:sendEvent(TFUContractSettingsSyncEvent.new(self:getSyncValues()))
        return true
    end
    return false
end

function ContractSettingsSafety:requestSettingChange(settingId, value)
    if not self:isKnownSetting(settingId) then return end
    local normalized = self:normalizeSetting(settingId, value)
    if normalized == nil then return end
    local index = self.SETTING_INDEX[settingId]
    if index == nil then return end

    if g_server ~= nil and g_currentMission ~= nil and g_currentMission.getIsServer ~= nil and g_currentMission:getIsServer() then
        normalized = self:applySetting(settingId, normalized, "hostAdmin", true)
        if normalized ~= nil then
            g_server:broadcastEvent(TFUContractSettingChangeEvent.new(index, normalized), false)
        end
    elseif g_client ~= nil and g_client.getServerConnection ~= nil then
        g_client:getServerConnection():sendEvent(TFUContractSettingChangeEvent.new(index, normalized))
    else
        self:applySetting(settingId, normalized, "singleplayer", true)
    end
end

function ContractSettingsSafety:install()
    if self.installed then return end
    self.installed = true

    -- Capture original class methods and install one closure per managed type.
    for _, typName in ipairs(self.TYPE_ORDER) do
        local managedType = typName
        local settingId = managedType .. "_minMax"
        local classObject = self:getClass(managedType)

        if classObject ~= nil then
            local originalSetter = classObject.setMaxNumInstance
            local originalCanRun = classObject.canRun
            local originalGenerator = classObject.tryGenerateMission
            self.originalSetters[managedType] = originalSetter
            self.originalCanRun[managedType] = originalCanRun
            self.originalGenerators[managedType] = originalGenerator

            classObject.setMaxNumInstance = function(_, value)
                return ContractSettingsSafety:applySetting(settingId, value, "classSetter", true)
            end

            if originalCanRun ~= nil then
                classObject.canRun = function(...)
                    if not ContractSettingsSafety:isEnabled(managedType) then return false end
                    return originalCanRun(...)
                end
            end

            if originalGenerator ~= nil then
                classObject.tryGenerateMission = function(...)
                    local limit = ContractSettingsSafety:getLimit(managedType)
                    if limit == nil or limit <= 0 then return nil end
                    local data = g_missionManager ~= nil and g_missionManager.getMissionTypeDataByName ~= nil
                        and g_missionManager:getMissionTypeDataByName(managedType) or nil
                    if data ~= nil and tonumber(data.numInstances) ~= nil and data.numInstances >= limit then return nil end
                    if ContractSettingsSafety:getTypeInstanceCount(managedType) >= limit then return nil end
                    return originalGenerator(...)
                end
            end

            classObject.changeSettingsEvent = function(_, changedSettingId, state)
                if changedSettingId == settingId then
                    ContractSettingsSafety:requestSettingChange(changedSettingId, state)
                end
            end

            classObject.onChangeSettings = function(_, changedSettingId, state)
                if changedSettingId == settingId then
                    return ContractSettingsSafety:applySetting(changedSettingId, state, "localChange", true)
                end
            end

            -- Disable the eight legacy per-type request events. One snapshot is used.
            classObject.loadSettingsEvent = function()
            end
        end
    end

    AdditionalContractTypes.changeSettingsEvent = function(_, settingId, state)
        if settingId == "additionalContract_successFactor" then
            ContractSettingsSafety:requestSettingChange(settingId, state)
        end
    end

    AdditionalContractTypes.onChangeSettings = function(_, settingId, state)
        if settingId == "additionalContract_successFactor" then
            return ContractSettingsSafety:applySetting(settingId, state, "localChange", true)
        end
    end

    AdditionalContractTypes.loadSettingsFromServer = function(_, mission, connection, x, y, z, viewDistanceCoeff)
        local isMultiplayer = g_currentMission ~= nil
            and g_currentMission.missionDynamicInfo ~= nil
            and g_currentMission.missionDynamicInfo.isMultiplayer
        if not isMultiplayer then return end

        if ContractSettingsSafety:sendSnapshot(connection) then return end
        if g_server == nil and g_client ~= nil and g_client.getServerConnection ~= nil then
            g_client:getServerConnection():sendEvent(TFUContractSettingsRequestEvent.new())
        end
    end

    local originalOnStartMap = AdditionalContractTypes.onStartMap
    AdditionalContractTypes.onStartMap = function(self, args)
        if originalOnStartMap ~= nil then originalOnStartMap(self, args) end
        ContractSettingsSafety:onStartMap()
    end

    local originalUpdate = AdditionalContractTypes.update
    AdditionalContractTypes.update = function(self, dt)
        if originalUpdate ~= nil then originalUpdate(self, dt) end
        ContractSettingsSafety:update(dt)
    end

    AdditionalContractTypes.getMissionTypeLimit = function(_, typName)
        return ContractSettingsSafety:getLimit(typName)
    end

    AdditionalContractTypes.isMissionTypeEnabled = function(_, typName)
        return ContractSettingsSafety:isEnabled(typName)
    end

    Logging.info("Additional Contracts | Installed TFU authoritative hard-disable/settings fix v7")
end

ContractSettingsSafety:install()
