Divergent/mods/Fair Fast Travel/gamedata/scripts/game_fast_travel.script

1562 lines
50 KiB
Plaintext
Raw Permalink Normal View History

2024-03-17 20:18:03 -04:00
--[[=====================================================================
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("<FFT> "..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("<FFT> 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