--[[ - actor_hits_npc: most damage talents executed here - actor_before_hit: most defense talents executed here - npc_hit: when npc hit npc and on npc death - all_functions: most talent functions - other_callbacks: other talents/functions/cooldowns/durations executed here on other callbacks --]] talents_key = talents_mcm.get_config("talents_key") talents_dbg = talents_mcm.get_config("talents_dbg") local ctime_to_t = utils_data.CTime_to_table local t_to_ctime = utils_data.CTime_from_table local gt = game.translate_string picked_class = "null" cooldowns_t = {} durations_t = {} -- sniper vars local deep_wound_var = {} cooldowns_t.evasion_cd = 0 -- trooper vars local thrill_var = false cooldowns_t.precision_var = 0 local precision_allow_dmg = false durations_t.barrage_var = 0 local collector_var = 0 durations_t.suppresion_fire_var = 0 cooldowns_t.cautious_var = 0 durations_t.leap_var = 0 -- jugger vars durations_t.fury_var = 0 durations_t.dismorale_var = 0 durations_t.sturdy_var = 0 cooldowns_t.robust_cd = 0 -- monster hunter vars local monster_hunter_trophies = {} durations_t.fierce_var = 0 fierce_stacks = 0 local deadly_blow_var = true -- small 0.15 delay against shotguns and explosives durations_t.special_diet_var = 0 local kindred_cd = nil local kindred_var = nil local kindred_prev_lvl = nil -- merc vars local merc_head_hunter_t = {} local modified_bullets_var = {} durations_t.overdrive_var = 0 durations_t.bullseye_var = 0 local liquidator_var = true -- small 0.15 delay against shotguns and explosives durations_t.chem_reliant_var = 0 durations_t.grenadier_var = 0 durations_t.flurry_var = 0 durations_t.reposition_var = 0 -- assassin vars durations_t.spot_weakness_var = 0 spot_weakness_stacks = 0 local acrobatic_var = false cooldowns_t.cunning_cd = 0 -- endurance durations_t.adrenaline_rush_var = 0 cooldowns_t.adrenaline_rush_var = 0 durations_t.courage_var = 0 local second_wind_cd = nil -- speech local artistic_cd = nil local cynical_cd = nil ---------------- -- machine guns table local machine_guns_t = { -- vanilla wpn_pkm = true, wpn_pkm_zulus = true, wpn_pkp = true, wpn_rpd = true, wpn_m249 = true, wpn_rpk = true, wpn_rpk74 = true, -- boomstick wpn_pkm_siber = true, wpn_pkp_siber = true, wpn_pk_siber = true, wpn_pkm_zenit_siber = true, wpn_pkp_tac_siber = true, wpn_pkm_zenit_shorty = true, } -- hit type conversion local hit_convert = { [hit.burn] = "burn", [hit.chemical_burn] = "chem", [hit.shock] = "shock", [hit.radiation] = "radi", [hit.wound] = "rup", [hit.strike] = "strike", [hit.fire_wound] = "bal", [hit.explosion] = "expl", [hit.telepatic] = "psi", } local class_special_stats = { ["sniper"] = { speed = 10, weight = -10 }, ["trooper"] = { speed = 0, weight = 0 }, ["jugger"] = { speed = -10, weight = 10 }, ["monster_hunter"] = { speed = 5, weight = -5 }, ["merc"] = { speed = -5, weight = 5 }, ["assassin"] = { speed = 10, weight = -10 }, } local descr_sizes_t = { [1] = 0.75, [2] = 1, [3] = 1.25, [4] = 1.5, [5] = 2, } talents_table = { ["offensive"] = { ["sniper"] = { sniper_rifles = { level = 0, max = 3, req = {all = 0}, pos = { x = 0, y = 0 }, wpns = {"w_sniper"}, damage = {3, 6, 10}, crit = {2, 3, 5}, descr = {"damage", "crit"} }, assault_rifles_novice = { level = 0, max = 3, req = {all = 0}, pos = { x = 2, y = 0 }, wpns = {"w_rifle"}, damage = {3, 6, 10}, crit = {1, 2, 3}, descr = {"damage", "crit"} }, pistols = { level = 0, max = 3, req = {all = 0}, pos = { x = 4, y = 0 }, wpns = {"w_pistol"}, damage = {5, 10, 15}, crit = {3, 6, 10}, descr = {"damage", "crit"} }, experienced_sniper = { level = 0, max = 1, req = {all = 3}, pos = { x = 1, y = 1 }, wpns = {"w_sniper"}, crit = {10}, descr = {"crit"}, func = "talent_experienced_sniper" }, steady = { level = 0, max = 3, req = {all = 6, experienced_sniper = 1}, pos = { x = 0, y = 2 }, wpns = {"w_sniper"}, crit = {3, 6, 10}, descr = {"crit"}, func = "talent_steady", hinder = "convenience" }, convenience = { level = 0, max = 3, req = {all = 6, experienced_sniper = 1}, pos = { x = 2, y = 2 }, wpns = {"w_sniper"}, crit = {5, 10, 15}, descr = {"crit"}, func = "talent_convenience", hinder = "steady" }, gunslinger = { level = 0, max = 1, req = {all = 6, pistols = 3}, pos = { x = 4, y = 2 }, wpns = {"w_pistol"}, damage = {15}, crit = {15}, reload = {40}, descr = {"damage", "crit", "reload"}, descr_size = 2, func = "talent_gunslinger" }, sharpshooter = { level = 0, max = 1, req = {all = 9, steady = 3}, pos = { x = 0, y = 3 }, wpns = {"w_sniper"}, crit = {0.05}, descr = {"crit"}, func = "talent_sharpshooter" }, ambush = { level = 0, max = 1, req = {all = 9, convenience = 3}, pos = { x = 2, y = 3 }, wpns = {"w_sniper"}, crit = {20}, descr = {"crit"}, func = "talent_ambush" }, deep_wound = { level = 0, max = 2, req = {all = 9}, pos = { x = 4, y = 3 }, wpns = {"w_sniper"}, bleed = {15, 30}, descr = {"bleed"} }, loner = { level = 0, max = 3, req = {all = 12}, pos = { x = 0, y = 4 }, wpns = {"w_sniper", "w_rifle"}, damage = {3, 6, 10}, descr = {"damage"}, func = "talent_loner" }, soft_spot = { level = 0, max = 3, req = {all = 12}, pos = { x = 2, y = 4 }, wpns = {"w_sniper"}, crit = {3, 6, 10}, descr = {"crit"}, func = "talent_soft_spot" }, nocturnal = { level = 0, max = 3, req = {all = 12}, pos = { x = 4, y = 4 }, wpns = {"w_sniper", "w_pistol"}, crit = {5, 10, 15}, descr = {"crit"}, func = "talent_nocturnal" }, carnage = { level = 0, max = 1, req = {all = 15}, pos = { x = 2, y = 5 }, wpns = {"w_sniper"}, damage = {10}, crit_damage = {50}, descr = {"damage", "crit_damage"} }, }, ["trooper"] = { assault_rifles = { level = 0, max = 3, req = {all = 0}, pos = { x = 0, y = 0 }, wpns = {"w_rifle"}, damage = {3, 6, 10}, descr = {"damage"} }, shotguns_novice = { level = 0, max = 3, req = {all = 0}, pos = { x = 2, y = 0 }, wpns = {"w_shotgun"}, damage = {5, 10, 15}, descr = {"damage"} }, submachine_guns = { level = 0, max = 3, req = {all = 0}, pos = { x = 4, y = 0 }, wpns = {"w_smg"}, damage = {3, 6, 10}, descr = {"damage"} }, experienced_rifleman = { level = 0, max = 1, req = {all = 3}, pos = { x = 1, y = 1 }, wpns = {"w_rifle"}, crit = {2}, descr = {"crit"} }, demolition_expert = { level = 0, max = 2, req = {all = 3}, pos = { x = 3, y = 1 }, wpns = {"w_explosive", "w_base"}, damage = {10, 20}, weight = {25, 50}, descr = {"damage", "weight"} }, duelist = { level = 0, max = 3, req = {all = 6, experienced_rifleman = 1}, pos = { x = 0, y = 2 }, wpns = {"w_rifle"}, damage = {3, 6, 10}, descr = {"damage"}, func = "talent_duelist", hinder = "focus" }, focus = { level = 0, max = 3, req = {all = 6, experienced_rifleman = 1}, pos = { x = 2, y = 2 }, wpns = {"w_rifle"}, crit = {0.01, 0.02, 0.03}, descr = {"crit"}, func = "talent_focus", hinder = "duelist" }, thrill = { level = 0, max = 1, req = {all = 9, duelist = 3}, pos = { x = 0, y = 3 }, wpns = {"w_rifle"}, damage = {15}, descr = {"damage"}, func = "talent_thrill" }, precision = { level = 0, max = 1, req = {all = 9, focus = 3}, pos = { x = 2, y = 3 }, wpns = {"w_rifle"}, damage = {5}, crit = {5}, descr = {"cooldown", "damage", "crit"}, cooldown = 1.2, descr_size = 2, func = "talent_precision" }, barrage = { level = 0, max = 1, req = {all = 9, submachine_guns = 3}, pos = { x = 4, y = 3 }, wpns = {"w_smg"}, damage = {10}, crit = {3}, descr = {"duration", "damage", "crit"}, duration = 3, descr_size = 2, func = "talent_barrage" }, reckless = { level = 0, max = 3, req = {all = 12}, pos = { x = 1, y = 4 }, wpns = {"w_rifle", "w_shotgun"}, damage = {0.07, 0.14, 0.2}, descr = {"damage"}, func = "talent_reckless" }, veteran_rifleman = { level = 0, max = 3, req = {all = 12}, pos = { x = 3, y = 4 }, wpns = {"w_rifle", "w_smg"}, crit = {1, 2, 3}, descr = {"crit"} }, collector = { level = 0, max = 1, req = {all = 15}, pos = { x = 1, y = 5 }, wpns = {"w_rifle"}, damage = {0.01}, descr = {"damage"}, descr_size = 5, func = "talent_collector" }, }, ["jugger"] = { shotguns = { level = 0, max = 3, req = {all = 0}, pos = { x = 0, y = 0 }, wpns = {"w_shotgun"}, damage = {2, 3, 5}, descr = {"damage"} }, machine_guns = { level = 0, max = 3, req = {all = 0}, pos = { x = 2, y = 0 }, wpns = {"w_machine_gun"}, damage = {2, 3, 5}, descr = {"damage"} }, submachine = { level = 0, max = 2, req = {all = 0}, pos = { x = 4, y = 0 }, wpns = {"w_smg"}, damage = {5, 10}, descr = {"damage"} }, hunter = { level = 0, max = 3, req = {all = 3}, pos = { x = 0, y = 1 }, wpns = {"w_shotgun", "w_machine_gun", "w_smg"}, damage = {2, 3, 5}, descr = {"damage"}, func = "talent_hunter" }, bounty_hunter = { level = 0, max = 3, req = {all = 3}, pos = { x = 2, y = 1 }, wpns = {"w_shotgun", "w_machine_gun", "w_smg"}, damage = {2, 3, 5}, descr = {"damage"}, func = "talent_bounty_hunter" }, mangle = { level = 0, max = 2, req = {all = 3}, pos = { x = 4, y = 1 }, wpns = {"w_melee"}, damage = {15, 30}, crit = {15, 30}, descr = {"damage", "crit"} }, heavy_guns_expert = { level = 0, max = 3, req = {all = 6}, pos = { x = 1, y = 2 }, wpns = {"w_shotgun", "w_machine_gun"}, damage = {2, 3, 5}, reload = {7, 13, 20}, descr = {"damage", "reload"} }, merciless = { level = 0, max = 3, req = {all = 9, heavy_guns_expert = 3}, pos = { x = 0, y = 3 }, wpns = {"w_shotgun", "w_machine_gun"}, damage = {3, 6, 10}, descr = {"damage"}, func = "talent_merciless", hinder = "fury" }, fury = { level = 0, max = 3, req = {all = 9, heavy_guns_expert = 3}, pos = { x = 2, y = 3 }, wpns = {"all"}, damage = {2, 3, 5}, descr = {"duration", "damage"}, duration = 20, func = "talent_fury", hinder = "merciless" }, bloodthirst = { level = 0, max = 1, req = {all = 9, mangle = 2}, pos = { x = 4, y = 3 }, wpns = {"w_melee"}, crit_damage = {50}, descr = {"crit_damage"} }, execution = { level = 0, max = 1, req = {all = 12, merciless = 3}, pos = { x = 0, y = 4 }, wpns = {"w_shotgun", "w_machine_gun"}, damage = {10}, hp_thresh = 40, descr = {"hp_thresh", "damage"}, func = "talent_execution" }, frightening = { level = 0, max = 1, req = {all = 12, fury = 3}, pos = { x = 2, y = 4 }, wpns = {"all"}, damage = {5}, radius = 75, descr = {"radius", "damage"}, func = "talent_frightening" }, selective = { level = 0, max = 2, req = {all = 12}, pos = { x = 4, y = 4 }, wpns = {"w_shotgun", "w_machine_gun"}, damage = {2, 5}, descr = {"damage"}, func = "talent_selective" }, mastery = { level = 0, max = 1, req = {all = 15}, pos = { x = 1, y = 5 }, wpns = {"w_shotgun", "w_machine_gun"}, reload = {10}, descr = {"reload"}, descr_size = 3, merciless_bonus = 5, execution_bonus = 15, fury_bonus = 5, frightening_bonus = 25 }, }, ["monster_hunter"] = { dog_hunter = { level = 0, max = 2, req = {all = 0}, pos = { x = 0, y = 0 }, wpns = {"all"}, damage = {7, 15}, descr = {"damage"}, func = "talent_dog_hunter" }, boar_hunter = { level = 0, max = 3, req = {all = 0}, pos = { x = 2, y = 0 }, wpns = {"all"}, damage = {5, 10, 15}, descr = {"damage"}, func = "talent_boar_hunter" }, zombie_hunter = { level = 0, max = 3, req = {all = 0}, pos = { x = 4, y = 0 }, wpns = {"all"}, damage = {5, 10, 15}, descr = {"damage"}, func = "talent_zombie_hunter" }, trophies = { level = 0, max = 1, req = {all = 3}, pos = { x = 1, y = 1 }, wpns = {"all"}, damage = {0.5}, descr = {"damage"}, descr_size = 4, func = "talent_trophies" }, deep_cut = { level = 0, max = 2, req = {all = 3}, pos = { x = 4, y = 1 }, wpns = {"w_melee"}, damage = {10, 20}, crit = {25, 50}, descr = {"damage", "crit"} }, beast_anatomy = { level = 0, max = 3, req = {all = 6, trophies = 1}, pos = { x = 0, y = 2 }, wpns = {"all"}, damage = {3, 6, 10}, descr = {"damage"}, descr_size = 2, func = "talent_beast_anatomy", hinder = "humanoid_anatomy" }, humanoid_anatomy = { level = 0, max = 3, req = {all = 6, trophies = 1}, pos = { x = 2, y = 2 }, wpns = {"all"}, damage = {3, 6, 10}, descr = {"damage"}, descr_size = 2, func = "talent_humanoid_anatomy", hinder = "beast_anatomy" }, expert_hunter = { level = 0, max = 2, req = {all = 6}, pos = { x = 4, y = 2 }, wpns = {"all"}, damage = {5, 10}, descr = {"damage"}, func = "talent_expert_hunter" }, beast_rivalry = { level = 0, max = 1, req = {all = 9, beast_anatomy = 3}, pos = { x = 0, y = 3 }, wpns = {"all"}, crit = {5}, descr = {"damage", "crit"}, func = "talent_beast_rivalry" }, humanoid_rivalry = { level = 0, max = 1, req = {all = 9, humanoid_anatomy = 3}, pos = { x = 2, y = 3 }, wpns = {"all"}, crit = {5}, descr = {"damage", "crit"}, func = "talent_humanoid_rivalry" }, master_hunter = { level = 0, max = 2, req = {all = 9}, pos = { x = 4, y = 3 }, wpns = {"all"}, damage = {5, 10}, descr = {"damage"}, func = "talent_master_hunter" }, fierce = { level = 0, max = 3, req = {all = 12}, pos = { x = 1, y = 4 }, wpns = {"all"}, damage = {1, 2, 3}, duration = 30, descr = {"duration", "damage"}, func = "talent_fierce" }, ranger = { level = 0, max = 3, req = {all = 12}, pos = { x = 3, y = 4 }, wpns = {"all"}, crit = {3, 6, 10}, descr = {"damage", "crit"}, func = "talent_ranger" }, deadly_blow = { level = 0, max = 1, req = {all = 15}, pos = { x = 2, y = 5 }, wpns = {"w_sniper", "w_rifle", "w_pistol", "w_shotgun", "w_smg", "w_machine_gun", "w_melee"}, func = "talent_deadly_blow" }, }, ["merc"] = { firearm_enthusiast = { level = 0, max = 3, req = {all = 0}, pos = { x = 0, y = 0 }, wpns = {"all"}, damage = {2, 3, 5}, descr = {"damage"} }, combat_engineer = { level = 0, max = 3, req = {all = 0}, pos = { x = 2, y = 0 }, wpns = {"w_explosive"}, damage = {10, 20, 30}, percents = {10, 20, 30}, descr = {"damage", "percents"} }, overdrive = { level = 0, max = 2, req = {all = 0}, pos = { x = 4, y = 0 }, wpns = {"all"}, damage = {5, 10}, crit = {2, 4}, duration = 600, descr = {"duration", "damage", "crit"}, descr_size = 2, func = "talent_overdrive" }, head_hunter = { level = 0, max = 1, req = {all = 3}, pos = { x = 1, y = 1 }, wpns = {"all"}, damage = {1}, descr = {"damage"}, descr_size = 3, func = "talent_head_hunter" }, arrogance = { level = 0, max = 2, req = {all = 3}, pos = { x = 4, y = 1 }, wpns = {"all"}, damage = {1, 2}, descr = {"damage"}, func = "talent_arrogance" }, aim_torso = { level = 0, max = 3, req = {all = 6, head_hunter = 1}, pos = { x = 0, y = 2 }, wpns = {"all"}, damage = {3, 6, 10}, descr = {"damage"}, func = "talent_aim_torso", hinder = "aim_limbs" }, aim_limbs = { level = 0, max = 3, req = {all = 6, head_hunter = 1}, pos = { x = 2, y = 2 }, wpns = {"all"}, crit = {2, 4, 6}, descr = {"crit"}, func = "talent_aim_limbs", hinder = "aim_torso" }, modified_bullets = { level = 0, max = 1, req = {all = 9, aim_torso = 3}, pos = { x = 0, y = 3 }, wpns = {"all"}, bleed = {0.5}, duration = 10, descr = {"duration", "bleed"} }, bullseye = { level = 0, max = 1, req = {all = 9, aim_limbs = 3}, pos = { x = 2, y = 3 }, wpns = {"all"}, crit = {10}, duration = 15, descr = {"duration", "crit"}, func = "talent_bullseye" }, searcher = { level = 0, max = 1, req = {all = 9, arrogance = 2}, pos = { x = 4, y = 3 }, wpns = {"all"} }, assault_rifles_expert = { level = 0, max = 3, req = {all = 12}, pos = { x = 0, y = 4 }, wpns = {"w_rifle"}, damage = {3, 6, 10}, crit = {1, 2, 3}, reload = {5, 10, 15}, descr = {"damage", "crit", "reload"} }, light_guns_expert = { level = 0, max = 2, req = {all = 12}, pos = { x = 2, y = 4 }, wpns = {"w_pistol", "w_smg"}, damage = {5, 10}, crit = {2, 5}, reload = {10, 20}, descr = {"damage", "crit", "reload"} }, sniper_rifles_expert = { level = 0, max = 3, req = {all = 12}, pos = { x = 4, y = 4 }, wpns = {"w_sniper"}, damage = {2, 3, 5}, crit = {5, 10, 15}, descr = {"damage", "crit"} }, liquidator = { level = 0, max = 1, req = {all = 15}, pos = { x = 2, y = 5 }, wpns = {"all"}, crit_damage = {20}, descr = {"crit_damage"} }, }, ["assassin"] = { smg_novice = { level = 0, max = 2, req = {all = 0}, pos = { x = 0, y = 0 }, wpns = {"w_smg"}, damage = {5, 10}, crit = {3, 5}, descr = {"damage", "crit"} }, handgun_novice = { level = 0, max = 2, req = {all = 0}, pos = { x = 2, y = 0 }, wpns = {"w_pistol"}, damage = {4, 8}, crit = {5, 10}, descr = {"damage", "crit"} }, knifeman = { level = 0, max = 3, req = {all = 0}, pos = { x = 4, y = 0 }, wpns = {"w_melee"}, damage = {10, 20 , 30}, crit = {10, 20, 30}, attack_speed = {13, 26, 40}, descr = {"damage", "crit", "attack_speed"} }, spot_weakness = { level = 0, max = 1, req = {all = 3}, pos = { x = 1, y = 1 }, wpns = {"w_smg", "w_pistol", "w_melee"}, crit = {3}, duration = 3, descr = {"crit"}, descr_size = 3, func = "talent_spot_weakness" }, undercover = { level = 0, max = 3, req = {all = 6, spot_weakness = 1}, pos = { x = 0, y = 2 }, wpns = {"w_smg", "w_pistol"}, damage = {5, 10, 15}, descr = {"damage"}, func = "talent_undercover", hinder = "tactical" }, tactical = { level = 0, max = 3, req = {all = 6, spot_weakness = 1}, pos = { x = 2, y = 2 }, wpns = {"w_smg", "w_pistol"}, crit = {5, 10, 15}, descr = {"crit"}, func = "talent_tactical", hinder = "undercover" }, backstab = { level = 0, max = 1, req = {all = 6, knifeman = 3}, pos = { x = 4, y = 2 }, wpns = {"w_melee"}, crit = {40}, crit_damage = {50}, descr = {"crit", "crit_damage"}, func = "talent_backstab" }, saboteur = { level = 0, max = 1, req = {all = 9, undercover = 3}, pos = { x = 0, y = 3 }, wpns = {"w_smg", "w_pistol"}, damage = {15}, descr = {"damage"}, descr_size = 2, func = "talent_saboteur" }, surprise_attack = { level = 0, max = 1, req = {all = 9, tactical = 3}, pos = { x = 2, y = 3 }, wpns = {"w_smg", "w_pistol"}, crit = {25}, crit_damage = {100}, descr = {"crit", "crit_damage"}, descr_size = 2, func = "talent_surprise_attack" }, assassination = { level = 0, max = 1, req = {all = 9, backstab = 1}, pos = { x = 4, y = 3 }, wpns = {"w_melee"}, crit_damage = {200}, descr = {"crit_damage"}, func = "talent_assassination" }, vile = { level = 0, max = 3, req = {all = 12}, pos = { x = 1, y = 4 }, wpns = {"w_smg"}, damage = {7, 14, 20}, descr = {"damage"}, func = "talent_vile" }, pragmatic = { level = 0, max = 3, req = {all = 12}, pos = { x = 3, y = 4 }, wpns = {"w_pistol"}, crit = {7, 14, 20}, crit_damage = {17, 34, 50}, descr = {"crit", "crit_damage"}, descr_size = 2, func = "talent_pragmatic" }, assassin_observant = { level = 0, max = 1, req = {all = 15}, pos = { x = 2, y = 5 }, wpns = {"all"} }, }, }, ["defensive"] = { ["sniper"] = { adaptive = { level = 0, max = 2, req = {all = 0}, pos = { x = 0, y = 0 }, outfits = {"o_medium", "o_light"}, burn = {7, 15}, chem = {7, 15}, shock = {7, 15}, descr = {"burn", "chem", "shock"} }, mild = { level = 0, max = 1, req = {all = 0}, pos = { x = 2, y = 0 }, outfits = {"o_medium", "o_light"} }, iron_stomach = { level = 0, max = 2, req = {all = 0}, pos = { x = 4, y = 0 }, outfits = {"o_medium", "o_light"}, radi = {5, 10}, rad_food = {25, 50}, descr = {"radi", "rad_food"} }, plastic_pads = { level = 0, max = 3, req = {all = 3}, pos = { x = 0, y = 1 }, outfits = {"o_medium", "o_light"}, rup = {3, 6, 10}, strike = {3, 6, 10}, descr = {"rup", "strike"} }, aluminum_plates = { level = 0, max = 3, req = {all = 3}, pos = { x = 2, y = 1 }, outfits = {"o_medium", "o_light"}, bal = {3, 6, 10}, expl = {3, 6, 10}, descr = {"bal", "expl"} }, nature_resistance = { level = 0, max = 3, req = {all = 6}, pos = { x = 1, y = 2 }, outfits = {"o_medium", "o_light"}, burn = {7, 13, 20}, chem = {7, 13, 20}, shock = {7, 13, 20}, radi = {5, 10, 15}, descr = {"burn", "chem", "shock", "radi"} }, rad_resistant = { level = 0, max = 1, req = {all = 6, iron_stomach = 2}, pos = { x = 4, y = 2 }, outfits = {"o_medium", "o_light"}, radi = {10}, descr = {"radi"}, descr_size = 2 }, technician = { level = 0, max = 1, req = {all = 9, nature_resistance = 3}, pos = { x = 0, y = 3 }, outfits = {"o_medium", "o_light"}, burn = {10}, chem = {10}, shock = {10}, radi = {10}, rup = {10}, strike = {10}, bal = {10}, expl = {10}, descr = {"burn", "chem", "shock", "radi", "rup", "strike", "bal", "expl"}, descr_size = 3, hinder = "inventor" }, inventor = { level = 0, max = 1, req = {all = 9, nature_resistance = 3}, pos = { x = 2, y = 3 }, outfits = {"o_light"}, hinder = "technician" }, expert_technician = { level = 0, max = 3, req = {all = 12, technician = 1}, pos = { x = 0, y = 4 }, outfits = {"o_medium", "o_light"}, burn = {0.05, 0.1, 0.15}, chem = {0.05, 0.1, 0.15}, shock = {0.05, 0.1, 0.15}, radi = {0.05, 0.1, 0.15}, descr = {"burn", "chem", "shock", "radi"}, descr_size = 2, func = "talent_expert_technician" }, agile = { level = 0, max = 3, req = {all = 12, inventor = 1}, pos = { x = 2, y = 4 }, outfits = {"o_light"}, burn = {5, 10, 15}, chem = {5, 10, 15}, shock = {5, 10, 15}, radi = {5, 10, 15}, rup = {5, 10, 15}, strike = {5, 10, 15}, bal = {5, 10, 15}, expl = {5, 10, 15}, evade = {5, 10, 15}, descr = {"burn", "chem", "shock", "radi", "rup", "strike", "bal", "expl", "evade"}, descr_size = 4, func = "talent_agile" }, reflexes = { level = 0, max = 1, req = {all = 15}, pos = { x = 1, y = 5 }, outfits = {"o_medium", "o_light"}, cooldown = 5 }, }, ["trooper"] = { ceramic_plates = { level = 0, max = 2, req = {all = 0}, pos = { x = 0, y = 0 }, outfits = {"o_medium", "o_heavy", "o_sci"}, rup = {2, 5}, strike = {2, 5}, bal = {2, 5}, expl = {2, 5}, descr = {"rup", "strike", "bal", "expl"}, descr_size = 2, func = "talent_trooper_general_armor" }, first_aid = { level = 0, max = 2, req = {all = 0}, pos = { x = 2, y = 0 }, outfits = {"o_medium", "o_heavy", "o_sci"}, heal = {10, 20}, descr = {"heal"} }, isolated = { level = 0, max = 2, req = {all = 0}, pos = { x = 4, y = 0 }, outfits = {"o_medium", "o_heavy", "o_sci"}, shock = {5, 10}, descr = {"shock"}, func = "talent_trooper_general_armor" }, armorer = { level = 0, max = 2, req = {all = 3}, pos = { x = 1, y = 1 }, outfits = {"o_medium", "o_heavy", "o_sci"}, rup = {2, 5}, strike = {2, 5}, bal = {2, 5}, expl = {2, 5}, descr = {"rup", "strike", "bal", "expl"}, descr_size = 2, func = "talent_trooper_general_armor" }, chem_resistant = { level = 0, max = 2, req = {all = 3}, pos = { x = 4, y = 1 }, outfits = {"o_medium", "o_heavy", "o_sci"}, chem = {5, 10}, descr = {"chem"}, func = "talent_trooper_general_armor" }, suppresion_fire = { level = 0, max = 3, req = {all = 6, armorer = 2}, pos = { x = 0, y = 2 }, outfits = {"o_medium", "o_heavy", "o_sci"}, rup = {2, 4, 6}, strike = {2, 4, 6}, bal = {2, 4, 6}, expl = {2, 4, 6}, descr = {"duration", "rup", "strike", "bal", "expl"}, descr_size = 2, duration = 5, func = "talent_suppresion_fire", hinder = "cautious" }, cautious = { level = 0, max = 3, req = {all = 6, armorer = 2}, pos = { x = 2, y = 2 }, outfits = {"o_medium", "o_sci"}, rup = {3, 6, 10}, strike = {3, 6, 10}, bal = {3, 6, 10}, expl = {3, 6, 10}, descr = {"rup", "strike", "bal", "expl", "cooldown"}, descr_size = 3, cooldown = 20, func = "talent_cautious", hinder = "suppresion_fire" }, fireproof = { level = 0, max = 2, req = {all = 6}, pos = { x = 4, y = 2 }, outfits = {"o_medium", "o_heavy", "o_sci"}, burn = {5, 10}, descr = {"burn"}, func = "talent_trooper_general_armor" }, composure = { level = 0, max = 1, req = {all = 9, suppresion_fire = 3}, pos = { x = 0, y = 3 }, outfits = {"o_medium", "o_heavy", "o_sci"}, rup = {5}, strike = {5}, bal = {5}, expl = {5}, descr = {"rup", "strike", "bal", "expl"}, descr_size = 4, func = "talent_composure" }, enduring = { level = 0, max = 1, req = {all = 9, cautious = 3}, pos = { x = 2, y = 3 }, outfits = {"o_medium", "o_sci"}, burn = {10}, chem = {10}, shock = {10}, descr = {"burn", "chem", "shock"}, descr_size = 2 }, adorned = { level = 0, max = 3, req = {all = 12}, pos = { x = 0, y = 4 }, outfits = {"o_medium", "o_heavy", "o_sci"}, burn = {0.7, 1.3, 2}, chem = {0.7, 1.3, 2}, shock = {0.7, 1.3, 2}, descr = {"burn", "chem", "shock"}, descr_size = 3, func = "talent_adorned" }, expert_armorer = { level = 0, max = 3, req = {all = 12}, pos = { x = 2, y = 4 }, outfits = {"o_medium", "o_heavy", "o_sci"}, burn = {0.3, 0.6, 1}, chem = {0.3, 0.6, 1}, shock = {0.3, 0.6, 1}, descr = {"burn", "chem", "shock"}, descr_size = 3, func = "talent_expert_armorer" }, last_stand = { level = 0, max = 3, req = {all = 12}, pos = { x = 4, y = 4 }, outfits = {"o_medium", "o_heavy", "o_sci"}, burn = {7, 13, 20}, chem = {7, 13, 20}, shock = {7, 13, 20}, rup = {7, 13, 20}, strike = {7, 13, 20}, bal = {7, 13, 20}, expl = {7, 13, 20}, descr = {"burn", "chem", "shock", "rup", "strike", "bal", "expl"}, descr_size = 3, func = "talent_last_stand" }, leap = { level = 0, max = 1, req = {all = 15}, pos = { x = 1, y = 5 }, outfits = {"o_medium", "o_heavy", "o_sci"}, rup = {15}, strike = {15}, bal = {15}, expl = {15}, descr = {"duration", "rup", "strike", "bal", "expl"}, descr_size = 3, duration = 5, func = "talent_leap" }, }, ["jugger"] = { steel_plates = { level = 0, max = 2, req = {all = 0}, pos = { x = 0, y = 0 }, outfits = {"o_medium", "o_heavy"}, bal = {5, 10}, expl = {5, 10}, descr = {"bal", "expl"} }, steel_pads = { level = 0, max = 2, req = {all = 0}, pos = { x = 2, y = 0 }, outfits = {"o_medium", "o_heavy"}, rup = {5, 10}, strike = {5, 10}, descr = {"rup", "strike"} }, regenerative = { level = 0, max = 1, req = {all = 0}, pos = { x = 4, y = 0 }, outfits = {"o_medium", "o_heavy"} }, heavy_armor_expert = { level = 0, max = 3, req = {all = 3}, pos = { x = 1, y = 1 }, outfits = {"o_medium", "o_heavy"}, rup = {2, 3, 5}, strike = {2, 3, 5}, bal = {2, 3, 5}, expl = {2, 3, 5}, psi = {3, 6, 10}, weight = {10, 20, 30}, descr = {"rup", "strike", "bal", "expl", "psi", "weight"}, descr_size = 2 }, dismorale = { level = 0, max = 3, req = {all = 6, heavy_armor_expert = 3}, pos = { x = 0, y = 2 }, outfits = {"o_medium", "o_heavy"}, rup = {2, 4, 7}, strike = {2, 4, 7}, bal = {2, 4, 7}, expl = {2, 4, 7}, descr = {"duration", "rup", "strike", "bal", "expl"}, descr_size = 2, duration = 20, func = "talent_dismorale", hinder = "sturdy" }, sturdy = { level = 0, max = 3, req = {all = 6, heavy_armor_expert = 3}, pos = { x = 2, y = 2 }, outfits = {"o_medium", "o_heavy"}, rup = {2, 4, 6}, strike = {2, 4, 6}, bal = {2, 4, 6}, expl = {2, 4, 6}, descr = {"duration", "rup", "strike", "bal", "expl"}, descr_size = 2, duration = 20, func = "talent_sturdy", hinder = "dismorale" }, sanity = { level = 0, max = 2, req = {all = 6}, pos = { x = 4, y = 2 }, outfits = {"o_medium", "o_heavy"}, psi = {15, 30}, descr = {"psi"} }, anxiety = { level = 0, max = 1, req = {all = 9, dismorale = 3}, pos = { x = 0, y = 3 }, outfits = {"o_medium", "o_heavy"}, rup = {5}, strike = {5}, bal = {5}, expl = {5}, descr = {"rup", "strike", "bal", "expl"}, descr_size = 2 }, turmoil = { level = 0, max = 1, req = {all = 9, sturdy = 3}, pos = { x = 2, y = 3 }, outfits = {"o_medium", "o_heavy"}, rup = {10}, strike = {10}, bal = {10}, expl = {10}, descr = {"rup", "strike", "bal", "expl", "radius"}, descr_size = 3, radius = 60, func = "talent_turmoil" }, bulky = { level = 0, max = 3, req = {all = 12}, pos = { x = 0, y = 4 }, outfits = {"o_medium", "o_heavy"}, rup = {5, 10, 15}, strike = {5, 10, 15}, bal = {5, 10, 15}, expl = {5, 10, 15}, speed = {-15}, descr = {"rup", "strike", "bal", "expl"}, descr_size = 2 }, secure = { level = 0, max = 3, req = {all = 12}, pos = { x = 2, y = 4 }, outfits = {"o_medium", "o_heavy"}, rup = {1.7, 3.5, 5}, strike = {1.7, 3.5, 5}, bal = {1.7, 3.5, 5}, expl = {1.7, 3.5, 5}, descr = {"rup", "strike", "bal", "expl"}, descr_size = 2, func = "talent_secure" }, lucid = { level = 0, max = 3, req = {all = 12, sanity = 2}, pos = { x = 4, y = 4 }, outfits = {"o_medium", "o_heavy"}, rup = {0.03, 0.06, 0.1}, strike = {0.03, 0.06, 0.1}, bal = {0.03, 0.06, 0.1}, expl = {0.03, 0.06, 0.1}, descr = {"rup", "strike", "bal", "expl"}, descr_size = 2, func = "talent_lucid" }, robust = { level = 0, max = 1, req = {all = 15}, pos = { x = 1, y = 5 }, outfits = {"o_medium", "o_heavy"}, rup = {50}, strike = {50}, bal = {50}, expl = {50}, descr = {"rup", "strike", "bal", "expl", "cooldown"}, descr_size = 2, cooldown = 240, func = "talent_robust" }, }, ["monster_hunter"] = { shoulder_pads = { level = 0, max = 2, req = {all = 0}, pos = { x = 1, y = 0 }, outfits = {"all"}, rup = {2, 5}, strike = {2, 5}, descr = {"rup", "strike"}, func = "talent_monster_hunter_general_armor" }, careful = { level = 0, max = 2, req = {all = 0}, pos = { x = 3, y = 0 }, outfits = {"all"}, burn = {5, 10}, chem = {5, 10}, shock = {5, 10}, descr = {"burn", "chem", "shock"}, func = "talent_monster_hunter_general_armor" }, melee_defense = { level = 0, max = 2, req = {all = 3}, pos = { x = 0, y = 1 }, outfits = {"all"}, rup = {7, 15}, strike = {7, 15}, descr = {"rup", "strike"}, func = "talent_melee_defense" }, special_diet = { level = 0, max = 2, req = {all = 3}, pos = { x = 2, y = 1 }, outfits = {"all"}, burn = {5, 10}, chem = {5, 10}, shock = {5, 10}, psi = {5, 10}, duration = 900, descr = {"duration", "burn", "chem", "shock", "psi"}, descr_size = 2, func = "talent_special_diet" }, clarity = { level = 0, max = 2, req = {all = 3}, pos = { x = 4, y = 1 }, outfits = {"all"}, psi = {5, 10}, descr = {"psi"}, func = "talent_monster_hunter_general_armor" }, tracker = { level = 0, max = 1, req = {all = 6}, pos = { x = 0, y = 2 }, outfits = {"all"} }, soft_armor = { level = 0, max = 3, req = {all = 6}, pos = { x = 2, y = 2 }, outfits = {"all"}, rup = {3, 6, 10}, strike = {3, 6, 10}, descr = {"rup", "strike"}, func = "talent_monster_hunter_general_armor" }, stable_mind = { level = 0, max = 1, req = {all = 6, clarity = 2}, pos = { x = 4, y = 2 }, outfits = {"all"}, psi = {20}, descr = {"psi"} }, beast_demeanor = { level = 0, max = 1, req = {all = 9, soft_armor = 3}, pos = { x = 1, y = 3 }, outfits = {"all"}, rup = {10}, strike = {10}, descr = {"rup", "strike"}, func = "talent_beast_demeanor", hinder = "humanoid_demeanor" }, humanoid_demeanor = { level = 0, max = 1, req = {all = 9, soft_armor = 3}, pos = { x = 3, y = 3 }, outfits = {"all"}, rup = {10}, strike = {10}, descr = {"rup", "strike"}, func = "talent_humanoid_demeanor", hinder = "beast_demeanor" }, second_skin = { level = 0, max = 3, req = {all = 12}, pos = { x = 1, y = 4 }, outfits = {"all"}, rup = {0.7, 1.4, 2.0}, strike = {0.7, 1.4, 2.0}, descr = {"rup", "strike"}, func = "talent_second_skin" }, custom_fit = { level = 0, max = 3, req = {all = 12}, pos = { x = 3, y = 4 }, outfits = {"o_medium"}, bal = {5, 10, 15}, expl = {5, 10, 15}, rup = {5, 10, 15}, strike = {5, 10, 15}, burn = {5, 10, 15}, chem = {5, 10, 15}, shock = {5, 10, 15}, psi = {5, 10, 15}, descr = {"bal", "expl", "rup", "strike", "burn", "chem", "shock", "psi"}, descr_size = 3 }, kindred = { level = 0, max = 1, req = {all = 15}, pos = { x = 2, y = 5 }, outfits = {"all"}, descr_size = 4, cooldown = 28800 }, }, ["merc"] = { mercenary_vest = { level = 0, max = 3, req = {all = 0}, pos = { x = 0, y = 0 }, outfits = {"o_medium", "o_heavy"}, bal = {2, 4, 7}, expl = {2, 4, 7}, descr = {"bal", "expl"} }, insensitive = { level = 0, max = 2, req = {all = 0}, pos = { x = 2, y = 0 }, outfits = {"o_medium", "o_heavy"}, burn = {10, 20}, chem = {10, 20}, shock = {10, 20}, descr = {"burn", "chem", "shock"} }, chem_reliant = { level = 0, max = 3, req = {all = 0}, pos = { x = 4, y = 0 }, outfits = {"o_medium", "o_heavy"}, bal = {4, 8, 13}, expl = {4, 8, 13}, duration = 600, descr = {"duration", "bal", "expl"}, descr_size = 2, func = "talent_chem_reliant" }, grenadier = { level = 0, max = 2, req = {all = 3}, pos = { x = 1, y = 1 }, outfits = {"o_medium", "o_heavy"}, bal = {5, 10}, expl = {5, 10}, duration = 20, descr = {"duration", "bal", "expl"}, descr_size = 2, func = "talent_grenadier" }, confusion = { level = 0, max = 3, req = {all = 6}, pos = { x = 1, y = 2 }, outfits = {"o_medium", "o_heavy"}, bal = {3, 6, 10}, expl = {3, 6, 10}, descr = {"bal", "expl"}, descr_size = 2, func = "talent_confusion" }, addictive = { level = 0, max = 1, req = {all = 6, chem_reliant = 3}, pos = { x = 4, y = 2 }, outfits = {"o_medium", "o_heavy"}, burn = {20}, chem = {20}, shock = {20}, descr = {"burn", "chem", "shock"}, descr_size = 2, func = "talent_addictive" }, direct = { level = 0, max = 3, req = {all = 9, confusion = 3}, pos = { x = 0, y = 3 }, outfits = {"o_medium", "o_heavy"}, percents = {22, 44, 66}, descr = {"percents"}, hinder = "disguise_master" }, disguise_master = { level = 0, max = 3, req = {all = 9, confusion = 3}, pos = { x = 2, y = 3 }, outfits = {"o_medium"}, disguise = {17, 34, 50}, descr = {"disguise"}, hinder = "direct" }, flurry = { level = 0, max = 1, req = {all = 12, direct = 3}, pos = { x = 0, y = 4 }, outfits = {"o_medium", "o_heavy"}, bal = {10}, expl = {10}, duration = 2, descr = {"duration", "bal", "expl"}, func = "talent_flurry" }, loose_fit = { level = 0, max = 1, req = {all = 12, disguise_master = 3}, pos = { x = 2, y = 4 }, outfits = {"o_medium"}, bal = {10}, expl = {10}, rup = {20}, strike = {20}, descr = {"bal", "expl", "rup", "strike"} }, reposition = { level = 0, max = 1, req = {all = 15}, pos = { x = 1, y = 5 }, outfits = {"o_medium", "o_heavy"}, bal = {10}, expl = {10}, duration = 6, descr = {"duration", "bal", "expl"}, descr_size = 2, func = "talent_reposition" }, }, ["assassin"] = { ordinary = { level = 0, max = 3, req = {all = 0}, pos = { x = 0, y = 0 }, outfits = {"all"}, disguise = {2, 3, 5}, descr = {"disguise"}, func = "talent_ordinary" }, acrobatic = { level = 0, max = 1, req = {all = 0}, pos = { x = 2, y = 0 }, outfits = {"o_medium", "o_sci", "o_light"} }, quiet = { level = 0, max = 3, req = {all = 0}, pos = { x = 4, y = 0 }, outfits = {"all"}, stealth = {3, 6, 10}, descr = {"stealth"}, func = "talent_quiet" }, evanescent = { level = 0, max = 1, req = {all = 3, ordinary = 3}, pos = { x = 0, y = 1 }, outfits = {"all"}, disguise = {5}, descr = {"disguise"}, descr_size = 2 }, lurking = { level = 0, max = 3, req = {all = 3}, pos = { x = 2, y = 1 }, outfits = {"all"}, disguise = {3, 6, 10}, stealth = {3, 6, 10}, descr = {"disguise", "stealth"}, func = "talent_lurking" }, subtle = { level = 0, max = 1, req = {all = 3, quiet = 3}, pos = { x = 4, y = 1 }, outfits = {"all"}, stealth = {5}, descr = {"stealth"}, descr_size = 2 }, covert = { level = 0, max = 1, req = {all = 6, lurking = 3}, pos = { x = 1, y = 2 }, outfits = {"o_medium", "o_sci"}, bal = {20}, expl = {20}, rup = {20}, strike = {20}, descr = {"bal", "expl", "rup", "strike"}, descr_size = 3, hinder = "cunning" }, cunning = { level = 0, max = 1, req = {all = 6, lurking = 3}, pos = { x = 3, y = 2 }, outfits = {"o_light"}, evade = {25}, cooldown = 3, descr = {"evade"}, descr_size = 2, hinder = "covert" }, deception = { level = 0, max = 3, req = {all = 9}, pos = { x = 0, y = 3 }, outfits = {"all"}, disguise = {2, 3, 5}, descr = {"disguise"}, func = "talent_deception" }, vague = { level = 0, max = 3, req = {all = 9}, pos = { x = 2, y = 3 }, outfits = {"all"}, disguise = {2, 3, 5}, stealth = {2, 3, 5}, descr = {"disguise", "stealth"}, descr_size = 2, func = "talent_vague" }, sneaky = { level = 0, max = 3, req = {all = 9}, pos = { x = 4, y = 3 }, outfits = {"all"}, stealth = {2, 3, 5}, descr = {"stealth"}, func = "talent_sneaky" }, incognito = { level = 0, max = 3, req = {all = 12, covert = 1}, pos = { x = 1, y = 4 }, outfits = {"o_medium", "o_sci"}, bal = {7, 14, 20}, expl = {7, 14, 20}, rup = {7, 14, 20}, strike = {7, 14, 20}, disguise = {3, 6, 10}, descr = {"bal", "expl", "rup", "strike", "disguise"}, descr_size = 2 }, ghost = { level = 0, max = 3, req = {all = 12, cunning = 1}, pos = { x = 3, y = 4 }, outfits = {"o_light"}, stealth = {3, 6, 10}, evade = {8, 16, 25}, burn = {15, 30, 45}, chem = {15, 30, 45}, shock = {15, 30, 45}, descr = {"stealth", "evade", "burn", "chem", "shock"}, descr_size = 2 }, elusive = { level = 0, max = 1, req = {all = 15}, pos = { x = 2, y = 5 }, outfits = {"o_medium", "o_sci", "o_light"}, disguise = {10}, stealth = {10}, speed = {15}, descr = {"disguise", "stealth"} }, }, }, ["endurance"] = { ["sniper"] = { ease = { level = 0, max = 3, req = {all = 0}, pos = { x = 0, y = 0 }, speed = {2, 3, 5}, descr = {"speed"}, func = "talent_ease" }, refreshing = { level = 0, max = 2, req = {all = 0}, pos = { x = 2, y = 0 }, stamina = {12, 25}, descr = {"stamina"} }, hiker = { level = 0, max = 2, req = {all = 0}, pos = { x = 4, y = 0 }, weight = {20, 40}, descr = {"weight"} }, resting = { level = 0, max = 3, req = {all = 3}, pos = { x = 1, y = 1 }, stamina = {0.33, 0.66, 1}, descr = {"stamina"}, func = "talent_resting" }, prudent = { level = 0, max = 3, req = {all = 3}, pos = { x = 3, y = 1 }, weight = {10, 20, 30}, descr = {"weight"} }, swifty = { level = 0, max = 3, req = {all = 6}, pos = { x = 0, y = 2 }, speed = {2, 3, 5}, descr = {"speed"}, func = "talent_swifty" }, tireless = { level = 0, max = 3, req = {all = 6}, pos = { x = 2, y = 2 }, stamina = {0.33, 0.66, 1}, descr = {"stamina"}, func = "talent_tireless" }, hoarder = { level = 0, max = 3, req = {all = 6}, pos = { x = 4, y = 2 }, weight = {2, 4, 6}, descr = {"weight"}, func = "talent_hoarder" }, adrenaline_rush = { level = 0, max = 1, req = {all = 9, swifty = 3}, pos = { x = 0, y = 3 }, speed = {15}, cooldown = 300, duration = 5, descr = {"duration", "speed", "cooldown"}, descr_size = 2 }, second_wind = { level = 0, max = 1, req = {all = 9, tireless = 3}, pos = { x = 2, y = 3 }, stamina = {50}, cooldown = 2400, descr = {"stamina", "cooldown"}, descr_size = 2 }, organizer = { level = 0, max = 1, req = {all = 9, hoarder = 3}, pos = { x = 4, y = 3 } }, insomnia = { level = 0, max = 2, req = {all = 12}, pos = { x = 0, y = 4 }, speed = {2, 5}, descr = {"speed"}, func = "talent_insomnia" }, courage = { level = 0, max = 2, req = {all = 12}, pos = { x = 2, y = 4 }, stamina = {0.5, 1}, duration = 5, descr = {"duration", "stamina"}, func = "talent_courage" }, belt_braces = { level = 0, max = 2, req = {all = 12}, pos = { x = 4, y = 4 }, weight = {20, 40}, descr = {"weight"} }, athletic = { level = 0, max = 1, req = {all = 15}, pos = { x = 1, y = 5 }, speed = {0.1}, descr = {"speed"}, descr_size = 2, func = "talent_athletic" }, iron_spine = { level = 0, max = 1, req = {all = 15}, pos = { x = 3, y = 5 }, weight = {0.2}, descr = {"weight"}, descr_size = 2, func = "talent_iron_spine" }, }, }, ["speech"] = { ["sniper"] = { preparation = { level = 0, max = 2, req = {all = 0}, pos = { x = 0, y = 0 }, buy = {-2, -5}, descr = {"buy"} }, confidence = { level = 0, max = 3, req = {all = 0}, pos = { x = 2, y = 0 }, defense = {3, 6, 9}, descr = {"defense"} }, supplier = { level = 0, max = 2, req = {all = 0}, pos = { x = 4, y = 0 }, sell = {2, 5}, descr = {"sell"} }, observant = { level = 0, max = 3, req = {all = 3}, pos = { x = 0, y = 1 }, buy = {-2, -4, -6}, descr = {"buy"} }, smuggler = { level = 0, max = 3, req = {all = 3}, pos = { x = 4, y = 1 }, sell = {4, 8, 12}, descr = {"sell"} }, gregarious = { level = 0, max = 3, req = {all = 6}, pos = { x = 0, y = 2 }, buy = {-2, -3, -5}, descr = {"buy"} }, encouragement = { level = 0, max = 3, req = {all = 6}, pos = { x = 2, y = 2 }, damage = {2, 4, 6}, descr = {"damage"} }, persuasion = { level = 0, max = 3, req = {all = 6}, pos = { x = 4, y = 2 }, sell = {2, 3, 5}, descr = {"sell"} }, charismatic = { level = 0, max = 3, req = {all = 9}, pos = { x = 0, y = 3 }, free = {3, 6, 10}, descr = {"free"} }, shady = { level = 0, max = 3, req = {all = 9}, pos = { x = 4, y = 3 }, stalkers_money = {1000, 2000, 3000}, sell = {3, 6, 10}, descr = {"stalkers_money", "sell"} }, artistic = { level = 0, max = 1, req = {all = 12, charismatic = 3}, pos = { x = 0, y = 4 }, cooldown = 7200, descr = {"cooldown"} }, rally = { level = 0, max = 1, req = {all = 12, encouragement = 3}, pos = { x = 2, y = 4 }, crit = {3}, descr = {"crit"} }, cynical = { level = 0, max = 1, req = {all = 12, shady = 3}, pos = { x = 4, y = 4 }, cooldown = 14400, descr_size = 3 }, well_liked = { level = 0, max = 1, req = {all = 15, artistic = 1}, pos = { x = 0, y = 5 } }, deceitful = { level = 0, max = 1, req = {all = 15, cynical = 1}, pos = { x = 4, y = 5 } }, }, }, } talents_table["endurance"]["trooper"] = talents_table["endurance"]["sniper"] talents_table["endurance"]["jugger"] = talents_table["endurance"]["sniper"] talents_table["endurance"]["monster_hunter"] = talents_table["endurance"]["sniper"] talents_table["endurance"]["merc"] = talents_table["endurance"]["sniper"] talents_table["endurance"]["assassin"] = talents_table["endurance"]["sniper"] talents_table["speech"]["trooper"] = talents_table["speech"]["sniper"] talents_table["speech"]["jugger"] = talents_table["speech"]["sniper"] talents_table["speech"]["monster_hunter"] = talents_table["speech"]["sniper"] talents_table["speech"]["merc"] = talents_table["speech"]["sniper"] talents_table["speech"]["assassin"] = talents_table["speech"]["sniper"] -- to change item weights local weight_talent_funcs = { ["demolition_expert"] = { func = "talent_weight_demolition_expert", on = {"ruck", "slot"}, off = {"drop"} }, ["heavy_armor_expert"] = { func = "talent_weight_heavy_armor_expert", on = {"ruck", "slot"}, off = {"drop"} }, ["hiker"] = { func = "talent_weight_hiker", on = {"ruck", "slot"}, off = {"drop"} }, ["prudent"] = { func = "talent_weight_prudent", on = {"ruck", "slot"}, off = {"drop"} }, ["organizer"] = { func = "talent_weight_organizer", on = {"ruck", "slot"}, off = {"drop"} }, ["belt_braces"] = { func = "talent_weight_belt_braces", on = {"belt"}, off = {"ruck", "drop"} }, } -- mutants table local mutants_t = { ["beast"] = { tushkano = "encyclopedia_mutants_tushkano", flesh = "encyclopedia_mutants_flesh", dog = "encyclopedia_mutants_blind_dog", boar = "encyclopedia_mutants_boar", cat = "encyclopedia_mutants_cat", pseudodog = "encyclopedia_mutants_pseudodog", SM_LURKER = "encyclopedia_mutants_lurker", chimera = "encyclopedia_mutants_chimera", giant = "encyclopedia_mutants_pseudogiant", }, ["hum"] = { zombie = "encyclopedia_mutants_zombie", fracture = "encyclopedia_mutants_fracture", snork = "encyclopedia_mutants_snork", SM_POLTER_G = "encyclopedia_mutants_poltergeist", SM_PYRO_G = "encyclopedia_mutants_pyrogeist", SM_PSEUDO_G = "encyclopedia_mutants_pseudogeist", SM_KARLIK = "encyclopedia_mutants_karlik", burer = "encyclopedia_mutants_burer", bloodsucker = "encyclopedia_mutants_bloodsucker", SM_PSYSUCKER = "encyclopedia_mutants_psysucker", controller = "encyclopedia_mutants_controller", }, } local stalker_comms_t = { stalker = true, monolith = true, csky = true, army = true, bandit = true, killer = true, ecolog = true, dolg = true, freedom = true, renegade = true, greh = true, } -- stalker torso bones table local stalker_bones_t = { bip01_l_clavicle = true, bip01_r_clavicle = true, bip01_spine2 = true, bip01_spine1 = true, bip01_spine = true, bip01_pelvis = true, } -- stalker head bones table local stalker_head_bones_t = { bip01_neck = true, bip01_head = true, eyelid_1 = true, eye_left = true, eye_right = true, jaw_1 = true, } -- actor_hits_npc function actor_hit_npc(npc, s_hit, bone_id, flags) local draft = s_hit.draftsman local draft_fits = draft and (IsStalker(draft) or IsMonster(draft)) if not (draft_fits and draft.alive and draft:alive() and draft:id() == 0) then return end if not (npc:alive()) then return end -- local wpn = draft:active_item() local wpn_id = s_hit.weapon_id local wpn = wpn_id and level.object_by_id(wpn_id) local kind = wpn and ini_sys:r_string_ex(wpn:section(), "kind") if not kind then return end -- machine guns kind = machine_guns_t[wpn:section()] and "w_machine_gun" or kind -- sniper if picked_class == "sniper" then -- deep_wound local deep_wound_t = talents_table["offensive"]["sniper"]["deep_wound"] if deep_wound_t.level > 0 and kind == "w_sniper" then deep_wound_var[npc:id()] = {} deep_wound_var[npc:id()].time = 60000 deep_wound_var[npc:id()].value = deep_wound_t.bleed[deep_wound_t.level] / 100 pr("adding bleed val: %s || to id: %s", deep_wound_t.bleed[deep_wound_t.level] / 100, npc:id()) end end -- merc if picked_class == "merc" then -- modified_bullets local modified_bullets_t = talents_table["offensive"]["merc"]["modified_bullets"] if modified_bullets_t.level > 0 and IsStalker(npc) then local bone_name = bone_id and npc:bone_name(bone_id) if bone_name and stalker_bones_t[bone_name] then modified_bullets_var[npc:id()] = {} modified_bullets_var[npc:id()].time = modified_bullets_t.duration * 1000 modified_bullets_var[npc:id()].value = modified_bullets_t.bleed[modified_bullets_t.level] / 100 pr("merc, modified_bullets, add val: %s || to id: %s", modified_bullets_t.bleed[modified_bullets_t.level] / 100, npc:id()) end end end -- trooper suppresion_fire if picked_class == "trooper" and talents_table["defensive"]["trooper"]["suppresion_fire"].level > 0 then durations_t.suppresion_fire_var = talents_table["defensive"]["trooper"]["suppresion_fire"].duration end -- endurance courage if talents_table["endurance"]["sniper"]["courage"].level > 0 then pr("courage, reset duration") durations_t.courage_var = talents_table["endurance"]["sniper"]["courage"].duration end -- calc actor hit from talents local pwr = 80 local crit = 0 local crit_dmg = 150 local class_t = talents_table["offensive"][picked_class] if class_t then for name, t in pairs(class_t) do -- talent picked and (for all weapons or active weapon matches) if t.level > 0 and (in_ar(t.wpns, "all") or in_ar(t.wpns, kind)) then pr("----------- OFFENSIVE -----------") pr("adding talent: %s || lvl: %s || wpn: %s", name, t.level, kind) -- add damage if t.damage then local val = t.damage[t.level] if this[t.func] then val = this[t.func](npc, val, bone_id) or 0 end pr("+damage: %s", val) pwr = pwr + val end -- add crit if t.crit then local val = t.crit[t.level] if this[t.func] then val = this[t.func](npc, val, bone_id) or 0 end pr("+crit: %s", val) crit = crit + val end -- add crit damage if t.crit_damage then local val = t.crit_damage[t.level] if this[t.func] then val = this[t.func](npc, val) or 0 end pr("+crit_damage: %s", val) crit_dmg = crit_dmg + val end -- trooper barrage set cd if name == "barrage" then durations_t.barrage_var = talents_table["offensive"]["trooper"]["barrage"].duration end -- set thrill var if name == "thrill" and thrill_var then thrill_var = false end -- set fierce duration if IsMonster(npc) and name == "fierce" then durations_t.fierce_var = talents_table["offensive"]["monster_hunter"]["fierce"].duration fierce_stacks = fierce_stacks + 1 if fierce_stacks > 5 then fierce_stacks = 5 end end -- monster hunter deadly blow if name == "deadly_blow" and deadly_blow_var then deadly_blow_var = false CreateTimeEvent("deadly_blow_delay_e", "deadly_blow_delay_a", 0.15, function() deadly_blow_var = true return true end) if talent_deadly_blow(npc, kind) then pwr = pwr + 100000 end end -- merc bullseye duration if name == "bullseye" then local bone_name = bone_id and npc:bone_name(bone_id) if bone_name and stalker_head_bones_t[bone_name] then CreateTimeEvent("bullseye_check_e_" .. npc:id(), "bullseye_check_a_" .. npc:id(), 0.5, function(id) local stalker = id and level.object_by_id(id) if stalker and IsStalker(stalker) and (not stalker:alive()) then -- stalker died from this headshot durations_t.bullseye_var = talents_table["offensive"]["merc"]["bullseye"].duration end return true end, npc:id()) end end -- merc liquidator if name == "liquidator" and liquidator_var then liquidator_var = false CreateTimeEvent("liquidator_delay_e", "liquidator_delay_a", 0.15, function() liquidator_var = true return true end) if talent_liquidator(npc) then pwr = pwr + 100000 end end -- assassin spot_weakness reset if name == "spot_weakness" then spot_weakness_stacks = 0 durations_t.spot_weakness_var = 0 end end end end -- new hit local new_hit = s_hit.power * (pwr / 100) pr("s_hit.power: %s || pwr: %s% || new_hit: %s", round_idp(s_hit.power, 4), pwr, round_idp(new_hit, 4)) -- crit if crit > 0 and (math.random(1, 100) <= crit) then new_hit = new_hit * (crit_dmg / 100) pr("crit proced, new_hit: %s", round_idp(new_hit, 4)) end -- set hit power s_hit.power = new_hit pr("~~~~~~~~~~~~~~~~~~~~~") end ------------------------------------- -- actor_before_hit function actor_on_before_hit(s_hit, bone_id, flags) if s_hit.power <= 0 then return end if s_hit.type == hit.telepatic then return end -- patched below local pwr = 120 local class_t = talents_table["defensive"][picked_class] local hit_type = s_hit.type hit_type = hit_type == hit.light_burn and hit.burn or hit_type -- sniper local draft = s_hit.draftsman if picked_class == "sniper" then local outfit = db.actor:item_in_slot(7) local kind = outfit and ini_sys:r_string_ex(outfit:section(), "kind") -- mild (fall dmg) if s_hit.type == hit.strike and kind and draft and draft:id() == 0 and talents_table["defensive"]["sniper"]["mild"].level > 0 then local val = (kind == "o_medium" and 45) or (kind == "o_light" and 70) or 0 pr("mild val: %s", val) pwr = pwr - val end -- reflexes (evasion) if (cooldowns_t.evasion_cd <= 0) and s_hit.power > 0.05 and kind and (not draft or draft:id() ~= 0) and talents_table["defensive"]["sniper"]["reflexes"].level > 0 then local agile_t = talents_table["defensive"]["sniper"]["agile"] local agile_evasion = agile_t.level > 0 and agile_t.evade[agile_t.level] or 0 local evasion_chance = (kind == "o_medium" and 35) or (kind == "o_light" and 35 + agile_evasion) or 0 if evasion_chance > 0 and math.random(1, 100) <= evasion_chance then play_evasion_anm() cooldowns_t.evasion_cd = talents_table["defensive"]["sniper"]["reflexes"].cooldown s_hit.power = 0 flags.ret_value = false return end end end -- jugger if picked_class == "jugger" then -- fury if draft and (IsStalker(draft) or IsMonster(draft)) then durations_t.fury_var = talents_table["offensive"]["jugger"]["fury"].duration end end -- endurance adrenaline_rush if s_hit.power > 0.2 and talents_table["endurance"]["sniper"]["adrenaline_rush"].level > 0 and cooldowns_t.adrenaline_rush_var <= 0 then local adrenaline_rush_t = talents_table["endurance"]["sniper"]["adrenaline_rush"] local val = adrenaline_rush_t.speed[adrenaline_rush_t.level] pr("adrenaline_rush triggered") cooldowns_t.adrenaline_rush_var = adrenaline_rush_t.cooldown durations_t.adrenaline_rush_var = adrenaline_rush_t.duration speed.add_speed("adrenaline_rush", 1 + val / 100, false, true) end -- assassin if picked_class == "assassin" then local outfit = db.actor:item_in_slot(7) local kind = outfit and ini_sys:r_string_ex(outfit:section(), "kind") -- cunning (fall dmg) if s_hit.type == hit.strike and kind and draft and draft:id() == 0 and talents_table["defensive"]["assassin"]["cunning"].level > 0 and kind == "o_light" then local val = 40 pr("cunning val: %s", val) pwr = pwr - val end -- cunning and ghost (evasion) if (cooldowns_t.cunning_cd <= 0) and s_hit.power > 0.05 and kind and (not draft or draft:id() ~= 0) and kind == "o_light" then local cunning_t = talents_table["defensive"]["assassin"]["cunning"] local ghost_t = talents_table["defensive"]["assassin"]["ghost"] local cunning_chance = cunning_t.level > 0 and cunning_t.evade[cunning_t.level] or 0 local ghost_chance = ghost_t.level > 0 and ghost_t.evade[ghost_t.level] or 0 local evade_chance = cunning_chance + ghost_chance pr("assassin, evade chance: %s", evade_chance) if math.random(1, 100) <= evade_chance then pr("- assassin, evade proc") play_evasion_anm() cooldowns_t.cunning_cd = cunning_t.cooldown s_hit.power = 0 flags.ret_value = false return end end end -- calc actor resists from talents if class_t then for name, t in pairs(class_t) do if t.level > 0 and (outfit_type(t.outfits) or in_ar(t.outfits, "all")) then pr("----------- DEFENSIVE -----------") pr("adding talent: %s || lvl: %s || type: %s", name, t.level, hit_convert[hit_type]) local t_type = hit_convert[hit_type] if t[t_type] then local val = t[t_type][t.level] if this[t.func] then val = this[t.func](val, draft) or 0 end -- if has sniper inventor if outfit_type({"o_light"}) and talents_table["defensive"]["sniper"]["inventor"].level > 0 then val = val * 2 end -- if has merc direct if picked_class == "merc" and outfit_type({"o_heavy"}) then local direct_t = talents_table["defensive"]["merc"]["direct"] if direct_t.level > 0 then local direct_val = direct_t.percents[direct_t.level] / 100 val = val * direct_val else val = 0 end end pr("+res: %s", val) pwr = pwr - val end -- trooper composure if name == "composure" and db.actor.power > 0.5 then local stamina_hit = s_hit.power * 0.05 * 2 if db.actor.power > stamina_hit then db.actor.power = db.actor.power - stamina_hit end end -- jugger sturdy set duration if name == "sturdy" then durations_t.sturdy_var = t.duration end end end end -- new hit local new_hit = s_hit.power * (pwr / 100) pr("s_hit.power: %s || pwr: %s% || new_hit: %s", round_idp(s_hit.power, 4), pwr, round_idp(new_hit, 4)) -- set hit power s_hit.power = new_hit pr("~~~~~~~~~~~~~~~~~~~~~") end -- patch arszy psi talents_base_arszi_psy_prot = arszi_psy.get_telepatic_protection_total function arszi_psy.get_telepatic_protection_total() local jugger_defensive_t = talents_table["defensive"]["jugger"] local monster_hunter_defensive_t = talents_table["defensive"]["monster_hunter"] local psi_mult = 0.8 local val = 0 -- jugger heavy_armor_expert local heavy_armor_expert_t = jugger_defensive_t["heavy_armor_expert"] if heavy_armor_expert_t.level > 0 and outfit_type(heavy_armor_expert_t.outfits) then val = val + (heavy_armor_expert_t.psi[heavy_armor_expert_t.level] / 100) pr("psi, heavy_armor_expert: +%s%", val * 100) end -- jugger sanity local sanity_t = jugger_defensive_t["sanity"] if sanity_t.level > 0 and outfit_type(sanity_t.outfits) then val = val + (sanity_t.psi[sanity_t.level] / 100) pr("psi, sanity: +%s%", val * 100) end -- monster hunter clarity local clarity_t = monster_hunter_defensive_t["clarity"] if clarity_t.level > 0 then local val_mult = outfit_type({"o_medium"}) and 2 or 1 val = val + val_mult * (clarity_t.psi[clarity_t.level] / 100) pr("psi, clarity: +%s%", val * 100) end -- monster hunter stable_mind local stable_mind_t = monster_hunter_defensive_t["stable_mind"] if stable_mind_t.level > 0 then val = val + (stable_mind_t.psi[stable_mind_t.level] / 100) pr("psi, stable_mind: +%s%", val * 100) end return talents_base_arszi_psy_prot() * (psi_mult + val) end talents_base_arszi_psy_controller = arszi_psy.get_controller_tube_damage function arszi_psy.get_controller_tube_damage(pos, hit_pwr) if talents_table["defensive"]["monster_hunter"]["stable_mind"].level > 0 and (math.random(1, 100) <= 35) then pr("psi, stable_mind, controller hit mitigated") return 0 end return talents_base_arszi_psy_controller(pos, hit_pwr) end -- functions disguise function disguise_general_mult(npc) local class_t = talents_table["defensive"][picked_class] if not class_t then return end local pwr = 100 for name, t in pairs(class_t) do if t.level > 0 and (outfit_type(t.outfits) or in_ar(t.outfits, "all")) and t.disguise then pr("----------- DISGUISE -----------") pr("adding talent: %s || lvl: %s", name, t.level) local val = t.disguise[t.level] if this[t.func] then val = this[t.func](val, npc) or 0 end -- covert bonus if class_t["covert"].level > 0 and outfit_type({"o_medium", "o_sci"}) then val = val * 1.5 end pr("+disguise: %s", val) pwr = pwr - val end end if pwr < 10 then pwr = 10 end pr("~~~~~~~~~~~~~~~~~~~~~") return pwr / 100 end function gain_disguise_exp(npc) if not (npc and npc:relation(db.actor) >= game_object.enemy and picked_class == "assassin" and talents_leveling and talents_mcm) then return end local dist = db.actor:position():distance_to(npc:position()) local val = 0.5 * (0.98)^dist -- pr("~disguise adding exp, def: %s", val * talents_mcm.get_config("defensive")) talents_leveling.add_exp("defensive", val * talents_mcm.get_config("defensive")) end base_disguise_calc = gameplay_disguise.calculate_npc_suspicion function gameplay_disguise.calculate_npc_suspicion(npc, id, t, awareness) local new_val = disguise_general_mult(npc) if not new_val then return math.floor(clamp(base_disguise_calc(npc, id, t, awareness), 1, 100)) else gain_disguise_exp(npc) -- defensive exp return math.floor(clamp(new_val * base_disguise_calc(npc, id, t, awareness), 1, 100)) end end ------------------------------------- -- functions stealth function stealth_general_mult(who) local class_t = talents_table["defensive"][picked_class] if not (who and who:id() == 0 and class_t) then return end local pwr = 100 for name, t in pairs(class_t) do if t.level > 0 and (outfit_type(t.outfits) or in_ar(t.outfits, "all")) and t.stealth then pr("----------- STEALTH -----------") pr("adding talent: %s || lvl: %s", name, t.level) local val = t.stealth[t.level] if this[t.func] then val = this[t.func](val) or 0 end -- cunning bonus if class_t["cunning"].level > 0 and outfit_type({"o_light"}) then val = val * 1.5 end pr("+stealth: %s", val) pwr = pwr - val end end if pwr < 10 then pwr = 10 end pr("~~~~~~~~~~~~~~~~~~~~~") return pwr / 100 end function gain_stealth_exp(npc, who) if not (npc and npc:relation(db.actor) >= game_object.enemy and picked_class == "assassin" and talents_leveling and talents_mcm) then return end if npc:see(db.actor) then return end local dist = db.actor:position():distance_to(npc:position()) local val = 0.5 * (0.98)^dist -- pr("-stealth adding exp, def: %s", val * talents_mcm.get_config("defensive")) talents_leveling.add_exp("defensive", val * talents_mcm.get_config("defensive")) end if stealth_mcm then -- if has stealth addon base_stealth_luminocity_mult = visual_memory_manager.get_luminocity_mult function visual_memory_manager.get_luminocity_mult(npc, who, luminocity, object_distance, distance) local new_val = stealth_general_mult(who) if not new_val then return base_stealth_luminocity_mult(npc, who, luminocity, object_distance, distance) else gain_stealth_exp(npc, who) -- defensive exp return new_val * base_stealth_luminocity_mult(npc, who, luminocity, object_distance, distance) end end else -- vanilla base_stealth_visible_value = visual_memory_manager.get_visible_value function visual_memory_manager.get_visible_value(npc,who,time_delta,time_quant,luminocity,velocity_factor,velocity,distance,object_distance,always_visible_distance) local new_val = stealth_general_mult(who) if not new_val then return base_stealth_visible_value(npc,who,time_delta,time_quant,luminocity,velocity_factor,velocity,distance,object_distance,always_visible_distance) else gain_stealth_exp(npc, who) -- defensive exp return new_val * base_stealth_visible_value(npc,who,time_delta,time_quant,luminocity,velocity_factor,velocity,distance,object_distance,always_visible_distance) end end end ------------------------------------- -- npc_hit function npc_on_before_hit(npc, s_hit, bone_id, flags) -- not very automatic xd local draft = s_hit.draftsman local draft_fits = draft and (IsStalker(draft) or IsMonster(draft)) if not (draft_fits and draft.alive and draft:alive() and draft:id() ~= 0) then return end if not (npc:alive()) then return end local pwr = 100 local crit = 0 local crit_dmg = 150 -- jugger if picked_class == "jugger" then -- frightening local frightening_t = talents_table["offensive"]["jugger"]["frightening"] if frightening_t.level > 0 then local val = talent_frightening(npc, frightening_t.damage[frightening_t.level]) or 0 pr("NPC frightening, sec: %s || val: %s", npc:section(), val) pwr = pwr + val end -- turmoil local turmoil_t = talents_table["defensive"]["jugger"]["turmoil"] local ac_comm = get_actor_true_community() local npc_comm = IsStalker(npc) and npc.character_community and npc:character_community() if turmoil_t.level > 0 and npc_comm then local npc_friend = not game_relations.is_factions_enemies(ac_comm, npc_comm) local draft_actor_dist = draft:position():distance_to(db.actor:position()) if npc_friend and draft_actor_dist and draft_actor_dist <= turmoil_t.radius then local val = turmoil_t.bal[turmoil_t.level] pr("NPC turmoil, friend sec: %s || enemy sec: %s || val: %s", npc:section(), draft:section(), val) pwr = pwr - val end end end -- speech confidence local confidence_t = talents_table["speech"]["sniper"]["confidence"] if confidence_t.level > 0 and npc:has_info("npcx_is_companion") then local val = confidence_t.defense[confidence_t.level] pr("NPC speech confidence, companion (target) sec: %s || shooter sec: %s || val: %s", npc:section(), draft:section(), val) pwr = pwr - val end -- speech encouragement local encouragement_t = talents_table["speech"]["sniper"]["encouragement"] if encouragement_t.level > 0 and draft:has_info("npcx_is_companion") then local val = encouragement_t.damage[encouragement_t.level] pr("NPC speech encouragement, companion sec: %s || target sec: %s || val: %s", draft:section(), npc:section(), val) pwr = pwr + val end -- speech rally local rally_t = talents_table["speech"]["sniper"]["rally"] if rally_t.level > 0 and draft:has_info("npcx_is_companion") then local val = rally_t.crit[rally_t.level] pr("NPC speech rally, companion sec: %s || target sec: %s || val: %s", draft:section(), npc:section(), val) crit = crit + val end -- new hit local new_hit = s_hit.power * (pwr / 100) -- crit if crit > 0 and (math.random(1, 100) <= crit) then new_hit = new_hit * (crit_dmg / 100) end -- set hit power s_hit.power = new_hit end function npc_on_death_callback(npc, who) -- trooper if picked_class == "trooper" then -- thrill if who:id() == 0 and talents_table["offensive"]["trooper"]["thrill"].level > 0 then thrill_var = true end -- collector if who:id() == 0 and talents_table["offensive"]["trooper"]["collector"].level > 0 then -- table with price local val = get_collector_val(npc) if val then collector_var = collector_var + val if collector_var > 1500 then collector_var = 1500 end end end end -- jugger if picked_class == "jugger" then -- dismorale if who:id() == 0 and talents_table["defensive"]["jugger"]["dismorale"].level > 0 then durations_t.dismorale_var = talents_table["defensive"]["jugger"]["dismorale"].duration if talents_table["defensive"]["jugger"]["anxiety"].level > 0 then durations_t.dismorale_var = durations_t.dismorale_var + 10 end end end -- monster hunter if picked_class == "monster_hunter" then -- trophies local mut_type = check_mutant_type(npc) if who:id() == 0 and mut_type and (not monster_hunter_trophies[mut_type]) and talents_table["offensive"]["monster_hunter"]["trophies"].level > 0 then monster_hunter_trophies[mut_type] = true end end -- merc if picked_class == "merc" then -- head hunter if who:id() == 0 and IsStalker(npc) and talents_table["offensive"]["merc"]["head_hunter"].level > 0 then local comm = npc.character_community and npc:character_community() if comm and stalker_comms_t[comm] and (not merc_head_hunter_t[comm]) then merc_head_hunter_t[comm] = true end end -- searcher if who:id() == 0 and IsStalker(npc) and talents_table["offensive"]["merc"]["searcher"].level > 0 then local money_ar = { "money_50_100", "money_100_200", "money_200_300" } for i = 1, 2 do local money_sec = money_ar[math.random(1, #money_ar)] if money_sec then pr("searcher, spawning %s", money_sec) alife_create_item(money_sec, npc) end end end end end ------------------------------------- -- all_functions -- functions attack (sniper) function talent_experienced_sniper(npc, val) if outfit_type({"o_medium", "o_light"}) then return val end end function talent_steady(npc, val) if outfit_type({"o_medium", "o_light"}) and IsMoveState('mcCrouch') then return val end end function talent_convenience(npc, val) if outfit_type({"o_light"}) then return val end end function talent_gunslinger(npc, val) local pistol = db.actor:active_item() local wpn1 = db.actor:item_in_slot(2) local wpn2 = db.actor:item_in_slot(3) if (wpn1 and pistol and pistol:id() == wpn1:id()) or (wpn2 and pistol and pistol:id() == wpn2:id()) then return val end end function talent_gunslinger_reload(obj) -- reload local gunslinger_t = talents_table["offensive"]["sniper"]["gunslinger"] if gunslinger_t.level <= 0 then return end if not (obj and IsPistol(obj)) then return end local wpn1 = db.actor:item_in_slot(2) local wpn2 = db.actor:item_in_slot(3) if (wpn1 and obj:id() == wpn1:id()) or (wpn2 and obj:id() == wpn2:id()) then local val = 1 + gunslinger_t.reload[gunslinger_t.level] / 100 return val end end function talent_sharpshooter(npc, val) -- if outfit_type({"o_medium", "o_light"}) then local dist_to = npc:position():distance_to(db.actor:position()) return val * math.ceil(dist_to) -- end end function talent_ambush(npc, val) if outfit_type({"o_light"}) and (not npc:see(db.actor)) then return val end end function talent_loner(npc, val) local companions = axr_companions.get_companion_count() if companions < 1 then return val end end function talent_soft_spot(npc, val) local npc_dir = vector():set(npc:direction()) npc_dir.y = 0 local dir_to_ac = vector():set(db.actor:position()):sub(npc:position()) dir_to_ac.y = 0 local dp = npc_dir:dotproduct(dir_to_ac) if dp < 0 then return val end end function talent_nocturnal(npc, val) if level.get_time_hours() < 6 or level.get_time_hours() >= 18 then return val end end ------------------------------------- -- functions attack (trooper) function talent_duelist(npc, val) if npc and npc:see(db.actor) then return val end end function talent_focus(npc, val) if outfit_type({"o_medium", "o_sci"}) then return val * db.actor.power * 100 end end function talent_thrill(npc, val) if thrill_var then return val end end function talent_precision(npc, val) if precision_allow_dmg then return val end end function talent_barrage(npc, val) if durations_t.barrage_var > 0 then return val end end function talent_reckless(npc, val) local missing_hp = 1 - db.actor.health return missing_hp * val * 100 end function talent_collector(npc, val) if collector_var > 0 then return collector_var * val end end ------------------------------------- -- functions attack (jugger) function talent_hunter(npc, val) if IsMonster(npc) then return val end end function talent_bounty_hunter(npc, val) if IsStalker(npc) then return val end end function talent_merciless(npc, val) local mastery_t = talents_table["offensive"]["jugger"]["mastery"] local extra_val = mastery_t.level > 0 and mastery_t.merciless_bonus or 0 if npc.health < db.actor.health then return val + extra_val end end function talent_heavy_guns_expert_reload(obj) -- reload local heavy_guns_expert_t = talents_table["offensive"]["jugger"]["heavy_guns_expert"] if heavy_guns_expert_t.level <= 0 then return end local kind = obj and ini_sys:r_string_ex(obj:section(), "kind") local kind_fits = (kind and kind == "w_shotgun") or machine_guns_t[obj:section()] if kind_fits then local val = 1 + heavy_guns_expert_t.reload[heavy_guns_expert_t.level] / 100 return val end end function talent_fury(npc, val) -- var set in actor_on_before_hit local mastery_t = talents_table["offensive"]["jugger"]["mastery"] local extra_val = mastery_t.level > 0 and mastery_t.fury_bonus or 0 if durations_t.fury_var > 0 then return val + extra_val end end function talent_execution(npc, val) local exec_t = talents_table["offensive"]["jugger"]["execution"] local mastery_t = talents_table["offensive"]["jugger"]["mastery"] local threshold = mastery_t.level > 0 and (mastery_t.execution_bonus + exec_t.hp_thresh) or exec_t.hp_thresh if npc.health < (threshold / 100) then return val end end function talent_frightening(npc, val) local ac_comm = get_actor_true_community() local npc_comm = IsStalker(npc) and npc.character_community and npc:character_community() local npc_enemy = npc_comm and game_relations.is_factions_enemies(ac_comm, npc_comm) if IsMonster(npc) or npc_enemy then local fright_t = talents_table["offensive"]["jugger"]["frightening"] local mastery_t = talents_table["offensive"]["jugger"]["mastery"] local radius = mastery_t.level > 0 and (mastery_t.frightening_bonus + fright_t.radius) or fright_t.radius local dist_to = npc:position():distance_to(db.actor:position()) if dist_to and dist_to < radius then return val end end end function talent_selective(npc, val) local wpn1 = db.actor:item_in_slot(2) local wpn2 = db.actor:item_in_slot(3) if (wpn1 and not wpn2) or (wpn2 and not wpn1) then return val end end ------------------------------------- -- functions attack (monster hunter) function talent_dog_hunter(npc, val) local mut_type_t = { "dog", "pseudodog", "cat" } local mut_type = check_mutant_type(npc) if mut_type and in_ar(mut_type_t, mut_type) then return val end end function talent_boar_hunter(npc, val) local mut_type_t = { "boar", "flesh", "SM_LURKER" } local mut_type = check_mutant_type(npc) if mut_type and in_ar(mut_type_t, mut_type) then return val end end function talent_zombie_hunter(npc, val) local mut_type_t = { "zombie", "fracture", "snork" } local mut_type = check_mutant_type(npc) if mut_type and in_ar(mut_type_t, mut_type) then return val end end function talent_trophies(npc, val) local trophies_mult = size_table(monster_hunter_trophies) if trophies_mult > 0 then return val * trophies_mult end end function talent_beast_anatomy(npc, val) local mut_type = check_mutant_type(npc, true) if mut_type and mut_type == "beast" then return val end end function talent_humanoid_anatomy(npc, val) local mut_type = check_mutant_type(npc, true) if mut_type and mut_type == "hum" then return val end end function talent_expert_hunter(npc, val) local mut_type_t = { "bloodsucker", "burer", "SM_PSYSUCKER", "SM_POLTER_G", "SM_PYRO_G", "SM_PSEUDO_G" } local mut_type = check_mutant_type(npc) if mut_type and in_ar(mut_type_t, mut_type) then return val end end function talent_beast_rivalry(npc, val) local mut_type = check_mutant_type(npc, true) if mut_type and mut_type == "beast" then return val end end function talent_humanoid_rivalry(npc, val) local mut_type = check_mutant_type(npc, true) if mut_type and mut_type == "hum" then return val end end function talent_master_hunter(npc, val) local mut_type_t = { "chimera", "controller", "giant" } local mut_type = check_mutant_type(npc) if mut_type and in_ar(mut_type_t, mut_type) then return val end end function talent_fierce(npc, val) if fierce_stacks > 0 then return val * fierce_stacks end end function talent_ranger(npc, val) if outfit_type({"o_medium"}) then return val end end function talent_deadly_blow(npc, kind) if not IsMonster(npc) then return end local chance = kind == "w_melee" and 5 or 1 if math.random(1, 100) <= chance then return true end end ------------------------------------- -- functions attack (merc) function talent_overdrive(npc, val) if durations_t.overdrive_var > 0 then return val end end function talent_overdrive_on_use(sec) local overdrive_t = talents_table["offensive"]["merc"]["overdrive"] if overdrive_t.level <= 0 then return end if string.find(sec, "stimpack") or (sec == "cocaine") then durations_t.overdrive_var = overdrive_t.duration end end function talent_head_hunter(npc, val) local head_hunter_mult = size_table(merc_head_hunter_t) if IsStalker(npc) and head_hunter_mult > 0 then return val * head_hunter_mult end end function talent_arrogance(npc, val) local arrogance_rank_t = { novice = 1, trainee = 2, experienced = 3, professional = 4, veteran = 5, expert = 6, master = 7, legend = 8 } local rank = ranks.get_obj_rank_name(npc) local rank_num = rank and arrogance_rank_t[rank] local ac_rank = ranks.get_obj_rank_name(db.actor) local ac_rank_num = ac_rank and arrogance_rank_t[ac_rank] if not (rank_num and ac_rank_num) then return end local diff = ac_rank_num - rank_num if diff > 0 then return val * diff end end function talent_aim_torso(npc, val, bone_id) local bone_name = npc and bone_id and npc:bone_name(bone_id) if bone_name and stalker_bones_t[bone_name] then return val end end function talent_aim_limbs(npc, val, bone_id) local bone_name = npc and bone_id and npc:bone_name(bone_id) if bone_name and (not stalker_bones_t[bone_name]) then return val end end function talent_bullseye(npc, val) if durations_t.bullseye_var > 0 then return val end end function talent_assault_rifles_expert_reload(obj) local assault_t = talents_table["offensive"]["merc"]["assault_rifles_expert"] if assault_t.level <= 0 then return end local kind = obj and ini_sys:r_string_ex(obj:section(), "kind") if kind and kind == "w_rifle" then local val = 1 + assault_t.reload[assault_t.level] / 100 return val end end function talent_light_guns_expert_reload(obj) local pistol_t = talents_table["offensive"]["merc"]["light_guns_expert"] if pistol_t.level <= 0 then return end local kind = obj and ini_sys:r_string_ex(obj:section(), "kind") if kind and (kind == "w_pistol" or kind == "w_smg") then local val = 1 + pistol_t.reload[pistol_t.level] / 100 return val end end function talent_liquidator(npc) if not (IsStalker(npc)) then return end if math.random(1, 100) <= 1 then return true end end ------------------------------------- -- functions attack (assassin) function talent_spot_weakness(npc, val) if spot_weakness_stacks > 0 then return val * spot_weakness_stacks end end function talent_undercover(npc, val) if not (IsStalker(npc)) then return end local comm = npc.character_community and npc:character_community() local outfit = db.actor:item_in_slot(7) local outfit_comm = outfit and ini_sys:r_string_ex(outfit:section(), "community") if not (comm and outfit_comm) then return end if (comm == outfit_comm) or (not game_relations.is_factions_enemies(comm, outfit_comm)) then return val end end function talent_tactical(npc, val) local wpn = db.actor:active_item() if wpn and wpn:weapon_is_silencer() then return val end end function talent_backstab(npc, val) local npc_dir = vector():set(npc:direction()) npc_dir.y = 0 local dir_to_ac = vector():set(db.actor:position()):sub(npc:position()) dir_to_ac.y = 0 local dp = npc_dir:dotproduct(dir_to_ac) if dp < 0 then return val end end function talent_saboteur(npc, val) local dist_to = npc:position():distance_to(db.actor:position()) local new_val = val - math.ceil(dist_to) * 0.1 return new_val > 0 and new_val or 0 end function talent_surprise_attack(npc, val) local in_danger = (db.storage[npc:id()] and db.storage[npc:id()].danger_flag) or (npc:best_enemy() and true) or false if not in_danger then return val end end function talent_assassination(npc, val) local npc_dir = vector():set(npc:direction()) npc_dir.y = 0 local dir_to_ac = vector():set(db.actor:position()):sub(npc:position()) dir_to_ac.y = 0 local dp = npc_dir:dotproduct(dir_to_ac) local in_danger = (db.storage[npc:id()] and db.storage[npc:id()].danger_flag) or (npc:best_enemy() and true) or false if dp < 0 and (not in_danger) then return val end end function talent_vile(npc, val) local npc_dir = vector():set(npc:direction()) npc_dir.y = 0 local dir_to_ac = vector():set(db.actor:position()):sub(npc:position()) dir_to_ac.y = 0 local dp = npc_dir:dotproduct(dir_to_ac) if dp < 0 then return val end end function talent_pragmatic(npc, val) local pistol = db.actor:active_item() local wpn1 = db.actor:item_in_slot(2) local wpn2 = db.actor:item_in_slot(3) local check1 = wpn1 and pistol and pistol:id() == wpn1:id() and (not wpn2) local check2 = wpn2 and pistol and pistol:id() == wpn2:id() and (not wpn1) if check1 or check2 then return val end end ------------------------------------- -- functions defense (sniper) function talent_iron_stomach(sec) local iron_stomach_t = talents_table["defensive"]["sniper"]["iron_stomach"] if iron_stomach_t.level <= 0 then return end local rad_add = ini_sys:r_float_ex(sec, "eat_radiation") if rad_add and rad_add > 0 then local rad_reduction = rad_add * (iron_stomach_t.rad_food[iron_stomach_t.level] / 100) pr("iron_stomach level: %s || cur_rad: %s || reduce rad by: %s", iron_stomach_t.level, db.actor.radiation, rad_reduction) if db.actor.radiation <= rad_reduction then db.actor.radiation = 0 else db.actor.radiation = db.actor.radiation - rad_reduction end pr("iron_stomach new rad: %s", db.actor.radiation) end end function talent_rad_resistant(sec) local rad_resistant_t = talents_table["defensive"]["sniper"]["rad_resistant"] if rad_resistant_t.level <= 0 then return end local rad_restore = ini_sys:r_float_ex(sec, "boost_radiation_restore") local boost_time = ini_sys:r_float_ex(sec, "boost_time") if rad_restore and rad_restore > 0 and boost_time then local rad_restore_tot = rad_restore * 6 * boost_time local rad_to_remove = rad_restore_tot * 0.35 pr("rad_resistant cur_rad: %s || rad_to_remove: %s", db.actor.radiation, rad_to_remove) if db.actor.radiation <= rad_to_remove then db.actor.radiation = 0 else db.actor.radiation = db.actor.radiation - rad_to_remove end pr("rad_resistant new rad: %s", db.actor.radiation) end end function talent_technician(item) local technician_t = talents_table["defensive"]["sniper"]["technician"] if technician_t.level > 0 and IsOutfit(item) then local kind = ini_sys:r_string_ex(item:section(), "kind") local cur_upgrades = utils_item.get_upgrades_installed(item, nil, true) if kind == "o_medium" and is_empty(cur_upgrades) then install_random_upgrade(item) end end end function talent_expert_technician(val) local outfit = db.actor:item_in_slot(7) if outfit then return outfit:condition() * 100 * val end end function talent_agile(val) if IsMoveState("mcAnyMove") then return val end end ------------------------------------- -- functions defense (trooper) function talent_trooper_general_armor(val) if outfit_type({"o_medium", "o_sci"}) then return val * 2 end return val end function talent_first_aid(sec) local first_aid_t = talents_table["defensive"]["trooper"]["first_aid"] if first_aid_t.level <= 0 then return end local hp_restore = ini_sys:r_float_ex(sec, "boost_health_restore") local boost_time = ini_sys:r_float_ex(sec, "boost_time") if hp_restore and hp_restore > 0 and boost_time then local hp_restore_tot = hp_restore * 6 * boost_time local hp_to_add = hp_restore_tot * (first_aid_t.heal[first_aid_t.level] / 100) pr("first_aid cur_hp: %s || hp_to_add: %s", db.actor.health, hp_to_add) if db.actor.health + hp_to_add >= 1 then db.actor.health = 1 else db.actor.health = db.actor.health + hp_to_add end pr("first_aid new hp: %s", db.actor.health) end end function talent_suppresion_fire(val) -- var set in actor_hit_npc if durations_t.suppresion_fire_var > 0 then return val end end function talent_cautious(val) if cooldowns_t.cautious_var <= 0 then cooldowns_t.cautious_var = talents_table["defensive"]["trooper"]["cautious"].cooldown return val * 2 end return val end function talent_composure(val) if db.actor.power > 0.5 then return val end end function talent_adorned(val) local belt_items = 0 db.actor:iterate_belt( function(owner, obj) belt_items = belt_items + 1 end) if belt_items > 0 then return val * belt_items end end function talent_expert_armorer(val) local upgrades = 0 local outfit = db.actor:item_in_slot(7) local cur_upgrades = outfit and utils_item.get_upgrades_installed(outfit, nil, true) if cur_upgrades then for name, _ in pairs(cur_upgrades) do upgrades = upgrades + 1 end end if upgrades > 10 then upgrades = 10 end if upgrades > 0 then return val * upgrades end end function talent_last_stand(val) if db.actor.health < 0.35 then return val end end function talent_leap(val) if durations_t.leap_var > 0 then return val end end ------------------------------------- -- functions defense (jugger) function talent_dismorale(val) if durations_t.dismorale_var > 0 then if talents_table["defensive"]["jugger"]["anxiety"].level > 0 then return val * 2 end return val end end function talent_sturdy(val) if durations_t.sturdy_var > 0 then return val end end function talent_turmoil(val, npc) local turmoil_t = talents_table["defensive"]["jugger"]["turmoil"] if npc and (IsStalker(npc) or IsMonster(npc)) and npc.alive and npc:alive() and db.actor:position():distance_to(npc:position()) <= turmoil_t.radius then return val end end function talent_secure(val) local companions = axr_companions.get_companion_count() if companions >= 1 then local ret_val = val * companions if ret_val > 15 then ret_val = 15 end return ret_val end end function talent_lucid(val) local psi_health = arszi_psy.get_psy_health() if psi_health then return psi_health * 100 * val end end function talent_robust(val) if cooldowns_t.robust_cd <= 0 then cooldowns_t.robust_cd = talents_table["defensive"]["jugger"]["robust"].cooldown return val end end ------------------------------------- -- functions defense (monster_hunter) function talent_monster_hunter_general_armor(val) if outfit_type({"o_medium"}) then return val * 2 end return val end function talent_melee_defense(val) local wpn = db.actor:active_item() local kind = wpn and ini_sys:r_string_ex(wpn:section(), "kind") if kind and kind == "w_melee" then return val end end function talent_special_diet(val) if durations_t.special_diet_var > 0 then return val end end function talent_special_diet_on_use(sec) local special_diet_t = talents_table["defensive"]["monster_hunter"]["special_diet"] if special_diet_t.level <= 0 then return end local kind = sec and ini_sys:r_string_ex(sec, "kind") if kind and (kind == "i_mutant_cooked" or kind == "i_mutant_raw") then durations_t.special_diet_var = special_diet_t.duration end end function talent_beast_demeanor(val, npc) local mut_type = npc and IsMonster(npc) and check_mutant_type(npc, true) if mut_type and mut_type == "beast" then return val end end function talent_humanoid_demeanor(val, npc) local mut_type = npc and IsMonster(npc) and check_mutant_type(npc, true) if mut_type and mut_type == "hum" then return val end end function talent_second_skin(val) local hides_on_belt = 0 db.actor:iterate_belt( function(owner, obj) local kind = obj and ini_sys:r_string_ex(obj:section(), "kind") if kind and kind == "i_mutant_belt" then hides_on_belt = hides_on_belt + 1 end end) if hides_on_belt > 0 then return val * hides_on_belt end end ------------------------------------- -- functions defense (merc) function talent_chem_reliant(val) if durations_t.chem_reliant_var > 0 then return val end end function talent_chem_reliant_on_use(sec) local chem_reliant_t = talents_table["defensive"]["merc"]["chem_reliant"] if chem_reliant_t.level <= 0 then return end if string.find(sec, "stimpack") or (sec == "morphine") then durations_t.chem_reliant_var = chem_reliant_t.duration end end function talent_addictive(val) if durations_t.chem_reliant_var > 0 then return val end end function talent_grenadier(val) if durations_t.grenadier_var > 0 then return val end end function talent_confusion(val, npc) if not (IsStalker(npc)) then return end local comm = npc.character_community and npc:character_community() local outfit = db.actor:item_in_slot(7) local outfit_comm = outfit and ini_sys:r_string_ex(outfit:section(), "community") if not (comm and outfit_comm) then return end if (comm == outfit_comm) or (not game_relations.is_factions_enemies(comm, outfit_comm)) then return val end end function talent_flurry(val) if durations_t.flurry_var > 0 then return val end end function talent_reposition(val) if durations_t.reposition_var > 0 then return val end end ------------------------------------- -- functions defense (assassin) function talent_ordinary(val) -- if has evanescent local evanescent_t = talents_table["defensive"]["assassin"]["evanescent"] if evanescent_t.level > 0 then return val end local state = level.actor_moving_state() if state >= 33 and state <= 42 then return val end end function talent_quiet(val) -- if has subtle local subtle_t = talents_table["defensive"]["assassin"]["subtle"] if subtle_t.level > 0 then return val end local active_item = db.actor:active_item() if not active_item then return val end end function talent_lurking(val) if level.get_time_hours() < 6 or level.get_time_hours() >= 18 then return val end end function talent_deception(val, npc) local ac_pos = db.actor:position() local npc_pos = npc:position() if ac_pos and npc_pos and ac_pos:distance_to(npc_pos) <= 50 then return val end end function talent_vague(val) local rain_density = weather.get_value_numric("rain_density") return val * rain_density end function talent_sneaky(val) local crouch_state = IsMoveState('mcCrouch') return crouch_state and val * 2 or val end ------------------------------------- -- functions endurance -- speed function talent_ease(val) local wpn = db.actor:active_item() if (not wpn) then return val end local single = ini_sys:r_float_ex(wpn:section(), "single_handed") if single and single == 1 then return val end end function talent_swifty(val) return val end function talent_insomnia(val) if level.get_time_hours() < 6 or level.get_time_hours() >= 18 then return val end end function talent_athletic(val) if outfit_type({"o_medium", "o_sci"}) then return val * db.actor.power * 100 / 2 elseif outfit_type({"o_light"}) then return val * db.actor.power * 100 end end -- stamina function talent_refreshing(sec) local refreshing_t = talents_table["endurance"]["sniper"]["refreshing"] if refreshing_t.level <= 0 then return end local kind = ini_sys:r_string_ex(sec, "kind") local thirst = ini_sys:r_float_ex(sec, "eat_thirstiness") if kind and kind == "i_drink" and thirst and thirst < 0 and db.actor.power < 1 then db.actor.power = db.actor.power + (refreshing_t.stamina[refreshing_t.level] / 100) if db.actor.power > 1 then db.actor.power = 1 end end end function talent_resting(val) if not IsMoveState("mcAnyMove") then return val end end function talent_tireless(val) return val end function talent_courage(val) if durations_t.courage_var > 0 then return val end end -- weight function talent_weight_demolition_expert(item, tal_t, update_weight) local weight = ini_sys:r_float_ex(item:section(), "inv_weight") local explosive_cl_t = { ["S_OG7B"] = true, ["S_VOG25"] = true, ["S_M209"] = true } local kind = ini_sys:r_string_ex(item:section(), "kind") local kind_fits = kind and kind == "w_explosive" local class = ini_sys:r_string_ex(item:section(), "class") local class_fits = class and explosive_cl_t[class] if weight and (kind_fits or class_fits) then local val = weight * (tal_t.weight[tal_t.level] / 100) return val end end function talent_weight_heavy_armor_expert(item, tal_t, update_weight) local weight = ini_sys:r_float_ex(item:section(), "inv_weight") local kind = ini_sys:r_string_ex(item:section(), "kind") local kind_fits = kind and (kind == "o_heavy" or kind == "o_medium") if kind_fits and weight then local val = weight * (tal_t.weight[tal_t.level] / 100) return val end end function talent_weight_hiker(item, tal_t, update_weight) local weight = ini_sys:r_float_ex(item:section(), "inv_weight") local kind = ini_sys:r_string_ex(item:section(), "kind") local kind_fits = kind and (kind == "i_food" or kind == "i_mutant_raw" or kind == "i_mutant_cooked") if kind_fits and weight then local val = weight * (tal_t.weight[tal_t.level] / 100) return val end end function talent_weight_prudent(item, tal_t, update_weight) local weight = ini_sys:r_float_ex(item:section(), "inv_weight") local kind = ini_sys:r_string_ex(item:section(), "kind") local kind_fits = kind and kind == "i_medical" if kind_fits and weight then local val = weight * (tal_t.weight[tal_t.level] / 100) return val end end function talent_weight_organizer(item, tal_t, update_weight) if item:weight() >= 0.2 and (not IsWeapon(item)) then return 0.1 end end function talent_weight_belt_braces(item, tal_t, update_weight) if update_weight then local item_on_belt = false db.actor:iterate_belt( function(owner, obj) if item:id() == obj:id() then item_on_belt = true end end) if not item_on_belt then return end end local weight = ini_sys:r_float_ex(item:section(), "inv_weight") if weight then local val = weight * (tal_t.weight[tal_t.level] / 100) return val end end function talent_hoarder(val) return val end function talent_iron_spine(val) if outfit_type({"o_medium", "o_sci"}) then return val * db.actor.health * 100 / 2 elseif outfit_type({"o_heavy"}) then return val * db.actor.health * 100 end end ------------------------------------- -- functions speech -- buy (on_get_item_cost) function talent_preparation(item) local preparation_t = talents_table["speech"]["sniper"]["preparation"] if preparation_t.level <= 0 then return end local kind = ini_sys:r_string_ex(item:section(), "kind") if not kind then return end local thirst = ini_sys:r_float_ex(item:section(), "eat_thirstiness") local food = kind == "i_food" local water = kind == "i_drink" and thirst and thirst < 0 if food or water then local val = preparation_t.buy[preparation_t.level] return val end end function talent_observant(item) local observant_t = talents_table["speech"]["sniper"]["observant"] if observant_t.level <= 0 then return end if IsWeapon(item) or IsAmmo(item) then local val = observant_t.buy[observant_t.level] return val end end function talent_gregarious(item) local gregarious_t = talents_table["speech"]["sniper"]["gregarious"] if gregarious_t.level <= 0 then return end local val = gregarious_t.buy[gregarious_t.level] return val end function talent_well_liked(item, trader_id) local well_liked_t = talents_table["speech"]["sniper"]["well_liked"] if well_liked_t.level <= 0 then return end local trader = trader_id and level.object_by_id(trader_id) if not (trader and trader.alive and trader:alive()) then return end local trader_comm if trader.section and trader:section() and (trader:section() == "m_trader" or trader:section() == "m_lesnik") then trader_comm = "stalker" else trader_comm = trader:character_community() end if not trader_comm then return end local gw = relation_registry.community_goodwill(trader_comm, 0) if gw then local val = clamp(gw / 200, 0, 8) return -val end end -- sell (on_get_item_cost) function talent_supplier(item) local supplier_t = talents_table["speech"]["sniper"]["supplier"] if supplier_t.level <= 0 then return end local kind = ini_sys:r_string_ex(item:section(), "kind") if not kind then return end if kind then local mutant_food = kind == "i_mutant_raw" or kind == "i_mutant_cooked" local mutant_part = kind == "i_mutant_part" local mutant_skin = kind == "i_mutant_belt" if mutant_food or mutant_part or mutant_skin then local val = supplier_t.sell[supplier_t.level] return val end end end function talent_smuggler(item) local smuggler_t = talents_table["speech"]["sniper"]["smuggler"] if smuggler_t.level <= 0 then return end local kind = ini_sys:r_string_ex(item:section(), "kind") if not kind then return end local eat_alcohol = ini_sys:r_float_ex(item:section(), "eat_alcohol") local medicine = kind == "i_medical" local alcohol = kind == "i_drink" and eat_alcohol and eat_alcohol > 0 local artefact = kind == "i_arty" or kind == "i_arty_junk" if medicine or alcohol or artefact then local val = smuggler_t.sell[smuggler_t.level] if artefact then val = val / 2 end return val end end function talent_persuasion(item) local persuasion_t = talents_table["speech"]["sniper"]["persuasion"] if persuasion_t.level <= 0 then return end local val = persuasion_t.sell[persuasion_t.level] return val end function talent_shady(item, profile) local shady_t = talents_table["speech"]["sniper"]["shady"] if shady_t.level <= 0 then return end local stalker_cfg = profile and profile.cfg and profile.cfg == "items\\trade\\trade_generic.ltx" if stalker_cfg then local val = shady_t.sell[shady_t.level] return val end end -- buy (on_item_buy) local charismatic_items_ar = {} function talent_charismatic(item, trader) local charismatic_t = talents_table["speech"]["sniper"]["charismatic"] if charismatic_t.level <= 0 then return end local cost = ini_sys:r_float_ex(item:section(), "cost") local discount = trade_manager.get_sell_discount(trader:id()) local tier = ini_sys:r_float_ex(item:section(), "tier") local item_name = ini_sys:r_string_ex(item:section(), "inv_name") if not (cost and discount and tier and tier <= 1) then return end local cost_to_return = math.ceil(cost * discount) pr("charismatic, store sec: %s || cost_to_return: %s", item:section(), cost_to_return) charismatic_items_ar[#charismatic_items_ar + 1] = {} charismatic_items_ar[#charismatic_items_ar].cost = cost_to_return charismatic_items_ar[#charismatic_items_ar].name = item_name or item:section() -- use only last created time event RemoveTimeEvent("delay_charismatic_e", "delay_charismatic_a") CreateTimeEvent("delay_charismatic_e", "delay_charismatic_a", 0.25, function() if #charismatic_items_ar <= 0 then pr("charismatic, no tier 1 items bought, return") return true end for idx, items_t in ipairs(charismatic_items_ar) do local chance = charismatic_t.free[charismatic_t.level] if math.random(1, 100) <= chance then -- chance procs then give money back pr("charismatic, proc, returning cost: %s for %s", items_t.cost, gt(items_t.name)) db.actor:give_money(items_t.cost) -- send news news_manager.send_tip(db.actor, string.format(gt("talents_charismatic_msg"), gt(items_t.name)), 0, nil, 7500) end end iempty_table(charismatic_items_ar) return true end) end -- sell (on_item_sell) local cynical_item_id -- remove on trade close function talent_cynical_set(item, profile, trader_id, new_cost, vanilla_cost) -- on_get_item_cost local cynical_t = talents_table["speech"]["sniper"]["cynical"] if cynical_t.level <= 0 then return end -- actor has coin if not (actor_has_coin()) then return end -- get trader comm local trader = trader_id and level.object_by_id(trader_id) if not (trader and trader.alive and trader:alive()) then return end local trader_comm if trader.section and trader:section() and (trader:section() == "m_trader" or trader:section() == "m_lesnik") then trader_comm = "stalker" else trader_comm = trader:character_community() end if not trader_comm then return end -- is stalker and rank fits local obj_rank = ranks.get_obj_rank_name(trader) local rank_fits = obj_rank and (obj_rank == "novice" or obj_rank == "trainee") local stalker_cfg = profile and profile.cfg and profile.cfg == "items\\trade\\trade_generic.ltx" if not (stalker_cfg and obj_rank) then return end -- item is misc local cost = new_cost or vanilla_cost local kind = ini_sys:r_string_ex(item:section(), "kind") if not (cost and kind and kind == "i_misc") then return end -- item to sell isnt set (or same item) if (not cynical_item_id) or (cynical_item_id == item:id()) then pr("cynical set item_id: %s || sec: %s || to price: %s", item:id(), item:section(), cost * 20) cynical_item_id = item:id() return cost * 20 end end function talent_cynical_sell(item, comm) -- if we sell this misc item if cynical_item_id and item:id() == cynical_item_id and comm then cynical_item_id = nil -- release coin, set cd local cur_time = game.get_game_time() local coin_item = db.actor:object("cynical_coin") if coin_item and coin_item:id() then pr("cynical release coin, set cd") alife_release_id(coin_item:id()) cynical_cd = ctime_to_t(cur_time) end -- goodwill hit local goodwill_hit = math.random(35, 70) pr("cynical, goodwill hit: %s", -goodwill_hit) relation_registry.change_community_goodwill(comm, 0, -goodwill_hit) -- send msg news_manager.send_tip(db.actor, string.format(gt("talents_cynical_msg"), gt("st_faction_" .. comm)), 0, nil, 7500) end end local deceitful_cost_ar = {} function talent_deceitful(item, trader) local deceitful_t = talents_table["speech"]["sniper"]["deceitful"] if deceitful_t.level <= 0 then return end local cost = ini_sys:r_float_ex(item:section(), "cost") local discount = trade_manager.get_buy_discount(trader:id()) local item_name = ini_sys:r_string_ex(item:section(), "inv_name") if not (cost and discount) then return end local cost_to_return = math.ceil(cost * discount * 2.5) pr("deceitful, store sec: %s || cost_to_return: %s", item:section(), cost_to_return) deceitful_cost_ar[#deceitful_cost_ar + 1] = {} deceitful_cost_ar[#deceitful_cost_ar].cost = cost_to_return deceitful_cost_ar[#deceitful_cost_ar].name = item_name or item:section() RemoveTimeEvent("delay_deceitful_e", "delay_deceitful_a") CreateTimeEvent("delay_deceitful_e", "delay_deceitful_a", 0.25, function() if #deceitful_cost_ar <= 0 then pr("deceitful, no tier 1 items bought, return") return true end for idx, items_t in ipairs(deceitful_cost_ar) do local chance = 2 if math.random(1, 100) <= chance then -- chance procs then give extra money pr("deceitful, proc, give cost: %s for %s", items_t.cost, items_t.name) db.actor:give_money(items_t.cost) -- send msg news_manager.send_tip(db.actor, string.format(gt("talents_deceitful_msg"), items_t.cost, gt(items_t.name)), 0, nil, 7500) end end iempty_table(deceitful_cost_ar) return true end) end -- guitar patch base_guitar_func = itms_manager.use_guitar function itms_manager.use_guitar(obj) talent_artistic(obj:section()) return base_guitar_func(obj) end base_harmonica_func = itms_manager.use_harmonica function itms_manager.use_harmonica(obj) talent_artistic(obj:section()) return base_harmonica_func(obj) end function talent_artistic(sec) local artistic_t = talents_table["speech"]["sniper"]["artistic"] if artistic_t.level <= 0 then return end if (not artistic_cd) and (sec == "guitar_a" or sec == "harmonica_a") then -- find all stalker communities around local gw_added = 0 local function itr(obj) if gw_added < 2 and obj and IsStalker(obj) and obj.alive and obj:alive() then local comm = obj.character_community() and obj:character_community() if comm then local val = math.random(15, 35) pr("artistic, add goodwill %s to comm %s", val, comm) relation_registry.change_community_goodwill(comm, 0, val) gw_added = gw_added + 1 -- add news news_manager.send_tip(db.actor, string.format(gt("talents_artistic_msg"), gt("st_faction_" .. comm)), 0, nil, 7500) end end end level.iterate_nearest(db.actor:position(), 30, itr) local cur_time = game.get_game_time() artistic_cd = ctime_to_t(cur_time) end end ------------------------------------- -- other_callbacks -- duration manager local dur_tmr = 0 local dur_upd = 0.25 function duration_update() local tg = time_global() if dur_tmr > tg then return end dur_tmr = tg + 1000 * dur_upd for var_name, time in pairs(durations_t) do if time > 0 then durations_t[var_name] = durations_t[var_name] - dur_upd if durations_t[var_name] <= 0 then durations_t[var_name] = 0 -- adrenaline_rush (remove speed) if var_name == "adrenaline_rush_var" then speed.remove_speed("adrenaline_rush") end -- fierce remove if var_name == "fierce_var" then fierce_stacks = 0 end -- spot_weakness remove if var_name == "spot_weakness_var" then spot_weakness_stacks = 0 end end end end end -- cooldown manager local cd_tmr = 0 local cd_upd = 0.25 function cooldown_update() local tg = time_global() if cd_tmr > tg then return end cd_tmr = tg + 1000 * cd_upd for var_name, time in pairs(cooldowns_t) do if time > 0 then cooldowns_t[var_name] = cooldowns_t[var_name] - cd_upd if cooldowns_t[var_name] <= 0 then cooldowns_t[var_name] = 0 end end end -- long cooldowns in game time local cur_time = game.get_game_time() -- kindred if (not kindred_cd) or (cur_time:diffSec(t_to_ctime(kindred_cd)) > talents_table["defensive"]["monster_hunter"]["kindred"].cooldown * 6) then -- start cd again kindred_cd = ctime_to_t(cur_time) -- allow spawn if dogs do not exist local dogs_exist = get_story_se_object("kindred_dogs") if not (dogs_exist) then kindred_var = true end end -- second_wind if second_wind_cd and (cur_time:diffSec(t_to_ctime(second_wind_cd)) > talents_table["endurance"]["sniper"]["second_wind"].cooldown * 6) then second_wind_cd = nil end -- artistic if artistic_cd and (cur_time:diffSec(t_to_ctime(artistic_cd)) > talents_table["speech"]["sniper"]["artistic"].cooldown * 6) then artistic_cd = nil end -- cynical if cynical_cd and (cur_time:diffSec(t_to_ctime(cynical_cd)) > talents_table["speech"]["sniper"]["cynical"].cooldown * 6) then cynical_cd = nil end end -- item use function actor_on_item_use(item) local sec = item:section() -- iron_stomach talent_iron_stomach(sec) -- rad_resistant talent_rad_resistant(sec) -- trooper first aid talent_first_aid(sec) -- endurance refreshing talent_refreshing(sec) -- monster hunter special diet talent_special_diet_on_use(sec) -- merc overdrive talent_overdrive_on_use(sec) -- merc chem_reliant talent_chem_reliant_on_use(sec) end -- item add upgrade function upgrades_to_ruck(item) -- sniper technician if picked_class == "sniper" then talent_technician(item) end end -- item weight reduction local item_weight_t = {} local inv_weight_upd = true function weight_to_slot(item) pr("to slot sec: %s", item:section()) try_change_item_weight(item, "slot") end function weight_to_ruck(item) pr("to ruck sec: %s", item:section()) try_change_item_weight(item, "ruck") end function weight_to_belt(item) pr("to belt sec %s", item:section()) try_change_item_weight(item, "belt") end function weight_on_drop(item) if item:section() == "inv_update_item_xd" then -- not to trigger weight update again return end pr("to drop sec: %s", item:section()) try_change_item_weight(item, "drop") end function weight_first_update() recalc_all_items_weight() end function server_entity_on_unregister(se_obj) if item_weight_t[se_obj.id] then item_weight_t[se_obj.id] = nil end end function try_change_item_weight(item, cb) -- "cb" to put in callbacks if item:section() == "inv_update_item_xd" then return end for typ, t in pairs(talents_table) do for class_name, t2 in pairs(t) do for talent, tal_t in pairs(t2) do if weight_talent_funcs[talent] and tal_t.level > 0 then -- in "on" callbacks (add weight) if cb and in_ar(weight_talent_funcs[talent].on, cb) then local val = this[weight_talent_funcs[talent].func](item, tal_t) if val then pr("add weight, sec: %s || talent: %s || cb: %s || val: %s", item:section(), talent, cb, -val) add_item_weight(item, talent, -val) end end -- in "off" callbacks (remove weight) if cb and in_ar(weight_talent_funcs[talent].off, cb) then pr("remove weight from sec: %s || talent: %s || cb: %s", item:section(), talent, cb) remove_item_weight(item, talent) end -- called without callbacks (first update/talent pickup) - add/update item weight if not cb then -- check conditions and add weight to item local val = this[weight_talent_funcs[talent].func](item, tal_t, true) if val then pr("recalc, sec: %s || talent: %s || val: %s", item:section(), talent, -val) add_item_weight(item, talent, -val) end end end end end end -- update actor total weight trigger_weight_update() end function add_item_weight(item, talent, val) -- add on callbacks local id = item:id() pr("- add_item_weight sec: %s || talent: %s || id: %s || val: %s", item:section(), talent, id, val) -- if item not in table - add if not item_weight_t[id] then item_weight_t[id] = {} end -- if talent in table then remove its weight from item if item_weight_t[id][talent] then pr("- add_item_weight remove weight, cur weight: %s || weight after remove: %s", item:weight(), item:weight() - item_weight_t[id][talent]) item:set_weight(item:weight() - item_weight_t[id][talent]) pr(" test1 item weight: %s", item:weight()) end -- dont let weight go below 0.001 if item:weight() + val <= 0.001 then val = item:weight() end -- set new item_weight_t[id][talent] = val -- add new pr("- add_item_weight add weight, cur weight: %s || weight after add: %s", item:weight(), item:weight() + val) item:set_weight(item:weight() + val) pr(" test2 item weight: %s", item:weight()) end function remove_item_weight(item, talent) -- remove on callbacks local id = item:id() if not (item_weight_t[id]) then return end -- remove talent weight from item if item_weight_t[id][talent] then pr("- remove_item_weight, cur weight: %s || weight after remove: %s", item:weight(), item:weight() - item_weight_t[id][talent]) item:set_weight(item:weight() - item_weight_t[id][talent]) item_weight_t[id][talent] = nil pr(" test3 item weight: %s", item:weight()) end -- remove item from table if no talents in it if is_empty(item_weight_t[id]) then item_weight_t[id] = nil end end function recalc_all_items_weight() -- for first update (or talent pick up) local function itr(owner, item) try_change_item_weight(item) end db.actor:iterate_inventory(itr, db.actor) end function remove_all_items_weight() -- for respec for id, t in pairs(item_weight_t) do -- if cant find item - remove from table local item = level.object_by_id(id) if not item then item_weight_t[id] = nil end -- item still here if item_weight_t[id] then for talent, val in pairs(t) do -- remove weight and delete from item table item:set_weight(item:weight() - val) item_weight_t[id][talent] = nil end -- remove item from table if it has no more talents if is_empty(item_weight_t[id]) then item_weight_t[id] = nil end end end end -- let me know if somebody know a better way to update actor weight after item:set_weight function trigger_weight_update() if not (inv_weight_upd) then return end pr("~ update actor weight") local se_obj = alife_create_item("inv_update_item_xd", db.actor) inv_weight_upd = false end function bind(obj) obj:bind_object(inventory_update_item(obj)) end class "inventory_update_item" (object_binder) function inventory_update_item:__init(obj) super(obj) end function inventory_update_item:update(delta) object_binder.update(self, delta) inv_weight_upd = true alife_release(self.object) end local leap function on_key_leap(k) if leap then return end if picked_class ~= "trooper" then return end local bind = dik_to_bind(k) if bind ~= 7 then return end if talents_table["defensive"]["trooper"]["leap"].level <= 0 then return end -- check if enough stamina local stamina_hit = 0.2 if talents_table["defensive"]["trooper"]["enduring"].level > 0 and outfit_type({"o_medium", "o_sci"}) then stamina_hit = stamina_hit * 0.5 end if db.actor.power <= stamina_hit then return end -- choose direction leap = (level.actor_moving_state() == 4 and "L") or (level.actor_moving_state() == 8 and "R") if not leap then return end -- reduce stamina db.actor.power = db.actor.power - stamina_hit -- set leap_var as timer durations_t.leap_var = talents_table["defensive"]["trooper"]["leap"].duration -- add speed and disable input speed.add_speed("talent_leap_speed", 15, false, true) game.only_allow_movekeys(true) CreateTimeEvent("roll_stop_e", "roll_stop_a", 0.6, function() -- remove speed and enable input level.release_action(bind_to_dik(key_bindings["k" .. leap .. "_STRAFE"])) speed.remove_speed("talent_leap_speed") game.only_allow_movekeys(false) leap = nil return true end) end function update_leap() if not leap then return end level.hold_action(bind_to_dik(key_bindings["k" .. leap .. "_STRAFE"])) end local regen_tmr = 0 function jugger_regeneration_update() local tg = time_global() if regen_tmr > tg then return end regen_tmr = tg + 1000 if picked_class ~= "jugger" then return end if talents_table["defensive"]["jugger"]["regenerative"].level <= 0 then return end local satiety = db.actor.satiety local satiety_critical = ini_sys:r_float_ex("actor_condition", "satiety_critical") local satiety_health_v = ini_sys:r_float_ex("actor_condition", "satiety_health_v") local critical_something = satiety >= satiety_critical and 1 - satiety_critical or satiety_critical local satiety_health_koef = (satiety - satiety_critical) / critical_something local delta_health = satiety_health_v * satiety_health_koef * 6 -- engine per 1 real second if delta_health > 0 then db.actor:change_health(delta_health) if db.actor.health > 1 then db.actor:set_health_ex(1) end end end function actor_on_hud_animation_play(t, obj) -- sniper gunslinger local gunslinger_val = talent_gunslinger_reload(obj) if gunslinger_val and string.find(t.anm_name, "reload") then t.anm_speed = t.anm_speed * gunslinger_val end -- jugger heavy_guns_expert local heavy_guns_expert_val = talent_heavy_guns_expert_reload(obj) if heavy_guns_expert_val and string.find(t.anm_name, "reload") then t.anm_speed = t.anm_speed * heavy_guns_expert_val end -- merc combat_engineer local combat_engineer_t = talents_table["offensive"]["merc"]["combat_engineer"] if combat_engineer_t.level > 0 then local kind = obj and ini_sys:r_string_ex(obj:section(), "kind") if kind and kind == "w_explosive" and t.anm_name == "anm_throw_begin" then local val = 1 + combat_engineer_t.percents[combat_engineer_t.level] / 100 t.anm_speed = t.anm_speed * val end end -- merc assault_rifles_expert local assault_rifles_expert_val = talent_assault_rifles_expert_reload(obj) if assault_rifles_expert_val and string.find(t.anm_name, "reload") then t.anm_speed = t.anm_speed * assault_rifles_expert_val end -- merc light_guns_expert local light_guns_expert_val = talent_light_guns_expert_reload(obj) if light_guns_expert_val and string.find(t.anm_name, "reload") then t.anm_speed = t.anm_speed * light_guns_expert_val end -- merc grenadier local grenadier_t = talents_table["defensive"]["merc"]["grenadier"] if grenadier_t.level > 0 and t.anm_name == "anm_throw" then durations_t.grenadier_var = grenadier_t.duration end -- assassin knifeman local knifeman_t = talents_table["offensive"]["assassin"]["knifeman"] if knifeman_t.level > 0 then local kind = obj and ini_sys:r_string_ex(obj:section(), "kind") if kind and kind == "w_melee" and (t.anm_name == "anm_attack" or t.anm_name == "anm_attack2") then local val = 1 + knifeman_t.attack_speed[knifeman_t.level] / 100 t.anm_speed = t.anm_speed * val end end end local deep_wound_tmr = 0 local deep_wound_upd = 500 function deep_wound_update() if is_empty(deep_wound_var) then return end local tg = time_global() if deep_wound_tmr > tg then return end deep_wound_tmr = tg + deep_wound_upd for id, t in pairs(deep_wound_var) do deep_wound_var[id].time = deep_wound_var[id].time - deep_wound_upd local npc = level.object_by_id(id) local npc_fits = npc and (IsStalker(npc) or IsMonster(npc)) and npc.alive and npc:alive() local bleeding = npc_fits and npc.bleeding if (not bleeding) or (bleeding <= 0) or (deep_wound_var[id].time <= 0) then deep_wound_var[id] = nil end -- continue if deep_wound_var[id] then local condition_sec = ini_sys:r_string_ex(npc:section(), "condition_sect") local bleed_v = condition_sec and ini_sys:r_string_ex(condition_sec, "bleeding_v") local extra_dmg = bleed_v and (bleeding * bleed_v * 6 * 0.5 * deep_wound_var[id].value) if extra_dmg then npc:change_health(-extra_dmg) end end end end local modified_bullets_tmr = 0 local modified_bullets_upd = 1000 function modified_bullets_update() if is_empty(modified_bullets_var) then return end local tg = time_global() if modified_bullets_tmr > tg then return end modified_bullets_tmr = tg + modified_bullets_upd for id, t in pairs(modified_bullets_var) do modified_bullets_var[id].time = modified_bullets_var[id].time - modified_bullets_upd local npc = level.object_by_id(id) local npc_fits = npc and IsStalker(npc) and npc.alive and npc:alive() if (not npc_fits) or (modified_bullets_var[id].time <= 0) then modified_bullets_var[id] = nil end -- continue if modified_bullets_var[id] then local dmg = t.value pr("modified_bullets, change npc id: %s || health from: %s to %s", id, round_idp(npc.health, 4), round_idp(npc.health - dmg, 4)) npc:change_health(-dmg) end end end local reposition_tmr = 0 local reposition_upd = 500 local reposition_moving_for = 0 function reposition_update() local tg = time_global() if reposition_tmr > tg then return end reposition_tmr = tg + reposition_upd -- return if not merc or has no talent if not (picked_class == "merc" and talents_table["defensive"]["merc"]["reposition"].level > 0) then return end -- reset if actor not in combat if is_empty(xr_combat_ignore.fighting_with_actor_npcs) then reposition_moving_for = 0 return end -- reset if actor not moving if not IsMoveState("mcAnyMove") then reposition_moving_for = 0 return end -- add moving time reposition_moving_for = reposition_moving_for + reposition_upd if reposition_moving_for >= 2000 then -- reset and give reposition buff reposition_moving_for = 0 durations_t.reposition_var = talents_table["defensive"]["merc"]["reposition"].duration end end local spot_weakness_tmr = 0 function spot_weakness_update() local tg = time_global() if spot_weakness_tmr > tg then return end spot_weakness_tmr = tg + 1000 -- return if not assassin or has no talent if not (picked_class == "assassin" and talents_table["offensive"]["assassin"]["spot_weakness"].level > 0) then return end local stop_itr = false -- check if enemy in 20 meters radius and enemy and not fighting actor local function itr(obj) if (not stop_itr) and (IsStalker(obj) or IsMonster(obj)) and obj:alive() and obj:id() ~= 0 then local ac_comm = get_actor_true_community() local npc_comm = IsStalker(obj) and obj.character_community and obj:character_community() local fighting_actor = xr_combat_ignore.fighting_with_actor_npcs[obj:id()] if (not fighting_actor) and (IsMonster(obj) or (npc_comm and game_relations.is_factions_enemies(ac_comm, npc_comm))) then stop_itr = true spot_weakness_stacks = spot_weakness_stacks + 1 durations_t.spot_weakness_var = talents_table["offensive"]["assassin"]["spot_weakness"].duration -- if has "observant" talent if talents_table["offensive"]["assassin"]["assassin_observant"].level > 0 then spot_weakness_stacks = 10 end end end end level.iterate_nearest(db.actor:position(), 20, itr) -- clamp stacks if spot_weakness_stacks > 10 then spot_weakness_stacks = 10 end end local acrobatic_tmr = 0 function acrobatic_elusive_update() local tg = time_global() if acrobatic_tmr > tg then return end acrobatic_tmr = tg + 2500 -- acrobatic local acrobatic_t = talents_table["defensive"]["assassin"]["acrobatic"] -- if talent picked but no bonus yet - add bonus if (not acrobatic_var) and acrobatic_t.level > 0 and outfit_type(acrobatic_t.outfits) then acrobatic_var = true local cur_jump = db.actor:get_actor_jump_speed() db.actor:set_actor_jump_speed(cur_jump * 1.3) -- if talent not picked but has bonus - remove bonus elseif acrobatic_var and (acrobatic_t.level <= 0 or (not outfit_type(acrobatic_t.outfits)) ) then acrobatic_var = false local cur_jump = db.actor:get_actor_jump_speed() db.actor:set_actor_jump_speed(cur_jump / 1.3) end -- elusive local elusive_t = talents_table["defensive"]["assassin"]["elusive"] -- if talent picked and we in combat - add speed local in_combat = not (is_empty(xr_combat_ignore.fighting_with_actor_npcs)) if in_combat and elusive_t.level > 0 then local val = elusive_t.speed[elusive_t.level] speed.add_speed("elusive", 1 + val / 100, false, true) -- if talent not picked or we not in combat - remove speed elseif (not in_combat) or elusive_t.level <= 0 then speed.remove_speed("elusive") end end function trooper_precision_on_fire(obj, wpn) if not (obj:id() == 0 and picked_class == "trooper" and wpn) then return end local precision_t = talents_table["offensive"]["trooper"]["precision"] if precision_t.level > 0 then local kind = ini_sys:r_string_ex(wpn:section(), "kind") if kind and kind == "w_rifle" then -- allow first bullet if cooldowns_t.precision_var <= 0 then precision_allow_dmg = true -- dont allow if cd still on else precision_allow_dmg = false end -- set cd cooldowns_t.precision_var = precision_t.cooldown end end end function merc_flurry_on_fire(obj, wpn) if not (obj:id() == 0 and picked_class == "merc" and wpn) then return end local flurry_t = talents_table["defensive"]["merc"]["flurry"] if flurry_t.level > 0 then durations_t.flurry_var = flurry_t.duration end end function on_sleep_kindred(hours) local kindred_t = talents_table["defensive"]["monster_hunter"]["kindred"] if kindred_t.level > 0 and kindred_var then kindred_var = false -- spawn dogs local id = smart_terrain.nearest_to_actor_smart.id local smart = id and alife_object(id) local squad = smart and SIMBOARD:create_squad(smart, "kindred_dogs") pr("kindred, dogs spawned on smart: %s", smart and smart:name()) end end function on_enemy_eval_kindred_our_dogs(obj, enemy, flags) -- for our dogs local obj_id, en_id = obj:id(), enemy:id() if obj_id == 0 then return end local kindred_t = talents_table["defensive"]["monster_hunter"]["kindred"] if kindred_t.level <= 0 then return end local obj_sq = get_object_squad(obj) local en_sq = en_id ~= 0 and get_object_squad(enemy) local fights_actor = xr_combat_ignore.fighting_with_actor_npcs -- if obj is dog (ignore actor and everyone except enemies around) if obj_sq and obj_sq:section_name() == "kindred_dogs" and ( en_id == 0 or (not fights_actor[en_id]) ) then flags.override = true flags.result = false return end -- if dog is enemy and obj not fighting actor (ignore dogs) if en_sq and en_sq:section_name() == "kindred_dogs" and (not fights_actor[obj_id]) then flags.override = true flags.result = false end end function on_enemy_eval_kindred_other_dogs(obj, enemy, flags) -- for other dogs local obj_id, en_id = obj:id(), enemy:id() if obj_id == 0 then return end local dogs_exist = get_story_se_object("kindred_dogs") if not dogs_exist then return end -- if (enemy actor or companion) and obj is dog local en_is_ac_or_comp = en_id == 0 or enemy:has_info("npcx_is_companion") local obj_kind = ini_sys:r_string_ex(obj:section(), "species") local obj_is_dog = obj_kind and (obj_kind == "dog" or obj_kind == "pseudodog") if en_is_ac_or_comp and obj_is_dog then flags.override = true flags.result = false return end -- if enemy is dog and (obj companion) local en_kind = ini_sys:r_string_ex(enemy:section(), "species") local en_is_dog = en_kind and (en_kind == "dog" or en_kind == "pseudodog") local obj_is_comp = obj:has_info("npcx_is_companion") if en_is_dog and obj_is_comp then flags.override = true flags.result = false end end local kindred_dogs_hit_t = { [hit.burn] = true, [hit.chemical_burn] = true, [hit.shock] = true } function monster_before_hit_kindred(obj, s_hit, bone_id, flags) local obj_sq = get_object_squad(obj) if not (obj_sq and obj_sq:section_name() == "kindred_dogs") then return end local draft = s_hit.draftsman if kindred_dogs_hit_t[s_hit.type] or (draft and IsAnomaly(draft)) then flags.ret_value = false end end function first_update_kindred() if not kindred_prev_lvl then kindred_prev_lvl = level.name() end if kindred_prev_lvl ~= level.name() then pr("kindred, level changed from %s to %s || tp dogs", kindred_prev_lvl, level.name()) kindred_prev_lvl = level.name() -- tp dogs if they exist local dogs_squad = get_story_se_object("kindred_dogs") if dogs_squad then TeleportSquad(dogs_squad, db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id()) end end end local tracker_particles_t = {} local tracker_particles_tmr = 0 function monster_update_tracker(npc) local is_bloodsucker = npc and npc:clsid() == clsid.bloodsucker_s if not (is_bloodsucker and npc:alive()) then return end local tracker_t = talents_table["defensive"]["monster_hunter"]["tracker"] if tracker_t.level <= 0 then return end local tg = time_global() if tracker_particles_tmr > tg then return end tracker_particles_tmr = tg + 250 -- draw particles tracker_particles_t[tg] = particles_object("damage_fx\\smoke") if tracker_particles_t[tg] then tracker_particles_t[tg]:play_at_pos(npc:position()) CreateTimeEvent("remove_bloodsucker_particle_e_" .. tg, "remove_bloodsucker_particle_a_" .. tg, 0.5, function(key) tracker_particles_t[key]:stop() tracker_particles_t[key] = nil return true end, tg) end end local endur_tmr = 0 local endur_upd_rate = 0.5 function endurance_stamina_upd() local tg = time_global() if endur_tmr > tg then return end endur_tmr = tg + 1000 * endur_upd_rate if db.actor.power >=1 then return end if db.actor.power < 0.05 and talents_table["endurance"]["sniper"]["second_wind"].level > 0 then local wind_t = talents_table["endurance"]["sniper"]["second_wind"] local cur_time = game.get_game_time() if (not second_wind_cd) then db.actor.power = db.actor.power + wind_t.stamina[wind_t.level] / 100 second_wind_cd = ctime_to_t(cur_time) end end local stamina_regen = 0 for name, t in pairs(talents_table["endurance"]["sniper"]) do if t.stamina and this[t.func] and t.level > 0 then local val = t.stamina[t.level] val = this[t.func](val) or 0 stamina_regen = stamina_regen + val * endur_upd_rate end end db.actor.power = db.actor.power + (stamina_regen / 100) if db.actor.power >= 1 then db.actor.power = 1 end end local speed_upd_tmr = 0 function endurance_speed_upd() local tg = time_global() if speed_upd_tmr > tg then return end speed_upd_tmr = tg + 1000 for name, t in pairs(talents_table["endurance"]["sniper"]) do if t.speed and this[t.func] and t.level > 0 then local val = t.speed[t.level] val = this[t.func](val) val = val and (val / 100) or 0 speed.add_speed(name, 1 + val, false, true) end end if talents_table["defensive"]["jugger"]["bulky"].level > 0 then local bulky_t = talents_table["defensive"]["jugger"]["bulky"] local outfits = outfit_type(bulky_t.outfits) local val = outfits and (bulky_t.speed[1] / 100) or 0 speed.add_speed("bulky", 1 + val, false, true) end if class_special_stats[picked_class] then local val = class_special_stats[picked_class].speed / 100 speed.add_speed(picked_class .. "_special", 1 + val, false, true) end end local weight_upd_tmr = 0 function endurance_weight_upd() local tg = time_global() if weight_upd_tmr > tg then return end weight_upd_tmr = tg + 1000 for name, t in pairs(talents_table["endurance"]["sniper"]) do if t.weight and this[t.func] and t.level > 0 then local val = t.weight[t.level] val = this[t.func](val) or 0 weight.add_weight(name, val, true) end end if class_special_stats[picked_class] then local val = class_special_stats[picked_class].weight weight.add_weight(picked_class .. "_special", val, false, true) end if ui_inventory and ui_inventory.GUI then ui_inventory.GUI:UpdateWeight() end end -- speech price change local cur_trader_id function on_get_item_cost(kind, item, profile, vanilla_cost, ret) -- buy from npc if profile.mode == 2 then local add_buy = 0 pr("------------ buy sec: %s ------------", item:section()) -- preparation local preparation_val = talent_preparation(item) if preparation_val then pr("preparation, sec: %s || val: %s", item:section(), preparation_val) add_buy = add_buy + preparation_val end -- observant local observant_val = talent_observant(item) if observant_val then pr("observant, sec: %s || val: %s", item:section(), observant_val) add_buy = add_buy + observant_val end -- gregarious local gregarious_val = talent_gregarious(item) if gregarious_val then pr("gregarious, sec: %s || val: %s", item:section(), gregarious_val) add_buy = add_buy + gregarious_val end -- well_liked local well_liked_val = talent_well_liked(item, cur_trader_id) if well_liked_val then pr("well_liked, sec: %s || val: %s", item:section(), well_liked_val) add_buy = add_buy + well_liked_val end -- buy total pr("BUY old cost: %s", ret.new_cost or vanilla_cost) ret.new_cost = (ret.new_cost or vanilla_cost) * (1 + add_buy / 100) pr("BUY new cost: %s", ret.new_cost) end -- sell to npc if profile.mode == 1 then local add_sell = 0 pr("------------ sell sec: %s ------------", item:section()) -- supplier local supplier_val = talent_supplier(item) if supplier_val then pr("supplier, sec: %s || val: %s", item:section(), supplier_val) add_sell = add_sell + supplier_val end -- smuggler local smuggler_val = talent_smuggler(item) if smuggler_val then pr("smuggler, sec: %s || val: %s", item:section(), smuggler_val) add_sell = add_sell + smuggler_val end -- persuasion local persuasion_val = talent_persuasion(item) if persuasion_val then pr("persuasion, sec: %s || val: %s", item:section(), persuasion_val) add_sell = add_sell + persuasion_val end -- shady local shady_val = talent_shady(item, profile) if shady_val then pr("shady, sec: %s || val: %s", item:section(), shady_val) add_sell = add_sell + shady_val end -- cynical local cynical_val = talent_cynical_set(item, profile, cur_trader_id, ret.new_cost, vanilla_cost) if cynical_val then pr("cynical, sec: %s || val: %s", item:section(), cynical_val) add_sell = add_sell + cynical_val end -- sell total pr("SELL old cost: %s", ret.new_cost or vanilla_cost) ret.new_cost = (ret.new_cost or vanilla_cost) * (1 + add_sell / 100) pr("SELL new cost: %s", ret.new_cost) end end function ActorMenu_on_before_init_mode(mode, flags, obj) if mode == "trade" and obj and obj:id() then local shady_t = talents_table["speech"]["sniper"]["shady"] local cfg = trade_manager.get_trade_profile(obj:id(), "cfg_ltx") if shady_t.level > 0 and cfg and cfg == "items\\trade\\trade_generic.ltx" then local money_per_level = shady_t.stalkers_money[1] -- shady (give/update npc money on trade init) for i = 1, shady_t.level do if (not obj:has_info("shady_" .. i)) then obj:give_info_portion("shady_" .. i) pr("shady level %s, give %s money for stalker", i, money_per_level) obj:give_money(money_per_level) end end end end end function open_trade(mode) if mode ~= 2 then return end local npc = mob_trade.GetTalkingNpc() local is_trader = npc and trade_manager.get_trade_profile(npc:id(), "cfg_ltx") if not is_trader then return end cur_trader_id = npc:id() end function close_trade(mode) if cur_trader_id and mode ~= 2 then cur_trader_id = nil end end local cyn_tmr = 0 function cynical_coin_update() local tg = time_global() if cyn_tmr > tg then return end cyn_tmr = tg + 5000 if talents_table["speech"]["sniper"]["cynical"].level > 0 and (not cynical_cd) then -- if no coin then spawn coin local has_coin = db.actor:object("cynical_coin") if not has_coin then alife_create_item("cynical_coin", db.actor) end end end function actor_on_item_buy(item) if not cur_trader_id then return end local trader = cur_trader_id and level.object_by_id(cur_trader_id) if not (trader and trader.alive and trader:alive()) then return end -- charismatic talent_charismatic(item, trader) end function actor_on_item_sell(item) if not cur_trader_id then return end local trader = cur_trader_id and level.object_by_id(cur_trader_id) if not (trader and trader.alive and trader:alive()) then return end local trader_comm if trader.section and trader:section() and (trader:section() == "m_trader" or trader:section() == "m_lesnik") then trader_comm = "stalker" else trader_comm = trader:character_community() end if item:section() == "inv_update_item_xd" then return end -- cynical talent_cynical_sell(item, trader_comm) -- deceitful talent_deceitful(item, trader) end ------------------------------------- -- misc function outfit_type(comp_t) local outfit = db.actor:item_in_slot(7) local kind = outfit and ini_sys:r_string_ex(outfit:section(), "kind") if kind and in_ar(comp_t, kind) then return true end end function in_ar(a, v) for i, val in ipairs(a) do if val == v then return true end end end function install_random_upgrade(item) local upgr_t = utils_item.get_upgrades_tree(item:section(), true) if (is_empty(upgr_t)) then return end local to_install = {} for row, t in pairs(upgr_t) do if t[1] and t[1].section then pr("store sec: %s", t[1].section) to_install[#to_install + 1] = t[1].section end end local picked_upgr = #to_install > 0 and to_install[math.random(1, #to_install)] if picked_upgr then pr("picked and installing upgrade: %s", picked_upgr) inventory_upgrades.force_upgrade = true item:install_upgrade(picked_upgr) inventory_upgrades.force_upgrade = false end end function play_evasion_anm() local anms = { "hit_left", "hit_right" } level.add_cam_effector("camera_effects\\" .. anms[math.random(1, #anms)] .. ".anm", 812415, false, "", 0, true, 2) end local collector_table = { ["mutant"] = { tushkano = 1, flesh = 1, zombie = 1, dog = 1, fracture = 2, boar = 2, cat = 2, pseudodog = 2, SM_LURKER = 3, snork = 3, SM_KARLIK = 3, SM_POLTER_G = 4, SM_PYRO_G = 4, SM_PSEUDO_G = 4, burer = 4, bloodsucker = 4, SM_PSYSUCKER = 4, chimera = 7, controller = 7, giant = 10 }, ["stalker"] = { novice = 2, trainee = 2, experienced = 3, professional = 3, veteran = 4, expert = 4, master = 5, legend = 5 }, } function get_collector_val(npc) if IsMonster(npc) then local mutant_str = ini_sys:r_string_ex(npc:section(), "kind") or ini_sys:r_string_ex(npc:section(), "species") or nil if mutant_str and collector_table["mutant"][mutant_str] then return collector_table["mutant"][mutant_str] end elseif IsStalker(npc) then local obj_rank_name = ranks.get_obj_rank_name(npc) local rank_val = obj_rank_name and collector_table["stalker"][obj_rank_name] if rank_val then return collector_table["stalker"][obj_rank_name] end end end function actor_has_coin() local has_coin = false db.actor:iterate_belt( function(owner, item) if item:section() == "cynical_coin" then has_coin = true end end) return has_coin end function check_mutant_type(npc, typ, str) if not npc then return end local mutant_type = ini_sys:r_string_ex(npc:section(), "kind") or ini_sys:r_string_ex(npc:section(), "species") if not mutant_type then return end if mutants_t["beast"][mutant_type] then return typ and "beast" or mutant_type elseif mutants_t["hum"][mutant_type] then return typ and "hum" or mutant_type end end function get_mutant_name(typ) local ret_str = "" -- return all of this typ as string if typ and mutants_t[typ] then for kind, name in pairs(mutants_t[typ]) do local s = gt(name) ret_str = ret_str .. " " .. s .. "," end end -- make string out of mutants that werent killed yet if typ == "trophies" then for mut_typ, t in pairs(mutants_t) do for kind, name in pairs(t) do if not (monster_hunter_trophies[kind]) then local s = gt(name) ret_str = ret_str .. " " .. s .. "," end end end end ret_str = string.sub(ret_str, 1, -2) -- remove last comma return ret_str end function get_left_communities() local ret_str = "" -- make string out of communities that werent killed yet for comm, _ in pairs(stalker_comms_t) do if not (merc_head_hunter_t[comm]) then local s = gt("st_faction_" .. comm) ret_str = ret_str .. " " .. s .. "," end end ret_str = string.sub(ret_str, 1, -2) -- remove last comma return ret_str end -- for stats, calculate cur and max stats (all "cur" are included in "max" too) local calc_stats_t = { ["offensive"] = { ["sniper"] = { cur = { "sniper_rifles", "assault_rifles_novice", "pistols", "experienced_sniper", "steady", "convenience", "gunslinger", "loner", "nocturnal", "carnage" }, max = { "sharpshooter", "ambush", "soft_spot" }, }, ["trooper"] = { cur = { "assault_rifles", "shotguns_novice", "submachine_guns", "experienced_rifleman", "focus", "thrill", "precision", "barrage", "reckless", "collector" }, max = { "duelist" }, }, ["jugger"] = { cur = { "shotguns", "machine_guns", "submachine", "heavy_guns_expert", "fury", "selective" }, max = { "hunter", "bounty_hunter", "merciless", "execution", "frightening" }, }, ["monster_hunter"] = { cur = { "trophies", "fierce", "ranger" }, max = { "beast_anatomy", "humanoid_anatomy", "beast_rivalry", "humanoid_rivalry" }, }, ["merc"] = { cur = { "firearm_enthusiast", "overdrive", "head_hunter", "bullseye", "assault_rifles_expert", "light_guns_expert", "sniper_rifles_expert" }, max = { "arrogance", "aim_torso", "aim_limbs" }, }, ["assassin"] = { cur = { "smg_novice", "handgun_novice", "knifeman", "spot_weakness", "tactical", "pragmatic" }, max = { "undercover", "backstab", "saboteur", "surprise_attack", "vile" }, }, }, ["defensive"] = { ["sniper"] = { cur = { "adaptive", "iron_stomach", "plastic_pads", "aluminum_plates", "nature_resistance", "rad_resistant", "technician", "expert_technician", "agile" }, max = { }, }, ["trooper"] = { cur = { "ceramic_plates", "isolated", "armorer", "chem_resistant", "suppresion_fire", "cautious", "fireproof", "composure", "enduring", "adorned", "expert_armorer", "last_stand", "leap" }, max = { }, }, ["jugger"] = { cur = { "steel_plates", "steel_pads", "heavy_armor_expert", "dismorale", "sturdy", "sanity", "anxiety", "bulky", "secure", "lucid" }, max = { "turmoil" }, }, ["monster_hunter"] = { cur = { "shoulder_pads", "careful", "melee_defense", "special_diet", "clarity", "soft_armor", "stable_mind", "second_skin", "custom_fit" }, max = { "beast_demeanor", "humanoid_demeanor" }, }, ["merc"] = { cur = { "mercenary_vest", "insensitive", "chem_reliant", "grenadier", "addictive", "flurry", "loose_fit", "reposition" }, max = { "confusion", "disguise_master" }, }, ["assassin"] = { cur = { "ordinary", "quiet", "evanescent", "lurking", "subtle", "covert", "cunning", "vague", "sneaky", "incognito", "ghost", "elusive" }, max = { "deception" }, }, }, ["endurance"] = { ["sniper"] = { speed = { "ease", "swifty", "insomnia", "athletic" }, stamina = { "resting", "tireless", "courage" }, weight = { "hoarder", "iron_spine" }, }, } } calc_stats_t["endurance"]["trooper"] = calc_stats_t["endurance"]["sniper"] calc_stats_t["endurance"]["jugger"] = calc_stats_t["endurance"]["sniper"] calc_stats_t["endurance"]["monster_hunter"] = calc_stats_t["endurance"]["sniper"] calc_stats_t["endurance"]["merc"] = calc_stats_t["endurance"]["sniper"] calc_stats_t["endurance"]["assassin"] = calc_stats_t["endurance"]["sniper"] local max_mult_t = { ["sharpshooter"] = 150, ["focus"] = 100, ["reckless"] = 75, ["collector"] = 1500, ["expert_technician"] = 100, ["cautious"] = 2, ["adorned"] = 5, ["expert_armorer"] = 10, ["secure"] = 5, ["lucid"] = 100, ["athletic"] = 100, ["iron_spine"] = 100, ["trophies"] = 20, ["fierce"] = 5, ["second_skin"] = 5, ["head_hunter"] = 11, ["arrogance"] = 7, ["spot_weakness"] = 10, ["sneaky"] = 2, } local trooper_def_mults = { "ceramic_plates", "isolated", "armorer", "chem_resistant", "fireproof" } local monster_hunter_def_mults = { "shoulder_pads", "careful", "soft_armor", "clarity" } local stats_conv_t = { ["sniper_rifle"] = "w_sniper", ["assault_rifle"] = "w_rifle", ["radiation"] = "radi", ["ballistic"] = "bal", ["rupture"] = "rup", ["explosive"] = "expl", } function r_stat(v) return round_idp(v, 1) end -- offensive returns { [1] = base_dmg, [2] = max_dmg, [3] = base_crit, [4] = max_crit } -- defensive returns { [1] = cur_res, [2] = max_res } -- endurance returns { [1] = cur_val, [2] = max_val } function calculate_stats(typ, class_name, req_typ) if not (typ and class_name) then return end local stat_t = calc_stats_t[typ] and calc_stats_t[typ][class_name] if not stat_t then return end -- offensive if typ == "offensive" and req_typ then return calc_stats_offensive(typ, class_name, req_typ, stat_t) end -- defensive if typ == "defensive" and req_typ then return calc_stats_defensive(typ, class_name, req_typ, stat_t) end -- endurance if typ == "endurance" and req_typ then return calc_stats_endurance(typ, class_name, req_typ, stat_t) end end function calc_stats_offensive(typ, class_name, req_typ, stat_t) local wpn = stats_conv_t[req_typ] or "w_" .. req_typ -- base/max dmg and crit local cur_dmg = 80 local cur_crit = 0 local max_dmg = 80 local max_crit = 0 for talent, t in pairs(talents_table[typ][class_name]) do -- if picked AND wpn fits if t.level > 0 and (in_ar(t.wpns, wpn) or in_ar(t.wpns, "all")) then -- mult for max local mult = max_mult_t[talent] or 1 -- cur if in_ar(stat_t.cur, talent) then -- damage if t.damage then local val = t.damage[t.level] if this[t.func] then val = this[t.func](nil, val) or 0 end cur_dmg = cur_dmg + val end -- crit if t.crit then local val = t.crit[t.level] if this[t.func] then val = this[t.func](nil, val) or 0 end cur_crit = cur_crit + val end end -- max if in_ar(stat_t.max, talent) or in_ar(stat_t.cur, talent) then -- damage if t.damage then local val = t.damage[t.level] * mult max_dmg = max_dmg + val -- jugger mastery local mastery_t = talents_table[typ][class_name]["mastery"] if mastery_t and mastery_t.level > 0 then if talent == "merciless" then max_dmg = max_dmg + mastery_t.merciless_bonus elseif talent == "fury" then max_dmg = max_dmg + mastery_t.fury_bonus end end end -- crit if t.crit then local val = t.crit[t.level] * mult max_crit = max_crit + val end end end end -- ret damage and crit return { r_stat(cur_dmg), r_stat(max_dmg), r_stat(cur_crit), r_stat(max_crit) } end function calc_stats_defensive(typ, class_name, req_typ, stat_t) local res = stats_conv_t[req_typ] or req_typ -- base and max values local cur_res = 0 local max_res = 0 for talent, t in pairs(talents_table[typ][class_name]) do -- talent is picked AND has our stat AND outfit fits if t.level > 0 and t[res] and (outfit_type(t.outfits) or in_ar(t.outfits, "all")) then -- mult for max local mult = max_mult_t[talent] or 1 -- trooper mult for max local trooper_mult = 1 if in_ar(trooper_def_mults, talent) and outfit_type({"o_medium", "o_sci"}) then trooper_mult = 2 end -- monster hunter mult for max local monster_hunter_mult = 1 if in_ar(monster_hunter_def_mults, talent) and outfit_type({"o_medium"}) then monster_hunter_mult = 2 end -- merc mult for cur and max local merc_mult = 1 if class_name == "merc" and outfit_type({"o_heavy"}) then local direct_t = talents_table[typ][class_name]["direct"] if direct_t.level > 0 then local direct_val = direct_t.percents[direct_t.level] / 100 merc_mult = merc_mult * direct_val else merc_mult = 0 end end -- inventor mult for both cur and max local inventor_mult = 1 if talents_table[typ][class_name]["inventor"] and talents_table[typ][class_name]["inventor"].level > 0 and outfit_type({"o_light"}) then inventor_mult = 2 end -- covert mult for both cur and max local covert_mult = 1 if res == "disguise" and class_name == "assassin" and outfit_type({"o_medium", "o_sci"}) then local covert_t = talents_table[typ][class_name]["covert"] if covert_t.level > 0 then covert_mult = 1.5 end end -- cunning mult for both cur and max local cunning_mult = 1 if res == "stealth" and class_name == "assassin" and outfit_type({"o_light"}) then local cunning_t = talents_table[typ][class_name]["cunning"] if cunning_t.level > 0 then cunning_mult = 1.5 end end -- cur if in_ar(stat_t.cur, talent) then local val = t[res][t.level] if this[t.func] then val = this[t.func](val) or 0 end cur_res = cur_res + val * inventor_mult * merc_mult * covert_mult * cunning_mult end -- max if in_ar(stat_t.max, talent) or in_ar(stat_t.cur, talent) then local val = t[res][t.level] * mult * trooper_mult * monster_hunter_mult * merc_mult * covert_mult * cunning_mult -- anxiety bonus local anxiety_t = talents_table[typ][class_name]["anxiety"] if talent == "dismorale" and anxiety_t and anxiety_t.level > 0 then val = val * 2 end -- clamp secure if talent == "secure" and val > 15 then val = 15 end max_res = max_res + val * inventor_mult end end end -- if not stealth / disguise / evade then return +80 if res ~= "stealth" and res ~= "disguise" and res ~= "evade" then cur_res = cur_res + 80 max_res = max_res + 80 end return { r_stat(cur_res), r_stat(max_res) } end function calc_stats_endurance(typ, class_name, req_typ, stat_t) local cur_val = 0 local max_val = 0 for talent, t in pairs(talents_table[typ][class_name]) do if t.level > 0 and stat_t[req_typ] and in_ar(stat_t[req_typ], talent) then local mult = max_mult_t[talent] or 1 -- cur value local val = t[req_typ][t.level] cur_val = cur_val + (this[t.func](val) or 0) -- max value max_val = max_val + val * mult end end -- stamina if req_typ == "stamina" then return { r_stat(cur_val), r_stat(max_val) } end -- weight if req_typ == "weight" then -- special cur_val = cur_val + class_special_stats[class_name].weight max_val = max_val + class_special_stats[class_name].weight return { r_stat(cur_val), r_stat(max_val) } end -- speed if req_typ == "speed" then -- bulky local bulky_t = talents_table["defensive"]["jugger"]["bulky"] if bulky_t and bulky_t.level > 0 and outfit_type(bulky_t.outfits) then cur_val = cur_val - 15 max_val = max_val - 15 end -- elusive local elusive_t = talents_table["defensive"]["assassin"]["elusive"] if elusive_t and elusive_t.level > 0 and outfit_type({"o_medium", "o_sci", "o_light"}) then local in_combat = not (is_empty(xr_combat_ignore.fighting_with_actor_npcs)) if in_combat then cur_val = cur_val + elusive_t.speed[elusive_t.level] max_val = max_val + elusive_t.speed[elusive_t.level] end end -- special cur_val = cur_val + class_special_stats[class_name].speed max_val = max_val + class_special_stats[class_name].speed end cur_val = cur_val + 100 max_val = max_val + 100 -- ret val return { r_stat(cur_val), r_stat(max_val) } end -- Tooltip requirements string function get_tooltip_req_str(typ, class_name, talent) local tal_t = talents_table[typ][class_name][talent] -- "all" points string local all_str = gt("talents_tooltip_req") .. " " .. gt("talents_tooltip_req_all") .. " " .. tal_t.req.all -- "all" met local all_met = false local spent_points = talents_leveling.talent_levels[typ].spent_points if spent_points >= tal_t.req.all then all_met = true end -- if only "all" in table if size_table(tal_t.req) <= 1 then return all_met and "" or all_str end -- check "other talent" requirement local req_talent_met = false local req_talent, req_level for req_k, req_v in pairs(tal_t.req) do if req_k ~= "all" then req_talent = req_k req_level = req_v end end if talents_table[typ][class_name][req_talent].level >= req_level then req_talent_met = true end -- if "other talent" requirement met if req_talent_met then return all_met and "" or all_str end -- "other talent" not met -- "all" met if all_met then return gt("talents_tooltip_req") .. " " .. gt(req_talent .. "_name") .. " " .. req_level end -- "other talent" and "all" not met return all_str .. ", " .. gt(req_talent .. "_name") .. " " .. req_level end -- Tooltip description local pos_str_ar = { "damage", "crit", "crit_damage", "reload", "bleed", "burn", "chem", "shock", "radi", "rup", "strike", "bal", "expl", "psi", "speed", "defense", "sell", "evade", "disguise", "stealth", "attack_speed" } local pos_str_exc_ar = { "refreshing", "hoarder", "second_wind" } local neg_str_ar = { "weight" } local none_str_ar = { "buy", "rad_food", "heal", "free", "stalkers_money", "hp_thresh", "radius", "percents" } local game_hours_cd = { "artistic", "second_wind", "deadly_blow" } function get_tooltip_descr_str(typ, class_name, talent) local class_t = talents_table[typ][class_name] local tal_t = talents_table[typ][class_name][talent] local descr_ar = tal_t.descr -- if descr field does not exist - return normal xml string if not descr_ar then return gt(talent .. "_descr") end -- collect variables local vals_ar = {} for idx, name in ipairs(descr_ar) do if tal_t[name] then -- get level + 1 or level if table and number if number if type(tal_t[name]) == "number" then if in_ar(game_hours_cd, talent) then -- game hours vals_ar[#vals_ar + 1] = tal_t[name] / 600 .. " " .. gt("talents_cd_game_hours") elseif name == "cooldown" then -- real seconds vals_ar[#vals_ar + 1] = tal_t[name] .. " " .. gt("talents_cd_sec") else -- number local val = tal_t[name] if talent == "dismorale" and class_t["anxiety"].level > 0 then -- dismorale duration val = 30 elseif talent == "execution" and class_t["mastery"].level > 0 then -- execution+mastery val = val + class_t["mastery"].execution_bonus elseif talent == "frightening" and class_t["mastery"].level > 0 then -- frightening+mastery val = val + class_t["mastery"].frightening_bonus end vals_ar[#vals_ar + 1] = val end elseif type(tal_t[name]) == "table" then local cur_val = tal_t[name][tal_t.level] or tal_t[name][tal_t.level + 1] local next_val = tal_t[name][tal_t.level + 1] or tal_t[name][tal_t.level] -- dismorale double if talent == "dismorale" and class_t["anxiety"].level > 0 then cur_val = cur_val * 2 next_val = next_val * 2 end -- mastery + merciless if talent == "merciless" and class_t["mastery"].level > 0 then cur_val = cur_val + class_t["mastery"].merciless_bonus next_val = next_val + class_t["mastery"].merciless_bonus end -- mastery + fury if talent == "fury" and class_t["mastery"].level > 0 then cur_val = cur_val + class_t["mastery"].fury_bonus next_val = next_val + class_t["mastery"].fury_bonus end -- have points if talents_leveling.talent_levels[typ].points > 0 then -- level = 0 (display "next") if tal_t.level <= 0 then vals_ar[#vals_ar + 1] = next_val -- level > 0 AND level not max (display "current -> next") elseif tal_t.level ~= tal_t.max then if in_ar(pos_str_ar, name) or in_ar(pos_str_exc_ar, talent) then -- +% vals_ar[#vals_ar + 1] = cur_val .. "% -> +" .. next_val elseif in_ar(neg_str_ar, name) then -- -% vals_ar[#vals_ar + 1] = cur_val .. "% -> -" .. next_val elseif name == "stamina" then -- +%per sec vals_ar[#vals_ar + 1] = cur_val .. "%/" .. gt("stat_stamina_per_sec") .. " -> +" .. next_val elseif in_ar(none_str_ar, name) then -- nothing% vals_ar[#vals_ar + 1] = cur_val .. "% -> " .. next_val end -- level is max (display "current") else vals_ar[#vals_ar + 1] = cur_val end -- have no points AND level = 0 (display "next") elseif tal_t.level <= 0 then vals_ar[#vals_ar + 1] = next_val -- have no points AND level > 0 (display "current") else vals_ar[#vals_ar + 1] = cur_val end end end end -- add collector stacks if talent == "collector" then vals_ar[#vals_ar + 1] = collector_var end -- add beast descr if talent == "beast_anatomy" then vals_ar[#vals_ar + 1] = get_mutant_name("beast") end -- add humanoid descr if talent == "humanoid_anatomy" then vals_ar[#vals_ar + 1] = get_mutant_name("hum") end -- add trophies descr if talent == "trophies" then vals_ar[#vals_ar + 1] = size_table(monster_hunter_trophies) * (tal_t.damage[tal_t.level] or tal_t.damage[tal_t.level + 1]) vals_ar[#vals_ar + 1] = get_mutant_name("trophies") end -- add head hunter descr if talent == "head_hunter" then vals_ar[#vals_ar + 1] = size_table(merc_head_hunter_t) * (tal_t.damage[tal_t.level] or tal_t.damage[tal_t.level + 1]) vals_ar[#vals_ar + 1] = get_left_communities() end -- make and return string return strformat(gt(talent .. "_descr"), unpack(vals_ar)) end function get_tooltip_height_mult(typ, class_name, talent) local tal_t = talents_table[typ][class_name][talent] return tal_t.descr_size and descr_sizes_t[tal_t.descr_size] or descr_sizes_t[1] end -- header for weapon/outfit types local kinds_to_display = { "w_sniper", "w_rifle", "w_smg", "w_shotgun", "w_pistol", "w_machine_gun", "o_light", "o_medium", "o_sci", "o_heavy", } BuildDescHeaderBase = ui_item.build_desc_header function ui_item.build_desc_header(obj, sec, str) str = str or gc(ini_sys:r_string_ex(sec, "description")) if (not str) then return "" end local kind = ini_sys:r_string_ex(sec, "kind") local purp_clr = utils_xml.get_color("d_purple") local grey_clr = utils_xml.get_color("ui_gray_1") if not kind then return BuildDescHeaderBase(obj, sec, str) end if IsWeapon(obj) or IsOutfit(obj) then if machine_guns_t[sec] then kind = "w_machine_gun" end if in_ar(kinds_to_display, kind) then return " " .. purp_clr .. gt("talents_header_krug") .. " " .. grey_clr .. gt("talents_header_" .. kind) .. "\\n \\n" .. BuildDescHeaderBase(obj, sec, str) end end return BuildDescHeaderBase(obj, sec, str) end function pr(...) if talents_dbg then printf(...) end end ------------------------------------- function actor_on_update() duration_update() cooldown_update() update_leap() jugger_regeneration_update() deep_wound_update() modified_bullets_update() reposition_update() spot_weakness_update() acrobatic_elusive_update() endurance_stamina_upd() endurance_speed_upd() endurance_weight_upd() cynical_coin_update() end function ActorMenu_on_trade_started() RegisterScriptCallback("actor_on_item_take", actor_on_item_buy) RegisterScriptCallback("actor_on_item_drop", actor_on_item_sell) end function ActorMenu_on_trade_closed() UnregisterScriptCallback("actor_on_item_take", actor_on_item_buy) UnregisterScriptCallback("actor_on_item_drop", actor_on_item_sell) -- reset cynical id if cynical_item_id then cynical_item_id = nil end end function save_state(m_data) m_data.picked_class = picked_class for typ, t in pairs(talents_table) do for class_name, t2 in pairs(t) do for talent, tal_t in pairs(t2) do m_data.talents_t = m_data.talents_t or {} m_data.talents_t[talent] = tal_t.level end end end m_data.collector_var = collector_var m_data.monster_hunter_trophies = monster_hunter_trophies m_data.special_diet_var = durations_t.special_diet_var m_data.overdrive_var = durations_t.overdrive_var m_data.chem_reliant_var = durations_t.chem_reliant_var m_data.kindred_cd = kindred_cd m_data.kindred_var = kindred_var m_data.kindred_prev_lvl = kindred_prev_lvl m_data.second_wind_cd = second_wind_cd m_data.artistic_cd = artistic_cd m_data.cynical_cd = cynical_cd m_data.merc_head_hunter_t = merc_head_hunter_t end function load_state(m_data) picked_class = m_data.picked_class or "null" for typ, t in pairs(talents_table) do for class_name, t2 in pairs(t) do for talent, tal_t in pairs(t2) do talents_table[typ][class_name][talent].level = m_data.talents_t and m_data.talents_t[talent] or 0 end end end collector_var = m_data.collector_var or 0 monster_hunter_trophies = m_data.monster_hunter_trophies or {} durations_t.special_diet_var = m_data.special_diet_var or 0 durations_t.overdrive_var = m_data.overdrive_var or 0 durations_t.chem_reliant_var = m_data.chem_reliant_var or 0 kindred_cd = m_data.kindred_cd or nil kindred_var = m_data.kindred_var or nil kindred_prev_lvl = m_data.kindred_prev_lvl or nil second_wind_cd = m_data.second_wind_cd or nil artistic_cd = m_data.artistic_cd or nil cynical_cd = m_data.cynical_cd or nil merc_head_hunter_t = m_data.merc_head_hunter_t or {} end function respec_talents() picked_class = "null" collector_var = 0 empty_table(monster_hunter_trophies) kindred_cd = nil second_wind_cd = nil artistic_cd = nil cynical_cd = nil empty_table(merc_head_hunter_t) for k, v in pairs(cooldowns_t) do cooldowns_t[k] = 0 end for k, v in pairs(durations_t) do durations_t[k] = 0 end remove_all_items_weight() for typ, t in pairs(talents_table) do for class_name, t2 in pairs(t) do for talent, talent_t in pairs(t2) do speed.remove_speed(talent) weight.remove_weight(talent) if talent_t.level > 0 then talents_table[typ][class_name][talent].level = 0 end end end end for class_name, t in pairs(class_special_stats) do speed.remove_speed(class_name .. "_special") weight.remove_weight(class_name .. "_special") end local coin = db.actor:object("cynical_coin") if coin then alife_release(coin) end local kindred_dogs_squad = get_story_se_object("kindred_dogs") if kindred_dogs_squad then alife_release(kindred_dogs_squad) end end function on_option_change() talents_key = talents_mcm.get_config("talents_key") talents_dbg = talents_mcm.get_config("talents_dbg") end function on_game_start() RegisterScriptCallback("save_state", save_state) RegisterScriptCallback("load_state", load_state) RegisterScriptCallback("npc_on_before_hit", actor_hit_npc) RegisterScriptCallback("monster_on_before_hit", actor_hit_npc) RegisterScriptCallback("npc_on_before_hit", npc_on_before_hit) RegisterScriptCallback("monster_on_before_hit", npc_on_before_hit) RegisterScriptCallback("actor_on_before_hit", actor_on_before_hit) RegisterScriptCallback("npc_on_death_callback", npc_on_death_callback) RegisterScriptCallback("monster_on_death_callback", npc_on_death_callback) RegisterScriptCallback("actor_on_item_use", actor_on_item_use) RegisterScriptCallback("actor_item_to_ruck", upgrades_to_ruck) RegisterScriptCallback("actor_item_to_slot", weight_to_slot) RegisterScriptCallback("actor_item_to_ruck", weight_to_ruck) RegisterScriptCallback("actor_item_to_belt", weight_to_belt) RegisterScriptCallback("actor_on_item_drop", weight_on_drop) RegisterScriptCallback("actor_on_first_update", weight_first_update) RegisterScriptCallback("server_entity_on_unregister", server_entity_on_unregister) RegisterScriptCallback("on_key_press", on_key_leap) RegisterScriptCallback("actor_on_hud_animation_play", actor_on_hud_animation_play) RegisterScriptCallback("actor_on_weapon_fired", trooper_precision_on_fire) RegisterScriptCallback("actor_on_weapon_fired", merc_flurry_on_fire) RegisterScriptCallback("actor_on_sleep", on_sleep_kindred) RegisterScriptCallback("on_enemy_eval", on_enemy_eval_kindred_our_dogs) RegisterScriptCallback("on_enemy_eval", on_enemy_eval_kindred_other_dogs) RegisterScriptCallback("monster_on_before_hit", monster_before_hit_kindred) RegisterScriptCallback("actor_on_first_update", first_update_kindred) RegisterScriptCallback("monster_on_update", monster_update_tracker) RegisterScriptCallback("on_get_item_cost", on_get_item_cost) RegisterScriptCallback("ActorMenu_on_before_init_mode", ActorMenu_on_before_init_mode) RegisterScriptCallback("actor_on_update", actor_on_update) RegisterScriptCallback("ActorMenu_on_mode_changed", open_trade) RegisterScriptCallback("ActorMenu_on_mode_changed", close_trade) RegisterScriptCallback("ActorMenu_on_trade_started", ActorMenu_on_trade_started) RegisterScriptCallback("ActorMenu_on_trade_closed", ActorMenu_on_trade_closed) RegisterScriptCallback("on_option_change", on_option_change) end