370 lines
15 KiB
Plaintext
370 lines
15 KiB
Plaintext
|
|
||
|
|
||
|
local function compute_fatal_hit_info(hits)
|
||
|
local fatal_hit = nil
|
||
|
local agg_hit = {}
|
||
|
local max_time = -1
|
||
|
|
||
|
soulslike.debug("Calculating fatal hit:")
|
||
|
soulslike.debug(hits)
|
||
|
|
||
|
-- Find the final blow
|
||
|
for _, value in ipairs(hits) do
|
||
|
if value.is_fatal then
|
||
|
fatal_hit = value
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
|
||
|
for _, value in ipairs(hits) do
|
||
|
max_time = math.max(max_time, value.time)
|
||
|
end
|
||
|
|
||
|
local min_time = soulslike.MAX_HIT_TIME
|
||
|
|
||
|
-- Convert to an aggregation of damage if there is a well defined draftsman_type
|
||
|
for _, hit_data in ipairs(hits) do
|
||
|
hit_data.draftsman = hit_data.draftsman_id and level.object_by_id(hit_data.draftsman_id) or nil
|
||
|
local power_weight = (hit_data.time - min_time) / soulslike.MAX_HIT_TIME
|
||
|
local draftsman_type = soulslike.entity_type.Other
|
||
|
if hit_data.draftsman then
|
||
|
if hit_data.draftsman:id() == AC_ID then
|
||
|
draftsman_type = soulslike.entity_type.Self
|
||
|
elseif IsStalker(hit_data.draftsman) then
|
||
|
draftsman_type = soulslike.entity_type.Stalker
|
||
|
elseif IsMonster(hit_data.draftsman) then
|
||
|
draftsman_type = soulslike.entity_type.Monster
|
||
|
elseif IsAnomaly(hit_data.draftsman) then
|
||
|
draftsman_type = soulslike.entity_type.Anomaly
|
||
|
end
|
||
|
end
|
||
|
if not agg_hit[draftsman_type] then
|
||
|
agg_hit[draftsman_type] = {
|
||
|
draftsman_type = draftsman_type
|
||
|
}
|
||
|
end
|
||
|
local data = agg_hit[draftsman_type]
|
||
|
data.power = (data.power or 0) + hit_data.power * (power_weight * (hit_data.is_fatal and 2 or 1))
|
||
|
end
|
||
|
|
||
|
soulslike.debug("Agg Hit Info:")
|
||
|
soulslike.debug(agg_hit)
|
||
|
|
||
|
local result = nil
|
||
|
|
||
|
for _, value in pairs(agg_hit) do
|
||
|
if not result then
|
||
|
result = value
|
||
|
elseif result.power < value.power then
|
||
|
result = value
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if result then
|
||
|
result.fatal_hit = fatal_hit
|
||
|
end
|
||
|
|
||
|
return result
|
||
|
end
|
||
|
|
||
|
local function find_backpack()
|
||
|
soulslike.debug("Looking for player backpack")
|
||
|
local has_backpack = false
|
||
|
local function find_backpack(_, item)
|
||
|
local sec = item:section()
|
||
|
has_backpack = SYS_GetParam(0, sec, "kind") == "i_backpack"
|
||
|
if has_backpack then return true end
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
db.actor:iterate_inventory(find_backpack)
|
||
|
|
||
|
if has_backpack then
|
||
|
soulslike.debug("Player has a backpack")
|
||
|
else
|
||
|
soulslike.debug("Player does not have a backpack")
|
||
|
end
|
||
|
return has_backpack
|
||
|
end
|
||
|
|
||
|
---------------------------------------------------------
|
||
|
-- Factory
|
||
|
---------------------------------------------------------
|
||
|
|
||
|
function create_by_id(scenario_id, state)
|
||
|
local scenario = nil
|
||
|
|
||
|
soulslike.debug_tip("Creating scenario "..tostring(scenario_id))
|
||
|
|
||
|
if scenario_id == soulslike.SCENARIOS.Default then
|
||
|
scenario = soulslike_scenarios.DefaultSoulslikeScenarioLogic(state)
|
||
|
end
|
||
|
|
||
|
if scenario_id == soulslike.SCENARIOS.RFDetectorStash then
|
||
|
scenario = soulslike_scenarios.RFDetectorSoulslikeScenarioLogic(state)
|
||
|
end
|
||
|
|
||
|
if scenario_id == soulslike.SCENARIOS.HiddenStash then
|
||
|
scenario = soulslike_scenarios.HiddenStashSoulslikeScenarioLogic(state)
|
||
|
end
|
||
|
|
||
|
if scenario_id == soulslike.SCENARIOS.NoLoss then
|
||
|
scenario = soulslike_scenarios.NoLossSoulslikeScenarioLogic(state)
|
||
|
end
|
||
|
|
||
|
if scenario == nil then
|
||
|
soulslike.debug("Unrecognized Scenario Id: "..tostring(scenario_id))
|
||
|
scenario = soulslike_scenarios.DefaultSoulslikeScenarioLogic(state)
|
||
|
end
|
||
|
|
||
|
return scenario
|
||
|
end
|
||
|
|
||
|
function create_new(hits)
|
||
|
local hit = compute_fatal_hit_info(hits)
|
||
|
|
||
|
if hit then
|
||
|
soulslike.debug("Fatal Hit Type: "..tostring(hit.draftsman_type))
|
||
|
end
|
||
|
-- We want to be able to debug each scenario using the
|
||
|
-- scenario weight to max, and in this case we want to
|
||
|
-- be able to remove the default scenario so its always
|
||
|
-- chosen.
|
||
|
|
||
|
local has_backpack = find_backpack()
|
||
|
local player_is_indoor = not level_weathers.valid_levels[level.name()]
|
||
|
local rescuer = nil
|
||
|
local looter = nil
|
||
|
local looter_type = nil
|
||
|
local scenario_loot_scalar = 1.0
|
||
|
local available_scenarios = {}
|
||
|
local game_state = soulslike.get_soulslike_state()
|
||
|
local spawn_position = vector():set(game_state.spawn_location.position.x, game_state.spawn_location.position.y, game_state.spawn_location.position.z)
|
||
|
local distance_from_spawn = spawn_position:distance_to_sqr(db.actor:position())
|
||
|
|
||
|
if soulslike_mcm.debug_the_noloss_scenario() then
|
||
|
soulslike.debug("Debugging noloss scenario.")
|
||
|
rescuer = soulslike.find_closest_friendly_stalker()
|
||
|
scenario_loot_scalar = 0
|
||
|
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.NoLoss, min = 0, max = 0, weight = 1.0})
|
||
|
elseif soulslike_mcm.debug_the_default_scenario() then
|
||
|
soulslike.debug("Debugging default scenario.")
|
||
|
rescuer = soulslike.find_closest_friendly_stalker()
|
||
|
looter = soulslike.find_closest_enemy()
|
||
|
scenario_loot_scalar = 1
|
||
|
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.Default, min = 0, max = 0, weight = 1.0})
|
||
|
elseif soulslike_mcm.debug_the_rf_scenario() then
|
||
|
soulslike.debug("Debugging rf detector scenario.")
|
||
|
|
||
|
rescuer = soulslike.find_closest_friendly_stalker()
|
||
|
looter = soulslike.find_closest_enemy()
|
||
|
scenario_loot_scalar = 1
|
||
|
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.RFDetectorStash, min = 0, max = 0, weight = 1.0})
|
||
|
elseif soulslike_mcm.debug_the_rf_scenario() then
|
||
|
soulslike.debug("Debugging hidden stash scenario.")
|
||
|
|
||
|
rescuer = soulslike.find_closest_friendly_stalker()
|
||
|
looter = soulslike.find_closest_enemy()
|
||
|
scenario_loot_scalar = 1
|
||
|
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.HiddenStash, min = 0, max = 0, weight = 1.0})
|
||
|
elseif distance_from_spawn <= 50 then
|
||
|
soulslike.debug("Player was killed "..tostring(distance_from_spawn).." meters their spawn point.")
|
||
|
|
||
|
rescuer = soulslike.find_closest_friendly_stalker()
|
||
|
scenario_loot_scalar = 0
|
||
|
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.NoLoss, min = 0, max = 0, weight = 1.0})
|
||
|
elseif distance_from_spawn <= 200 then
|
||
|
soulslike.debug("Player was killed "..tostring(distance_from_spawn).." meters their spawn point.")
|
||
|
|
||
|
rescuer = soulslike.find_closest_friendly_stalker()
|
||
|
looter = soulslike.find_closest_enemy()
|
||
|
scenario_loot_scalar = (distance_from_spawn - 50) / 150
|
||
|
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.NoLoss, min = 0, max = 0, weight = 1.0})
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.Default, min = 0, max = 0, weight = scenario_loot_scalar})
|
||
|
elseif player_is_indoor then
|
||
|
soulslike.debug("Player died indoor, oof.")
|
||
|
-- TODO: Need to figure out this scenario later, not sure what to do....
|
||
|
-- It will be very difficult to get your gear back, prob annoyingly frustrating...
|
||
|
-- For now, no loss scenario
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.NoLoss, min = 0, max = 0, weight = 1.0})
|
||
|
elseif hit and hit.draftsman_type == soulslike.entity_type.Self then
|
||
|
soulslike.debug("Player killed themself. haha idiot.")
|
||
|
|
||
|
rescuer = soulslike.find_closest_friendly_stalker()
|
||
|
looter = soulslike.find_closest_enemy()
|
||
|
scenario_loot_scalar = 0.75
|
||
|
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.NoLoss, min = 0, max = 0, weight = 0.05})
|
||
|
|
||
|
if has_backpack then
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.Default, min = 0, max = 0, weight = 1.0})
|
||
|
end
|
||
|
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.RFDetectorStash, min = 0, max = 0, weight = soulslike_mcm.rf_detector_scenario_weight()})
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.HiddenStash, min = 0, max = 0, weight = soulslike_mcm.hidden_stash_scenario_weight()})
|
||
|
elseif hit and
|
||
|
hit.draftsman_type == soulslike.entity_type.Stalker and
|
||
|
hit.fatal_hit and
|
||
|
hit.fatal_hit.draftsman:general_goodwill(db.actor) >= soulslike.RELATIONS.NEUTRALS then
|
||
|
soulslike.debug("Friendly/Neutral Stalker killed the player.")
|
||
|
|
||
|
rescuer = hit.draftsman
|
||
|
looter = nil
|
||
|
scenario_loot_scalar = 0.0
|
||
|
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.NoLoss, min = 0, max = 0, weight = 1.0})
|
||
|
elseif hit and
|
||
|
hit.draftsman_type == soulslike.entity_type.Stalker then
|
||
|
soulslike.debug("Enemy Stalker killed the player.")
|
||
|
|
||
|
rescuer = soulslike.find_closest_friendly_stalker()
|
||
|
looter = soulslike.find_closest_enemy_stalker()
|
||
|
scenario_loot_scalar = 1.0
|
||
|
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.NoLoss, min = 0, max = 0, weight = 0.05})
|
||
|
|
||
|
if has_backpack then
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.Default, min = 0, max = 0, weight = 1.0})
|
||
|
end
|
||
|
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.RFDetectorStash, min = 0, max = 0, weight = soulslike_mcm.rf_detector_scenario_weight()})
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.HiddenStash, min = 0, max = 0, weight = soulslike_mcm.hidden_stash_scenario_weight()})
|
||
|
elseif hit and
|
||
|
hit.draftsman_type == soulslike.entity_type.Monster then
|
||
|
soulslike.debug("Mutant killed the player.")
|
||
|
|
||
|
rescuer = soulslike.find_closest_friendly_stalker()
|
||
|
looter = soulslike.find_closest_enemy_mutant()
|
||
|
scenario_loot_scalar = 1.0
|
||
|
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.NoLoss, min = 0, max = 0, weight = 0.05})
|
||
|
|
||
|
if has_backpack then
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.Default, min = 0, max = 0, weight = 1.0})
|
||
|
end
|
||
|
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.RFDetectorStash, min = 0, max = 0, weight = soulslike_mcm.rf_detector_scenario_weight()})
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.HiddenStash, min = 0, max = 0, weight = soulslike_mcm.hidden_stash_scenario_weight()})
|
||
|
elseif hit and
|
||
|
hit.draftsman_type == soulslike.entity_type.Anomaly then
|
||
|
soulslike.debug("Player died to an anomaly.")
|
||
|
|
||
|
rescuer = soulslike.find_closest_friendly_stalker()
|
||
|
looter = soulslike.find_closest_enemy()
|
||
|
scenario_loot_scalar = 0.25
|
||
|
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.NoLoss, min = 0, max = 0, weight = 0.05})
|
||
|
|
||
|
if has_backpack then
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.Default, min = 0, max = 0, weight = 1.0})
|
||
|
else
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.RFDetectorStash, min = 0, max = 0, weight = soulslike_mcm.rf_detector_scenario_weight()})
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.HiddenStash, min = 0, max = 0, weight = soulslike_mcm.hidden_stash_scenario_weight()})
|
||
|
end
|
||
|
elseif hit and
|
||
|
hit.draftsman_type == soulslike.entity_type.Other then
|
||
|
soulslike.debug("Player died to damage over time of some sort.")
|
||
|
|
||
|
rescuer = soulslike.find_closest_friendly_stalker()
|
||
|
looter = soulslike.find_closest_enemy()
|
||
|
scenario_loot_scalar = 0.25
|
||
|
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.NoLoss, min = 0, max = 0, weight = 0.05})
|
||
|
|
||
|
if has_backpack then
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.Default, min = 0, max = 0, weight = 1.0})
|
||
|
else
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.RFDetectorStash, min = 0, max = 0, weight = soulslike_mcm.rf_detector_scenario_weight()})
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.HiddenStash, min = 0, max = 0, weight = soulslike_mcm.hidden_stash_scenario_weight()})
|
||
|
end
|
||
|
else
|
||
|
soulslike.warn("I have no idea how the player died.")
|
||
|
|
||
|
rescuer = soulslike.find_closest_friendly_stalker()
|
||
|
looter = soulslike.find_closest_enemy()
|
||
|
scenario_loot_scalar = 0.25
|
||
|
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.NoLoss, min = 0, max = 0, weight = 0.05})
|
||
|
|
||
|
if has_backpack then
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.Default, min = 0, max = 0, weight = 1.0})
|
||
|
else
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.RFDetectorStash, min = 0, max = 0, weight = soulslike_mcm.rf_detector_scenario_weight()})
|
||
|
table.insert(available_scenarios, {id = soulslike.SCENARIOS.HiddenStash, min = 0, max = 0, weight = soulslike_mcm.hidden_stash_scenario_weight()})
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if looter and IsStalker(looter) then
|
||
|
looter_type = soulslike.entity_type.Stalker
|
||
|
elseif looter and IsMonster(looter) then
|
||
|
looter_type = soulslike.entity_type.Monster
|
||
|
end
|
||
|
|
||
|
local is_in_water = load_var(db.actor,"grw_in_water") == true
|
||
|
|
||
|
if is_in_water then
|
||
|
soulslike.debug("Actor died in the water, reducing loot scalar.")
|
||
|
scenario_loot_scalar = scenario_loot_scalar * 0.5
|
||
|
end
|
||
|
|
||
|
if #available_scenarios == 0 then
|
||
|
soulslike.error("No scenario chosen because all scenario weights are at 0 and remove default was enabled.")
|
||
|
return nil
|
||
|
end
|
||
|
|
||
|
local weight_sum = 0
|
||
|
for _, value in pairs(available_scenarios) do
|
||
|
weight_sum = weight_sum + value.weight
|
||
|
end
|
||
|
|
||
|
local min = 0
|
||
|
for _, value in pairs(available_scenarios) 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 scenario_id = nil
|
||
|
for id, value in pairs(available_scenarios) do
|
||
|
if(roll >= value.min and roll <= value.max) then
|
||
|
scenario_id = value.id
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if scenario_id == nil then
|
||
|
soulslike.debug("ERROR: Unable to determine scenario from roll: "..tostring(roll))
|
||
|
soulslike.debug(available_scenarios)
|
||
|
scenario_id = soulslike.SCENARIOS.NoLoss
|
||
|
end
|
||
|
|
||
|
local scenario_logic = create_by_id(scenario_id)
|
||
|
local level_name = level.name()
|
||
|
|
||
|
scenario_logic:SetLevelName(game.translate_string(level_name))
|
||
|
scenario_logic:SetLootScalar(scenario_loot_scalar)
|
||
|
scenario_logic:SetIsInDoor(player_is_indoor)
|
||
|
scenario_logic:SetIsInWater(is_in_water)
|
||
|
scenario_logic:SetRescuer(rescuer)
|
||
|
|
||
|
if looter then
|
||
|
scenario_logic:SetLooter(looter)
|
||
|
scenario_logic:SetLooterType(looter_type)
|
||
|
end
|
||
|
|
||
|
if hit and hit.fatal_hit then
|
||
|
scenario_logic:SetKillerType(hit.draftsman_type)
|
||
|
scenario_logic:SetKiller(hit.fatal_hit.draftsman)
|
||
|
scenario_logic:SetFatalHitType(hit.fatal_hit.type)
|
||
|
end
|
||
|
|
||
|
return scenario_logic
|
||
|
end
|