-- Extra features ported from pirate-perfection, translated to HexTrainer's
-- CloneClass/.orig toggle style and verified against the decompiled 1.152.269
-- source. Grouped here to keep Dexterity/Mission from ballooning.

HT.Extra = {}

local function classExists(name)
    return rawget(_G, name) ~= nil or _G[name] ~= nil
end

-- ---------------------------------------------------------------- player combat

-- No delay between melee attacks (clears the melee cooldown timers each check).
function HT.Extra:setNoDelayMelee(value)
    HT:setSetting("enable_no_delay_melee", value)
    HT:updateJsonValue("dexterity", "ut_item_extra_toggle_no_delay_melee", value)
    _G.CloneClass(PlayerStandard)
    if value then
        function PlayerStandard:_check_action_melee(...)
            if self._state_data then
                self._state_data.melee_expire_t = nil
                self._state_data.melee_repeat_expire_t = nil
            end
            return PlayerStandard.orig._check_action_melee(self, ...)
        end
    else
        PlayerStandard._check_action_melee = PlayerStandard.orig._check_action_melee
    end
    HT:addAlert(value and "No melee delay ON" or "No melee delay OFF", HT.colors.success, false)
end

-- Higher jump while running.
function HT.Extra:setHighJump(value)
    HT:setSetting("enable_high_jump", value)
    HT:updateJsonValue("dexterity", "ut_item_extra_toggle_high_jump", value)
    _G.CloneClass(PlayerStandard)
    if value then
        function PlayerStandard:_start_action_jump(t, action_start_data, ...)
            if self._running and type(action_start_data) == "table" and action_start_data.jump_vel_z then
                action_start_data.jump_vel_z = action_start_data.jump_vel_z * 2.5
            end
            return PlayerStandard.orig._start_action_jump(self, t, action_start_data, ...)
        end
    else
        PlayerStandard._start_action_jump = PlayerStandard.orig._start_action_jump
    end
    HT:addAlert(value and "High jump ON" or "High jump OFF", HT.colors.success, false)
end

-- ----------------------------------------------------------------------- stealth

-- Cops never get permission to open fire.
function HT.Extra:setCopsDontShoot(value)
    HT:setSetting("enable_cops_dont_shoot", value)
    HT:updateJsonValue("mission", "ut_item_extra_toggle_cops_dont_shoot", value)
    _G.CloneClass(CopMovement)
    if value then
        function CopMovement:set_allow_fire(state, ...)
            if not state then return CopMovement.orig.set_allow_fire(self, state, ...) end
        end
        function CopMovement:set_allow_fire_on_client(state, ...)
            if not state then return CopMovement.orig.set_allow_fire_on_client(self, state, ...) end
        end
    else
        CopMovement.set_allow_fire = CopMovement.orig.set_allow_fire
        CopMovement.set_allow_fire_on_client = CopMovement.orig.set_allow_fire_on_client
    end
    HT:addAlert(value and "Cops don't shoot ON" or "Cops don't shoot OFF", HT.colors.success, false)
end

-- Stop AI from running to panic buttons / alarms.
function HT.Extra:setPreventPanicButtons(value)
    HT:setSetting("enable_prevent_panic_buttons", value)
    HT:updateJsonValue("mission", "ut_item_extra_toggle_prevent_panic_buttons", value)
    _G.CloneClass(CopMovement)
    if value then
        function CopMovement:action_request(action_desc, ...)
            if type(action_desc) == "table" and action_desc.variant == "run" then return false end
            return CopMovement.orig.action_request(self, action_desc, ...)
        end
    else
        CopMovement.action_request = CopMovement.orig.action_request
    end
    HT:addAlert(value and "Prevent panic buttons ON" or "Prevent panic buttons OFF", HT.colors.success, false)
end

-- Disable all security cameras (stops them updating/detecting).
function HT.Extra:setDisableCameras(value)
    HT:setSetting("enable_disable_cameras", value)
    HT:updateJsonValue("mission", "ut_item_extra_toggle_disable_cameras", value)
    pcall(function()
        if SecurityCamera and SecurityCamera.cameras then
            for _, unit in pairs(SecurityCamera.cameras) do
                -- Only toggle cameras that have actually initialized their detection
                -- (_last_detect_t set). Touching uninitialised ones makes the camera's
                -- own update do arithmetic on a nil _last_detect_t -> crash.
                if alive(unit) and unit:base() and unit:base()._last_detect_t ~= nil
                    and unit:base().set_update_enabled then
                    unit:base():set_update_enabled(not value)
                end
            end
        end
    end)
    HT:addAlert(value and "Cameras disabled" or "Cameras enabled", HT.colors.success, false)
