Added New Mods and Profiles Folders

This is a complete rebuild of the modpack, with all new mods and updates for 1.5.3 of Anomaly.
This commit is contained in:
2025-01-14 05:07:53 -05:00
parent 85c665b107
commit 376b4b9689
21217 changed files with 546254 additions and 0 deletions
@@ -0,0 +1,962 @@
--[[
Jabbers
05APR2023
Jabbers' Soulslike Anomaly Mod
--]]
local version = "0.30-beta"
local tools_list = {
["itm_basickit"] = true,
["itm_advancedkit"] = true,
["itm_expertkit"] = true,
["itm_drugkit"] = true,
["itm_ammokit"] = true,
["itm_gunsmith_toolkit"] = true,
["itm_artefactskit"] = true,
}
local scenario_logic = nil
RELATIONS = {
FRIENDS = 1000,
BUDDIES = 500,
NEUTRALS = 0,
ENEMIES = -1000
}
SCENARIOS = {
Default = 1,
RFDetectorStash = 2,
HiddenStash = 3,
NoLoss = 4,
}
hit_type_to_str = {
[hit.light_burn] = "Light Burn",
[hit.burn] = "Burn",
[hit.strike] = "Strike",
[hit.shock] = "Shock",
[hit.wound] = "Wound",
[hit.radiation] = "Radiation",
[hit.telepatic] = "Telepatic",
[hit.chemical_burn] = "Chemical Burn",
[hit.explosion] = "Explosion",
[hit.fire_wound] = "Fire",
}
entity_type = {
Stalker = 1,
Monster = 2,
Anomaly = 3,
Self = 4,
Other = 4
}
----------------------------------------
-- Classes
----------------------------------------
MAX_HIT_POOL_COUNT = 30
MAX_HIT_TIME = 30000
local hit_queue = soulslike_classes.TimedQueue(MAX_HIT_POOL_COUNT, MAX_HIT_TIME)
----------------------------------------
-- Helpers
----------------------------------------
function try(func, ...)
local status, error_or_result = pcall(func, ...)
if not status then
soulslike.error(error_or_result)
return false
else
return error_or_result
end
end
function math.clamp(x, min, max)
if x < min then return min end
if x > max then return max end
return x
end
function table.get_length(T)
local count = 0
for _ in pairs(T) do count = count + 1 end
return count
end
function table.has_value(tab, val)
for _, value in ipairs(tab) do
if value == val then
return true
end
end
return false
end
----------------------------------------
-- DEBUG
----------------------------------------
local function print_table (tbl, indent)
if not indent then
indent = 0
end
utils_data.debug_write(string.rep(" ", indent) .. "{")
indent = indent + 2
if (type(tbl) == "userdata") then
utils_data.debug_write("<userdata>,\n")
else
for k, v in pairs(tbl) do
local toprint = string.rep(" ", indent)
if (type(k) == "number") then
toprint = toprint .. "[" .. k .. "] = "
elseif (type(k) == "string") then
toprint = toprint .. k .. " = "
end
if (type(v) == "number") then
utils_data.debug_write(toprint .. v .. ",")
elseif (type(v) == "string") then
utils_data.debug_write(toprint .. "\"" .. v .. "\",")
elseif (type(v) == "table") then
utils_data.debug_write(toprint)
print_table(v, indent + 2)
else
utils_data.debug_write(toprint .. "\"" .. tostring(v) .. "\",")
end
end
end
utils_data.debug_write(string.rep(" ", indent-2) .. "}")
end
local function log(log_type, output)
if not output then
utils_data.debug_write("[Soulslike] "..log_type..": ".."(nil)")
elseif (type(output) == "table") then
utils_data.debug_write("[Soulslike] "..log_type..": ")
print_table(output)
else
utils_data.debug_write("[Soulslike] "..log_type..": "..output)
end
end
function debug(output)
log("DEBUG", output)
end
function info(output)
log("INFO ", output)
end
function warn(output)
log("WARN ", output)
end
function error(output)
log("ERROR", output)
end
function debug_tip(text, delay)
debug(text)
if not soulslike_mcm.show_debug_tips() then
return
end
local text = "[Soulslike] "..tostring(text)
if not db.actor then
return
end
local ico = "ui_inGame2_Dengi_otdani"
local text_color = utils_xml.get_color("pda_white")
text = text_color .. text
if delay == nil then
delay = 6000
end
news_manager.send_tip(db.actor, text, nil, ico, delay)
end
----------------------------------------
-- Globals
----------------------------------------
function _G.IsSoulslikeMode()
return not IsHardcoreMode() and axr_main.config and axr_main.config:r_value("character_creation","new_game_soulslike_mode",1) == true or alife_storage_manager.get_state().enable_soulslike_mode == true
end
function _G.IsToolkit(o, s)
if not (s) then
s = o and o:section()
end
return tools_list[s]
end
----------------------------------------
-- Helper Functions
----------------------------------------
function get_soulslike_state()
local game_state = alife_storage_manager.get_state()
if not game_state.soulslike then
game_state.soulslike = {
created_stashes = {},
spawn_location = {
level = nil,
position = {
x = nil,
y = nil,
z = nil,
},
angle = {
x = nil,
y = nil,
z = nil,
},
level_vertex_id = nil,
game_vertex_id = nil,
},
note_message_data = {},
hidden_stashes = {}
}
end
-- Backward save compatibility
if not game_state.soulslike.note_message_data then
game_state.soulslike.note_message_data = {}
end
if not game_state.soulslike.hidden_stashes then
game_state.soulslike.hidden_stashes = {}
end
if not game_state.soulslike.created_stashes then
game_state.soulslike.created_stashes = {}
end
if not game_state.soulslike.spawn_location then
game_state.soulslike.spawn_location = {
level = nil,
position = {
x = nil,
y = nil,
z = nil,
},
angle = {
x = nil,
y = nil,
z = nil,
},
level_vertex_id = nil,
game_vertex_id = nil,
}
end
return game_state.soulslike
end
function set_spawn(show_message)
local se_actor = alife():actor()
local state = get_soulslike_state()
state.spawn_location.level = level.name()
state.spawn_location.position.x = se_actor.position.x
state.spawn_location.position.y = se_actor.position.y
state.spawn_location.position.z = se_actor.position.z
state.spawn_location.angle.x = se_actor.angle.x
state.spawn_location.angle.y = se_actor.angle.y
state.spawn_location.angle.z = se_actor.angle.z
state.spawn_location.level_vertex_id = se_actor.m_level_vertex_id
state.spawn_location.game_vertex_id = se_actor.m_game_vertex_id
debug("Saved spawn location data:")
if show_message then
local str = game.translate_string("st_soulslike_spawn_location_set")
actor_menu.set_msg(1, str, 4)
end
end
function find_closest_enemy()
local enemy = nil
local enemy_dist = 150
local sim = alife()
local gg = game_graph()
local level_name = level.name()
if not sim then return end
if not gg then return end
for i=1,65534 do
local se_obj = sim:object(i)
if se_obj and (level_name == sim:level_name(gg:vertex(se_obj.m_game_vertex_id):level_id())) then
local cls = se_obj:clsid()
local sec = se_obj:section_name()
local is_valid = false
if IsStalker(nil,cls) and string.find(sec,"sim_default_") and se_obj:alive() then
local comm = se_obj:community()
if (comm ~= "trader") and (comm ~= "zombied") then
is_valid = true
end
elseif IsMonster(nil,cls) then
is_valid = true
end
if is_valid then
local dist = se_obj.position:distance_to_sqr(db.actor:position())
if enemy_dist > dist then
if IsStalker(nil,cls) then
local comm = se_obj:community()
debug("Found enemy "..se_obj:name().." from the "..comm.." community "..tostring(dist).." meters away.")
enemy = se_obj
elseif IsMonster(nil,cls) then
enemy = se_obj
end
end
end
end
end
return enemy
end
function find_closest_enemy_mutant()
local enemy = nil
local enemy_dist = 75
local sim = alife()
local gg = game_graph()
local level_name = level.name()
if not sim then return end
if not gg then return end
for i=1,65534 do
local se_obj = sim:object(i)
if se_obj and (level_name == sim:level_name(gg:vertex(se_obj.m_game_vertex_id):level_id())) then
local cls = se_obj:clsid()
if IsMonster(nil,cls) then
local dist = se_obj.position:distance_to_sqr(db.actor:position())
if enemy_dist > dist then
if IsMonster(nil,cls) then
enemy = se_obj
end
end
end
end
end
return enemy
end
function find_closest_enemy_stalker()
local friend = nil
local friend_dist = 100000
for i=1, #db.OnlineStalkers do
local id = db.OnlineStalkers[i]
local npc = db.storage[id] and db.storage[id].object or level.object_by_id(id)
if npc then
local dist = npc:position():distance_to_sqr(db.actor:position())
local is_friend = npc:general_goodwill(db.actor) <= RELATIONS.ENEMIES
if npc:alive() and is_friend and friend_dist > dist and (not get_object_story_id(id)) then
local comm = npc:character_community()
debug("Found friend "..npc:name().." from the "..comm.." community "..tostring(dist).." meters away.")
friend = npc
friend_dist = dist
end
end
end
return friend
end
function find_closest_friendly_stalker()
local friend = nil
local friend_dist = 100000
for i=1, #db.OnlineStalkers do
local id = db.OnlineStalkers[i]
local npc = db.storage[id] and db.storage[id].object or level.object_by_id(id)
if npc then
local dist = npc:position():distance_to_sqr(db.actor:position())
local is_friend = npc:general_goodwill(db.actor) >= RELATIONS.ENEMIES
if npc:alive() and is_friend and friend_dist > dist and (not get_object_story_id(id)) then
local comm = npc:character_community()
debug("Found friend "..npc:name().." from the "..comm.." community "..tostring(dist).." meters away.")
friend = npc
friend_dist = dist
end
end
end
return friend
end
function force_save(type)
--if game isn't already paused, then force a pause here
local force_pause
if not (device():is_paused()) then
device():pause(true)
force_pause = true
end
local Y, M, D, h
Y, M, D, h = game.get_game_time():get(Y, M, D, h)
local m = level.get_time_minutes()
if m < 10 then
m = ("0"..m)
end
local comm = utils_xml.get_special_txt(db.actor:character_community())
local map = utils_xml.get_special_txt(level.name())
local date = string.format("%d.%d.%d %d-%d", D, M, Y, h, m)
local file_name = "soulslike_"..comm.." - "..map.." "..date.." - "..type
exec_console_cmd("save ".. file_name)
if (force_pause) then
device():pause(false)
end
end
----------------------------------------
-- Dream Callbacks
----------------------------------------
function wakeup_callback()
debug("wakeup_callback")
xr_effects.enable_ui(db.actor, nil)
exec_console_cmd("snd_volume_music "..tostring(_G.mus_vol))
exec_console_cmd("snd_volume_eff "..tostring(_G.amb_vol))
_G.amb_vol = 0
_G.mus_vol = 0
disable_info("tutorial_sleep")
disable_info("actor_is_sleeping")
disable_info("sleep_active")
debug("Looking for scenario logic.")
if scenario_logic then
debug("Completing scenario.")
scenario_logic:OnComplete()
else
error("No logic state")
end
local data = get_soulslike_state()
scenario_logic:destroy()
scenario_logic = nil
data.logic_state = nil
end
function dream_callback()
debug("dream_callback")
level.add_cam_effector("camera_effects\\sleep.anm", 10, false, "soulslike.wakeup_callback")
local hours = math.random(6,14)
level.change_game_time(0,hours,0)
db.actor.power = 1
SendScriptCallback("actor_on_sleep", hours)
end
----------------------------------------
-- Game Callbacks
----------------------------------------
local function actor_on_before_death(who, flags)
if not IsSoulslikeMode() then
return
end
-- Pretty sure this fixes arena fights, still need to test
if has_alife_info("bar_arena_fight") then
debug('Actor was in an arena fight, ignoring death.')
return
end
debug('Actor died')
game_statistics.increment_statistic("deaths")
hit_queue:Invalidate()
scenario_logic = soulslike_scenario_logic_factory.create_new(hit_queue:Values())
if scenario_logic then
scenario_logic:OnDeath()
flags.ret_value = false
end
hit_queue:Clear()
end
local function on_before_save_input(flags, type, text)
if not IsSoulslikeMode() then
return
end
-- No hardcore save setting, allow saving
if not soulslike_mcm.is_hardcore_save_enabled() then
return
end
-- Hardcore save is enabled, but we still want to save at campfires
-- We just return to let the regular saving work.
if soulslike_mcm.override_campfire_hardcore_saves() then
return
end
-- All other scenarios flow through here and we just disallow saving
if not level_weathers.valid_levels[level.name()] then
return
end
debug('User tried to save')
local str = game.translate_string("st_save_only_when_sleeping")
actor_menu.set_msg(1, str, 4)
exec_console_cmd("main_menu off")
flags.ret = true
end
local function try_send_inventory_examined_message(stash_id)
local data = get_soulslike_state()
local stash_data = data.created_stashes[stash_id]
if stash_data and not stash_data.examine then
local lost_items = stash_data.lost_items
debug('Stash not yet examined.')
debug(lost_items)
if #lost_items > 0 then
local msg = "You examine your belongings and find that you were missing the following items: "
local item_groups = {}
for _, sec in pairs(lost_items) do
if not item_groups[sec] then
item_groups[sec] = {
section = sec,
count = 1
}
else
item_groups[sec].count = item_groups[sec].count + 1
end
end
local item_names = {}
for _, group in pairs(item_groups) do
local inv_name = ui_item.get_sec_name(group.section)
if group.count > 1 then
table.insert(item_names, tostring(group.count).." x "..inv_name)
else
table.insert(item_names, inv_name)
end
end
msg = msg..table.concat(item_names, ", ")
local ui_sender = news_manager.tips_icons['default']
db.actor:give_game_news("", msg, ui_sender, 0, 20000)
stash_data.examine = true
end
end
end
local function actor_on_item_take_from_box(box,obj)
if not IsSoulslikeMode() then
return
end
local state = get_soulslike_state()
local id = box:id()
if (box:section() == "inv_backpack") then
if (box:is_inv_box_empty()) then
hide_hud_inventory()
local se_obj = alife_object(id)
if se_obj then
alife_release(se_obj)
try_send_inventory_examined_message(id)
state.created_stashes[id] = nil
end
end
end
end
local function actor_on_stash_remove(data)
if not IsSoulslikeMode() then
return
end
local state = get_soulslike_state()
if state.created_stashes[data.stash_id] then
data.cancel = true
try_send_inventory_examined_message(state.stash_id)
end
end
local function on_console_execute(name, ...)
if not IsSoulslikeMode() then
return
end
if(name == "save") then
debug(name)
local extraArgs = {...}
if extraArgs then
local last_save_file_name = table.concat(extraArgs," ")
debug(last_save_file_name)
if soulslike_mcm.is_hardcore_save_enabled() then
last_save_file_name = string.lower(last_save_file_name)
debug("Don't delete: ".. last_save_file_name)
local uuid = get_soulslike_state().uuid
local fs = getFS()
if not fs then return end
local flist = fs:file_list_open_ex("$game_saves$",bit_or(FS.FS_ListFiles,FS.FS_RootOnly),"*.scoc")
local f_cnt = flist:Size()
for it=0, f_cnt-1 do
local file = flist:GetAt(it)
local file_name = string.sub(file:NameFull(), 0, (string.len(file:NameFull()) - string.len(".scoc")))
local scoc_path = fs:update_path('$game_saves$', '')..file_name..".scoc"
local scop_path = fs:update_path('$game_saves$', '')..file_name..".scop"
local dds_path = fs:update_path('$game_saves$', '')..file_name..".dds"
local f = io.open(scoc_path,"rb")
if f then
local data = f:read("*all")
f:close()
if (data) then
local decoded = alife_storage_manager.decode(data)
local d_soulslike = decoded and decoded.soulslike
if (d_soulslike and (d_soulslike.uuid == uuid)) then
debug("/ Soulslike mode | file: "..file_name)
file_name = string.lower(file_name)
if file_name ~= last_save_file_name then
debug("~ Soulslike mode | delete save file: "..file_name)
local scoc_path_bak = fs:update_path('$game_saves$', '').."soulslike-backup/"..file_name..".scoc"
local scop_path_bak = fs:update_path('$game_saves$', '').."soulslike-backup/"..file_name..".scop"
local dds_path_bak = fs:update_path('$game_saves$', '').."soulslike-backup/"..file_name..".dds"
fs:file_copy(scoc_path, scoc_path_bak)
fs:file_copy(scop_path, scop_path_bak)
fs:file_copy(dds_path, dds_path_bak)
ui_load_dialog.delete_save_game(file_name)
end
end
end
end
end
end
end
end
end
local function physic_object_on_use_callback(box, who)
local data = get_soulslike_state()
if not IsInvbox(box) or not data.hidden_stashes or not data.hidden_stashes[box:id()] then
return
end
local id = box:id()
local stash_data = data.hidden_stashes[id]
local stash_id = stash_data.stash_id
local se_obj = alife_object(stash_id)
local stash = level.object_by_id(stash_id)
if not stash then
warn("Not expected, unable to find stash id linked to "..tostring(id)..". Please save and reload near the stash to see if it solves the issue.")
debug("Removing INVALID PDA marker "..tostring(id))
level.map_remove_object_spot(id, "secondary_task_location")
return
end
local sim = alife();
if not sim then return end
if se_obj and not se_obj.online then
se_obj:switch_online()
end
sim:set_switch_online(stash_id,true)
sim:set_switch_offline(stash_id,false)
try_send_inventory_examined_message(stash_id)
local function transfer_item(temp,item)
debug("Transfering item "..item:section())
stash:transfer_item(item, box)
end
if stash_data.radio_id then
debug("Clearing radio stash "..tostring(stash_data.radio_id))
item_radio.clear_stash(stash_data.radio_level, stash_data.radio_id)
end
debug("Transfering items from hidden stash "..tostring(id).." to static stash "..tostring(stash_id))
stash:iterate_inventory_box(transfer_item)
alife_release(stash)
debug("Removing PDA marker "..tostring(id))
level.map_remove_object_spot(id , "secondary_task_location")
data.hidden_stashes[id] = nil
end
local function load_state(data)
if not IsSoulslikeMode() then
return
end
local data = get_soulslike_state()
if not data.logic_state then
debug("No logic state")
elseif data.logic_state and not data.logic_state.scenario_id then
warn("Logic state exists without scenario id")
elseif data.logic_state and data.logic_state.scenario_id then
-- Reinitialize the last scenario using the saved logic state.
scenario_logic = soulslike_scenario_logic_factory.create_by_id(data.logic_state.scenario_id, data.logic_state)
end
end
local function save_state(data)
if not IsSoulslikeMode() then
return
end
local data = get_soulslike_state()
if not data.spawn_location.level and level.name() ~= 'fake_start' then
set_spawn(false)
end
if scenario_logic then
-- Save the logic state for the current scenario so we can restore it on load
-- This is for the purposes of ChangeLevel which unloads the scripts and then
-- reloads them, so we need to be able to reinitialize the scenario as it was
-- before changing levels
data.logic_state = scenario_logic.logic_state;
end
end
local function on_level_changing()
if not IsSoulslikeMode() then
return
end
debug("On on_level_changing load.")
local data = get_soulslike_state()
if not data.spawn_location.level and level.name() ~= 'fake_start' then
set_spawn(false)
end
end
local function on_game_load()
if not IsSoulslikeMode() then
return
end
debug("On game load.")
local data = get_soulslike_state()
local sim = alife()
if sim then
debug("Updating position and status of hidden stashes.")
for box_id, value in pairs(data.hidden_stashes) do
local se_hidden_stash = alife_object(value.stash_id)
local se_box = alife_object(box_id)
if not se_hidden_stash then
debug("Unable to find hidden stash "..tostring(value.stash_id))
elseif not se_box then
debug("Unable to find box id "..tostring(box_id))
else
debug("Moving hidden stash"..tostring(value.stash_id))
sim:teleport_object(value.stash_id, se_box.m_game_vertex_id, se_box.m_level_vertex_id, se_box.position)
debug("Setting hidden stash online")
if not se_box.online then
se_box:switch_online()
end
for i=1,65534 do
local se_obj = sim:object(i)
if (se_obj and se_obj.parent_id == id) then
debug("Moving "..se_obj.section.." id:"..tostring(value.stash_id))
sim:teleport_object(value.stash_id, se_box.m_game_vertex_id, se_box.m_level_vertex_id, se_box.position)
if se_obj then
if not se_obj.online then
debug("Setting "..se_obj.section.." online")
se_obj:switch_online()
end
end
end
end
end
end
end
-- Write out an identifier so we can use it later
-- to identify other saves with the same ID if the
-- user is playing with Hardcore Saves enabled.
if not data.uuid then
data.uuid = GAME_VERSION .. "_" .. tostring(math.random(100)) .. tostring(math.random()) .. tostring(math.random(1000))
end
debug("Looking for scenario logic.")
if scenario_logic then
debug("Calling scenario respawn.")
scenario_logic:OnRespawn()
end
end
local function actor_on_sleep(hours)
if not IsSoulslikeMode() then
return
end
soulslike.debug('actor_on_sleep')
-- Only force save if we aren't running a scenario
if not scenario_logic then
soulslike.force_save("sleep")
end
end
local detour_actor_on_before_hit = nil
local function actor_on_before_hit(shit, bone_id, flags)
if not IsSoulslikeMode() then
if detour_actor_on_before_hit then
detour_actor_on_before_hit(shit, bone_id, flags)
end
return
end
local damage = shit.power
if grok_actor_damage_balancer and grok_actor_damage_balancer.damage then
--debug("Using damage from Grok's Damage Balancer")
damage = grok_actor_damage_balancer.damage
end
--debug("Player hit:")
--debug("shit.type: "..tostring(hit_type_to_str[shit.type]))
--debug("shit.power: "..tostring(damage))
--debug("shit.impulse: "..tostring(shit.impulse))
--debug("shit.draftsman: "..(shit and shit.draftsman:name() or "<nil>"))
if shit.draftsman then
--debug("shit.draftsman: "..(shit and shit.draftsman:name() or "<nil>"))
end
local health = db.actor.health
local is_fatal = shit.power >= health
hit_queue:Enqueue({
type = shit.type,
power = damage,
is_fatal = is_fatal,
time = time_global(),
draftsman_id = shit.draftsman and shit.draftsman:id() or nil,
})
--debug("is_fatal: "..tostring(is_fatal))
end
local function npc_on_net_spawn(npc, se_obj)
if not IsSoulslikeMode() then
return
end
local state = get_soulslike_state()
if npc and IsStalker(npc) and state.tracked_ambushers and state.tracked_ambushers[npc:id()] then
debug("Setting up ambushers "..tostring(npc:id()))
local ambusher_state = state.tracked_ambushers[npc:id()]
local position = vector():set(ambusher_state.position.x, ambusher_state.position.y, ambusher_state.position.z)
npc:set_mental_state(ambusher_state.mental_state)
npc:set_body_state(ambusher_state.body_state)
npc:set_movement_type(ambusher_state.movement_type)
npc:set_sight(ambusher_state.sight_type, nil, 0)
npc:set_desired_position(position)
npc:set_desired_direction()
if soulslike_mcm.debug_squad_spawns() then
level.map_add_object_spot_ser(npc:id(), "secondary_task_location", "DEBUG: Ambush NPC")
end
state.tracked_ambushers[npc:id()] = nil
end
end
local function actor_on_first_update()
debug("actor_on_first_update")
end
function on_game_start()
debug('Version: '..version)
RegisterScriptCallback("actor_on_stash_remove", actor_on_stash_remove)
RegisterScriptCallback("actor_on_first_update", actor_on_first_update)
RegisterScriptCallback("actor_on_item_take_from_box", actor_on_item_take_from_box)
RegisterScriptCallback("actor_on_before_death", actor_on_before_death)
RegisterScriptCallback("on_before_save_input", on_before_save_input)
RegisterScriptCallback("save_state", save_state)
RegisterScriptCallback("load_state", load_state)
RegisterScriptCallback("on_level_changing", on_level_changing)
RegisterScriptCallback("on_game_load", on_game_load)
RegisterScriptCallback("actor_on_sleep", actor_on_sleep)
RegisterScriptCallback("on_console_execute", on_console_execute)
RegisterScriptCallback("actor_on_before_hit", actor_on_before_hit)
RegisterScriptCallback("physic_object_on_use_callback", physic_object_on_use_callback)
--RegisterScriptCallback("npc_on_net_spawn", npc_on_net_spawn)
end
@@ -0,0 +1,74 @@
class "TimedQueue"
function TimedQueue:__init(max_count, expiriation_ms)
self.data = {}
self.first = 0
self.last = -1
self.max_count = max_count
self.expiriation_ms = expiriation_ms
end
function TimedQueue:Clear()
self.data = {}
self.first = 0
self.last = -1
--soulslike.debug("TimedQueue:Clear")
end
function TimedQueue:Enqueue(value)
value.__cached_time = time_global()
self.last = self.last + 1
self.data[self.last] = value
--soulslike.debug("TimedQueue:Enqueue")
end
function TimedQueue:Dequeue()
local result
if (self.first > self.last) then
result = nil
else
result = self.data[self.first]
self.data[self.first] = nil
self.first = self.first + 1
end
--soulslike.debug("TimedQueue:Dequeue")
return result
end
function TimedQueue:Peek()
local result
if (self.first > self.last) then
result = nil
else
result = self.data[self.first]
end
return result
end
function TimedQueue:Values()
--soulslike.debug("TimedQueue:Values")
--soulslike.debug(self.data)
local values = {}
for _,v in pairs(self.data) do
if v then
table.insert(values, v)
end
end
return values
end
function TimedQueue:Invalidate()
while true do
local next = self:Peek()
if next == nil then
break
end
if next.__cached_time + self.expiriation_ms < time_global() then
--soulslike.debug("TimedQueue:Dequeue_time")
self:Dequeue()
else
break;
end
end
end
@@ -0,0 +1,644 @@
--[[
Jabbers
05APR2023
Jabbers' Soulslike Anomaly Mod
--]]
function on_mcm_load()
local op = {
id = "soulslike",
gr = { }
}
local general = {
id = "general",
text = "ui_mcm_soulslike_general",
sh = true,
gr = {
{
id = "title",
type = "slide",
link = "ui_options_slider_gameplay_diff",
text = "ui_mcm_soulslike_general_title",
size = {512,50},
spacing = 20
},
{
id = "desc_mcm",
type = "desc",
text = "ui_mcm_soulslike_general_description",
clr = {255, 255 ,255 ,255},
},
{
id = "desc_mcm",
type = "desc",
text = "ui_mcm_soulslike_general_disclaimer",
clr = {255, 255 ,255 ,0},
},
},
}
table.insert(op.gr, general)
local character = {
id = "character",
text = "ui_mcm_soulslike_character",
sh = true,
gr = {
{
id = "title",
type = "slide",
link = "ui_options_slider_gameplay_diff",
text = "ui_mcm_soulslike_character_title",
size = {512,50},
spacing = 20
},
{
id = "health_loss_on_respawn",
type = "track",
val = 2,
min = 0.0,
max = 0.75,
step = 0.01,
def = 0.75
},
{
id = "rank_loss_percent",
type = "track",
val = 2,
min = 0,
max = 0.75,
step = 0.01,
def = 0.02
},
{
id = "rep_loss_percent",
type = "track",
val = 2,
min = 0,
max = 0.75,
step = 0.01,
def = 0.05
}
},
}
table.insert(op.gr, character)
local items = {
id = "items",
text = "ui_mcm_soulslike_items",
sh = true,
gr = {
{
id = "title",
type = "slide",
link = "ui_options_slider_gameplay_diff",
text = "ui_mcm_soulslike_items_title",
size = {512,50},
spacing = 20
},
{
id = "keep_equipped_items_on_death",
type = "check",
val = 1,
def = false
},
{
id = "desc_mcm",
type = "desc",
text = "ui_mcm_soulslike_items_scalar_description",
clr = {255, 255 ,255 ,255},
},
{
id = "item_loss_scalar",
type = "track",
val = 2,
min = 0,
max = 0.75,
step = 0.01,
def = 0.20
},
{
id = "item_condition_loss_scalar",
type = "track",
val = 2,
min = 0,
max = 0.25,
step = 0.01,
def = 0.05
},
{
id = "desc_mcm",
type = "desc",
text = "ui_mcm_soulslike_items_allow_desc",
clr = {255, 255 ,255 ,255},
},
{
id = "allow_weapon_loss",
type = "check",
val = 1,
def = true
},
{
id = "allow_outfit_loss",
type = "check",
val = 1,
def = true
},
{
id = "allow_headgear_loss",
type = "check",
val = 1,
def = true
},
{
id = "allow_artifact_loss",
type = "check",
val = 1,
def = true
},
{
id = "allow_tool_loss",
type = "check",
val = 1,
def = true
},
},
}
table.insert(op.gr, items)
local scenarios = {
id = "scenarios",
text = "ui_mcm_soulslike_scenarios",
sh = true,
gr = {
{
id = "title",
type = "slide",
link = "ui_options_slider_gameplay_diff",
text = "ui_mcm_soulslike_scenarios_title",
size = {512,50},
spacing = 20
},
{
id = "desc_mcm",
type = "desc",
text = "ui_mcm_soulslike_scenarios_description",
clr = {255, 255 ,255 ,255},
},
-- {
-- id = "rf_detector_scenario_weight",
-- type = "track",
-- val = 2,
-- min = 0.0,
-- max = 1.0,
-- step = 0.01,
-- def = 0.10
-- },
-- {
-- id = "hidden_stash_scenario_weight",
-- type = "track",
-- val = 2,
-- min = 0.0,
-- max = 1.0,
-- step = 0.01,
-- def = 0.10
-- },
{
id = "looter_npcs_marked",
type = "check",
val = 1,
def = false
}
},
}
table.insert(op.gr, scenarios)
local ambush = {
id = "ambush",
text = "ui_mcm_soulslike_ambush",
sh = true,
gr = {
{
id = "title",
type = "slide",
link = "ui_options_slider_gameplay_diff",
text = "ui_mcm_soulslike_ambush_title",
size = {512,50},
spacing = 20
},
{
id = "desc_mcm",
type = "desc",
text = "ui_mcm_soulslike_ambush_description",
clr = {255, 255 ,255 ,255},
},
{
id = "mutant_ambush_chance",
type = "track",
val = 2,
min = 0.0,
max = 1.0,
step = 0.01,
def = 0.25
},
{
id = "allow_boar_ambush",
type = "check",
val = 1,
def = true
},
{
id = "allow_flesh_ambush",
type = "check",
val = 1,
def = true
},
{
id = "allow_dogs_ambush",
type = "check",
val = 1,
def = true
},
{
id = "allow_cats_ambush",
type = "check",
val = 1,
def = true
},
{
id = "allow_snorks_ambush",
type = "check",
val = 1,
def = true
},
{
id = "allow_bloodsucker_ambush",
type = "check",
val = 1,
def = true
},
{
id = "allow_burer_ambush",
type = "check",
val = 1,
def = true
},
{
id = "allow_chimera_ambush",
type = "check",
val = 1,
def = true
},
{
id = "allow_controller_ambush",
type = "check",
val = 1,
def = true
},
{
id = "stalker_ambush_chance",
type = "track",
val = 2,
min = 0.0,
max = 1.0,
step = 0.01,
def = 0.25
},
{
id = "allow_stalker_novice_ambush",
type = "check",
val = 1,
def = true
},
{
id = "allow_stalker_advanced_ambush",
type = "check",
val = 1,
def = true
},
{
id = "allow_stalker_veteran_ambush",
type = "check",
val = 1,
def = true
},
{
id = "allow_stalker_sniper_ambush",
type = "check",
val = 1,
def = true
}
},
}
table.insert(op.gr, ambush)
local hardcore = {
id = "hardcore",
text = "ui_mcm_soulslike_hardcore",
sh = true,
gr = {
{
id = "title",
type = "slide",
link = "ui_options_slider_gameplay_diff",
text = "ui_mcm_soulslike_hardcore_title",
size = {512,50},
spacing = 20
},
{
id = "desc_mcm",
type = "desc",
text = "ui_mcm_soulslike_hardcore_save_description",
clr = {255, 255 ,255 ,0},
},
{
id = "is_enabled",
type = "check",
val = 1,
def = false
},
{
id = "override_campfire_hardcore_saves",
type = "check",
val = 1,
def = false
}
},
}
table.insert(op.gr, hardcore)
local debug = {
id = "debug",
text = "ui_mcm_soulslike_debug",
sh = true,
gr = {
{
id = "title",
type = "slide",
link = "ui_options_slider_gameplay_diff",
text = "ui_mcm_soulslike_debug_title",
size = {512,50},
spacing = 20
},
{
id = "is_enabled",
type = "check",
val = 1,
def = false
},
{
id = "enable_tips",
type = "check",
val = 1,
def = false
},
-- {
-- id = "debug_hidden_stashes",
-- type = "check",
-- val = 1,
-- def = false
-- },
{
id = "debug_squad_spawns",
type = "check",
val = 1,
def = false
},
{
id = "debug_item_loss",
type = "check",
val = 1,
def = false
},
{
id = "debug_the_tarkov_looter",
type = "check",
val = 1,
def = false
},
{
id = "debug_the_noloss_scenario",
type = "check",
val = 1,
def = false
},
{
id = "debug_the_default_scenario",
type = "check",
val = 1,
def = false
},
-- {
-- id = "debug_the_rf_scenario",
-- type = "check",
-- val = 1,
-- def = false
-- },
-- {
-- id = "debug_the_hidden_stash_scenario",
-- type = "check",
-- val = 1,
-- def = false
-- },
{
id = "debug_always_spawn_ambush",
type = "check",
val = 1,
def = false
}
},
}
table.insert(op.gr, debug)
return op
end
local function get_config(key, default_when_nil)
local opt = ui_mcm and ui_mcm.get("soulslike/"..key)
if opt == nil then
opt = default_when_nil
end
return opt
end
-- Hardcore
function is_hardcore_save_enabled()
return get_config("hardcore/is_enabled", false)
end
function override_campfire_hardcore_saves()
return get_config("hardcore/override_campfire_hardcore_saves", false)
end
-- Scenarios
function are_looter_npcs_marked()
return get_config("scenarios/looter_npcs_marked", false)
end
function rf_detector_scenario_weight()
return 0.0 --get_config("scenarios/rf_detector_scenario_weight", 0.10)
end
function hidden_stash_scenario_weight()
return 0.0 --get_config("scenarios/hidden_stash_scenario_weight", 0.10)
end
function scattered_stash_scenario_weight()
return 0.0 --get_config("scenarios/scattered_stash_scenario_weight", 0.02)
end
function nearby_dead_stalker_scenario_weight()
return get_config("scenarios/nearby_dead_stalker_scenario_weight", 0.10)
end
-- Items
function get_item_loss_scalar()
return get_config("items/item_loss_scalar", 0.2)
end
function get_item_condition_loss_percent()
return get_config("items/item_condition_loss_scalar", 0.05)
end
function allow_toolkit_loss()
return get_config("items/allow_tool_loss", true)
end
function allow_artifact_loss()
return get_config("items/allow_artifact_loss", true)
end
function allow_headgear_loss()
return get_config("items/allow_headgear_loss", true)
end
function allow_outfit_loss()
return get_config("items/allow_outfit_loss", true)
end
function allow_weapon_loss()
return get_config("items/allow_weapon_loss", true)
end
function get_keep_equipped_items_on_death()
return get_config("items/keep_equipped_items_on_death", false)
end
-- Ambush
function mutant_ambush_chance()
return get_config("ambush/mutant_ambush_chance", 0.5)
end
function stalker_ambush_chance()
return get_config("ambush/stalker_ambush_chance", 0.5)
end
function allow_boar_ambush()
return get_config("ambush/allow_boar_ambush", true)
end
function allow_flesh_ambush()
return get_config("ambush/allow_flesh_ambush", true)
end
function allow_dogs_ambush()
return get_config("ambush/allow_dogs_ambush", true)
end
function allow_cats_ambush()
return get_config("ambush/allow_cats_ambush", true)
end
function allow_snorks_ambush()
return get_config("ambush/allow_snorks_ambush", true)
end
function allow_bloodsucker_ambush()
return get_config("ambush/allow_bloodsucker_ambush", true)
end
function allow_burer_ambush()
return get_config("ambush/allow_burer_ambush", true)
end
function allow_chimera_ambush()
return get_config("ambush/allow_chimera_ambush", true)
end
function allow_controller_ambush()
return get_config("ambush/allow_controller_ambush", true)
end
function allow_stalker_novice_ambush()
return get_config("ambush/allow_stalker_novice_ambush", true)
end
function allow_stalker_advanced_ambush()
return get_config("ambush/allow_stalker_advanced_ambush", true)
end
function allow_stalker_veteran_ambush()
return get_config("ambush/allow_stalker_veteran_ambush", true)
end
function allow_stalker_sniper_ambush()
return get_config("ambush/allow_stalker_sniper_ambush", true)
end
-- Character
function get_health_loss_percent()
return get_config("character/health_loss_on_respawn", 0.75)
end
function rank_loss_percent()
return get_config("character/rank_loss_percent", 0.05)
end
function rep_loss_percent()
return get_config("character/rep_loss_percent", 0.02)
end
-- Debug
function is_debug_enabled()
return get_config("debug/is_enabled", false)
end
function show_debug_tips()
return get_config("debug/enable_tips", false)
end
function debug_hidden_stashes()
return is_debug_enabled() and get_config("debug/debug_hidden_stashes", false)
end
function debug_squad_spawns()
return is_debug_enabled() and get_config("debug/debug_squad_spawns", false)
end
function debug_item_loss()
return is_debug_enabled() and get_config("debug/debug_item_loss", false)
end
function debug_remove_default_scenario()
return is_debug_enabled() and get_config("debug/debug_remove_default_scenario", false)
end
function debug_the_tarkov_looter()
return is_debug_enabled() and get_config("debug/debug_the_tarkov_looter", false)
end
function debug_the_noloss_scenario()
return is_debug_enabled() and get_config("debug/debug_the_noloss_scenario", false)
end
function debug_the_default_scenario()
return is_debug_enabled() and get_config("debug/debug_the_default_scenario", false)
end
function debug_the_rf_scenario()
return false -- is_debug_enabled() and get_config("debug/debug_the_rf_scenario", false)
end
function debug_the_hidden_stash_scenario()
return false -- is_debug_enabled() and get_config("debug/debug_the_hidden_stash_scenario", false)
end
function debug_always_spawn_ambush()
return is_debug_enabled() and get_config("debug/debug_always_spawn_ambush", false)
end
@@ -0,0 +1,183 @@
local function rescuer_initial_msg_factory()
local messages = {
"st_soulslike_rescuer_initial_1",
"st_soulslike_rescuer_initial_2",
"st_soulslike_rescuer_initial_3",
"st_soulslike_rescuer_initial_4",
"st_soulslike_rescuer_initial_5",
}
return game.translate_string(messages[math.random(#messages)])
end
local function rescuer_eat_drink_msg_factory()
local messages = {
"st_soulslike_rescuer_food_1",
"st_soulslike_rescuer_food_2",
"st_soulslike_rescuer_food_3",
"st_soulslike_rescuer_food_4",
"st_soulslike_rescuer_food_5",
}
return game.translate_string(messages[math.random(#messages)])
end
local function rescuer_pda_marker_msg_factory()
local messages = {
"st_soulslike_rescuer_pda_1",
"st_soulslike_rescuer_pda_2",
"st_soulslike_rescuer_pda_3",
"st_soulslike_rescuer_pda_4",
"st_soulslike_rescuer_pda_5",
}
return game.translate_string(messages[math.random(#messages)])
end
local function rescuer_used_items_msg_factory()
local messages = {
"st_soulslike_rescuer_used_items_1",
"st_soulslike_rescuer_used_items_2",
"st_soulslike_rescuer_used_items_3",
"st_soulslike_rescuer_used_items_4",
"st_soulslike_rescuer_used_items_5",
}
return game.translate_string(messages[math.random(#messages)])
end
local function rescuer_stay_safe_msg_factory()
local messages = {
"st_soulslike_rescuer_stay_safe_1",
"st_soulslike_rescuer_stay_safe_2",
"st_soulslike_rescuer_stay_safe_3",
"st_soulslike_rescuer_stay_safe_4",
"st_soulslike_rescuer_stay_safe_5",
}
return game.translate_string(messages[math.random(#messages)])
end
local function rescuer_moster_looter_msg_factory(enemy_name, enemy_comm)
local messages = {
"st_soulslike_rescuer_monster_looter_1",
"st_soulslike_rescuer_monster_looter_2",
"st_soulslike_rescuer_monster_looter_3",
"st_soulslike_rescuer_monster_looter_4",
"st_soulslike_rescuer_monster_looter_5",
}
return game.translate_string(messages[math.random(#messages)])
end
local function rescuer_enemy_looter_msg_factory(enemy_name, enemy_comm)
local messages = {
"st_soulslike_rescuer_enemy_looter_1",
"st_soulslike_rescuer_enemy_looter_2",
"st_soulslike_rescuer_enemy_looter_3",
"st_soulslike_rescuer_enemy_looter_4",
"st_soulslike_rescuer_enemy_looter_5",
}
return strformat(game.translate_string(messages[math.random(#messages)]), enemy_comm, enemy_name)
end
local function rescuer_enemy_looter_tarkov_looter_msg_factory(enemy_name, enemy_comm)
local messages = {
"st_soulslike_rescuer_enemy_tarkov_looter_1",
"st_soulslike_rescuer_enemy_tarkov_looter_2",
"st_soulslike_rescuer_enemy_tarkov_looter_3",
"st_soulslike_rescuer_enemy_tarkov_looter_4",
"st_soulslike_rescuer_enemy_tarkov_looter_5",
}
return strformat(game.translate_string(messages[math.random(#messages)]), enemy_comm, enemy_name)
end
local function rescuer_enemy_radio_freq_msg_factory(freq)
local messages = {
"st_soulslike_rescuer_radio_freq_1",
"st_soulslike_rescuer_radio_freq_2",
"st_soulslike_rescuer_radio_freq_3",
"st_soulslike_rescuer_radio_freq_4",
"st_soulslike_rescuer_radio_freq_5",
}
return strformat(game.translate_string(messages[math.random(#messages)]), freq)
end
local function rescuer_hidden_stash_msg_factory(freq)
local messages = {
"st_soulslike_hidden_stash_1",
"st_soulslike_hidden_stash_2",
"st_soulslike_hidden_stash_3",
"st_soulslike_hidden_stash_4",
"st_soulslike_hidden_stash_5",
}
return strformat(game.translate_string(messages[math.random(#messages)]), freq)
end
local function recuer_indoor_msg_factory(level_name)
local messages = {
"st_soulslike_rescuer_indoor_1",
"st_soulslike_rescuer_indoor_2",
"st_soulslike_rescuer_indoor_3",
"st_soulslike_rescuer_indoor_4",
"st_soulslike_rescuer_indoor_5",
}
return strformat(game.translate_string(messages[math.random(#messages)]), level_name)
end
function create(rescuer, params)
if rescuer then
local msg = params.player_died_indoor
and recuer_indoor_msg_factory(params.level_name)
or rescuer_initial_msg_factory()
local looter = params.enemy
if params.gave_food_or_water then
msg = msg.." "..rescuer_eat_drink_msg_factory()
end
if looter then
if looter.tarkov_experience then
--TODO: Make message factory for this
msg = msg.." "..rescuer_enemy_looter_tarkov_looter_msg_factory(looter.name, looter.community)
else
msg = msg.." "..rescuer_enemy_looter_msg_factory(looter.name, looter.community)
end
end
if params.has_pda_marker then
msg = msg.." "..rescuer_pda_marker_msg_factory()
end
if params.has_stash_pda_marker then
msg = msg.." "..rescuer_hidden_stash_msg_factory()
end
if params.radio_freq then
msg = msg.." "..rescuer_enemy_radio_freq_msg_factory(params.radio_freq)
end
if params.items_were_lost then
if params.looter_type == soulslike.entity_type.Monster then
msg = msg.." "..rescuer_moster_looter_msg_factory()
else
msg = msg.." "..rescuer_used_items_msg_factory()
end
end
msg = msg.." "..rescuer_stay_safe_msg_factory()
return msg
else
local msg = game.translate_string("st_soulslike_rescuer_initial_generic")
if params.gave_food_or_water then
msg = msg.." "..game.translate_string("st_soulslike_rescuer_food_generic")
end
if params.has_pda_marker or params.has_stash_pda_marker or params.has_multi_stash_pda_marker then
msg = msg.." " ..game.translate_string("st_soulslike_rescuer_pda_generic")
end
if params.radio_freq then
msg = msg.." "..game.translate_string(strformat("st_soulslike_rescuer_radio_generic", params.radio_freq))
end
return msg
end
end
@@ -0,0 +1,28 @@
function menu_read(obj) -- return "read" name
local p = obj:parent()
if not (p and p:id() == AC_ID) then return end
return game.translate_string("st_item_read")
end
function func_radio_freq(obj)
local sec = obj:section()
if (not IsItem("letter",sec)) then
return
end
local id = obj:id()
local data = soulslike.get_soulslike_state()
if data.note_message_data and data.note_message_data[id] then
local note_data = data.note_message_data[id]
local freq = note_data.freq;
local level_name = utils_xml.get_special_txt(note_data.level_name)
local msg = strformat(game.translate_string("st_soulslike_rescuers_radio_frequency_note_message"), freq, level_name)
actor_menu.set_msg(2, msg, 10)
hide_hud_inventory()
end
end
@@ -0,0 +1,369 @@
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
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,109 @@
local _InitControls = ui_sleep_dialog.UISleep.InitControls
local _InitCallbacks = ui_sleep_dialog.UISleep.InitCallbacks
local _TestAndShow = ui_sleep_dialog.UISleep.TestAndShow
local _OnButtonSleep = ui_sleep_dialog.UISleep.OnButtonSleep
function ui_sleep_dialog.UISleep.OnButtonSleep(self)
if not soulslike.IsSoulslikeMode() then
_OnButtonSleep(self)
return
end
local bleeding = db.actor.bleeding > 0
local radiation = db.actor.radiation > 0
-- Prevent sleep if bleeding and/or iradiated.
if (bleeding or radiation) then
if (bleeding and radiation) then
actor_menu.set_msg(1, game.translate_string("st_sleep_bleeding_irradiated"),5)
elseif (bleeding) then
actor_menu.set_msg(1, game.translate_string("st_sleep_bleeding"),4)
elseif (radiation) then
actor_menu.set_msg(1, game.translate_string("st_sleep_irradiated"),4)
end
disable_info("sleep_active")
if self:IsShown() then
self:HideDialog()
Unregister_UI("UISleep")
end
return
end
-- Check if actor is inside a safe zone
local actor_hide = GetEvent("current_safe_cover") and true or false
-- Check if actor is inside a tent
if (not actor_hide) then
actor_hide = item_tent.get_nearby_tent(1.5)
end
-- If all is no, dont sleep
if (not actor_hide) then
actor_menu.set_msg(1, game.translate_string("st_cant_sleep_find_shelter_mlr"),4)
disable_info("sleep_active")
if self:IsShown() then
self:HideDialog()
Unregister_UI("UISleep")
end
return
end
_OnButtonSleep(self)
end
function ui_sleep_dialog.UISleep.TestAndShow(self, force)
if not soulslike.IsSoulslikeMode() then
_TestAndShow(self, force)
return
end
if force then
_TestAndShow(self, force)
else
-- Detour and show the UI so we can let the user
-- set their spawn point. Move the sleep check code
-- to the Sleep button click instead
self:Initialize()
self:ShowDialog(true)
Register_UI("UISleep","ui_sleep_dialog")
end
end
function ui_sleep_dialog.UISleep.InitControls(self)
_InitControls(self)
if not soulslike.IsSoulslikeMode() then
return
end
local xml = CScriptXmlInit()
xml:ParseFile("soulslike_ui_sleep_dialog.xml")
local function move(ctrl, deltaX, deltaY)
local pos = ctrl:GetWndPos()
ctrl:SetWndPos(vector2():set( pos.x + deltaX ,pos.y + deltaY ))
end
move(self.btn_sleep, 52, 0)
move(self.btn_cancel, 52, 0)
self.btn_set_spawn = xml:Init3tButton("btn_set_spawn", self.back)
self:Register(self.btn_set_spawn, "btn_set_spawn")
if not utils_xml.is_widescreen() then
move(self.btn_set_spawn, 40, 0)
end
end
function ui_sleep_dialog.UISleep.InitCallbacks(self)
_InitCallbacks(self)
if not soulslike.IsSoulslikeMode() then
return
end
self:AddCallback("btn_set_spawn", ui_events.BUTTON_CLICKED, self.OnButtonSetSpawn, self)
end
function ui_sleep_dialog.UISleep:OnButtonSetSpawn()
soulslike.set_spawn(true);
end
@@ -0,0 +1,142 @@
local _Main_Controls = ui_mm_faction_select.UINewGame.Main_Controls
local _Main_CallBacks = ui_mm_faction_select.UINewGame.Main_CallBacks
local _OnStartGame = ui_mm_faction_select.UINewGame.OnStartGame
local _OnCheckSetAzazel = ui_mm_faction_select.UINewGame.OnCheckSetAzazel
function ui_mm_faction_select.UINewGame.Main_Controls(self, rand)
_Main_Controls(self, rand)
local function move(ctrl, deltaX, deltaY)
local pos = ctrl:GetWndPos()
ctrl:SetWndPos(vector2():set( pos.x + deltaX ,pos.y + deltaY ))
end
if efp_utils_mcm then
move(self.ck_azazel_mode_cap, 0, -10)
move(self.ck_azazel_mode, 0, -10)
else
move(self.ck_story_cap, 0, -10)
move(self.ck_story, 0, -10)
move(self.ck_azazel_mode_cap, 0, -15)
move(self.ck_azazel_mode, 0, -15)
move(self.ck_survival_cap, 0, -20)
move(self.ck_survival, 0, -20)
end
local xml = CScriptXmlInit()
xml:ParseFile ("soulslike_ui_mm_gamemode.xml")
self.ck_soulslike_mode_cap = xml:InitStatic("main_dialog:options:cap_check_soulslike_mode",self.templ_options)
self.ck_soulslike_mode = xml:InitCheck("main_dialog:options:check_soulslike_mode", self.templ_options)
self:Register(self.ck_soulslike_mode,"check_soulslike_mode")
if efp_utils_mcm then
move(self.ck_soulslike_mode_cap, 0, 65)
move(self.ck_soulslike_mode, 0, 65)
end
self.ck_states["ck_soulslike_mode"] = false
end
function ui_mm_faction_select.UINewGame.Main_CallBacks(self)
_Main_CallBacks(self)
self:AddCallback("check_soulslike_mode", ui_events.BUTTON_CLICKED, self.OnCheckSetSoulslike, self)
self:AddCallback("check_hardcore", ui_events.BUTTON_CLICKED, self.OnCheckHardcore, self)
end
function ui_mm_faction_select.UINewGame.OnStartGame(self)
if (not self.access) then
return
end
local character_name = self.character_name:GetText()
if axr_main and axr_main.config and character_name and character_name ~= "" then
local is_soulslike_mode = self.ck_soulslike_mode and self.ck_soulslike_mode:GetCheck() and true or nil
printf('[Soulslike] OnStartGame:is_soulslike_mode='..tostring(is_soulslike_mode))
axr_main.config:w_value("character_creation", "new_game_soulslike_mode", is_soulslike_mode)
axr_main.config:save()
end
_OnStartGame(self)
end
function ui_mm_faction_select.UINewGame.OnCheckSetAzazel(self)
_OnCheckSetAzazel(self)
if (not self.access) then
return
end
self.ck_soulslike_mode:SetCheck(false)
self.ck_states["ck_soulslike_mode"] = false
end
function ui_mm_faction_select.UINewGame:OnCheckHardcore()
if (not self.access) then
return
end
self.ck_soulslike_mode:SetCheck(false)
self.ck_states["ck_soulslike_mode"] = false
end
function ui_mm_faction_select.UINewGame:OnCheckSetSoulslike()
if (not self.access) then
return
end
self.ck_azazel_mode:SetCheck(false)
self.ck_states["ck_azazel_mode"] = false
self.ck_hardcore:SetCheck(false)
self.ck_states["ck_hardcore"] = false
self:LoadMap()
end
local function on_game_load(binder)
local config = axr_main.config
if not (config) then
printf('[Soulslike] on_game_load:no config')
return
end
local need_save
-- Gameplay Options
if (USE_MARSHAL) then
if (config:r_value("character_creation","new_game_soulslike_mode", 1) == true) then
printf('[Soulslike] on_game_load:enable_soulslike_mode=true')
alife_storage_manager.get_state().enable_soulslike_mode = true
config:w_value("character_creation","new_game_soulslike_mode")
need_save = true
end
end
if (need_save) then
config:save()
end
end
local function actor_on_first_update()
if not soulslike.IsSoulslikeMode() then
return
end
local state = alife_storage_manager.get_state()
if not state.soulslike_mode_initial_spawn_set and level.name() ~= 'fake_start' then
printf('Initial spawn point set to '..level.name())
soulslike.set_spawn(false)
state.soulslike_mode_initial_spawn_set = true
end
end
function on_game_start()
RegisterScriptCallback("on_game_load",on_game_load)
RegisterScriptCallback("actor_on_first_update", actor_on_first_update)
end