1562 lines
50 KiB
Plaintext
1562 lines
50 KiB
Plaintext
--[[=====================================================================
|
|
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 |