end

-- Disable pagers: many calls, always-bluffable, so pagers can't raise the alarm.
HT.Extra._pagerBackup = nil
function HT.Extra:setDisablePagers(value)
    HT:setSetting("enable_disable_pagers", value)
    HT:updateJsonValue("mission", "ut_item_extra_toggle_disable_pagers", value)
    pcall(function()
        local pager = tweak_data.player.alarm_pager
        if not HT.Extra._pagerBackup then HT.Extra._pagerBackup = deep_clone(pager) end
        if value then
            -- Do NOT touch first_call_delay: setting it huge leaves downed guards
            -- stuck "pending" so you can't body-bag them.
            --
            -- TRULY UNLIMITED PAGERS, two parts:
            --  1. nr_of_calls (-> total_nr_calls per guard) set absurdly high so a
            --     guard's pager can ring effectively forever without ever reaching
            --     the count that auto-raises the alarm (copbrain checks
            --     nr_calls_made < total_nr_calls). 9999 ~= infinite for any heist.
            --  2. bluff_success_chance all 1.0: copbrain indexes this by number of
            --     PREVIOUS successful bluffs, clamped to #table, and succeeds when
            --     rand() < chance. All-1.0 = every answered pager ALWAYS succeeds,
            --     no matter how many you've already done. No trailing 0 (which would
            --     mark a "last call"), so pagers never run dry and never alarm.
            pager.nr_of_calls = { 9999, 9999 }
            local ones = {}
            for i = 1, 32 do ones[i] = 1 end
            pager.bluff_success_chance = ones
            pager.bluff_success_chance_w_skill = deep_clone(ones)
        else
            for k, v in pairs(HT.Extra._pagerBackup) do pager[k] = v end
        end
    end)
    HT:addAlert(value and "Pagers disabled" or "Pagers enabled", HT.colors.success, false)
end

-- Auto-resolve any active corpse pager so you never have to answer it and the
-- body becomes baggable. Polled from persist/main while Disable Pagers is on.
-- Uses the brain's bluff flow (which always succeeds because we set bluff to 1),
-- NOT interaction:interact() -- the latter can native-crash like cook/gage.
HT.Extra._answeredPagers = {}
function HT.Extra:autoAnswerPagers()
    if not HT:getSetting("enable_disable_pagers") then return end
    local player = managers.player and managers.player:player_unit()
    if not alive(player) then return end
    if not managers.interaction or not managers.interaction._interactive_units then return end
    for _, unit in pairs(managers.interaction._interactive_units) do
        if alive(unit) then
            local interaction = unit.interaction and unit:interaction()
            if interaction and interaction.tweak_data == "corpse_alarm_pager"
                and interaction.active and interaction:active() then
                local key = unit:key()
                if not HT.Extra._answeredPagers[key] then
                    HT.Extra._answeredPagers[key] = true
                    pcall(function()
                        unit:brain():on_alarm_pager_interaction("started", player)
                        unit:brain():on_alarm_pager_interaction("complete", player)
                    end)
                end
            end
        end
    end
end

-- ----------------------------------------------------------------------- revive

-- Auto-revive: instantly revive any downed unit (incl. you) the moment its
-- revive interaction becomes active.
function HT.Extra:setAutoRevive(value)
    HT:setSetting("enable_auto_revive", value)
    HT:updateJsonValue("troll", "ut_item_extra_toggle_auto_revive", value)
    _G.CloneClass(ReviveInteractionExt)
    if value then
        function ReviveInteractionExt:set_active(active, ...)
            ReviveInteractionExt.orig.set_active(self, active, ...)
            if active then
                pcall(function()
                    local player = managers.player and managers.player:player_unit()
                    if alive(player) then self:interact(player) end
                end)
            end
        end
    else
        ReviveInteractionExt.set_active = ReviveInteractionExt.orig.set_active
    end
    HT:addAlert(value and "Auto-revive ON" or "Auto-revive OFF", HT.colors.success, false)
end

-- ---------------------------------------------------------------------- bags etc

