955 lines
28 KiB
Plaintext
955 lines
28 KiB
Plaintext
|
|
||
|
--[[
|
||
|
Jabbers
|
||
|
05APR2023
|
||
|
Jabbers' Soulslike Anomaly Mod
|
||
|
--]]
|
||
|
|
||
|
local version = "0.28-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.")
|
||
|
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
|
||
|
|
||
|
function on_game_start()
|
||
|
debug('Version: '..version)
|
||
|
|
||
|
RegisterScriptCallback("actor_on_stash_remove", actor_on_stash_remove)
|
||
|
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
|