451 lines
13 KiB
Plaintext
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 |