1246 lines
48 KiB
Plaintext
1246 lines
48 KiB
Plaintext
|
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
|