--[[===================================================================== Fair Fast Travel System Version: 2.3.4 Updated: 20230923 Author: Catspaw (CatspawMods @ ModDB) Source: https://www.moddb.com/mods/stalker-anomaly/addons/fair-fast-travel-duration-for-anomaly-151 https://www.youtube.com/channel/UCtG8fiWPUZEzWlkUn60btAw Based on the Fast Travel system in Anomaly 1.5.2 by: Alundaio (original author) sLoPpYdOtBiGhOlE (marker names, menu and config options, etc) Additional credits: TheMrDemonized (Limited and Paid Fast Travel, code help) Grimscribe (Immersive Fast Travel Overhaul) Arszi (Radiation Overhaul) -- ====================================================================== -- Vanilla settings -- ==================================================================--]] longnames = false markermessage = false basecfg = { ["ft"] = { enabled = 0, combat = false, weight = false, damage = false, storm = false, notime = false, }, ["bp"] = { enabled = false, combat = false, weight = false, damage = false, storm = false, notime = false, }, ["gt"] = { enabled = true, combat = false, weight = true, damage = true, storm = false, notime = false, }, } tsmarts = nil -- ====================================================================== -- FFT settings -- ====================================================================== script_version = "2.3.4" local debuglogs = false -- Debug logging for troubleshooting issues - this should always be "false" in the release version local verbose = true -- Noisier verbose logging - no effect if debuglogs is false local gts = game.translate_string -- shortcuts local gg = game_graph local get_game_time = game.get_game_time local get_start_time = level.get_start_time local err_ignored = "st_fftd_guides_ignored" local err_noguides = "st_fftd_guides_none" local err_guidesfar = "st_fftd_guides_far" local err_toopoor = "st_fftd_toopoor" local err_enemies = "st_fftd_enemy_guides" skillsystem_exists = (haru_skills ~= nil) skillsystem_enabled = false skillsystem_started = false tick_int = 1080 local next_tick = time_global() stored_satiety = 1 survival_paused = false skillspeed_coef = 1.0 dist_cost_coef = 5 enc_damage_mult = 0.2 npc_combat_timeout = 10000 npc_combat_distance = 100 local actor_last_seen = 0 combat_npcs = {} flags = {} trip_in_progress = false guide_travel = true -- Enable hiring guides from the PDA - configurable in MCM guide_travel_time = true -- If true, guide travel includes the time it takes the guide to reach you guides_local_only = false -- If true, guides will only come meet you if you're on the same map guide_max_dist = nil -- The max distance a guide will travel to meet you, or nil if no limit local guides_loyal = false -- If enabled, only guides from the player's faction are willing to come meet them - not fully implemented guides_ignore_status = true -- If enabled, guides ignore wounded and overweight status for fast travel local ftuseoldcalcs = false -- Whether to use vanilla Anomaly distance formula instead of FFTD's ftdisguisefix = true -- Whether to use FFTD's fix for disguises getting exposed when you fast travel allow_debug_travel = false shared_ft_cooldown = false allow_open_bp = false -- Re-enables vanilla feature of opening backpacks from anywhere on the map use_warfare_fix = false -- Fix for WAO compatibility, skips adding fast travel icons to map cond_damage_mode = 1 disable_gear_damage = false -- Killswitch for gear damage feature minimum_health = 0 minimum_satiety = 0 local menu_options = {} local clr = { ["yel"] = "%c[255,250,218,94]", ["wht"] = "%c[255,220,220,220]", ["grn"] = "%c[255,0,255,0]", ["red"] = "%c[255,255,0,0]", } local all_travel_types = { ["ft"]="st_fftd_travelname_ft", ["bp"]="st_fftd_travelname_bp", ["gt"]="st_fftd_travelname_gt", } travel_data = { ["ft"] = { ["cfg"] = { useoldcalcs = false, usedisguisefix = true, allow_pin_travel= true, travel_cooldown = 0, cost_coef = 0, }, ["loc"] = { mpm = 10, rnd_enabled = false, pause_stats = false, gear_dmg = 0, rnd_hours_min = 0, rnd_hours_max = 0, rnd_minutes_min = 0, rnd_minutes_max = 0, }, ["map"] = { mpm = 15, rnd_enabled = true, pause_stats = false, gear_dmg = 1, rnd_hours_min = 1, rnd_hours_max = 0, rnd_minutes_min = 0, rnd_minutes_max = 59, }, }, ["bp"] = { ["cfg"] = { useoldcalcs = false, usedisguisefix = true, allow_pin_travel= false, travel_cooldown = 0, cost_coef = 0, }, ["loc"] = { mpm = 10, rnd_enabled = false, useoldcalcs = false, pause_stats = false, gear_dmg = 0, rnd_hours_min = 0, rnd_hours_max = 0, rnd_minutes_min = 0, rnd_minutes_max = 0, }, ["map"] = { mpm = 15, rnd_enabled = true, useoldcalcs = false, pause_stats = false, gear_dmg = 0.5, rnd_hours_min = 1, rnd_hours_max = 0, rnd_minutes_min = 0, rnd_minutes_max = 59, }, }, ["gt"] = { ["cfg"] = { useoldcalcs = false, usedisguisefix = true, allow_pin_travel= true, add_travel_time = true, local_only = true, ignore_status = true, --guides_loyal = false, travel_cooldown = 0, cost_coef = 2.25, enabled = true, }, ["loc"] = { mpm = 10, rnd_enabled = false, useoldcalcs = false, pause_stats = true, gear_dmg = 0, rnd_hours_min = 0, rnd_hours_max = 0, rnd_minutes_min = 0, rnd_minutes_max = 0, }, ["map"] = { mpm = 8, rnd_enabled = false, useoldcalcs = false, pause_stats = true, gear_dmg = 0.5, rnd_hours_min = 0, rnd_hours_max = 0, rnd_minutes_min = 0, rnd_minutes_max = 0, }, }, } local td = travel_data -- ====================================================================== -- Lookup tables for guide travel -- ====================================================================== guide_ignored_levels = { -- Levels in which guides will refuse to come meet you ["l03u_agr_underground"] = true, ["jupiter_underground"] = true, ["l11_hospital"] = true, ["labx8"] = true, ["l23_x9"] = true, ["l04u_labx18"] = true, ["l08u_brainlab"] = true, ["l13u_warlab"] = true, ["l10u_bunker"] = true, ["l12u_control_monolith"] = true, --["pripyat"] = true, ["l11_pripyat"] = true, ["l10_radar"] = true, ["l12_stancia"] = true, ["l12_stancia_2"] = true, ["l12u_sarcofag"] = true, ["l13_generators"] = true, } faction_guides = { ["guid_dv_mal_mlr"] = { comm = "bandit", enabled = false, pos = nil, level_name = "", }, ["guid_marsh_mlr"] = { comm = "csky", enabled = false, pos = nil, level_name = "", }, ["mil_freedom_guid"] = { comm = "freedom", enabled = false, pos = nil, level_name = "", }, ["ds_killer_guide_main_base"] = { comm = "killer", enabled = false, pos = nil, level_name = "", }, ["guid_bar_stalker_navigator"] = { comm = "stalker", enabled = false, pos = nil, level_name = "", }, ["guid_pri_a15_mlr"] = { comm = "stalker", enabled = false, pos = nil, level_name = "", }, ["guid_jup_stalker_garik"] = { comm = "stalker", enabled = false, pos = nil, level_name = "", }, } guides_by_level_name = {} -- Initialized dynamically based on faction_guides so that the data -- should be accurate even if the player has modded the guide spawns guidespots_by_level_name = { -- Places guides know how to get to, which are used as possible guide -- starting locations for calculating how far one must travel to meet -- the player ["jupiter"] = { ["jupiter_guide"] = {-50.628776550293,3.4879410266876,199.19316101074, 635022, 4478}, ["jupiter_guide_mon"] = {388.51870727539,4.2755246162415,-127.71376800537, 1328885, 4677}, --jup_depo = {423.711029052734, 9.95538330078125, -91.8379669189453,1384937,4816}, -- Jupiter - Hideout below factory ["jupiter_guide_bandit"] = {-436.17681884766,0.023663967847824,-352.67199707031, 4818, 4632}, }, ["k00_marsh"] = { ["marsh_guide"] = {641.22045898438,9.9145269393921,463.4518737793, 531344, 226}, ["guid_marsh_mlr"] = {}, -- will be loaded at runtime based on smart }, ["k01_darkscape"] = { ["darkscape_guide_mil"] = {416.24505615234,-2.0682604312897,-381.73083496094, 896271, 1114}, }, ["k02_trucks_cemetery"] = { ["cemetery_guide"] = {232.85961914063,7.5902180671692,308.79092407227, 749878, 5167}, }, ["l01_escape"] = { ["escape_guide"] = {-52.625671386719,-10.05776309967,-64.016700744629,236395,559}, -- Cordon - Inside the tunnel ["escape_village_guide"] = {-168.36848449707,-20.235736846924,-155.74464416504, 81102, 452}, ["escape_guide_mil"] = {-132.42623901367,-30.139842987061,-394.85430908203, 122849, 373}, }, ["l02_garbage"] = { ["garbage_guide"] = {20.42943572998,0.3997375369072,239.76965332031, 178228, 858}, }, ["l03_agroprom"] = { ["agro_guide"] = {-8.4884414672852,-4.9986891746521,-321.14392089844, 190767, 874}, ["agro_guide_mil"] = {-175.5033416748,5.3152875900269,-186.05902099609, 46523, 1037}, }, ["l04_darkvalley"] = { ["guid_dv_mal_mlr"] = {}, -- will be loaded at runtime based on smart }, ["l05_bar"] = { ["l05_bar_guide"] = {57.820430755615,-0.0016859173774719,123.71918487549, 26958, 1745}, }, --["l06_rostok"] = { -- ["l06_rostok_fft"] = {}, --}, ["l07_military"] = { ["freedom_base_guide"] = {-323.85864257813,-25.816766738892,55.820461273193, 23506, 2110}, ["mil_freedom_guid"] = {}, }, ["l08_yantar"] = { ["yantar_guide"] = {209.00457763672,6.3322243690491,-246.66212463379, 151270, 2190}, ["yantar_from_freedom"] = {170.966796875,1.8648002147675,87.093704223633,147072,2213}, ["mil_to_yantar"] = {53.360538482666,-11.887294769287,-280.54272460938,91046,2288}, }, ["l09_deadcity"] = { ["deadcity_mercbase"] = {-56.60,8.92,37.57,96425,2367}, }, --["l10_limansk"] = { -- ["l10_limansk_fft"] = {}, --}, ["l10_radar"] = { ["radar_guid"] = {659.78375244141,-43.879955291748,170.1953125, 227224, 2538}, }, ["l10_red_forest"] = { ["red_forest_guid"] = {30.185470581055,-0.0034164488315582,16.55979347229, 81736, 2812}, ["red_forest_guid_mon"] = {-174.38987731934,0.39412915706635,-268.97796630859, 3338, 2790}, }, ["l11_pripyat"] = { ["l11_pripyat_guid"] = {-124.84135437012,-0.4021889269352,100.8790435791, 6083, 3068}, ["l11_pripyat_guid_mon"] = {-125.87271881104,-2.3243350982666,79.815246582031, 5834, 2930}, ["pripyat_guid"] = {-247.2088470459,-0.51004129648209,-133.41319274902, 5072, 615}, }, ["l12_stancia"] = { ["stancia_guide"] = {993.24591064453,-0.12632231414318,-317.42514038086, 461400, 3214}, }, ["pripyat"] = { ["pripyat_2_guid"] = {203.07299804688,-0.39944335818291,207.8582611084, 261726, 3067}, ["pripyat_guid_mon"] = {-118.44010162354,-0.50860524177551,103.26635742188, 77100, 5050}, ["out_entrance"] = {186.211975097656, -0.50654798746109, -255.105880737305,416193,4909}, -- Outskirts - exit from Jupiter Underground ["guid_pri_a15_mlr"] = {}, }, ["zaton"] = { ["zaton_guid"] = {120.02467346191,-7.3487234115601,185.89865112305, 1172089, 4457}, ["zat_trash_station"] = {430.74,30.45,-420.38,1671704,4454}, } } -- ====================================================================== -- Utility functions -- ====================================================================== local function dl(logtext,...) if debuglogs then printf(" "..logtext,...) end end local function vl(logtext,...) if debuglogs and verbose then dl("[V] "..logtext,...) end end function dec2(num) if num == 0 then return 0 end return math.floor(num * 100) / 100 end function dotip(tiptext,dur,src,beep,icon,snd) -- vl("Tip call received: dur %s | src \"%s\" | beep %s\n\"%s\"",dur,src,beep,tiptext) if tiptext == nil then return end db.actor:give_game_news(src or gts("st_fftd_ftsystem"), tiptext, icon or "ui_iconsTotal_found_money", 0, dur or 5000) if beep then xr_sound.set_sound_play(AC_ID, snd or "pda_tips") end end function get_time_elapsed() return math.floor(get_game_time():diffSec(get_start_time())) end function safeid(obj) local id = obj.id if (not id) or (id and type(id) ~= "number") then id = obj:id() end return id end local function use_skillsystem() return skillsystem_exists and skillsystem_enabled and skillsystem_started end local function clear_flags() flags = {} end function bbp_extra_weight() -- vanilla function local w = 0 local function itr(npc,itm) if (npc:is_on_belt(itm)) then w = w + (itm and ((ini_sys:r_float_ex(itm:section(),"additional_inventory_weight") or 0) * itm:condition()) or 0) end end db.actor:iterate_inventory(itr,db.actor) local bkpk = db.actor:item_in_slot(13) w = w + (bkpk and ini_sys:r_float_ex(bkpk:section(),"additional_inventory_weight") or 0) return w end -- ====================================================================== -- Condition checks - prereqs for travel -- The silent option in these isn't currently used, but is there in case -- it's ever necessary to do one of these checks without taking action -- ====================================================================== function actor_in_recent_combat() local apos = db.actor:position() local xr_combat_npcs = xr_combat_ignore.fighting_with_actor_npcs if is_empty(xr_combat_npcs) then vl("actor_in_recent_combat: Not fighting with any NPCs, returning false") return false end for id,_ in pairs(xr_combat_npcs) do if id and (id > 0) then local npc = get_object_by_id(id) local npos = npc and npc:position() local dist = apos:distance_to(npos) or 0 if dist <= npc_combat_distance then local now = time_global() if npc and npc:see(db.actor) then actor_last_seen = now vl("NPC %s saw actor at %s") return true end if actor_last_seen < (now + npc_combat_timeout) then vl("Actor has been seen in the last %s seconds",npc_combat_timeout/1000) return true end end end end end function combat_travel_ok(tt,silent) -- Prevent fast travel while in combat local fighting = false local combattravel = basecfg[tt].combat if not (combattravel) then fighting = actor_in_recent_combat() dl("Condition check: fighting = %s | combattravel %s",fighting,combattravel) if fighting and not (silent or combattravel) then hide_hud_pda() cancel_travel_message(gts("st_travel_event")) end end return combattravel or not fighting end function weight_travel_ok(tt,silent) -- Prevent fast travel while overloaded local overweight = false local weighttravel = basecfg[tt].weight if not weighttravel then local suit = db.actor:item_in_slot(7) overweight = (db.actor:get_total_weight() - db.actor:get_actor_max_walk_weight() - (suit and suit:get_additional_max_weight() or 0) - bbp_extra_weight()) > 0 dl("Condition check: overweight = %s | weighttravel %s",overweight,weighttravel) if overweight and not (silent or weighttravel) then hide_hud_pda() cancel_travel_message(gts("st_travel_overloaded")) end end return weighttravel or not overweight end function damage_travel_ok(tt,silent) -- Prevent fast travel if injured, bleeding, and/or iradiated local damagetravel = basecfg[tt].damage if not damagetravel then local actor = db.actor local hurt = (actor.health < minimum_health) local radiation = (actor.radiation > 0) if radiation and arszi_radiation then local arrad = arszi_radiation radiation = (arrad.settings.fastTravelWithRads == 1) and (db.actor.radiation <= arrad.settings.RADIATION_THRESHOLD) or (arrad.settings.fastTravelWithRads == 2) dl("arszi_radiation installed, radiation = %s",radiation) end local bleeding = (actor.bleeding > 0) local cond = actor:cast_Actor():conditions() local hangry = (cond:GetSatiety() < minimum_satiety) local damaged = (hurt or bleeding or radiation or hangry) dl("Condition check: damaged = %s | damagetravel %s",damaged,damagetravel) if damaged and not (silent or damagetravel) then local hurtmsg = "" if ((hurt or bleeding) and radiation) then hurtmsg = "st_sleep_bleeding_irradiated" elseif (hurt or bleeding) then hurtmsg = "st_sleep_bleeding" elseif (radiation) then hurtmsg = "st_sleep_irradiated" end hide_hud_pda() cancel_travel_message(gts(hurtmsg)) end end return damagetravel or not damaged end function storm_travel_ok(tt,silent) -- Prevent fast travel if an emission or psi-storm currently ongoing local stormy = false local stormtravel = basecfg[tt].storm if not (stormtravel) then stormy = (xr_conditions.surge_started() or psi_storm_manager.is_started()) dl("Condition check: stormy = %s | stormtravel %s",stormy,stormtravel) if stormy and not (silent or stormtravel) then hide_hud_pda() cancel_travel_message(gts("st_travel_event")) end end return stormtravel or not stormy end function guide_travel_ok(tt,silent) if not guide_enabled then return true end clear_flags() local level_name = level.name() local guide_ok = not guide_ignored_levels[level_name] dl("Condition check: guide_ok = %s | guide_ignored_levels[%s] %s",guide_ok,level_name,guide_ignored_levels[level_name]) if not guide_ok then if silent then flags.erst = gts(err_ignored) else hide_hud_pda() cancel_travel_message(gts(err_ignored)) end end return guide_ok end function travel_cost_coef(tt) return td and td[tt] and td[tt]["cfg"] and td[tt]["cfg"].cost_coef or 0 end function cost_to_travel(dist,tt) tt = tt or "gt" -- return math.floor(dist * dist_cost_coef) return math.floor(dist * travel_cost_coef(tt)) end function charge_for_travel(cost,silent,msg,icon,snd) msg = msg or "st_fftd_farepaid_guide" local dur = 10000 local can_afford = db.actor:money() >= cost if can_afford then dl("Charging actor %s RU",cost) db.actor:give_money(-cost) if not silent then local st_cost = clr.yel..tostring(cost).." ".. clr.wht..gts("st_fftd_rub") local tiptext = string.format(gts(msg),st_cost) dotip(tiptext,dur,gts("st_fftd_ftsystem"),true,icon,snd) end end return can_afford end function pin_travel_allowed(tt) return td[tt] and td[tt]["cfg"].allow_pin_travel end function travel_cooldown_enabled(tt) tt = tt or "ft" local cden = (td and td[tt]["cfg"].travel_cooldown or 0) > 0 dl("travel_cooldown_enabled(%s) = %s",tt,cden) return cden end function travel_on_cooldown(tt) tt = tt or "ft" if not travel_cooldown_enabled(tt) then return end local next_travel = td[tt].next_travel or 0 local t = get_time_elapsed() if t and next_travel then local toosoon = (t < next_travel) dl("travel_on_cooldown(%s) = %s",toosoon) return toosoon end end function travel_cooldown_remaining(tt) tt = tt or "ft" local elapsed = get_time_elapsed() local cdrem = math.ceil(td[tt].next_travel - elapsed) dl("travel_cooldown_remaining(%s) = %s",tt,cdrem) return cdrem end function update_travel_cooldown(tt) if shared_ft_cooldown then dl("shared_ft_cooldown is %s, triggering cooldowns for all travel types",shared_ft_cooldown) for k,_ in pairs (all_travel_types) do td[k].next_travel = get_time_elapsed() + (td[k]["cfg"].travel_cooldown * 6) end else td[tt].next_travel = get_time_elapsed() + (td[tt]["cfg"].travel_cooldown * 6) dl("shared_ft_cooldown is %s | gte %s | next_travel for %s is %s",shared_ft_cooldown,get_time_elapsed(),tt,td[tt].next_travel) end end -- ====================================================================== -- Mapspot generation -- ====================================================================== function fftd_generate_mapspot_text(id,level_name,travel_type) local se_obj = id and (id > 0) and alife_object(id) if not (se_obj) then return end local name = gts(se_obj:name()) local paw = tasks_placeable_waypoints local quick_pin = paw and paw.valid_travel_target(id) or false if quick_pin then name = paw.pins[id].text end local tt = "ft" if all_travel_types[travel_type] then tt = travel_type end local using_guide = (tt == "gt") local diff_map = (level_name ~= level.name()) local maploc = "loc" if diff_map then maploc = "map" end -- Calculate distance local israndom = td[tt][maploc].israndom local time_coef = td[tt][maploc].time_coef local dest = se_obj local distcalc = calc_distance_for(dest,td[tt][maploc].useoldcalcs,using_guide) local ftdist = distcalc and distcalc.dist or 0 if ftdist < 0 then menu_options[distcalc.erst] = {} menu_options[distcalc.erst].tt = tt menu_options[distcalc.erst].valid = false return gts(distcalc.erst) end -- Calculate cost, if applicable local ftcosttxt = "" local text_end = "" if travel_cost_coef(tt) > 0 then local ftcost = cost_to_travel(ftdist,tt) text_end = ": "..tostring(ftcost).." "..gts("st_fftd_rub") dl("Paid travel enabled, ftdist is now %s - cost will be %s\n \nText: "..ftcosttxt,ftdist,ftcost) end -- Calculate estimated duration from final distance and generate its text local ftestdur = "" local ftmpm = 10 ftmpm = td[tt][maploc].mpm if td[tt][maploc].israndom then ftestdur = gts("st_fftd_mapspot_estdur").." " end local ftdur = math.floor(ftdist / 100 * ftmpm * skillspeed_coef) local fthours = math.floor(ftdur / 60) local ftmins = ftdur - (fthours * 60) local durtext = ftestdur..fthours..":"..string.format("%02d",ftmins)..")" local mapspot_text = "" local levname = gts(level_name).." " local destname = name.." (" local dist = tostring(math.floor(ftdist)) local meters = gts("st_fftd_mapspot_meters")..", " -- Generate final mapspot text from all elements based on travel type local travel_to = gts("st_pda_fast_travel_to").." " if using_guide then travel_to = gts("st_fftd_mapspot_guideto").." " end if (longnames) then destname = levname..destname end local is_backpack = se_obj:name():find("^inv_backpack") or se_obj:name():find("^inv_actor_backpack") or false if is_backpack then travel_to = gts("st_pda_backpack_travel_to").." " local hint = se_load_var(id,nil,"stash_hint_text") if hint then destname = hint.." (" end end mapspot_text = travel_to..destname..dist..meters..durtext..text_end dl("generated mapspot_text: ",mapspot_text) menu_options[mapspot_text] = {} menu_options[mapspot_text].tt = tt menu_options[mapspot_text].valid = true return mapspot_text end function map_spot_menu_add_property(property_ui,id,level_name) empty_table(menu_options) local se_obj = id and (id > 0) and alife_object(id) vl("map_spot_menu_add_property called with %s, %s",id,level_name) if not (se_obj) then return end local ft_enabled = basecfg["ft"].enabled local bp_enabled = basecfg["bp"].enabled local gt_enabled = basecfg["gt"].enabled local is_backpack = se_obj:name():find("^inv_backpack") or se_obj:name():find("^inv_actor_backpack") or false local is_smart = se_obj:clsid() == clsid.smart_terrain local valid_smart = (DEV_DEBUG and allow_debug_travel) or ((ft_enabled > 0) and is_smart and tsmarts[se_obj:name()]) or ((ft_enabled > 1) and is_smart) or false local paw = tasks_placeable_waypoints local quick_pin = paw and paw.valid_travel_target(id) or false for tt,tname in spairs(all_travel_types) do local condition = false if travel_cooldown_enabled(tt) and travel_on_cooldown(tt) then local secsleft = math.floor(travel_cooldown_remaining(tt)/6) local minsleft = math.floor(secsleft / 60) local hrsleft = math.floor(minsleft / 60) secsleft = secsleft % 60 local h_and_m = (hrsleft > 0) and (minsleft > 0) local m_and_s = (hrsleft == 0) and (minsleft > 0) and (secsleft > 0) local showsecs = (hrsleft == 0) and (minsleft < 5) and (secsleft > 0) local secst = " "..(((secsleft > 1) and gts("st_fft_seconds") or false) or gts("st_fft_second")) local minst = " "..(((minsleft > 1) and gts("st_fft_minutes") or false) or gts("st_fft_minute")) local hrst = " "..(((hrsleft > 1) and gts("st_fft_hours") or false) or gts("st_fft_hour")) local hleft = (hrsleft > 0) and (tostring(hrsleft)..hrst) or "" local mleft = (minsleft > 0) and (tostring(minsleft)..minst) or "" local sleft = showsecs and (tostring(secsleft)..secst) or "" local timeleft = hleft..(h_and_m and ", " or "")..mleft..(showsecs and m_and_s and ", " or "")..sleft local travelname = gts("ui_mcm_menu_"..tt.."cfg") local notyet = string.format(gts("st_fftd_notyet"),travelname,timeleft) menu_options[notyet] = {} menu_options[notyet].tt = tt menu_options[notyet].valid = false property_ui:AddItem(notyet) else if tt == "ft" then condition = (not is_backpack) and valid_smart or (quick_pin and pin_travel_allowed(tt)) elseif tt == "bp" then condition = bp_enabled and is_backpack elseif tt == "gt" then condition = gt_enabled and (is_smart or (quick_pin and pin_travel_allowed(tt))) end if condition then local mapspot = fftd_generate_mapspot_text(id,level_name,tt) vl("generated mapspot %s",mapspot) property_ui:AddItem(mapspot) end end end if is_backpack and allow_open_bp then if (level.object_by_id(id)) then property_ui:AddItem(gts("bp_open")) end end end function nearest_smart(levelid,target) local smid_nearest,dist_nearest,name_nearest,result for name,smid in pairs(SIMBOARD.smarts_by_names) do if levelid == gg():vertex(smid.m_game_vertex_id):level_id() then local dist = smid.position:distance_to(target) if (SIMBOARD.smarts[smid.id]) and (not smid_nearest or (dist < dist_nearest)) then smid_nearest = smid dist_nearest = dist name_nearest = name end end end result = { smid = smid_nearest or 0, dist = dist_nearest or 0, name = name_nearest or "" } dl("nearest_smart: %s at %s",name,dist_nearest) return result end function nearest_guidespot_to_smart(dest) local target = dest.position or nil local lvid = dest.level_id local destlvl = alife():level_name(dest.level_id) local destname = dest:name() if target and lvid then return nearest_guidespot(target,lvid,destlvl,destname) end end function nearest_guidespot_to_actor(local_only) local target = db.actor:position() local lvid = alife():level_id() local destlvl = level.name() local destname = "actor" return nearest_guidespot(target,lvid,destlvl,destname,local_only) end function level_guides(tbl,level_name) local result = {} if level_name and tbl[level_name] then result = tbl[level_name] else for k,v in pairs(tbl) do vl("| level_guides: k %s | v %s",k,v) for g,p in pairs(v) do vl("| | level_guides: g %s | p %s",g,p) result[g] = p end end end return result end function nearest_guidespot(target,lvid,destlvl,destname,local_only) if not destlvl then return end local result = {} if guide_ignored_levels[destlvl] then result = { smid = nil, dist = -1, name = "ignored", erst = gts(err_ignored), } return result end if guides_loyal then -- Feature not yet ready, don't enable guides_loyal local level_name = level.name() local found = false for k,v in pairs (faction_guides) do local comm = v.comm local enemies = game_relations.is_factions_enemies(comm,db.actor:character_community()) if (v.level_name == level_name) and not enemies then found = true end end if not found then result = { smid = nil, dist = -1, name = "enemies", erst = gts(err_enemies), } return result end end local smid_nearest = nil local dist_nearest = 0 local name_nearest = "" dl("game_fast_travel.nearest_guidespot: Checking nearest_guidespot for \n| "..destname..": lvid %s | destlvl %s | target not nil %s",lvid,destlvl,target ~= nil) local nearby_spots = {} local ct = 0 if lvid and target then vl("#guidespots_by_level_name: %s | ",#guidespots_by_level_name) nearby_spots = level_guides(guidespots_by_level_name,destlvl) if local_only then vl("local_only = %s",local_only) nearby_spots = level_guides(guides_by_level_name,destlvl) end vl("| Found %s guidespots, searching for nearest to lvid %s in %s",#nearby_spots,lvid,destlvl) for name,smid in pairs(SIMBOARD.smarts_by_names) do local smlvid = gg():vertex(smid.m_game_vertex_id):level_id() if lvid == smlvid then vl("| | Found smart %s with matching lvid %s",name,smlvid) for k,v in pairs(nearby_spots) do vl("| | | Checking distance between smart %s and guidespot %s",name,k) local vec = vector():set(v[1],v[2],v[3]) local dist = smid.position:distance_to(vec) local refused = 0 if (SIMBOARD.smarts[smid.id]) and (not smid_nearest or (dist < dist_nearest)) then vl("| | | %s is closest so far to %s (%sm) @ %s,%s,%s,%s,%s",k,destname,math.floor(dist),v[1],v[2],v[3],v[4],v[5]) smid_nearest = smid dist_nearest = dist name_nearest = name end end end end if smid_nearest then if (dist_nearest > guide_max_dist) then result = { smid = nil, dist = -1, name = "toofar", erst = gts(err_guidesfar), } dl("Nearest guide to %s (%s) is too far away: %s",destname,name_nearest,dist_nearest) else result = { smid = smid_nearest or 0, dist = dist_nearest or -1, name = name_nearest or "error", erst = (name == "error") and gts(err_noguides), } dl("Found nearest guidespot to %s: %s at %s",destname,name_nearest,dist_nearest) end return result else dl("No nearby guidespots found") return nil end else dl("levelid (%s) is invalid, nothing to do",levelid) return nil end end function global_distance_between(smart,target) return math.pow(warfare.distance_to_xz_sqr(global_position.from(alife_object(smart.id)),global_position.from(target)), 0.5) end function calc_distance_for(dest,useoldcalcs,using_guide) --vl("calc_distance_for(%s,%s,%s)",dest,useoldcalcs,using_guide) local result = { smid = nil, dist = -1, name = "", erst = nil, } local actor_pos = db.actor:position() local actor_lvl = alife():level_id() local using_guide = using_guide or false result.dist = actor_pos:distance_to(dest.position) if using_guide or not useoldcalcs then local get_nearest = nearest_smart(actor_lvl,actor_pos) result.dist = global_distance_between(get_nearest.smid,dest) dl("calc_distance_for: guide_travel_time %s | using guide %s | useoldcalcs %s | result.dist %s",guide_travel_time,using_guide,useoldcalcs,result.dist) if using_guide and guide_travel_time then dl("guide_travel_time is %s, calculating additional duration",guide_travel_time) local guide_start_dist = 0 local actor_guide = nearest_guidespot_to_actor(guides_local_only) if actor_guide and (actor_guide.dist >= 0) then guide_start_dist = global_distance_between(get_nearest.smid,actor_guide.smid) dl("Guide would travel %sm to get to player, then %sm to destination",guide_start_dist,result.dist) local basedist = result.dist result.dist = basedist + guide_start_dist dl("Guide travel time enabled, result.dist is now %s (was %s)",result.dist,basedist) else result = actor_guide end end end return result end function distance_to_duration(tt,maploc,dist) local ftdist = dist or 10 local ftdur = 60 local fthours = 1 local ftmins = 0 local time_coef = td[tt][maploc].mpm or 10 local pause_survival = td[tt][maploc].pause_stats or false local israndom = td[tt][maploc].rnd_enabled or false local minrnd_m = td[tt][maploc].rnd_minutes_min or 0 local maxrnd_m = td[tt][maploc].rnd_minutes_max or 59 local minrnd_h = td[tt][maploc].rnd_hours_min or 0 local maxrnd_h = td[tt][maploc].rnd_hours_max or 1 ftdur = math.floor(ftdist / 100 * time_coef * skillspeed_coef) fthours = math.floor(ftdur / 60) ftmins = ftdur - (fthours * 60) if israndom then if maxrnd_m > minrnd_m then ftmins = ftmins+math.random(minrnd_m,maxrnd_m) end if maxrnd_h > minrnd_h then if ftmins > 60 then fthours = fthours+math.random(minrnd_h,maxrnd_h)+1 ftmins = ftmins-60 else fthours = fthours+math.random(minrnd_h,maxrnd_h) end end end local result = { t = ftdur, h = fthours, m = ftmins, } return result end -- ====================================================================== -- Travel logic -- ====================================================================== function cancel_travel_message(error_string) vl("Trip canceled, clearing trip in progress flag") trip_in_progress = false actor_menu.set_msg(1, error_string,4) end function set_disguise_timer(on_or_off) if ui_options.get("gameplay/disguise/state") and ftdisguisefix then local after = on_or_off or false if after then gameplay_disguise.delet_memory() end local disguise_time = ui_options.get("gameplay/disguise/stay_time") if disguise_time then axr_main.config:w_value("options", "gameplay/disguise/stay_time", onoff) gameplay_disguise.update_settings() end end end function damage_gear_condition(dist,gear_dmg_fac,debug_only) if disable_gear_damage or (cond_damage_mode < 0) then return end if ((dist or 0) <= 0) or ((gear_dmg_fac or 0) <= 0) then return end local function adj_dmg(dmg,coef,min,max) return clamp(math.floor(dmg * coef * 100) / 1000,min or 0,max or 0.95) end local dist_coef = dist / 10000 local base_dmg = math.sqrt(1 - math.pow(dist_coef - 1, 2)) / 2 local enc_dmg = base_dmg * enc_damage_mult local enc_diff = db.actor:get_total_weight() - db.actor:get_actor_max_walk_weight() - (suit and suit:get_additional_max_weight() or 0) - bbp_extra_weight() local pre_adj_dmg = base_dmg if enc_diff > 0 then vl("enc_diff is %s, actor is overburdened - adding %s to cond dmg",enc_diff,enc_dmg) pre_adj_dmg = base_dmg + enc_dmg end local cond_dmg = adj_dmg(pre_adj_dmg,gear_dmg_fac,0.001,0.95) local slots_to_dmg = { -- In case it's ever necessary to adjust damage on a per-slot basis [1] = 1, [2] = 1, [3] = 1, [7] = 1, [12] = 1, } if cond_damage_mode > 2 then slots_to_dmg[7] = nil slots_to_dmg[12] = nil elseif cond_damage_mode > 1 then slots_to_dmg[1] = nil slots_to_dmg[2] = nil slots_to_dmg[3] = nil elseif cond_damage_mode > 0 then empty_table(slots_to_dmg) local r = math.floor(math.random(1,5)) if r == 4 then r = 7 end if r == 5 then r = 12 end slots_to_dmg[r] = 1 end dl("Calculating gear condition damage: dist %s -> coef %s | gear_dmg_fac %s | base_dmg %s | enc_dmg %s | pre_adj %s | cond_dmg %s",dist,dist_coef,gear_dmg_fac,base_dmg,enc_dmg,pre_adj_dmg,cond_dmg) if debug_only then return cond_dmg end for s,slot_coef in pairs(slots_to_dmg) do local itm = db.actor:item_in_slot(s) if itm then local itm_cond = itm:condition() local cond_dmg_final = cond_dmg * slot_coef local sec = itm:section() itm:set_condition(itm_cond - cond_dmg_final) vl("Item %s in slot %s condition: %s (%s - %s)",sec,s,itm:condition(),itm_cond,cond_dmg_final) end end return cond_dmg_final end local satdiff = 0 function pause_survival_stats(disable) dl("pause_survival_stats called") if disable == nil then -- This should only be nil if pause_survival_stats is called by timeevent UnregisterScriptCallback("actor_on_first_update", actor_on_first_update) survival_paused = false end local cond = db.actor:cast_Actor():conditions() dl("pause_survival_stats: feature is %s, disable is %s",pause_survival,disable) if disable then stored_satiety = cond:GetSatiety() dl("pause_survival_stats: storing current player satiety: %s",stored_satiety) else dl("pause_survival_stats: restoring cached player satiety loss") local curr = cond:GetSatiety() satdiff = stored_satiety - curr dl("pause_survival_stats: current satiety is %s (stored %s, satdiff %s)",curr,stored_satiety,satdiff) dl("pause_survival_stats: executing change") db.actor.satiety = stored_satiety dl("current satiety is now %s (stored %s)",cond:GetSatiety(),stored_satiety) survival_paused = false if db.actor and actor_status.HUD then actor_status.HUD:Update(true) end end return true end function map_spot_menu_property_clicked(property_ui,id,level_name,prop) local se_obj = id and (id > 0) and alife_object(id) if not se_obj then return end if allow_open_bp and (prop == gts("bp_open")) then dl("allow_open_bp is %s and prop is %s",allow_open_bp,prop) local b = level.object_by_id(id) if (b) then hide_hud_inventory() b:use(db.actor) end return end if not (menu_options and menu_options[prop] and menu_options[prop].valid) then return end local tt = menu_options[prop].tt if not tt then return end using_guide = tt == "gt" dl("Matched, checking preconditions") if not ( combat_travel_ok(tt) and weight_travel_ok(tt) and damage_travel_ok(tt) and storm_travel_ok(tt) and guide_travel_ok(tt) ) then return end vl("Setting trip in progress flag") trip_in_progress= true local dest = se_obj local dest_pos = dest.position or nil local actor_pos = db.actor:position() local actor_lvl = alife():level_id() local ftdist = db.actor:position():distance_to(dest_pos) -- default vanilla calc local mapchange = (level_name ~= level.name()) local faretxt = "st_fftd_farepaid_noguide" local maploc = "loc" if mapchange then maploc = "map" end -- Calculate the fast travel distance and cost (if relevant) local distcalc = calc_distance_for(dest,td[tt][maploc].useoldcalcs,using_guide) ftdist = distcalc and distcalc.dist vl("ftdist %s | tt %s | maploc %s | using_guide %s",ftdist,tt,maploc,using_guide) if using_guide then if (ftdist < 0) then hide_hud_pda() cancel_travel_message(distcalc.erst) return end faretxt = "st_fftd_farepaid_guide" end if travel_cost_coef(tt) > 0 then local cost = cost_to_travel(ftdist,tt) or 100 dl("clicked: dist: %s cost: %s",ftdist,cost) if not charge_for_travel(cost,false,faretxt) then hide_hud_pda() cancel_travel_message(gts(err_toopoor)) return end end dl("game_fast_travel - notime is false, begin calculating trip duration") -- forward time when traveling if not basecfg[tt].notime then local ftdur = distance_to_duration(tt,maploc,ftdist) local pause_survival = td[tt][maploc].pause_stats or false if pause_survival then survival_paused = true end set_disguise_timer(false) db.actor:set_can_be_harmed(false) level.change_game_time(0,ftdur.h,ftdur.m) db.actor:set_can_be_harmed(true) set_disguise_timer(true) if pause_survival and mapchange then dl("Setting callback for level change to restore stats") RegisterScriptCallback("actor_on_first_update",actor_on_first_update) elseif pause_survival then dl("Creating timeevent to restore stats") CreateTimeEvent("fast_travel_resync","fast_travel_resync",0,game_fast_travel.pause_survival_stats) end surge_manager.get_surge_manager().time_forwarded = true psi_storm_manager.get_psi_storm_manager().time_forwarded = true level_weathers.get_weather_manager():forced_weather_change() end if travel_cooldown_enabled(tt) then update_travel_cooldown(tt) end local gdf = td[tt][maploc].gear_dmg or 0 damage_gear_condition(ftdist,gdf) if (se_obj.online) then db.actor:set_actor_position(se_obj.position) hide_hud_pda() else ChangeLevel(se_obj.position, se_obj.m_level_vertex_id, se_obj.m_game_vertex_id, VEC_ZERO, true) end dl("Trip complete, clearing trip in progress flag") trip_in_progress = false return true end function mcm_oldver_import() if not ui_mcm then return end if ui_mcm.get("fftd/version") ~= nil then return end printf(" Performing one-time import of pre-1.8 Fair Fast Travel settings\nYou may see MCM complain about a bunch of nil values; this is harmless and expected") for tt,en in pairs(all_travel_types) do if en then for maploc,defs in pairs(td[tt]) do if type(defs) == "table" then for var,_ in pairs(defs) do if tt ~= "gt" then local oldpath,newpath if maploc == "cfg" then oldpath = "fftd/"..tt.."/"..var newpath = "fftd/"..tt.."cfg/"..var else oldpath = "fftd/"..tt.."/"..maploc..var newpath = "fftd/"..tt.."cfg/"..maploc..var end local val = ui_mcm.get(oldpath) ui_mcm.set(newpath,val) ui_mcm.set(oldpath,nil) end end end end end end end -- ====================================================================== -- Callbacks and misc system functions -- ====================================================================== function update_mcm() if not ui_mcm then return end mcm_oldver_import() dl("Loading settings from MCM - nil value warnings are harmless and expected, ignore") skillsystem_enabled = ui_mcm.get("fftd/gen/skillsystem_enabled") shared_ft_cooldown = ui_mcm.get("fftd/gen/shared_cooldown") npc_combat_dist = ui_mcm.get("fftd/gen/combat_dist") or npc_combat_dist npc_combat_timeout = ui_mcm.get("fftd/gen/combat_timeout") or npc_combat_timeout allow_debug_travel = ui_mcm.get("fftd/gen/allow_debug_travel") allow_open_bp = ui_mcm.get("fftd/gen/allow_open_bp") use_warfare_fix = ui_mcm.get("fftd/gen/use_warfare_fix") local ti = ui_mcm.get("fftd/gen/tick_interval") if ti and (type(ti) == "number") and ti > 0 then tick_int = ti end cond_damage_mode = tonumber(ui_mcm.get("fftd/gen/cond_dmg_mode")) or 0 minimum_health = clamp(tonumber(ui_mcm.get("fftd/gen/minimum_health") or minimum_health),0,1) minimum_satiety = clamp(tonumber(ui_mcm.get("fftd/gen/minimum_satiety") or minimum_satiety),0,1) local dbug = ui_mcm.get("fftd/gen/debuglogs_enabled") if dbug ~= nil then debuglogs = dbug end for tt,en in pairs(all_travel_types) do if en then for maploc,defs in pairs(td[tt]) do vl("maploc %s | defs %s | tt %s",maploc,defs,tt) if type(defs) == "table" then for var,_ in pairs(defs) do local mcmpath = "" if maploc == "cfg" then mcmpath = "fftd/"..tt.."cfg/"..var else mcmpath = "fftd/"..tt.."cfg/"..maploc..var end local val = ui_mcm.get(mcmpath) vl("Setting td["..tt.."]["..maploc.."]."..var.." to "..mcmpath..": %s",val) td[tt][maploc][var] = val end end end end end local gtcfg = td["gt"]["cfg"] basecfg["gt"].enabled = gtcfg.enabled basecfg["gt"].damage = gtcfg.ignore_status basecfg["gt"].weight = gtcfg.ignore_status guide_travel_time = gtcfg.add_travel_time guides_local_only = gtcfg.local_only --guides_loyal = td["gt"]["cfg"].guides_loyal -- Feature not ready yet dl("Guide travel settings: enabled %s | travel_time %s | local_only %s | ignore_status %s",basecfg["gt"].enabled,guide_travel_time,guides_local_only,guides_ignore_status) if use_skillsystem() then skillspeed_coef = dec2(1 / haru_skills.skills_stats["endurance"].speed_modifier) dl("skillsystem Skill System found, travel time modifier: %s",skillspeed_coef) else dl("skillsystem Skill System not used, travel time modifier defaulting to %s",skillspeed_coef) end ui_mcm.set("fftd/version",script_version) end -- Used for Visit_Only mode, catches the player near the marker and updates things. function actor_on_interaction(typ, obj, name) if (basecfg["ft"].enabled ~= 1) or (use_warfare_fix and IsWarfare()) or (typ ~= "smarts") or (not (tsmarts[name])) then return end if (level.map_has_object_spot(obj.id, "fast_travel") == 0) then if (longnames) then local smart = alife():object(obj.id) local level_name = alife():level_name(smart and gg():vertex(smart.m_game_vertex_id):level_id()) level.map_add_object_spot(obj.id, "fast_travel", gts(level_name).." "..gts(name)) if (markermessage) then actor_menu.set_msg(1, gts(level_name)..": "..gts(name).." "..gts("st_fast_travel_discovered"), 4) end else level.map_add_object_spot(obj.id, "fast_travel", gts(name)) if (markermessage) then actor_menu.set_msg(1, gts(name).." "..gts("st_fast_travel_discovered"), 4) end end end end function set_disguise_timer(on_or_off) if ui_options.get("gameplay/disguise/state") and ftdisguisefix then local after = on_or_off or false if after then gameplay_disguise.delet_memory() end local disguise_time = ui_options.get("gameplay/disguise/stay_time") if disguise_time then axr_main.config:w_value("options", "gameplay/disguise/stay_time", onoff) gameplay_disguise.update_settings() end end end function update_settings() basecfg["ft"].enabled = ui_options.get("gameplay/fast_travel/state") basecfg["ft"].combat = ui_options.get("gameplay/fast_travel/on_combat") basecfg["ft"].weight = ui_options.get("gameplay/fast_travel/on_overweight") basecfg["ft"].damage = ui_options.get("gameplay/fast_travel/on_damage") basecfg["ft"].storm = ui_options.get("gameplay/fast_travel/on_emission") basecfg["ft"].notime = ui_options.get("gameplay/fast_travel/time") basecfg["bp"].enabled = ui_options.get("gameplay/backpack_travel/state") basecfg["bp"].combat = ui_options.get("gameplay/backpack_travel/on_combat") basecfg["bp"].weight = ui_options.get("gameplay/backpack_travel/on_overweight") basecfg["bp"].damage = ui_options.get("gameplay/backpack_travel/on_damage") basecfg["bp"].storm = ui_options.get("gameplay/backpack_travel/on_emission") basecfg["bp"].notime = ui_options.get("gameplay/backpack_travel/time") basecfg["gt"].combat = ui_options.get("gameplay/fast_travel/on_combat") basecfg["gt"].storm = ui_options.get("gameplay/fast_travel/on_emission") basecfg["gt"].notime = ui_options.get("gameplay/fast_travel/time") longnames = ui_options.get("gameplay/fast_travel/long_names") markermessage = ui_options.get("gameplay/fast_travel/visit_message") local faction = character_community(db.actor):sub(7) local pini = ini_file("plugins\\faction_quick_travel.ltx") tsmarts = utils_data.collect_section(pini,faction,true) if not (is_empty(tsmarts)) then local level_name local sim,gg = alife(),gg for i=1,65534 do local smart = sim:object(i) if (smart and smart:clsid() == clsid.smart_terrain and tsmarts[smart:name()]) then local smartname = gts(smart:name()) if (level.map_has_object_spot(i, "fast_travel")) then level.map_remove_object_spot(i, "fast_travel") end if not (use_warfare_fix and IsWarfare()) then if (basecfg["ft"].enabled == 1) then if (game_statistics.has_actor_visited_smart(smart:name()) == true) then if (longnames) then level_name = sim:level_name(smart and gg():vertex(smart.m_game_vertex_id):level_id()) level.map_add_object_spot(i, "fast_travel", gts(level_name).." "..smartname) else level.map_add_object_spot(i, "fast_travel", smartname) end end vl("Added FT smart for: %s: %s",level_name,smartname) elseif (basecfg["ft"].enabled == 2) then if (longnames) then level_name = sim:level_name(smart and gg():vertex(smart.m_game_vertex_id):level_id()) level.map_add_object_spot(i, "fast_travel", gts(level_name).." "..smartname) else level.map_add_object_spot(i, "fast_travel", smartname) end vl("Added FT smart for: %s: %s",gts(level_name),smartname) end end end end end -- Override defaults with config values from MCM, if applicable update_mcm() return true end function actor_on_first_update() dl("Unregistering callback and restoring survival stats") pause_survival_stats(false) end function actor_on_update() local now = time_global() if next_tick > now then return end next_tick = now + tick_int actor_in_recent_combat() if skillsystem_started or (not skillsystem_enabled) or (not skillsystem_exists) then return end if skillsystem_exists and not skillsystem_started then local sl = haru_skills and haru_skills.skills_levels local end_lvl = sl and sl["endurance"] and sl["endurance"].current_level skillsystem_started = end_lvl ~= nil end end function load_state(data) if not data.fast_travel_system then return end dl("load_state: Loading saved state") local fts = data.fast_travel_system if fts then survival_paused = fts.survival_paused for tt,_ in pairs(all_travel_types) do td[tt].next_travel = fts.cooldowns and fts.cooldowns[tt] or 0 end end if survival_paused then dl("Fast travel to new map in progress, survival stats will be restored on load") stored_satiety = fts.stored_satiety RegisterScriptCallback("actor_on_first_update", actor_on_first_update) end end function save_state(data) data.fast_travel_system = {} local fts = data.fast_travel_system fts.survival_paused = survival_paused or false fts.stored_satiety = stored_satiety fts.cooldowns = {} for tt,_ in pairs(all_travel_types) do fts.cooldowns[tt] = td[tt].next_travel or 0 end end function on_game_load() vl("Loading faction guides") for k,v in pairs(faction_guides) do local guidesquad = k.."_squad" local spawn_target = ini_sys:r_string_ex(guidesquad,"target_smart") local guidesmart = SIMBOARD:get_smart_by_name(spawn_target) local guidepos = guidesmart.position local fg = faction_guides[k] local gvid = guidesmart.m_game_vertex_id local ggv = gg():vertex(gvid) local lvid = ggv:level_id() local level_name = alife():level_name(guidesmart and gg():vertex(guidesmart.m_game_vertex_id):level_id()) fg.pos = { guidepos.x, guidepos.y, guidepos.z, lvid, gvid, } fg.level_name = level_name fg.enabled = true if guides_by_level_name[level_name] then guides_by_level_name[level_name][k] = fg.pos else guides_by_level_name[level_name] = {[k] = fg.pos} end if guidespots_by_level_name[level_name] then guidespots_by_level_name[level_name][k] = fg.pos else guidespots_by_level_name[level_name] = {[k] = fg.pos} end end guide_max_dist = guide_max_dist or 50000 update_settings() end function actor_on_stash_create(stash) se_save_var(stash.stash_id,nil,"stash_hint_text",stash.stash_name) end function on_game_start() RegisterScriptCallback("map_spot_menu_add_property",map_spot_menu_add_property) RegisterScriptCallback("map_spot_menu_property_clicked",map_spot_menu_property_clicked) RegisterScriptCallback("on_game_load",on_game_load) RegisterScriptCallback("save_state",save_state) RegisterScriptCallback("load_state",load_state) RegisterScriptCallback("on_option_change",update_settings) RegisterScriptCallback("actor_on_interaction", actor_on_interaction) RegisterScriptCallback("actor_on_update", actor_on_update) RegisterScriptCallback("actor_on_stash_create",actor_on_stash_create) end