Divergent/mods/Zone Customization Project/gamedata/scripts/game_setup.script

557 lines
15 KiB
Plaintext

--[[
- Created by tdef
- Updated by Tronex
- Randomized world items on new game
- Released blacklisted objects on new game
- Created: 2018/10/27
- 2019/31/3 script now read from config to set up
- 2019/4/25 objects to release are now handled by another config
- 2019/5/20 improved the way suffled consumables uses are set
used ini:
items\settings\dynamic_item_spawn.ltx
plugins\new_game_setup.ltx
set enable_debug to true, for debugging and map markers
--]]
-- these vehicles are supposed to shoot at you but call of misery broke them so they don't
-- also you can board them, turn them on and drive around so should remove them?
local ini_dyn
local enable_debug = false
local inited = false
local sfind = string.find
local world_itm_info = {} -- [name] = {}
local world_itm_off = {} -- [name] = true
local world_itm_num = {} -- [name] = num
local world_itm_on = {} -- [id] = name
local itm_list = {}
local limited_uses = {}
function get_itm_type(name)
if sfind(name,"kolbasa")
or sfind(name,"conserva")
or sfind(name,"bread")
then
return "food"
end
if sfind(name,"energy")
or sfind(name,"vodka")
or sfind(name,"drink")
then
return "drink"
end
if sfind(name,"drug")
or sfind(name,"antirad")
or sfind(name,"bandage")
or sfind(name,"medkit")
then
return "medical"
end
--if sfind(name,"repair") then
--return "tool"
--end
if sfind(name,"ammo") then
return "ammo"
end
if sfind(name,"misc")
then
return "misc"
end
return "NA"
end
function print_debug(...)
if enable_debug then
printf(...)
end
end
local marker_by_type = {
["kit"] = "item_kit",
["medical"] = "item_medical",
["food"] = "item_food",
["drink"] = "item_drink",
["ammo"] = "item_ammo",
["misc"] = "item_misc",
}
function add_marker(name, section, id, typ)
if enable_debug then
local spot = marker_by_type[typ] or marker_by_type["misc"]
level.map_add_object_spot_ser(id, spot, "Name: " .. name .. " \\nType: " .. typ .. " \\nSection: " .. section)
end
end
function remove_marker(id, typ)
if enable_debug then
local spot = marker_by_type[typ] or marker_by_type["misc"]
if (level.map_has_object_spot(id, spot) ~= 0) then
level.map_remove_object_spot(id, spot)
end
end
end
function init_settings()
if (inited) then return end
inited = true
ini_dyn = ini_file("items\\settings\\dynamic_item_spawn.ltx")
local n,m = 0,0
local result, id, value = "","",""
local name, info = "","",""
-- Gather items list
n = ini_dyn:line_count("categories") or 0
for i=0,n-1 do
result, id, value = ini_dyn:r_line_ex("categories",i,"","")
itm_list[id] = {}
m = ini_dyn:line_count(id) or 0
for ii=0,m-1 do
result, name, info = ini_dyn:r_line_ex(id,ii,"","")
if name and info then
for j=1,tonumber(info) do
local size = #itm_list[id] + 1
itm_list[id][size] = name
print_debug("- Game Setup | itm_list[%s][%s] = %s", id, size, name)
end
end
end
end
-- Gather recorded items pos
n = ini_dyn:line_count("levels") or 0
for i=0,n-1 do
result, id, value = ini_dyn:r_line_ex("levels",i,"","")
m = ini_dyn:line_count(id) or 0
for ii=0,m-1 do
result, name, info = ini_dyn:r_line_ex(id,ii,"","")
if name and info then
local t = str_explode(info,",")
if (#t == 6) and (t[1] ~= "NA") then
world_itm_info[name] = {
typ = t[1],
x = tonumber(t[2]),
y = tonumber(t[3]),
z = tonumber(t[4]),
lvl_id = tonumber(t[5]),
gm_id = tonumber(t[6]),
}
end
end
end
end
-- Gather uses
n = ini_dyn:line_count("possible_uses") or 0
for i=0,n-1 do
result, id, value = ini_dyn:r_line_ex("possible_uses",i,"","")
if id and value then
local t = str_explode(value,",")
limited_uses[id] = { tonumber(t[1]) or 1 , tonumber(t[2]) or 1 }
end
end
-- Make list of non-spawned items
for name,info in pairs(world_itm_info) do
world_itm_off[name] = true
end
for id,name in pairs(world_itm_on) do
world_itm_off[name] = nil
end
print_debug("- Game Setup | world_itm_info: %s - world_itm_on: %s - world_itm_off: %s", size_table(world_itm_info), size_table(world_itm_on), size_table(world_itm_off))
end
function try_spawn_world_item(ignore)
-- Get spawn place name
local _name
if ignore then
_name = random_key_table(world_itm_off)
else
local lvl_short = txr_routes.get_map(level.name())
local t = {}
-- Gather validated item places to spawn at
for name,_ in pairs(world_itm_off) do
if (not sfind(name,lvl_short)) then
t[#t+1] = name
end
end
_name = (#t > 0) and t[math.random(#t)]
end
-- Return if not available place has been found
if (not _name) then
print_debug("! Game Setup | can't find available item place", _name)
return
end
-- Return if place already has spawned item
for id,name in pairs(world_itm_on) do
if (name == _name) then
print_debug("! Game Setup | place {%s} is already occupied", _name)
return
end
end
-- Get info
local info = world_itm_info[_name]
if (not info) then
print_debug("! Game Setup | no info is found for {%s}", _name)
return
end
-- Get section
local itm_type = info.typ and itm_list[info.typ]
local section = itm_type and itm_type[math.random(#itm_type)]
if (not section) then
print_debug("! Game Setup | couldn't get section [%s] for type (%s)", section, info.typ)
return
end
if (not ini_sys:section_exist(section)) then
print_debug("! Game Setup | section [%s] doesn't exist", section)
return
end
-- Info check
if not (info.x and info.y and info.z and info.lvl_id and info.gm_id and true) then
print_debug("! Game Setup | item {%s} has wrong or incomplete info", name)
return
end
-- Spawn and adjust uses/condition/ammo size
if IsItem("ammo",section) then
local pos = vector():set(info.x, info.y, info.z)
local se_obj = alife_create_item(section, {pos, info.lvl_id, info.gm_id})
if se_obj then
add_marker(_name, section, se_obj.id, info.typ)
world_itm_on[se_obj.id] = _name
world_itm_off[_name] = nil
local box_size = ini_sys:r_u32(section, "box_size")
world_itm_num[_name] = math.random( math.ceil(box_size * 0.25) , math.ceil(box_size * 0.75) )
print_debug("/ Game Setup | created ammo [%s](%s) - place: %s - size = %s", section, se_obj.id, _name, world_itm_num[_name])
else
print_debug("! Game Setup | ammo [%s] couldn't be created", section)
end
else
local pos = vector():set(info.x, info.y, info.z)
local se_obj = alife_create_item(section, {pos, info.lvl_id, info.gm_id})
if se_obj then
add_marker(_name, section, se_obj.id, info.typ)
world_itm_on[se_obj.id] = _name
world_itm_off[_name] = nil
-- Multi-use
if limited_uses[section] then
world_itm_num[_name] = math.random(limited_uses[section][1], limited_uses[section][2])
print_debug("/ Game Setup | created multiuse item [%s](%s) - place: %s - uses = %s", section, se_obj.id, _name, world_itm_num[_name])
else
local is_using_con = utils_item.is_degradable(nil, section)
if is_using_con then
-- Parts
if IsItem("part",section) then
world_itm_num[_name] = random_choice(0.5,0.75,1)
print_debug("/ Game Setup | created degraded item [%s](%s) - place: %s - con = %s", section, se_obj.id, _name, world_itm_num[_name])
-- Degradable items
else
world_itm_num[_name] = (math.random(30,70)/100)
print_debug("/ Game Setup | created degraded item [%s](%s) - place: %s - con = %s", section, se_obj.id, _name, world_itm_num[_name])
end
else
print_debug("/ Game Setup | created item [%s](%s)", section, se_obj.id)
end
end
else
print_debug("! Game Setup | item [%s] couldn't be created", section)
end
end
end
function is_world_item(id)
if id and world_itm_on[id] then
--print_debug("! Game Setup | is_world_item[%s]", id)
return true
end
--print_debug("/ Game Setup | is_world_item[%s]", id)
return false
end
-- TODO IN 1.6 OR WHENEVER WE CAN EDIT ALL.SPAWN
-- remove these 2 objects because vetham is making new office for medic and they get in the way
function bar_medic_remove_stuff()
if not alife_storage_manager.get_state().duty_medic_fix then
alife_storage_manager.get_state().duty_medic_fix = true
for i=1,65534 do
local se = alife():object(i)
if se and (se:name() == 'bar_physic_object_mlr_0002' or se:name() == 'bar_physic_object_mlr_0003') then
alife():release(se)
end
end
end
end
-- TODO IN 1.6 OR WHENEVER WE CAN EDIT ALL.SPAWN
-- remove these 4 objects because they're stuck in the train and physics impulse makes them jitter around at 5 fps
function darkscape_remove_physics_objects()
if not alife_storage_manager.get_state().darkscape_phys_fix then
alife_storage_manager.get_state().darkscape_phys_fix = true
for i=1,65534 do
local se = alife():object(i)
if se and (se:name() == 'ds_physic_destroyable_object_0046' or se:name() == 'ds_physic_object_0009' or se:name() == 'ds_physic_object_0010' or se:name() == 'ds_physic_object_0002') then
alife():release(se)
end
end
end
end
-- TODO IN 1.6 OR WHENEVER WE CAN EDIT ALL.SPAWN
-- delete the chair and move smart cover in this new position
-- OR
-- make the chair part of level geometry and delete the object
-- OR
-- find a way to make that specific object not react to physics
function freedom_medic_fix()
if not alife_storage_manager.get_state().freedom_medic_fix then
alife_storage_manager.get_state().freedom_medic_fix = true
for i=1,65534 do
local se = alife():object(i)
if se then
if se:name() == 'mil_physic_object_0048' then
alife():release(se)
elseif se:name() == 'sc_freedom_medic_mlr' then
alife():teleport_object(i, 2165, 315401, vector():set(27.681089401245, -6.9381303787231, 17.38550567627))
end
end
end
end
end
-------------------------------
-- CALLBACKS
-------------------------------
local function actor_on_first_update()
init_settings()
freedom_medic_fix()
bar_medic_remove_stuff()
darkscape_remove_physics_objects()
if alife_storage_manager.get_state().item_removal_done or IsTestMode() then
UnregisterScriptCallback("actor_on_first_update",actor_on_first_update)
return
end
alife_storage_manager.get_state().item_removal_done = true
print_debug("- Game Setup | create dynamic items")
local ini_setup = ini_file("plugins\\new_game_setup.ltx")
local enabled = true --ini_dyn:r_bool_ex("settings","enabled") or false
if (not enabled) then
return
end
-- Release static items and mines
local sim = alife()
local boxes = {}
for i=1, 65534 do
local se_obj = sim:object(i)
if se_obj then
local name = se_obj:name()
local cls = se_obj:clsid()
if cls == clsid.inventory_box_s then
--print_debug('%s_%s is a box', i, name)
boxes[i] = true
elseif ini_dyn:line_exist("replace_items",name) then
--print_debug('releasing %s', name)
--sim:release(se_obj, true)
alife_release(se_obj)
end
if ini_setup:line_exist("remove_objects",name) then
print_debug('/ Game Setup | Releasing object (%s)', name)
-- Clear inventory boxes from their manager
if (cls == clsid.inventory_box_s) then
treasure_manager.release_stash_by_id(se_obj.id)
end
safe_release_manager.release(se_obj)
end
end
end
-- Clear stashes
for i=1, 65534 do
local se_obj = sim:object(i)
if se_obj then
local name = se_obj:name()
if boxes[se_obj.parent_id] and (not sfind(name, 'mlr_strelok_item')) then
print_debug('/ Game Setup | Releasing {%s} from box', name)
--sim:release(se_obj, true)
alife_release(se_obj)
end
end
end
-- Setup items
local multi = game_difficulties.get_eco_factor("random_items") or 0.5
-- ZCP
if smr_amain_mcm.get_config("smr_enabled") then
multi = smr_loot_mcm.get_config("random_items")
end
-- ZCP END
multi = (multi < 1) and multi or 1
local num = math.ceil(size_table(world_itm_off) * multi)
for i=1,num do
try_spawn_world_item(true)
end
print_debug("- Game Setup | world_itm_info: %s - world_itm_on: %s - world_itm_off: %s", size_table(world_itm_info), size_table(world_itm_on), size_table(world_itm_off))
end
local tg_stkr = 0
local function actor_on_update()
if time_global() < tg_stkr then
return
end
-- No need to process if actor is outside cordon / visited more levels / not a loner / Warfare is active
if (level.name() ~= "l01_escape")
or IsWarfare()
or (game_statistics.get_statistic_count("level_changes") > 1)
or (get_actor_true_community() ~= "stalker")
then
UnregisterScriptCallback("actor_on_update",actor_on_update)
return
end
-- Remove common military or mutant squads
local on_act_lvl = simulation_objects.is_on_the_actor_level
for id,v in pairs( SIMBOARD.squads ) do
local squad = alife_object(id)
if squad and squad.common and (squad.player_id == "army") and on_act_lvl(squad) then
squad:remove_squad()
break
end
end
tg_stkr = time_global() + 10000
end
local function actor_on_item_take(obj)
local id = obj:id()
if world_itm_on[id] then
local name = world_itm_on[id]
local section = obj:section()
local info = world_itm_info[name]
if (not info) then
print_debug("! Game Setup | can't get info for {%s}", name)
end
-- Spawn a new world item
try_spawn_world_item()
-- Switch state
world_itm_on[id] = nil
world_itm_off[name] = true
-- Read info
local num = world_itm_num[name]
if num then
-- Ammo
if IsItem("ammo",section) then
obj:ammo_set_count(num)
print_debug("- Game Setup | taken world ammo [%s](%s) is set to %s ammo - info name: %s", section, id, num, name)
world_itm_num[name] = nil
-- Multi-use
elseif limited_uses[section] then
alife_process_item( section, id , {uses = num} )
print_debug("- Game Setup | taken world consumable [%s](%s) is set to %s uses - info name: %s", section, id, num, name)
world_itm_num[name] = nil
-- Condition
elseif utils_item.is_degradable(nil, section) then
alife_process_item( section, id , {cond = num} )
print_debug("- Game Setup | taken world degraded item [%s](%s) is set to %s condition - info name: %s", section, id, num, name)
world_itm_num[name] = nil
end
-- Normal
else
print_debug("- Game Setup | taken world item [%s](%s) - info name: %s", section, id, uses, name)
end
-- Send message
itms_manager.send_itm_msg(section)
remove_marker(id, info.typ)
end
-- Ammo aggregation (it's important to start ammo aggregation after sorting taken world ammo size first, to prevent issues)
if IsAmmo(obj) then
item_weapon.ammo_aggregation(obj)
end
end
local function save_state(m_data)
m_data.world_itm_on = world_itm_on
m_data.world_itm_num = world_itm_num
print_debug("# SAVING: world_itm_on [%s] - world_itm_num [%s]", size_table(world_itm_on), size_table(world_itm_num))
end
local function load_state(m_data)
world_itm_on = m_data.world_itm_on or {}
world_itm_num = m_data.world_itm_num or {}
print_debug("# LOADING: world_itm_on [%s] - world_itm_num [%s]", size_table(world_itm_on), size_table(world_itm_num))
end
function on_game_start()
RegisterScriptCallback("actor_on_first_update",actor_on_first_update)
RegisterScriptCallback("actor_on_update",actor_on_update)
RegisterScriptCallback("actor_on_item_take",actor_on_item_take)
RegisterScriptCallback("save_state",save_state)
RegisterScriptCallback("load_state",load_state)
end