-- Infinite body bags (never consumed + topped up).
function HT.Extra:setInfiniteBodyBags(value)
    HT:setSetting("enable_infinite_body_bags", value)
    HT:updateJsonValue("mission", "ut_item_extra_toggle_infinite_body_bags", value)
    _G.CloneClass(PlayerManager)
    if value then
        function PlayerManager:on_used_body_bag() end
        pcall(function() managers.player:_set_body_bags_amount(17) end)
    elseif not HT:getSetting("enable_unlimited_equipment") then
        PlayerManager.on_used_body_bag = PlayerManager.orig.on_used_body_bag
    end
    HT:addAlert(value and "Infinite body bags ON" or "Infinite body bags OFF", HT.colors.success, false)
end

-- Infinite cable ties (uses the game's own -1 = infinite sentinel on the special).
function HT.Extra:setInfiniteCableTies(value)
    HT:setSetting("enable_infinite_cable_ties", value)
    HT:updateJsonValue("mission", "ut_item_extra_toggle_infinite_cable_ties", value)
    pcall(function()
        local pm = managers.player
        if not pm or not pm._equipment or not pm._equipment.specials then return end
        if value then
            if not pm._equipment.specials["cable_tie"] then
                pcall(function() pm:add_special({ name = "cable_tie", amount = 1 }) end)
            end
            local ct = pm._equipment.specials["cable_tie"]
            if ct then ct.amount = Application:digest_value(-1, true) end
            pcall(function() managers.hud:set_cable_ties_amount(HUDManager.PLAYER_PANEL, -1) end)
        else
            local ct = pm._equipment.specials["cable_tie"]
            if ct then ct.amount = Application:digest_value(5, true) end
            pcall(function() managers.hud:set_cable_ties_amount(HUDManager.PLAYER_PANEL, 5) end)
        end
    end)
    HT:addAlert(value and "Infinite cable ties ON" or "Infinite cable ties OFF", HT.colors.success, false)
end

-- Non-consumable deployables: placed ammo/doc/grenade/body bags never run out
-- (refunds each amount taken). Host-side effect.
function HT.Extra:setNonConsumableEquipment(value)
    HT:setSetting("enable_non_consumable_equipment", value)
    HT:updateJsonValue("mission", "ut_item_extra_toggle_non_consumable_equipment", value)
    local function hijackTake(clsName, method, amountField)
        if not classExists(clsName) then return end
        local cls = _G[clsName]
        _G.CloneClass(cls)
        if value then
            cls[method] = function(self, ...)
                local r = cls.orig[method](self, ...)
                if type(r) == "number" and type(self[amountField]) == "number" then
                    self[amountField] = self[amountField] + r
                end
                return r
            end
        elseif cls.orig and cls.orig[method] then
            cls[method] = cls.orig[method]
        end
    end
    hijackTake("GrenadeCrateBase", "take_grenade", "_grenade_amount")
    hijackTake("BodyBagsBagBase", "take_bodybag", "_bodybag_amount")
    hijackTake("AmmoBagBase", "_take_ammo", "_ammo_amount")
    hijackTake("DoctorBagBase", "_take", "_amount")
    HT:addAlert(value and "Non-consumable equipment ON" or "Non-consumable equipment OFF", HT.colors.success, false)
end

-- Secure every loose loot bag on the map at once (host). One-shot button / F7.
-- Directly secures each carry via the loot manager and removes the bag, so it
-- counts toward the escape without you hauling them.
function HT.Extra:secureAllLoot()
    if not HT:isInHeist() then HT:addAlert("ut_alert_in_heist_only_feature", HT.colors.warning); return end
    if not HT:isHost() then HT:addAlert("ut_alert_host_only_feature", HT.colors.warning); return end
    local count = 0
    pcall(function()
        local peer_id = managers.network:session():local_peer():id()
        for _, unit in pairs(World:find_units_quick("all", 14)) do
            if alive(unit) and unit.carry_data and unit:carry_data() then
                local cd = unit:carry_data()
                pcall(function()
                    local carry_id = cd:carry_id()
                    local mult = cd:multiplier() or 1
                    managers.loot:secure(carry_id, mult, false, peer_id)
                    World:delete_unit(unit)
                    count = count + 1
                end)
            end
        end
    end)
    if count > 0 then
        HT:addAlert("Secured " .. count .. " loot bag(s)", HT.colors.success, false)
    else
        HT:addAlert("No loose loot bags found", HT.colors.warning, false)
    end
end

-- ------------------------------------------------------------------- spawn gage

HT.Extra.gageUnits = {
    "units/pd2_dlc_gage_jobs/pickups/gen_pku_gage_green/gen_pku_gage_green",
    "units/pd2_dlc_gage_jobs/pickups/gen_pku_gage_yellow/gen_pku_gage_yellow",
    "units/pd2_dlc_gage_jobs/pickups/gen_pku_gage_red/gen_pku_gage_red",
    "units/pd2_dlc_gage_jobs/pickups/gen_pku_gage_blue/gen_pku_gage_blue",
    "units/pd2_dlc_gage_jobs/pickups/gen_pku_gage_purple/gen_pku_gage_purple",
}

function HT.Extra:spawnGagePackage()
    if not HT:isInHeist() then
        HT:addAlert("ut_alert_in_heist_only_feature", HT.colors.warning)
        return
    end
    local ray = HT:getCrosshairRay()
    if not ray then
        HT:addAlert("ut_alert_cannot_spawn", HT.colors.danger)
        return
    end
    pcall(function()
        if tweak_data.gage_assignment then
            tweak_data.gage_assignment.get_num_assignment_units = function() return 9999 end
        end
    end)
    local ok = pcall(function()
        local name = HT.Extra.gageUnits[math.random(1, #HT.Extra.gageUnits)]
        World:spawn_unit(Idstring(name), ray.hit_position, Rotation(math.random(0, 360), 0, 0))
    end)
    if ok then
        HT:addAlert("Gage package spawned", HT.colors.success, false)
    else
        HT:addAlert("ut_alert_cannot_spawn_unit", HT.colors.danger)
    end
end

-- =========================================================== Half 2: QoL / plan

-- Disable the skill-data validation that strips hacked/illegal skills on load.
-- (Applies on the next save load / game start.)
function HT.Extra:setDisableSkillsCheck(value)
    HT:setSetting("enable_disable_skills_check", value)
    HT:updateJsonValue("configuration", "ut_item_extra_toggle_disable_skills_check", value)
    if classExists("SkillTreeManager") then
        _G.CloneClass(SkillTreeManager)
        if value then
            function SkillTreeManager:_verify_loaded_data() end
        else
            SkillTreeManager._verify_loaded_data = SkillTreeManager.orig._verify_loaded_data
        end
    end
    HT:addAlert(value and "Skills check disabled" or "Skills check enabled", HT.colors.success, false)
end

-- Don't pause the game when someone drops in / is joining.
function HT.Extra:setNoPause(value)
    HT:setSetting("enable_no_pause", value)
    HT:updateJsonValue("configuration", "ut_item_extra_toggle_no_pause", value)
    if classExists("BaseNetworkSession") then
        _G.CloneClass(BaseNetworkSession)
        local function unpause()
            pcall(function()
                Application:set_pause(false)
                SoundDevice:set_rtpc("ingame_sound", 1)
            end)
        end
        if value then
            function BaseNetworkSession:load(...)
                local was = Application:paused()
                local r = BaseNetworkSession.orig.load(self, ...)
                if not was then unpause() end
                return r
            end
            if BaseNetworkSession.orig.on_drop_in_pause_request_received then
                function BaseNetworkSession:on_drop_in_pause_request_received(...)
                    local was = Application:paused()
                    local r = BaseNetworkSession.orig.on_drop_in_pause_request_received(self, ...)
                    if not was then unpause() end
                    return r
                end
            end
        else
            BaseNetworkSession.load = BaseNetworkSession.orig.load
            BaseNetworkSession.on_drop_in_pause_request_received = BaseNetworkSession.orig.on_drop_in_pause_request_received
        end
    end
    HT:addAlert(value and "No drop-in pause ON" or "No drop-in pause OFF", HT.colors.success, false)
end

-- Skip the heist intro: spawn players the moment the level audio starts (host).
function HT.Extra:setSkipIntro(value)
    HT:setSetting("enable_skip_intro", value)
    HT:updateJsonValue("configuration", "ut_item_extra_toggle_skip_intro", value)
    if classExists("IngameWaitingForPlayersState") then
        _G.CloneClass(IngameWaitingForPlayersState)
        if value then
            function IngameWaitingForPlayersState:_start_audio(...)
                IngameWaitingForPlayersState.orig._start_audio(self, ...)
                -- _start_audio sets self._audio_started = true, which is exactly the
                -- precondition the game's own _skip() needs. Calling it here runs the
                -- proper skip path (stop briefing -> streaming-aware _start_delay ->
                -- spawn_players) and syncs the skip to all peers. The old version
                -- called spawn_players() directly, which jumped the gun before assets
                -- finished streaming and left the blackscreen intro up -- i.e. it
                -- didn't actually skip. _skip() is host-gated internally.
                pcall(function() self:_skip() end)
            end
        else
            IngameWaitingForPlayersState._start_audio = IngameWaitingForPlayersState.orig._start_audio
        end
    end
    HT:addAlert(value and "Skip intro ON" or "Skip intro OFF", HT.colors.success, false)
end

-- Preplanning is free and needs no favours.
function HT.Extra:setFreePreplanning(value)
    HT:setSetting("enable_free_preplanning", value)
    HT:updateJsonValue("preplanning", "ut_item_extra_toggle_free_preplanning", value)
    if classExists("MoneyManager") then
        _G.CloneClass(MoneyManager)
        if value then
            function MoneyManager:get_preplanning_type_cost() return 0 end
            function MoneyManager:can_afford_preplanning_type() return true end
            function MoneyManager:get_preplanning_votes_cost() return 0 end
        else
            MoneyManager.get_preplanning_type_cost = MoneyManager.orig.get_preplanning_type_cost
            MoneyManager.can_afford_preplanning_type = MoneyManager.orig.can_afford_preplanning_type
            MoneyManager.get_preplanning_votes_cost = MoneyManager.orig.get_preplanning_votes_cost
        end
    end
    if classExists("PrePlanningManager") then
        _G.CloneClass(PrePlanningManager)
        if value then
            function PrePlanningManager:get_type_budget_cost() return 0 end
            function PrePlanningManager:can_reserve_mission_element() return true end
            function PrePlanningManager:can_vote_on_plan() return true end
            -- "Favours" in the UI = the preplanning budget. Force a huge total so
            -- you always have enough (the cost-overrides above keep spend at 0).
            function PrePlanningManager:get_current_budget()
                return 0, 9999
            end
        else
            PrePlanningManager.get_type_budget_cost = PrePlanningManager.orig.get_type_budget_cost
            PrePlanningManager.can_reserve_mission_element = PrePlanningManager.orig.can_reserve_mission_element
            PrePlanningManager.can_vote_on_plan = PrePlanningManager.orig.can_vote_on_plan
            PrePlanningManager.get_current_budget = PrePlanningManager.orig.get_current_budget
        end
    end
    HT:addAlert(value and "Free preplanning ON" or "Free preplanning OFF", HT.colors.success, false)
end

-- Host's chosen preplanning always wins the vote.
function HT.Extra:setHostChoosePlan(value)
    HT:setSetting("enable_host_choose_plan", value)
    HT:updateJsonValue("preplanning", "ut_item_extra_toggle_host_choose_plan", value)
    if classExists("PrePlanningManager") then
        _G.CloneClass(PrePlanningManager)
        if value then
            function PrePlanningManager:_update_majority_votes(...)
                if Network:is_client() then
                    return PrePlanningManager.orig._update_majority_votes(self, ...)
                end
                local winners
                pcall(function()
                    local local_peer_id = managers.network:session():local_peer():id()
                    local plan_data = self:get_vote_council()[local_peer_id]
                    if plan_data then
                        winners = {}
                        for plan, data in pairs(plan_data) do winners[plan] = { data[1], data[2] } end
                        self._saved_majority_votes = winners
                    end
                end)
                if winners then return winners end
                return PrePlanningManager.orig._update_majority_votes(self, ...)
            end
        else
            PrePlanningManager._update_majority_votes = PrePlanningManager.orig._update_majority_votes
        end
    end
    HT:addAlert(value and "Host choose plan ON" or "Host choose plan OFF", HT.colors.success, false)
end

-- Unlock every preplanning asset (one-shot).
function HT.Extra:unlockAllAssets()
    if not HT:isInGame() then HT:addAlert("ut_alert_in_game_only_feature", HT.colors.warning); return end
    pcall(function()
        local assets = managers.assets
        if not assets then return end
        if Network:is_server() and assets.unlock_all_availible_assets then
            assets:unlock_all_availible_assets()
        elseif assets.get_all_asset_ids then
            local session = managers.network:session()
            for _, id in pairs(assets:get_all_asset_ids(true)) do
                if Network:is_server() then
                    assets:unlock_asset(id)
                elseif session then
                    session:send_to_host("server_unlock_asset", id)
                end
            end
        end
    end)
    HT:addAlert("Unlocked all assets", HT.colors.success, false)
end

-- Reserve (buy) every preplanning element (host). Forces free preplanning first.
function HT.Extra:buyAllPreplanning()
    if not HT:isInGame() then HT:addAlert("ut_alert_in_game_only_feature", HT.colors.warning); return end
    if not HT:isHost() then HT:addAlert("ut_alert_host_only_feature", HT.colors.warning); return end
    pcall(function() if not HT:getSetting("enable_free_preplanning") then HT.Extra:setFreePreplanning(true) end end)
    pcall(function()
        local pp = managers.preplanning
        if not pp or not pp._mission_elements_by_type then return end
        local equipments = { "bodybags_bag", "grenade_crate", "ammo_bag", "health_bag" }
        for ptype, array in pairs(pp._mission_elements_by_type) do
            for _, element in pairs(array) do
                local t = ptype
                if table.contains(equipments, ptype) then t = equipments[math.random(1, #equipments)] end
                pcall(function() pp:reserve_mission_element(t, element:id()) end)
            end
        end
    end)
    HT:addAlert("Reserved all preplanning", HT.colors.success, false)
end

-- Set all active drills/saws to a given time (seconds).
function HT.Extra:applyDrillTime()
    if not HT:isInHeist() then HT:addAlert("ut_alert_in_heist_only_feature", HT.colors.warning); return end
    local seconds = tonumber(HT:getSetting("drill_time")) or 1
    local count = 0
    pcall(function()
        for _, unit in pairs(World:find_units_quick("all", 1)) do
            if alive(unit) and unit.timer_gui and unit:timer_gui() then
                local tg = unit:timer_gui()
                if tg._current_timer and math.floor(tg._current_timer) ~= -1 then
                    pcall(function()
                        tg:_start(seconds)
                        if managers.network:session() then
                            managers.network:session():send_to_peers_synched("start_timer_gui", tg._unit, seconds)
                        end
                        if not tg._jammed then tg:set_jammed(true) end
                        if tg._jammed then tg:set_jammed(false) end
                        count = count + 1
                    end)
                end
            end
        end
    end)
    HT:addAlert("Set " .. count .. " drill(s) to " .. seconds .. "s", HT.colors.success, false)
end

-- Persistent QoL toggles (NOT cleared by Reset / boot): re-applied at boot.
function HT.Extra:reapplyPersistent()
    pcall(function() if HT:getSetting("enable_disable_skills_check") then HT.Extra:setDisableSkillsCheck(true) end end)
    pcall(function() if HT:getSetting("enable_no_pause") then HT.Extra:setNoPause(true) end end)
    pcall(function() if HT:getSetting("enable_skip_intro") then HT.Extra:setSkipIntro(true) end end)
    pcall(function() if HT:getSetting("enable_free_preplanning") then HT.Extra:setFreePreplanning(true) end end)
    pcall(function() if HT:getSetting("enable_host_choose_plan") then HT.Extra:setHostChoosePlan(true) end end)
end

-- Re-applied on heist start (camera/revive/etc. operate on units that respawn).
function HT.Extra:reapply()
    HT.Extra._answeredPagers = {}
    pcall(function() if HT:getSetting("enable_no_delay_melee") then HT.Extra:setNoDelayMelee(true) end end)
    pcall(function() if HT:getSetting("enable_high_jump") then HT.Extra:setHighJump(true) end end)
    pcall(function() if HT:getSetting("enable_cops_dont_shoot") then HT.Extra:setCopsDontShoot(true) end end)
    pcall(function() if HT:getSetting("enable_prevent_panic_buttons") then HT.Extra:setPreventPanicButtons(true) end end)
    pcall(function() if HT:getSetting("enable_disable_cameras") then HT.Extra:setDisableCameras(true) end end)
    pcall(function() if HT:getSetting("enable_disable_pagers") then HT.Extra:setDisablePagers(true) end end)
    pcall(function() if HT:getSetting("enable_auto_revive") then HT.Extra:setAutoRevive(true) end end)
    pcall(function() if HT:getSetting("enable_infinite_body_bags") then HT.Extra:setInfiniteBodyBags(true) end end)
    pcall(function() if HT:getSetting("enable_infinite_cable_ties") then HT.Extra:setInfiniteCableTies(true) end end)
    pcall(function() if HT:getSetting("enable_non_consumable_equipment") then HT.Extra:setNonConsumableEquipment(true) end end)
end
