-- Yet Another Campfire Saving -- Last modified: 2022.12.04 -- https://github.com/Ishmaeel/YetAnotherCampfireSaving local campfire_saving = true local campfire_radius = 15 local base_radius = 50 local boss_radius = 10 local token_granted = time_infinite local token_downtime = 1000 * 60 * 5 -- Five minutes local token_redeemed = false local notifications = false local not_mommy = false local neutral_bases = false local enable_friendly_bosses = false local enable_neutral_bosses = false local getstr = game.translate_string local silent = true bonus_bases = {} bonus_bases.pri_monolith = { monolith = true } bonus_bases.pri_a18_smart_terrain = { killer = true } function require_shelter() return surge_manager.actor_in_cover() end function always_allow() return true end function never_allow() return false end the_bawses = { zat_b18_noah = require_shelter, m_trader = require_shelter, m_lesnik = require_shelter, trader_monolith_kbo = always_allow, mechanic_monolith_kbo = always_allow } function on_game_start() bind_campfire.check_no_nearby_campfire = check_no_nearby_campfire RegisterScriptCallback("on_before_save_input", on_before_save_input) RegisterScriptCallback("on_option_change", on_option_change) RegisterScriptCallback("actor_on_first_update", actor_on_first_update) RegisterScriptCallback("on_console_execute", on_console_execute) RegisterScriptCallback("on_key_release", on_key_release) on_option_change() end function on_before_save_input(flags, typ_, text) if campfire_saving and level_weathers.valid_levels[level.name()] then token_redeemed = false local error_message = nil local nearby_campfire = bind_campfire.get_nearby_campfire(campfire_radius, true) if not nearby_campfire then error_message = strformat(getstr("st_ui_no_save_campfire_base"), text) elseif not nearby_campfire:is_on() then error_message = strformat(getstr("st_ui_no_save_campfire_unlit"), text) end local within_safe_space = is_within_friendly_base() if not within_safe_space then local evaluate_boss_safety = get_friendly_boss_safety_function() if evaluate_boss_safety then within_safe_space = evaluate_boss_safety() end end if error_message and not within_safe_space then token_redeemed = redeem_token() -- always prevent saving. exec_console_cmd("main_menu off") flags.ret = true -- only show message if token is not redeemed. if not token_redeemed then actor_menu.set_msg(1, error_message, 4) end end end end function is_within_friendly_base() local friendly_bases = get_friendly_bases() for _, smart in pairs(friendly_bases) do if smart.dist_to_actor < base_radius and surge_manager.actor_in_cover() then return true end end return false end function get_friendly_bases() local friendly_bases = {} local community = gameplay_disguise.get_default_comm() for _, smart in pairs(SIMBOARD.smarts_by_names) do if ish_campfire_debug then ish_campfire_debug.draw_map_dot(smart) end if smart.is_on_actor_level and smart.props.base > 0 then -- Base is considered friendly if it's marked for player's faction or "all" local is_safe = smart.props[community] > 0 or smart.props.all > 0 -- Some bases are not properly marked for factions in vanilla configs, so we have an override table. is_safe = is_safe or (bonus_bases[smart:name()] and bonus_bases[smart:name()][community]) -- Optional: Other factions may let you use their bases if you have good relations. is_safe = is_safe or neutral_bases and is_base_neutral(smart, community) if is_safe then table.insert(friendly_bases, smart) end end end return friendly_bases end function is_base_neutral(smart, community) for _, faction in pairs(game_relations.factions_table) do local influence = smart.props[faction] if influence > 0 then local relation = relation_registry.community_relation(faction, community) local goodwill = relation_registry.community_goodwill(faction, db.actor:id()) if relation + goodwill >= 0 then return true end end end return false end function get_friendly_boss_safety_function() local boss_safety_function = never_allow local function iterate_func(obj) if obj:id() > 0 then local section = obj:section() if the_bawses[section] then local boss_alive = obj:alive() local boss_relation = game_relations.get_npcs_relation(obj, db.actor) if d and d.news then local relationship = game_relations.game_relations_by_num[boss_relation] d.news("Boss found: %s - %s, %s", section, relationship, boss_alive and "alive" or "dead") end if boss_alive and boss_relation == game_object.friend or (enable_neutral_bosses and boss_relation == game_object.neutral) then boss_safety_function = the_bawses[section] return true end end end end if enable_friendly_bosses then level.iterate_nearest(db.actor:position(), boss_radius, iterate_func) end return boss_safety_function end function check_no_nearby_campfire() return false -- Prevent vanilla campfire checking function from interfering. end function on_option_change() if ui_mcm then campfire_saving = ui_mcm.get("yasc/enableCampfireSaving") notifications = ui_mcm.get("yasc/enableNotifications") not_mommy = ui_mcm.get("yasc/isNotMommy") neutral_bases = ui_mcm.get("yasc/allowNeutralBases") enable_friendly_bosses = ui_mcm.get("yasc/allowSanctuaries") >= 1 enable_neutral_bosses = ui_mcm.get("yasc/allowSanctuaries") >= 2 end end function actor_on_first_update() extend_token_downtime() end function on_console_execute(cmd) if cmd ~= "save" then return end if token_redeemed then switch_to_rl_mode() else extend_token_downtime() end end function on_key_release(key) if key == DIK_keys.DIK_F5 then token_redeemed = redeem_token(silent and campfire_saving) if token_redeemed then create_emergency_save() end end end function extend_token_downtime() token_granted = time_global() + token_downtime end function redeem_token(suppress_notification) local time_remaining = token_granted - time_global() local has_token = time_remaining <= 0 if has_token and (key_state(DIK_keys.DIK_LSHIFT) ~= 0 or key_state(DIK_keys.DIK_RSHIFT) ~= 0) then return true end if not suppress_notification then send_notification(time_remaining) end return false end function create_emergency_save() exec_console_cmd(string.format("save %s - %ssave", (user_name() or "player"), get_parent_type())) end function switch_to_rl_mode() exec_console_cmd("quit") end function send_notification(time_remaining) if notifications then local parent_type = get_parent_type() if time_remaining > 0 then news(getstr("token_pending"), parent_type, time_remaining / 1000) else news(getstr("token_granted"), parent_type) end end end function get_parent_type() return getstr(string.format("mommy_%s", not not_mommy)) end function news(message, ...) message = string.format(message, ...) if db.actor then db.actor:give_game_news(getstr("ui_mcm_yacs_title"), message, "ui_inGame2_PD_storonnik_ravnovesiya", 0, 5000, 0) else printf(message) end end