557 lines
15 KiB
Plaintext
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
|