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) if corpse and 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 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