local ignore_list = { ["bolt"] = true, ["itm_actor_backpack"] = true, -- Quick Release ["device_pda"] = true, ["device_pda_1"] = true, ["device_pda_2"] = true, ["device_pda_3"] = true, ["detector_radio"] = true, ["wpn_knife"] = true, ["wpn_knife2"] = true, ["wpn_knife3"] = true, ["wpn_knife4"] = true, ["wpn_knife5"] = true, ["wpn_knife6"] = true, ["wpn_knife7"] = true, ["wpn_knife8"] = true, ["wpn_knife9"] = true, ["wpn_axe"] = true, ["wpn_axe2"] = true, ["wpn_axe3"] = true, ["bandage"] = true, ["medkit"] = true, } local obj_to_spawn_classes = { -- kind ["AI_STL_S"] = "NPC (Stalker)", ["AI_TRD_S"] = "NPC (Stalker)", ["SM_KAR"] = "NPC (Mutant)", ["SM_BLOOD"] = "NPC (Mutant)", ["SM_BOARW"] = "NPC (Mutant)", ["SM_BURER"] = "NPC (Mutant)", ["SM_CAT_S"] = "NPC (Mutant)", ["SM_CHIMS"] = "NPC (Mutant)", ["SM_CONTR"] = "NPC (Mutant)", ["SM_DOG_S"] = "NPC (Mutant)", ["SM_FLESH"] = "NPC (Mutant)", ["SM_IZLOM"] = "NPC (Mutant)", ["SM_GIANT"] = "NPC (Mutant)", ["SM_POLTR"] = "NPC (Mutant)", ["SM_P_DOG"] = "NPC (Mutant)", ["SM_DOG_P"] = "NPC (Mutant)", ["SM_DOG_F"] = "NPC (Mutant)", ["SM_SNORK"] = "NPC (Mutant)", ["SM_TUSHK"] = "NPC (Mutant)", ["SM_ZOMBI"] = "NPC (Mutant)", ["SM_RAT"] = "NPC (Mutant)", ["SM_KARLIK"] = "NPC (Mutant)", ["SM_LURKER"] = "NPC (Mutant)", ["SM_PSYSUCKER"] = "NPC (Mutant)", ["C_HLCP_S"] = "Vehicles", ["C_NIVA"] = "Vehicles", ["SCRPTCAR"] = "Vehicles", ["ON_OFF_S"] = "Squads", ["O_PHYSIC"] = "Physic (Misc.)", ["O_DSTRBL"] = "Physic (Misc.)", ["P_DSTRBL"] = "Physic (Misc.)", ["O_PHYS_S"] = "Physic (Misc.)", ["O_DSTR_S"] = "Physic (Misc.)", ["S_INVBOX"] = "Physic (Misc.)", ["O_INVBOX"] = "Physic (Misc.)", ["S_EXPLO"] = "Physic (Misc.)", ["II_EXPLO"] = "Physic (Misc.)", ["ZS_MBALD"] = "Anomaly", ["ZS_GALAN"] = "Anomaly", ["ZS_MINCE"] = "Anomaly", ["ZS_RADIO"] = "Anomaly", ["ZS_TORRD"] = "Anomaly", ["ZS_NGRAV"] = "Anomaly", ["Z_MBALD"] = "Anomaly", ["Z_RADIO"] = "Anomaly", ["Z_CFIRE"] = "Anomaly", ["Z_NOGRAV"] = "Anomaly", ["Z_TORRID"] = "Anomaly", ["Z_RUSTYH"] = "Anomaly", ["ZS_BFUZZ"] = "Anomaly", ["ZS_AMEBA"] = "Anomaly", ["AI_PHANT"] = "Phantom", } local mutant_spawn_type = { Boar = 1, Flesh = 2, Dog = 3, Cat = 4, Snork =5, BloodSucker = 6, Burer = 7, Chimera = 8, Controller = 9 } local stalker_spawn_type = { Novice = 1, Advanced = 2, Veteran = 2, Sniper = 2, } local mutant_spawns = { --Common Mutants { section = "simulation_boar", weight = 1.00, type = mutant_spawn_type.Boar }, { section = "simulation_flesh", weight = 0.90, type = mutant_spawn_type.Flesh }, { section = "simulation_dog", weight = 0.80, type = mutant_spawn_type.Dog }, { section = "simulation_cat", weight = 0.60, type = mutant_spawn_type.Cat }, { section = "simulation_boar_3_5", weight = 0.50, type = mutant_spawn_type.Boar }, { section = "simulation_mix_boar_flesh", weight = 0.40, type = mutant_spawn_type.Flesh }, { section = "simulation_mix_dogs", weight = 0.20, type = mutant_spawn_type.Dog }, { section = "simulation_dog_5_7", weight = 0.15, type = mutant_spawn_type.Dog }, { section = "simulation_pseudodog", weight = 0.12, type = mutant_spawn_type.Dog }, { section = "simulation_cat_3_5", weight = 0.10, type = mutant_spawn_type.Cat }, { section = "simulation_snork", weight = 0.08, type = mutant_spawn_type.Snork }, --Rare Mutants { section = "simulation_bloodsucker", weight = 0.04, type = mutant_spawn_type.BloodSucker }, { section = "simulation_burer1", weight = 0.03, type = mutant_spawn_type.Burer }, { section = "simulation_chimera", weight = 0.02, type = mutant_spawn_type.Chimera }, { section = "simulation_bloodsucker_2weak", weight = 0.02, type = mutant_spawn_type.BloodSucker }, { section = "simulation_chimera_2weak", weight = 0.01, type = mutant_spawn_type.Chimera }, { section = "simulation_controller", weight = 0.01, type = mutant_spawn_type.Controller }, } local stalker_spawns = { --Loner { section = "stalker_sim_squad_novice", community = "stalker", weight = 1.00, type = stalker_spawn_type.Novice }, { section = "stalker_sim_squad_advanced", community = "stalker", weight = 0.50, type = stalker_spawn_type.Advanced }, { section = "stalker_sim_squad_veteran", community = "stalker", weight = 0.10, type = stalker_spawn_type.Veteran }, --Bandit { section = "bandit_sim_squad_novice", community = "bandit", weight = 1.00, type = stalker_spawn_type.Novice }, { section = "bandit_sim_squad_advanced", community = "bandit", weight = 0.50, type = stalker_spawn_type.Advanced }, { section = "bandit_sim_squad_veteran", community = "bandit", weight = 0.10, type = stalker_spawn_type.Veteran }, --Merc { section = "merc_sim_squad_novice", community = "killer", weight = 0.75, type = stalker_spawn_type.Novice }, { section = "merc_sim_squad_advanced", community = "killer", weight = 0.25, type = stalker_spawn_type.Advanced }, { section = "merc_sim_squad_veteran", community = "killer", weight = 0.10, type = stalker_spawn_type.Veteran }, --Duty { section = "duty_sim_squad_novice", community = "dolg", weight = 1.00, type = stalker_spawn_type.Novice }, { section = "duty_sim_squad_advanced", community = "dolg", weight = 0.10, type = stalker_spawn_type.Advanced }, { section = "duty_sim_squad_veteran", community = "dolg", weight = 0.10, type = stalker_spawn_type.Veteran }, --Freedom { section = "freedom_sim_squad_novice", community = "freedom", weight = 1.00, type = stalker_spawn_type.Novice }, { section = "freedom_sim_squad_advanced", community = "freedom", weight = 0.10, type = stalker_spawn_type.Advanced }, { section = "freedom_sim_squad_veteran", community = "freedom", weight = 0.10, type = stalker_spawn_type.Veteran }, --Military { section = "army_sim_squad_novice", community = "army", weight = 1.00, type = stalker_spawn_type.Novice }, { section = "army_sim_squad_advanced", community = "army", weight = 0.10, type = stalker_spawn_type.Advanced }, { section = "army_sim_squad_veteran", community = "army", weight = 0.10, type = stalker_spawn_type.Veteran }, { section = "army_sim_squad_sniper", community = "army", weight = 0.05, type = stalker_spawn_type.Sniper }, --Clear Sky { section = "csky_sim_squad_novice", community = "csky", weight = 1.00, type = stalker_spawn_type.Novice }, { section = "csky_sim_squad_advanced", community = "csky", weight = 0.10, type = stalker_spawn_type.Advanced }, { section = "csky_sim_squad_veteran", community = "csky", weight = 0.10, type = stalker_spawn_type.Veteran }, --Renegades { section = "renegade_sim_squad_novice", community = "renegade", weight = 0.50, type = stalker_spawn_type.Novice }, { section = "renegade_sim_squad_advanced", community = "renegade", weight = 0.20, type = stalker_spawn_type.Advanced }, { section = "renegade_sim_squad_veteran", community = "renegade", weight = 0.10, type = stalker_spawn_type.Veteran }, --Clear Sky { section = "csky_sim_squad_novice", community = "csky", weight = 0.50, type = stalker_spawn_type.Novice }, { section = "csky_sim_squad_advanced", community = "csky", weight = 0.20, type = stalker_spawn_type.Advanced }, { section = "csky_sim_squad_veteran", community = "csky", weight = 0.10, type = stalker_spawn_type.Veteran }, --Monolith { section = "monolith_sim_squad_novice", community = "monolith", weight = 0.50, type = stalker_spawn_type.Novice }, { section = "monolith_sim_squad_advanced", community = "monolith", weight = 0.20, type = stalker_spawn_type.Advanced }, { section = "monolith_sim_squad_veteran", community = "monolith", weight = 0.10, type = stalker_spawn_type.Veteran }, --Sin { section = "greh_sim_squad_novice", community = "greh", weight = 0.50, type = stalker_spawn_type.Novice }, { section = "greh_sim_squad_advanced", community = "greh", weight = 0.20, type = stalker_spawn_type.Advanced }, { section = "greh_sim_squad_veteran", community = "greh", weight = 0.10, type = stalker_spawn_type.Veteran }, --UNISG { section = "isg_sim_squad_novice", community = "isg", weight = 0.50, type = stalker_spawn_type.Novice }, { section = "isg_sim_squad_advanced", community = "isg", weight = 0.20, type = stalker_spawn_type.Advanced }, { section = "isg_sim_squad_veteran", community = "isg", weight = 0.10, type = stalker_spawn_type.Veteran }, --Ecologist { section = "ecolog_sim_squad_novice", community = "ecolog", weight = 0.50, type = stalker_spawn_type.Novice }, { section = "ecolog_sim_squad_advanced", community = "ecolog", weight = 0.20, type = stalker_spawn_type.Advanced }, { section = "ecolog_sim_squad_veteran", community = "ecolog", weight = 0.10, type = stalker_spawn_type.Veteran }, } local function is_equipped(id) local is_equipped = false for i=1,12 do slot = db.actor:item_in_slot(i) if (slot and slot:id() == id) then is_equipped = true break end end return is_equipped end --------------------------------------------------------- -- Base Scenario Class --------------------------------------------------------- class "SoulslikeScenarioLogic" function SoulslikeScenarioLogic:__init(state) if state then self.logic_state = state soulslike.debug("Scenario created with state:") soulslike.debug(state) else local rank = db.actor:character_rank() if soulslike_mcm.debug_item_loss() then soulslike.debug("Debugging item loss, setting max rank:") rank = 56000 end local ranked_chance = math.clamp(math.sqrt(rank / 500) * math.log(rank * rank) / math.log(1000) * 1.5, 0, 50) / 66 soulslike.debug("Creating logic state.") self.logic_state = { rank = rank, ranked_chance = ranked_chance, item_condition_loss_percent = math.clamp(ranked_chance * soulslike_mcm.get_item_condition_loss_percent(), 0, 1), keep_equipped_items_on_death = soulslike_mcm.get_keep_equipped_items_on_death(), item_loss_scalar = soulslike_mcm.get_item_loss_scalar(), health_loss_percent = soulslike_mcm.get_health_loss_percent(), rank_loss_percent = soulslike_mcm.rank_loss_percent(), rep_loss_percent = soulslike_mcm.rep_loss_percent(), allow_weapon_loss = soulslike_mcm.allow_weapon_loss(), allow_artifact_loss = soulslike_mcm.allow_artifact_loss(), allow_outfit_loss = soulslike_mcm.allow_outfit_loss(), allow_headgear_loss = soulslike_mcm.allow_headgear_loss(), allow_toolkit_loss = soulslike_mcm.allow_toolkit_loss(), mutant_ambush_chance = soulslike_mcm.mutant_ambush_chance(), stalker_ambush_chance = soulslike_mcm.stalker_ambush_chance(), allow_boar_ambush = soulslike_mcm.allow_boar_ambush(), allow_flesh_ambush = soulslike_mcm.allow_flesh_ambush(), allow_dogs_ambush = soulslike_mcm.allow_dogs_ambush(), allow_cats_ambush = soulslike_mcm.allow_cats_ambush(), allow_snorks_ambush = soulslike_mcm.allow_snorks_ambush(), allow_bloodsucker_ambush = soulslike_mcm.allow_bloodsucker_ambush(), allow_burer_ambush = soulslike_mcm.allow_burer_ambush(), allow_chimera_ambush = soulslike_mcm.allow_chimera_ambush(), allow_controller_ambush = soulslike_mcm.allow_controller_ambush(), allow_stalker_novice_ambush = soulslike_mcm.allow_stalker_novice_ambush(), allow_stalker_advanced_ambush = soulslike_mcm.allow_stalker_advanced_ambush(), allow_stalker_veteran_ambush = soulslike_mcm.allow_stalker_veteran_ambush(), allow_stalker_sniper_ambush = soulslike_mcm.allow_stalker_sniper_ambush(), allow_npc_looting = true, are_looter_npcs_marked = soulslike_mcm.are_looter_npcs_marked(), death_location = { position = { x = nil, y = nil, z = nil }, level_vertex_id = nil, game_vertex_id = nil }, story = { gave_food_or_water = nil, has_pda_marker = nil, has_stash_pda_marker = nil, radio_freq = nil, items_were_lost = nil, enemy = nil, -- { name, community, tarkov_experience } player_died_indoor = nil, player_died_in_water = nil, level_name = nil, } } soulslike.debug("Scenario created without state.") end self.game_state = soulslike.get_soulslike_state() end function SoulslikeScenarioLogic:SetIsInDoor(player_is_indoor) self.logic_state.player_died_indoor = player_is_indoor end function SoulslikeScenarioLogic:SetIsInWater(is_in_water) self.logic_state.player_died_in_water = is_in_water end function SoulslikeScenarioLogic:SetLevelName(name) self.logic_state.story.level_name = name end function SoulslikeScenarioLogic:SetLootScalar(scenario_loot_scalar) self.logic_state.scenario_loot_scalar = scenario_loot_scalar end function SoulslikeScenarioLogic:SetRescuer(npc) if npc and (type(npc.id) == "function") then self.logic_state.rescuer_id = npc:id() elseif npc and npc.id then self.logic_state.rescuer_id = npc.id end end function SoulslikeScenarioLogic:SetLooter(npc) if npc and (type(npc.id) == "function") then self.logic_state.looter_id = npc:id() elseif npc and npc.id then self.logic_state.looter_id = npc.id end end function SoulslikeScenarioLogic:SetLooterType(entity_type) self.logic_state.looter_type = entity_type self.logic_state.story.looter_type = entity_type end function SoulslikeScenarioLogic:SetKiller(npc) if npc and (type(npc.id) == "function") then self.logic_state.killer_id = npc:id() elseif npc and npc.id then self.logic_state.killer_id = npc.id end end function SoulslikeScenarioLogic:SetKillerType(entity_type) self.logic_state.killer_type = entity_type end function SoulslikeScenarioLogic:SetFatalHitType(hit_type) self.logic_state.fatal_hit_type = hit_type end function SoulslikeScenarioLogic:destroy() self.logic_state = nil self.game_state = nil end function SoulslikeScenarioLogic:HealActor() local heath = 1 - self.logic_state.health_loss_percent db.actor:set_health_ex(heath) db.actor.power = 1 db.actor.radiation = 0 db.actor.bleeding = 1 db.actor.psy_health = 1 save_var(db.actor,'grw_in_water',nil) if arszi_psy then arszi_psy.set_psy_health(1.0) end end function SoulslikeScenarioLogic:ApplyRankLoss() local rank = db.actor:character_rank() local loss_percent = self.logic_state.rank_loss_percent local rank_loss = math.abs(rank * loss_percent) soulslike.debug("Player has lost "..tostring(rank_loss).." rank.") db.actor:set_character_rank(db.actor:character_rank() + (-rank_loss or 0)) game_statistics.check_for_rank_change() end function SoulslikeScenarioLogic:ApplyRepLoss() local rep = db.actor:character_reputation() local loss_percent = self.logic_state.rep_loss_percent local rep_loss = math.abs(rep * loss_percent) soulslike.debug("Player has lost "..tostring(rep_loss).." repuation.") db.actor:set_character_reputation(db.actor:character_reputation() + (-rep_loss or 0)) game_statistics.check_for_reputation_change() end function SoulslikeScenarioLogic:ApplyItemConditionLoss(item, condition_loss_percent) local cond = item:condition() local sec = item:section() local degrade_factor = cond * condition_loss_percent local is_grenade = IsGrenade(item) local is_weapon = (IsWeapon(item) and not is_grenade) local is_outfit = IsOutfit(item) local is_headgear = IsHeadgear(item) local use_condition = utils_item.is_degradable(item, sec) local has_cond = use_condition or is_weapon or is_outfit or is_headgear if has_cond then soulslike.debug("Degrading ".. sec.. " condition ".. tostring(cond).." by "..tostring(degrade_factor)) -- utils_item.degrade releases the item if the condition is 0 local condition = utils_item.degrade(item, degrade_factor) return condition end -- No condition to be returned. return nil end function SoulslikeScenarioLogic:ApplyTransferItemsPreConditions() local is_magazine = magazine_binder and magazine_binder.is_magazine local is_carried_mag = magazine_binder and magazine_binder.is_carried_mag local toggle_carried_mag = magazine_binder and magazine_binder.toggle_carried_mag local function check_item(_, item) local sec = item:section() if is_magazine and is_magazine(sec) and is_carried_mag(item:id()) then soulslike.debug("Toggling carried mag: "..sec) local result = toggle_carried_mag(item:id()) soulslike.debug("Mag toggled to: "..tostring(result)) end end soulslike.debug("Inventory precondition checks") db.actor:iterate_inventory(check_item) end function SoulslikeScenarioLogic:ApplyTransferItemsPostConditions() end function SoulslikeScenarioLogic:HandleItemsAndRespawn() self:ApplyTransferItemsPreConditions() local actor = db.actor local position = actor:position() self.logic_state.death_location = { position = { x = position.x, y = position.y, z = position.z }, level_vertex_id = actor:level_vertex_id(), game_vertex_id = actor:game_vertex_id() } local se_stash = self:CreateStash() if se_stash then soulslike.debug("Stash created "..tostring(se_stash.id)) local function transfer_and_respawn() local pack = level.object_by_id(se_stash.id) if pack then --soulslike.try(function() soulslike.debug("Stash exists in game") self:TransferItems(se_stash.id) self:ApplyTransferItemsPostConditions() self:RespawnActor() --end) return true else soulslike.debug("Stash does not yet exist in game") return false end end --We have to create a timer to give the game time to spawn the backpack. CreateTimeEvent(0, "transfer_and_respawn", 0, transfer_and_respawn) else soulslike.debug("Stash was not created") self:RespawnActor() end end function SoulslikeScenarioLogic:OnDeath() if not self.logic_state.scenario_id then error("Soulslike: No scenario id defined!") end bind_stalker_ext.invulnerable_time = time_global() + 30000 actor_status.deactivate_hud() self:HealActor() self:ApplyRankLoss() self:ApplyRepLoss() self:HandleItemsAndRespawn() end function SoulslikeScenarioLogic:GiveFoodAndWater() -- Satiety local conditions = db.actor:cast_Actor():conditions() local satiety = conditions:GetSatiety() local red_icon_satiety = conditions:SatietyCritical() * 0.5 satiety = normalize(satiety, red_icon_satiety, 1) satiety = math.clamp( satiety / 5.65 , 0, 0.185) soulslike.debug({satiety = satiety}) if satiety < 0.5 then self.logic_state.story.gave_food_or_water = true db.actor.satiety = 0.5 end -- Thirst local thirst = 1 - actor_status_thirst.get_water_deprivation() -- 1 full local red_icon_thirst = 1 - normalize(5760, 0, 10000) -- def 0.424 red thirst = normalize(thirst, red_icon_thirst, 1) thirst = math.clamp(thirst / 5.65, 0, 0.185) soulslike.debug({thirst = thirst}) if thirst < 0.5 then self.logic_state.story.gave_food_or_water = true -- HACK... deal with it :shades: soulslike.debug("HACK updating thirst.") actor_status_thirst.load_state({drink = {last_drink = 3500, chk_drink = nil}}) actor_status_thirst.actor_on_update() end if self.logic_state.story.gave_food_or_water then soulslike.debug("Spawning bread and water on actor.") alife_create_item('bread', db.actor) alife_create_item('water_drink', db.actor) end end function SoulslikeScenarioLogic:GiveMeds() end function SoulslikeScenarioLogic:GiveWeapon() end function SoulslikeScenarioLogic:GiveOutfit() end function SoulslikeScenarioLogic:GiveMask() end function SoulslikeScenarioLogic:TrackAmbusher(id, mental_state, body_state, movement_type, sight_type, position, level_vertex_id, game_vertex_id) if not self.game_state.tracked_ambushers then self.game_state.tracked_ambushers = {} end self.game_state.tracked_ambushers[id] = { mental_state = mental_state, body_state = body_state, movement_type = movement_type, sight_type = sight_type, position = position, level_vertex_id = level_vertex_id, game_vertex_id = game_vertex_id } end function SoulslikeScenarioLogic:SpawnAmbush() soulslike.debug("Checking ambush chance.") local loss_chance_dice_roll = math.random(0, 100) local spawns = {} local sim = alife() if not sim then return end local debug_spawn = soulslike_mcm.debug_always_spawn_ambush() if debug_spawn then soulslike.debug("Ambush debugging enabled") loss_chance_dice_roll = 0 end soulslike.debug({ killer_type = self.logic_state.killer_type, has_mutant_bait = self.logic_state.has_mutant_bait, mutant_ambush_chance = self.logic_state.mutant_ambush_chance, stalker_ambush_chance = self.logic_state.stalker_ambush_chance, loss_chance_dice_roll = loss_chance_dice_roll }) if self.logic_state.killer_type == soulslike.entity_type.Monster and self.logic_state.has_mutant_bait and loss_chance_dice_roll < self.logic_state.mutant_ambush_chance * 100 then soulslike.debug("Setting up mutant spawn table.") for _, spawn in pairs(mutant_spawns) do if (spawn.type == mutant_spawn_type.Boar and self.logic_state.allow_boar_ambush) or (spawn.type == mutant_spawn_type.Flesh and self.logic_state.allow_flesh_ambush) or (spawn.type == mutant_spawn_type.Dog and self.logic_state.allow_dogs_ambush) or (spawn.type == mutant_spawn_type.Cat and self.logic_state.allow_cats_ambush) or (spawn.type == mutant_spawn_type.Snork and self.logic_state.allow_snorks_ambush) or (spawn.type == mutant_spawn_type.Burer and self.logic_state.allow_burer_ambush) or (spawn.type == mutant_spawn_type.BloodSucker and self.logic_state.allow_bloodsucker_ambush) or (spawn.type == mutant_spawn_type.Chimera and self.logic_state.allow_chimera_ambush) or (spawn.type == mutant_spawn_type.Controller and self.logic_state.allow_controller_ambush) then table.insert(spawns, spawn) end end elseif self.logic_state.killer_type == soulslike.entity_type.Stalker and loss_chance_dice_roll < self.logic_state.stalker_ambush_chance * 100 then local killer = self.logic_state.killer_id and sim:object(self.logic_state.killer_id) or nil if killer then soulslike.debug("Setting up stalker spawn table.") local community = killer:community() soulslike.debug("killer community - "..community) for _, spawn in pairs(stalker_spawns) do if spawn.community == community then soulslike.debug(spawn) if (spawn.type == stalker_spawn_type.Novice and self.logic_state.allow_stalker_novice_ambush) or (spawn.type == stalker_spawn_type.Advanced and self.logic_state.allow_stalker_advanced_ambush) or (spawn.type == stalker_spawn_type.Veteran and self.logic_state.allow_stalker_veteran_ambush) or (spawn.type == stalker_spawn_type.Sniper and self.logic_state.allow_stalker_sniper_ambush) then soulslike.debug("Spawn accepted.") table.insert(spawns, spawn) end end end end else soulslike.debug("No ambush scenario detected.") return end soulslike.debug("Spawns:") soulslike.debug(spawns) if table.get_length(spawns) == 0 then soulslike.debug("No valid spawns for ambush") return end local weight_sum = 0 for _, value in pairs(spawns) do weight_sum = weight_sum + value.weight end local min = 0 for _, value in pairs(spawns) do value.min = min + 1 value.max = math.floor(min + (100 * (value.weight / weight_sum))) min = value.max end local roll = math.random(1, 100) local section = nil for _, value in pairs(spawns) do if(roll >= value.min and roll <= value.max) then section = value.section break end end soulslike.debug_tip("Spawning ambush "..section) local class = ini_sys:r_string_ex(section,"class") local kind = ini_sys:r_string_ex(section,"kind") -- special class name for the sake of correct listing if kind then class = kind end if self.logic_state.death_location.position.x then local pos = vector():set( self.logic_state.death_location.position.x, self.logic_state.death_location.position.y, self.logic_state.death_location.position.z) local lvid = self.logic_state.death_location.level_vertex_id local gvid = self.logic_state.death_location.game_vertex_id local group = obj_to_spawn_classes[class] local is_squad = string.find(group, "Squad") local se_obj = section and alife_create(section, pos, lvid, gvid) if se_obj then if is_squad then se_obj:create_npc(nil, pos, lvid, gvid) local sim = alife() if not sim then return end for k in se_obj:squad_members() do local se_npc = k.object or k.id and sim:object(k.id) if (se_npc) then SIMBOARD:setup_squad_and_group(se_npc) SendScriptCallback("squad_on_npc_creation", se_obj, se_npc) -- TODO: Need to have them guard (this is code for stalkers... not sure what to do about mutants) self:TrackAmbusher( k.id, anim.look_around, move.standing, move.stand, look.search, self.logic_state.death_location.position) end end if soulslike_mcm.debug_squad_spawns() then level.map_add_object_spot_ser(se_obj.id, "secondary_task_location", "DEBUG: Spawn "..section) end soulslike.debug_tip(strformat("Spawned squad [%s] (%s)", section, se_obj.id)) else -- TODO: Need to have them guard (this is code for stalkers... not sure what to do about mutants) self:TrackAmbusher( se_obj.id, anim.look_around, move.standing, move.stand, look.search, self.logic_state.death_location.position, lvid, gvid) if soulslike_mcm.debug_squad_spawns() then level.map_add_object_spot_ser(se_obj.id, "secondary_task_location", "DEBUG: Spawn "..section) end soulslike.debug_tip(strformat("Spawned object [%s] (%s)", section, se_obj.id)) end else soulslike.debug(strformat("No server object made for [%s]", section)) end else soulslike.debug("No nearby smarts, to dumb") end end function SoulslikeScenarioLogic:OnComplete() self:GiveFoodAndWater() self:GiveMeds() self:GiveWeapon() self:GiveOutfit() self:GiveMask() self:SpawnAmbush() actor_status.activate_hud() self:SendWakeupMessage() if soulslike_mcm.is_hardcore_save_enabled() then soulslike.force_save("respawn") end actor_menu.set_msg(1, strformat(game.translate_string("st_death_count"), game_statistics.get_statistic_count("deaths")),8) end function SoulslikeScenarioLogic:RespawnActor() soulslike.debug("Respawning actor") local position = vector():set(self.game_state.spawn_location.position.x, self.game_state.spawn_location.position.y, self.game_state.spawn_location.position.z) local level_vertex_id = self.game_state.spawn_location.level_vertex_id local game_vertex_id = self.game_state.spawn_location.game_vertex_id level.add_pp_effector("black_infinite.ppe", 5606, true) local function respawn() soulslike.debug("Changing levels") ChangeLevel(position, level_vertex_id, game_vertex_id, VEC_ZERO, false) return true end CreateTimeEvent(0, "respawn", 1, respawn) self:StopSurgeAndStorm() end function SoulslikeScenarioLogic:SendWakeupMessage() soulslike.debug("SoulslikeScenarioLogic:SendWakeupMessage") local rescuer; if self.logic_state.rescuer_id then soulslike.debug("Looking up rescuer id "..tostring(self.logic_state.rescuer_id)) rescuer = alife_object(self.logic_state.rescuer_id) end soulslike.debug("Creating message.") local msg = soulslike_message_factory.create(rescuer, self.logic_state.story) soulslike.debug("Message created: "..msg) if rescuer then soulslike.debug("Sending tip to player from rescuer.") news_manager.send_tip(db.actor, msg, nil, rescuer, 20000) else soulslike.debug("No rescuer, its a mystery...") local ui_sender = news_manager.tips_icons['default'] db.actor:give_game_news("", msg, ui_sender, 0, 20000) end end function SoulslikeScenarioLogic:AdvanceTime() self.logic_state.is_advancing_time = true xr_effects.disable_ui(db.actor, nil) level.add_cam_effector("camera_effects\\sleep.anm", 10, false, "soulslike.dream_callback") level.add_pp_effector("sleep_fade.ppe", 11, false) _G.mus_vol = get_console_cmd(2,"snd_volume_music") _G.amb_vol = get_console_cmd(2,"snd_volume_eff") exec_console_cmd("snd_volume_music 0") exec_console_cmd("snd_volume_eff 0") level.add_pp_effector("surge_fade.ppe", 11, false) db.actor:give_info_portion("actor_is_sleeping") end function SoulslikeScenarioLogic:StopSurgeAndStorm(to_id) -- skip surge due to bug with killing actor on switch surge_manager.stop_surge() local psi_storm_manager = psi_storm_manager.get_psi_storm_manager() if (psi_storm_manager.started) then psi_storm_manager:finish(true) end end function SoulslikeScenarioLogic:IsItemLossAllowed(item) -- If the player died in the water, no items will be lost... -- A sort of i dont want to touch the gear in the water and get irradiated -- play from the looter. if self.logic_state.player_died_in_water then return false end local sec = item:section() local is_grenade = IsGrenade(item) local is_artefact = IsArtefact(item) local is_weapon = (IsWeapon(item) and not is_grenade) local is_outfit = IsOutfit(item) local is_headgear = IsHeadgear(item) local is_backpack = item and SYS_GetParam(0, sec, "kind") == "i_backpack" local is_toolkit = IsToolkit(item) local is_loss_allowed = true if is_weapon and not self.logic_state.allow_weapon_loss then soulslike.debug("Not allowed to lose weapon "..sec) is_loss_allowed = false elseif is_artefact and not self.logic_state.allow_artifact_loss then soulslike.debug("Not allowed to lose artefact "..sec) is_loss_allowed = false elseif is_outfit and not self.logic_state.allow_outfit_loss then soulslike.debug("Not allowed to lose outfit "..sec) is_loss_allowed = false elseif is_headgear and not self.logic_state.allow_headgear_loss then soulslike.debug("Not allowed to lose headgear "..sec) is_loss_allowed = false elseif is_toolkit and not self.logic_state.allow_toolkit_loss then soulslike.debug("Not allowed to lose toolkit "..sec) is_loss_allowed = false elseif is_backpack then soulslike.debug("Not allowed to lose bakcpack "..sec) is_loss_allowed = false end return is_loss_allowed end function SoulslikeScenarioLogic:TryStalkerLooter(item, container, looter) local sec = item:section() local to_id = container:id() if(self.logic_state.allow_npc_looting and looter) then soulslike.debug("Transfering item to enemy: "..sec) table.insert(self.game_state.created_stashes[to_id].lost_items, sec) db.actor:transfer_item(item, looter) self.logic_state.looter = true return true end return false end function SoulslikeScenarioLogic:TryMonsterLooter(item, container, looter) local sec = item:section() local to_id = container:id() local is_mutant_meat = SYS_GetParam(0, sec, "kind") == "i_mutant_raw" local is_food = SYS_GetParam(0, sec, "kind") == "i_food" local is_mutant_belt = SYS_GetParam(0, sec, "kind") == "i_mutant_belt" self.logic_state.has_mutant_bait = self.logic_state.has_mutant_bait or is_mutant_meat or is_food or is_mutant_belt if(self.logic_state.allow_npc_looting and looter and (is_mutant_meat or is_food or is_mutant_belt)) then soulslike.debug("Monster ate item: "..sec) table.insert(self.game_state.created_stashes[to_id].lost_items, sec) alife_release(item) return true end return false end function SoulslikeScenarioLogic:TryTarkovLooter(item, container, looter) local loss_chance_dice_roll = math.random(0, 100) local sec = item:section() local is_grenade = IsGrenade(item) local is_weapon = (IsWeapon(item) and not is_grenade) if soulslike_mcm.debug_the_tarkov_looter() then loss_chance_dice_roll = 1 end if loss_chance_dice_roll <= 1 and is_weapon and item_parts then local parts = item_parts.get_parts_con(item) if parts then local keys = {} local numitems = 0 for k,v in pairs(parts) do numitems = numitems + 1 table.insert(keys, k) end local index = math.random(1, numitems) local sec = keys[index] local condition = parts[keys[index]] soulslike.debug("Creating item "..sec.." with condition "..tostring(condition)) local max_con_obj = 0.999 local min_con_obj = 0.001 local se_result = alife_create(sec, looter:position(), looter:level_vertex_id(), looter:game_vertex_id(), looter:id(), false) local data_result = utils_stpk.get_item_data(se_result) data_result.condition = clamp((condition / 100) , min_con_obj , max_con_obj) utils_stpk.set_item_data(data_result,se_result) alife():register(se_result) parts[sec] = -1 -- KEKW -- TODO: Spawn part on looter self.logic_state.looter = true -- TODO: Add message text for this self.logic_state.got_tarkoved = true end soulslike.debug("Transfering item "..sec.." to container") db.actor:transfer_item(item, container) return true end return false end function SoulslikeScenarioLogic:TransferItem(item, container, looter) local sec = item:section() local to_id = container:id() if ignore_list[sec] then soulslike.debug("Item is in ignore list: "..sec) return false end if ini_sys:r_bool_ex(sec,"quest_item",false) then soulslike.debug("Item quest item: "..sec) return false end local keep_equipped_items_on_death = self.logic_state.keep_equipped_items_on_death if keep_equipped_items_on_death and is_equipped(item:id()) then soulslike.debug("Not allowed tolose equipped item "..sec) return false end local is_loss_allowed = self:IsItemLossAllowed(item) if is_loss_allowed then local loss_chance_dice_roll = math.random(0, 100) local loss_chance = math.floor(self.logic_state.ranked_chance * self.logic_state.item_loss_scalar * 100) loss_chance = math.clamp(loss_chance, 0, 100) * self.logic_state.scenario_loot_scalar local is_mutant_meat = SYS_GetParam(0, sec, "kind") == "i_mutant_raw" local is_food = SYS_GetParam(0, sec, "kind") == "i_food" local is_mutant_belt = SYS_GetParam(0, sec, "kind") == "i_mutant_belt" self.logic_state.has_mutant_bait = self.logic_state.has_mutant_bait or is_mutant_meat or is_food or is_mutant_belt if self.logic_state.item_condition_loss_percent > 0 then local condition = self:ApplyItemConditionLoss(item, self.logic_state.item_condition_loss_percent) if condition == 0 then soulslike.debug("Item ".. sec.. " was degraded into oblivion") self.logic_state.story.items_were_lost = true table.insert(self.game_state.created_stashes[to_id].lost_items, sec) end end -- We never want to delete the players backpack -- since it makes up the created stash if loss_chance ~= 0 and loss_chance_dice_roll < loss_chance then loss_chance_dice_roll = math.random(0, 100) -- This shit is going to be funny as fuck for me to know -- that there is a 1% chance for this to happen if looter and self:TryTarkovLooter(item, container, looter) then return false end local is_looter_stalker = self.logic_state.looter_type == soulslike.entity_type.Stalker local is_looter_mutant = self.logic_state.looter_type == soulslike.entity_type.Monster if looter and is_looter_stalker and self:TryStalkerLooter(item, container, looter) then return false end if looter and is_looter_mutant and self:TryMonsterLooter(item, container, looter) then return false end local is_ammo = IsAmmo(item) local is_medical = SYS_GetParam(0, sec, "kind") == "i_medical" local is_food = SYS_GetParam(0, sec, "kind") == "i_food" if is_ammo or is_food or is_medical then soulslike.debug("Item lost "..sec) table.insert(self.game_state.created_stashes[to_id].lost_items, sec) alife_release(item) self.logic_state.story.items_were_lost = true return false end end end soulslike.debug("Transfering item "..sec) db.actor:transfer_item(item, container) return true end function SoulslikeScenarioLogic:TransferItems(to_id) local container = level.object_by_id(to_id) local enemy_looter = self.logic_state.looter_id and level.object_by_id(self.logic_state.looter_id) or nil if enemy_looter and self.logic_state.looter_type == soulslike.entity_type.Stalker then soulslike.debug("Scenario Looter "..enemy_looter:character_community().." "..enemy_looter:character_name()) end soulslike.debug("Transfering items") local function release_actor_item(_, item) self:TransferItem(item, container, enemy_looter) end db.actor:iterate_inventory(release_actor_item) if enemy_looter and self.logic_state.looter and self.logic_state.looter_type == soulslike.entity_type.Stalker then self.logic_state.story.enemy = { name = enemy_looter:character_name(), community = enemy_looter:character_community(), tarkov_experience = self.logic_state.got_tarkoved } if self.logic_state.are_looter_npcs_marked then level.map_add_object_spot_ser(enemy_looter:id(), "secondary_task_location", self.logic_state.enemy_looter_name) end end end function SoulslikeScenarioLogic:OnRespawn() bind_stalker_ext.invulnerable_time = time_global() + 1 self:HealActor() self:AdvanceTime() local rank = db.actor:character_rank() local rep = db.actor:character_reputation() soulslike.debug('Character rank: '..rank) soulslike.debug('Character rep: '..rep) end --------------------------------------------------------- -- Default Scenario -- Stash dropped where the player died -- Marked on PDA --------------------------------------------------------- class "DefaultSoulslikeScenarioLogic" (SoulslikeScenarioLogic) function DefaultSoulslikeScenarioLogic:__init(state) super (state) self.logic_state.scenario_id = soulslike.SCENARIOS.Default end function DefaultSoulslikeScenarioLogic:CreateStash() soulslike.debug("Creating stash") local actor = db.actor local se_stash = alife_create("inv_backpack", actor:position(), actor:level_vertex_id(), actor:game_vertex_id()) if se_stash then self.game_state.created_stashes[se_stash.id] = { lost_items = {}, examine = false } self.logic_state.stash_id = se_stash.id end return se_stash end function DefaultSoulslikeScenarioLogic:ApplyTransferItemsPostConditions() self.logic_state.story.has_pda_marker = true level.map_add_object_spot_ser(self.logic_state.stash_id, "secondary_task_location", db.actor:character_name() .. "'s items") end --------------------------------------------------------- -- RF Detector Scenario -- Hidden stash used for item drop -- RF Detector must be used to find the stash -- TODO: Add task to the players PDA --------------------------------------------------------- class "RFDetectorSoulslikeScenarioLogic" (SoulslikeScenarioLogic) function RFDetectorSoulslikeScenarioLogic:__init(state) super (state) self.logic_state.scenario_id = soulslike.SCENARIOS.RFDetectorStash end function RFDetectorSoulslikeScenarioLogic:CreateStash() -- For some reason, "box_in_same_map" doesn't actually mean same map in the treasure_manager. local old_box_in_same_map = treasure_manager.box_in_same_map treasure_manager.box_in_same_map = function(id) local obj1 = alife():actor() local obj2 = alife_object(id) return simulation_objects.is_on_the_same_level(obj1, obj2) end local id = treasure_manager.get_random_stash("treasure", nil ,true, true) treasure_manager.box_in_same_map = old_box_in_same_map -- If we could not grab a ransom stash, we need to return nil so we don't transfer anything if not id then return nil end soulslike.debug("Random stash found: "..tostring(id)) local se_treasure = alife_object(id) -- If we could not grab a ransom stash, we need to return nil so we don't transfer anything if not se_treasure then return nil end local se_stash = alife_create("hidden_box", se_treasure.position, se_treasure.m_level_vertex_id, se_treasure.m_game_vertex_id) if (se_stash) then local sim = alife() if sim then -- force strictly online sim:set_switch_online(se_stash.id,true) sim:set_switch_offline(se_stash.id,false) end self.game_state.created_stashes[se_stash.id] = { lost_items = {}, examine = false } if not self.game_state.hidden_stashes then self.game_state.hidden_stashes = {} end self.logic_state.hidden_stash_id = se_stash.id self.logic_state.treasure_stash_id = id local lvl = level.name() self.game_state.hidden_stashes[id] = { stash_id = se_stash.id, radio_id = id, radio_level = lvl } end return se_stash end function RFDetectorSoulslikeScenarioLogic:ApplyTransferItemsPostConditions() local lvl = level.name() soulslike.debug("Added radio target to level: "..lvl) self.logic_state.story.radio_freq = math.random(30, 300) item_radio.add_stash(lvl, self.logic_state.treasure_stash_id, self.logic_state.story.radio_freq) if soulslike_mcm.debug_hidden_stashes() then soulslike.debug("Adding PDA marker "..tostring(self.logic_state.treasure_stash_id)) level.map_add_object_spot_ser(self.logic_state.treasure_stash_id, "secondary_task_location", "DEBUG: RF DETECTOR STASH.") end local se_note = alife_create_item('soulslike_rescuers_radio_frequency_note', db.actor) self.game_state.note_message_data[se_note.id] = { freq = self.logic_state.story.radio_freq, level_name = lvl } end --------------------------------------------------------- -- Hidden Stash Scenario -- Hidden stash used for item drop -- TODO: Add task to the players PDA --------------------------------------------------------- class "HiddenStashSoulslikeScenarioLogic" (SoulslikeScenarioLogic) function HiddenStashSoulslikeScenarioLogic:__init(state) super (state) self.logic_state.scenario_id = soulslike.SCENARIOS.HiddenStash end function HiddenStashSoulslikeScenarioLogic:CreateStash() -- For some reason, "box_in_same_map" doesn't actually mean same map in the treasure_manager. local old_box_in_same_map = treasure_manager.box_in_same_map treasure_manager.box_in_same_map = function(id) local obj1 = alife():actor() local obj2 = alife_object(id) return simulation_objects.is_on_the_same_level(obj1, obj2) end local id = treasure_manager.get_random_stash("treasure", nil ,true, true) treasure_manager.box_in_same_map = old_box_in_same_map -- If we could not grab a ransom stash, we need to return nil so we don't transfer anything if not id then return nil end soulslike.debug("Random stash found: "..tostring(id)) local actor = db.actor local se_stash = alife_create("hidden_box", actor:position(), actor:level_vertex_id(), actor:game_vertex_id()) if (se_stash) then self.game_state.created_stashes[se_stash.id] = { lost_items = {}, examine = false } if not self.game_state.hidden_stashes then self.game_state.hidden_stashes = {} end self.logic_state.hidden_stash_id = se_stash.id self.logic_state.treasure_stash_id = id self.game_state.hidden_stashes[id] = { stash_id = se_stash.id, } end return se_stash end function HiddenStashSoulslikeScenarioLogic:ApplyTransferItemsPostConditions() soulslike.debug("Adding PDA marker "..tostring(self.logic_state.treasure_stash_id)) local value = game.translate_string("st_soulslike_your_items") self.logic_state.story.has_stash_pda_marker = true level.map_add_object_spot_ser(self.logic_state.treasure_stash_id, "secondary_task_location", value) end --------------------------------------------------------- -- No Loss scenario -- Players items are all returned --------------------------------------------------------- class "NoLossSoulslikeScenarioLogic" (SoulslikeScenarioLogic) function NoLossSoulslikeScenarioLogic:__init(state) super (state) self.logic_state.scenario_id = soulslike.SCENARIOS.NoLoss end function NoLossSoulslikeScenarioLogic:CreateStash() return nil end function NoLossSoulslikeScenarioLogic:TransferItems(_) -- Do Nothing end function NoLossSoulslikeScenarioLogic:IsItemLossAllowed(item) return false end