Divergent/mods/NPC Looting Fix/gamedata/scripts/npc_looting_fix.script

451 lines
13 KiB
Plaintext

local AlwaysDetectDistance, StateWalk, StateRun, MonsterLootCommunities
function xr_corpse_detection.evaluator_corpse:evaluate()
if not (self.object:alive()) then
return false
end
if (xr_danger.has_danger(self.object)) then
return false
end
if (xr_conditions.surge_started() == true) then
return false
end
local npc = self.object
if (IsWounded(npc) or npc:best_enemy())then
return false
end
local st = db.storage[npc:id()]
if (st) and ((st.active_scheme == "camper") or (st.help_wounded and st.help_wounded.selected_id ~= nil) or (st.gather_items and st.gather_items.selected_id ~= nil)) then
return false
end
self.a.analse_mode = xr_logic.pick_section_from_condlist(db.actor, self.object, self.a.mutant_corpse_analysis)
self.a.enabled = xr_logic.pick_section_from_condlist(db.actor, self.object, self.a.corpse_detection_enabled)
if (self.a.analse_mode == "false" and self.a.enabled == "false") then
return false
end
if (self:find_valid_target()) then
return true
end
return false
end
local rank_coeffs_table = {
novice = 20,
trainee = 20,
experienced = 25,
professional = 35,
veteran = 40,
expert = 50,
master = 55,
legend = 60
}
function xr_corpse_detection.get_all_from_corpse(npc)
if not (db.actor) then
return
end
local id = npc:id()
local st = db.storage[id] and db.storage[id].corpse_detection
-- sanity check, this should never happen
if not (st) then
return
end
local corpse_npc_id = st.selected_corpse_id
local corpse_npc = corpse_npc_id and db.storage[corpse_npc_id] and db.storage[corpse_npc_id].object or corpse_npc_id and level.object_by_id(corpse_npc_id)
-- reset all scheme dependent variables
if (st.selected_corpse_id) then
if (db.storage[st.selected_corpse_id]) then
db.storage[st.selected_corpse_id].corpse_already_selected = nil
end
end
st.__stimer = nil
st.mutant_analysed = nil
st.vertex_id = nil
st.vertex_position = nil
st.selected_corpse_id = nil
st.state = nil
st.index = 1
st.nearest_corpse_dist = nil
st.nearest_corpse_vertex = nil
st.nearest_corpse_position = nil
st.nearest_id = nil
if (corpse_npc == nil or corpse_npc:alive() == true) then
return
end
if not(IsStalker(corpse_npc)) then
if (get_story_object("yan_ecolog_semenov")) then
local need = load_var(db.actor,"yan_ecolog_semenov_task_1_tissue_need") or 0
if (need > 0) then
local count = load_var(db.actor,"yan_ecolog_semenov_task_1_tissue_count") or 0
save_var(db.actor,"yan_ecolog_semenov_task_1_tissue_count",count + 1)
end
end
local looted = se_load_var(corpse_npc:id(),corpse_npc:name(),"looted") == "true"
if (not looted) and ui_mutant_loot then
local loot = {}
ui_mutant_loot.loot_mutant(corpse_npc:section(),corpse_npc:clsid(),loot,npc,nil,corpse_npc)
local is_there_loot
for sec,t in pairs(loot) do
is_there_loot = true
break
end
if (is_there_loot) then
xr_sound.set_sound_play(id,"corpse_loot_good")
else
xr_sound.set_sound_play(id,"corpse_loot_bad")
end
se_save_var(corpse_npc:id(),corpse_npc:name(),"looted","true")
game_statistics.increment_npc_statistic(npc,"field_dressings")
end
return
end
local item_value = 0
local npc_rank = ranks.get_obj_rank_name(npc)
local inv_weight = npc:get_total_weight()
local max_weight = rank_coeffs_table[npc_rank] / 1.5
local function get_item(corpse,item)
if (xr_corpse_detection.lootable_table[item:section()] ~= nil) then
if (IsItem("money", item:section())) then
alife_release(item)
else
item_value = item:cost() * item:condition() / (rank_coeffs_table[npc_rank] * (item:weight() + 0.1))
if (inv_weight + item:weight() > max_weight) then
if (not IsArtefact(item)) then
item_value = item_value / inv_weight
end
end
if (item_value > 99 or math.random(item_value) > 5) then
corpse:transfer_item(item,npc)
inv_weight = inv_weight + item:weight()
SendScriptCallback("npc_on_get_all_from_corpse",npc,corpse_npc,item,xr_corpse_detection.lootable_table[item:section()])
end
end
end
end
corpse_npc:iterate_inventory(get_item,corpse_npc)
if item_money then
item_money.npc_on_loot_money(npc, corpse_npc)
end
if (item_value > rank_coeffs_table[npc_rank] * 10) then
xr_sound.set_sound_play(id,"corpse_loot_good")
elseif (math.random(1,100)/100 < 0.5) then
xr_sound.set_sound_play(id,"corpse_loot_bad")
end
local count = load_var(npc,"s_loot_count") or 0
if (count >= 255) then
count = 254
end
save_var(npc,"s_loot_count",count+1)
game_statistics.increment_npc_statistic(npc,"corpse_looted")
se_save_var(corpse_npc:id(),corpse_npc:name(),npc:id(),"true")
end
function xr_corpse_detection.evaluator_corpse:find_valid_target()
if (self.a.cstackprevent) then
printf("C Stack Overflow Prevention: warning xr_corpse_detection scheme making repeated calls without return; save now and reload!")
return false
end
if (self.a.selected_corpse_id) then -- looting process
local corpse = db.storage[self.a.selected_corpse_id] and db.storage[self.a.selected_corpse_id].object or level.object_by_id(self.a.selected_corpse_id)
local state = state_mgr.get_state(self.object)
-- Code demonized - add check for looting state so the npc wont stop looting if actor is near a corpse
if corpse and (state == "search_corpse" or state == "field_dress" or state == "scaner_crouch" or not xr_corpse_detection.near_actor(corpse)) then
return true
elseif (db.storage[self.a.selected_corpse_id]) then
db.storage[self.a.selected_corpse_id].corpse_already_selected = nil
end
-- code end
end
local tg = time_global()
self.a.__dtimer = not self.a.__dtimer and tg + 250 or self.a.__dtimer
if (tg < self.a.__dtimer) then
return false
end
self.a.__dtimer = nil
if not (self.a.index) then
self.a.index = 1
end
if not (self.a.memory) then
self.a.memory = {}
end
local npc = self.object
local size = #self.a.memory
if (size == 0) then
self.a.cstackprevent = true
for o in npc:memory_visible_objects() do
local obj = o and o:object()
if (obj and (IsStalker(obj) or IsMonster(obj)) and obj:alive() ~= true and not xr_corpse_detection.near_actor(obj)) then
size = size + 1
self.a.memory[size] = obj:id()
end
end
self.a.cstackprevent = nil
end
if (size == 0 or self.a.index > size) then
if (self.a.nearest_id and db.storage[self.a.nearest_id] and db.storage[self.a.nearest_id].corpse_already_selected == nil) then
if (self.a.selected_corpse_id and db.storage[self.a.selected_corpse_id]) then
db.storage[self.a.selected_corpse_id].corpse_already_selected = nil
end
self.a.vertex_id = self.a.nearest_corpse_vertex
self.a.vertex_position = self.a.nearest_corpse_position
self.a.selected_corpse_id = self.a.nearest_id
self.a.state = self.a.nearest_state
db.storage[self.a.selected_corpse_id].corpse_already_selected = npc:id() -- current looter
self.a.index = 1
self.a.nearest_corpse_dist = nil
self.a.nearest_corpse_vertex = nil
self.a.nearest_corpse_position = nil
self.a.nearest_id = nil
return true
end
self.a.index = 1
self.a.nearest_corpse_dist = nil
self.a.nearest_corpse_vertex = nil
self.a.nearest_corpse_position = nil
self.a.nearest_id = nil
empty_table(self.a.memory)
return false
end
local id = self.a.memory[self.a.index]
local corpse_npc = id and db.storage[id] and db.storage[id].object
if (corpse_npc and corpse_npc:alive() ~= true and db.storage[id] and not db.storage[self.a.selected_corpse_id] and not se_load_var(corpse_npc:id(),corpse_npc:name(),npc:id())) then
if (db.storage[id].corpse_already_selected == nil) then
local is_stalker = IsStalker(corpse_npc)
local can_loot_mutants = not is_stalker and not se_load_var(corpse_npc:id(),corpse_npc:name(),"looted") and (self.a.analse_mode == "true" or self.a.enabled == "true" and MonsterLootCommunities[character_community(npc)])
if (self.a.enabled == "true" and is_stalker or can_loot_mutants) then
local corpse_pos = utils_obj.safe_bone_pos(corpse_npc,"bip01_spine")
local dist = npc:position():distance_to_sqr(corpse_pos)
if (dist < AlwaysDetectDistance) and (self.a.nearest_corpse_dist == nil or dist <= self.a.nearest_corpse_dist) then
local corpse_vertex = level.vertex_id(corpse_pos)
if level.vertex_position(corpse_vertex):distance_to_sqr(corpse_pos) > 16 then
corpse_vertex = corpse_npc:level_vertex_id()
end
if (npc:accessible(corpse_vertex) and level.vertex_position(corpse_vertex):distance_to_sqr(corpse_pos) <= 15) then
self.a.nearest_corpse_dist = dist
self.a.nearest_corpse_vertex = corpse_vertex
self.a.nearest_corpse_position = corpse_pos
self.a.nearest_id = id
self.a.nearest_state = dist < math.random(5,30) and StateWalk or StateRun
end
end
end
elseif math.random(4) > 1 then
se_save_var(corpse_npc:id(),corpse_npc:name(),npc:id(),"true")
end
end
self.a.index = self.a.index + 1
return false
end
function xr_corpse_detection.on_game_start()
local ini = ini_file("ai_tweaks\\xr_corpse_detection.ltx")
AlwaysDetectDistance = 1500
StateWalk = ini:r_string_ex("settings","state_walk") or "walk_noweap"
StateRun = ini:r_string_ex("settings","state_run") or "rush"
MonsterLootCommunities = utils_data.collect_section(ini,"loot_mutant_communities",true)
xr_corpse_detection.lootable_table = xr_corpse_detection.get_loot_table(ini)
end
-- not used
function xr_corpse_detection.has_valuable_loot() end
function xr_corpse_detection.set_valuable_loot() end
----------------------------------------------------------------------
local Items = {}
function xr_gather_items.eva_gather_itm:find_valid_item()
if (self.st.cstackprevent) then
printf("C Stack Overflow Prevention: warning xr_gather_items scheme making repeated calls without return; save now and reload!")
return false
end
if (self.st.selected_id) then
local se_itm = self.st.selected_id ~= 0 and self.st.selected_id ~= 65535 and alife_object(self.st.selected_id)
if (se_itm and se_itm.parent_id == 65535) then
local itm = level.object_by_id(se_itm.id)
if (itm) then
return true
end
end
Items[self.st.selected_id] = nil
self.st.selected_id = nil
end
local tg = time_global()
self.st.__dtimer = not self.st.__dtimer and tg + 250 or self.st.__dtimer
if (tg < self.st.__dtimer) then
return false
end
self.st.__dtimer = nil
local loot_table = xr_corpse_detection.lootable_table or utils_data.collect_section(ini,"lootable",true)
if not (loot_table) then
return false
end
if not (self.st.index) then
self.st.index = 1
end
if not (self.st.memory) then
self.st.memory = {}
end
local npc = self.object
local size = #self.st.memory
if (size == 0) then
local obj_id
self.st.cstackprevent = true
for o in npc:memory_visible_objects() do
local obj = o and o:object()
if (obj) then
obj_id = obj:id()
if (loot_table[obj:section()] ~= nil) then
size = size + 1
table.insert(self.st.memory,obj_id)
end
end
end
self.st.cstackprevent = nil
if (size == 0) then
return false
end
end
if (size == 0 or self.st.index > size) then
if (self.st.nearest_id and Items[self.st.nearest_id] == nil and (not game_setup.is_world_item(self.st.nearest_id))) then
if (self.st.selected_id and Items[self.st.selected_id]) then
Items[self.st.selected_id] = nil
end
self.st.selected_id = self.st.nearest_id
self.st.vid = self.st.nearest_vid
self.st.vid_pos = self.st.nearest_pos
Items[self.st.selected_id] = npc:id()
self.st.stage = 1
self.st.nearest_dist = nil
self.st.nearest_vid = nil
self.st.nearest_pos = nil
self.st.nearest_id = nil
self.st.index = 1
local be = npc:best_enemy()
local dist = npc:position():distance_to(self.st.vid_pos)
if (be or dist < 3) then
self.st.state = "patrol"
else
self.st.state = "rush"
end
return true
end
self.st.index = 1
self.st.nearest_dist = nil
self.st.nearest_vid = nil
self.st.nearest_pos = nil
self.st.nearest_id = nil
empty_table(self.st.memory)
return false
end
local itm_id = self.st.memory[self.st.index]
self.st.index = self.st.index + 1
local itm = itm_id and itm_id ~= 0 and itm_id ~= 65535 and level.object_by_id(itm_id)
local se_itm = itm and loot_table[itm:section()] and Items[itm_id] == nil and alife_object(itm_id)
if not (se_itm and se_itm.parent_id == 65535) then
return false
end
if (get_object_story_id(itm_id)) then
return false
end
if (IsArtefact(nil,se_itm:clsid())) then
if (xr_logic.pick_section_from_condlist(db.actor, npc, self.st.gather_artefact_items_enabled) == "false") then
return false
end
else
if not (npc:see(itm)) then
return false
end
end
if (npc:best_enemy()) then
if not (IsWeapon(itm)) then
return false
end
if (IsWeapon(npc:active_item())) then
return false
end
else
if (itm:condition() < 0.44) then
return false
end
end
local itm_pos = itm:position()
local dist = npc:position():distance_to_sqr(itm_pos)
if (self.st.nearest_dist == nil or dist < self.st.nearest_dist) and (dist < 1225) then -- 1225 is MaxDetectDistance
local vid = level.vertex_id(itm_pos)
if (level.vertex_position(vid):distance_to_sqr(itm_pos) > 16) then
vid = itm:level_vertex_id()
end
if (npc:accessible(vid) and level.vertex_position(vid):distance_to_sqr(itm_pos) <= 15) then
self.st.nearest_dist = dist
self.st.nearest_vid = vid
self.st.nearest_pos = itm_pos
self.st.nearest_id = itm_id
end
end
return false
end