525 lines
16 KiB
Plaintext
525 lines
16 KiB
Plaintext
|
--[[
|
||
|
- Remade by Tronex
|
||
|
- 2019/4/23
|
||
|
|
||
|
Edit Log:
|
||
|
2020/5/29 - Vintar - added Warfare compatibility
|
||
|
|
||
|
- Global functions for assault tasks, with support for pre-info before accepting
|
||
|
|
||
|
Parameters for precondition
|
||
|
P[1] = (string) task_id
|
||
|
P[2] = (num) scan mode (1 to 5)
|
||
|
1 = same level
|
||
|
2 = same or nearby levels
|
||
|
3 = neaby levels only
|
||
|
4 = far levels
|
||
|
5 = all levels
|
||
|
P[3] = (num) minimum squad members size
|
||
|
P[4] = (num) minumum stay time for squad
|
||
|
P[5] = (bool) if true, scan will includes scripted squads
|
||
|
P[6] = (bool) if true, the factions declared in "status_functor_params" are enemy factions to traget, otherwise script assume they are natural and will search for matual enemies to them. If first faction is "monster" it will be a mutant squad hunt
|
||
|
P[7] = (string) specific smart to target
|
||
|
|
||
|
|
||
|
Example of usage in configs:
|
||
|
precondition = validate_assault_task( mil_smart_terrain_7_7_freedom_leader_stalker_task_2 : 2 : 1 : nil : false : true : nil )
|
||
|
target_functor = assault_task_target_functor
|
||
|
status_functor = assault_task_status_functor
|
||
|
status_functor_params = dolg
|
||
|
on_job_descr = %=setup_assault_task( mil_smart_terrain_7_7_freedom_leader_stalker_task_2 )%
|
||
|
|
||
|
|
||
|
--]]
|
||
|
|
||
|
-- Cache
|
||
|
local cache_assault = {}
|
||
|
local cache_assault_func = {}
|
||
|
|
||
|
local sfind = string.find
|
||
|
|
||
|
local factions_list = { -- List of allowed factions
|
||
|
["stalker"] = true,
|
||
|
["dolg"] = true,
|
||
|
["freedom"] = true,
|
||
|
["csky"] = true,
|
||
|
["ecolog"] = true,
|
||
|
["killer"] = true,
|
||
|
["army"] = true,
|
||
|
["bandit"] = true,
|
||
|
["monolith"] = true,
|
||
|
}
|
||
|
|
||
|
local blacklisted_maps = { -- List of maps to skip in scans
|
||
|
-- North
|
||
|
["l13_generators"] = true,
|
||
|
["l12_stancia_2"] = true,
|
||
|
["l12_stancia"] = true,
|
||
|
["l11_pripyat"] = true,
|
||
|
["l10_radar"] = true,
|
||
|
["l11_hospital"] = true,
|
||
|
|
||
|
-- Underground
|
||
|
["jupiter_underground"] = true,
|
||
|
["labx8"] = true,
|
||
|
["l03u_agr_underground"] = true,
|
||
|
["l04u_labx18"] = true,
|
||
|
["l08u_brainlab"] = true,
|
||
|
["l10u_bunker"] = true,
|
||
|
["l12u_control_monolith"] = true,
|
||
|
["l12u_sarcofag"] = true,
|
||
|
["l13u_warlab"] = true,
|
||
|
|
||
|
["fake_start"] = true
|
||
|
}
|
||
|
|
||
|
|
||
|
---------------------------< Utility >---------------------------
|
||
|
function is_legit_mutant_squad(squad)
|
||
|
local section = squad and squad:section_name()
|
||
|
return squad and (not sfind(section,"tushkano")) and (not sfind(section,"rat")) and true or false
|
||
|
end
|
||
|
|
||
|
function evaluate_smarts_squads(task_id, tbl, smart, squad_def, faction_def)
|
||
|
if (not smart) then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local smrt_id = smart.id
|
||
|
local smrt_name = smart:name()
|
||
|
|
||
|
if (simulation_objects.base_smarts[smrt_name] == true) then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local smrt = smrt_id and SIMBOARD.smarts[smrt_id]
|
||
|
if (not smrt) then
|
||
|
return
|
||
|
end
|
||
|
--printf("~ %s | scanning smart: %s", task_id, smrt_name)
|
||
|
|
||
|
for sq_id,_ in pairs(smrt.squads) do
|
||
|
--printf("# %s | found squad (%s) in smart: %s", task_id, sq_id, smrt_name)
|
||
|
|
||
|
-- if warfare, override checks on stay time, default squad stuff
|
||
|
-- if smart's squad is on its level + they are targeting it
|
||
|
local squad = alife_object(sq_id)
|
||
|
if squad and simulation_objects.is_on_the_same_level(squad, smart)
|
||
|
and squad.current_target_id and (squad.current_target_id == smrt_id)
|
||
|
and (squad.current_action == 1)
|
||
|
and (((squad:npc_count() >= squad_def.num)
|
||
|
and squad.stay_time
|
||
|
and ((not squad_def.stay_time) or (squad_def.stay_time and (game.get_game_time():diffSec(squad.stay_time) <= tonumber(squad_def.stay_time))))
|
||
|
and (squad_def.scripted or (not squad:get_script_target())))
|
||
|
or (_G.WARFARE and not squad:get_script_target()))
|
||
|
then
|
||
|
--printf("# %s | smart (%s) [%s] w/ squad (%s) [%s] = Checking", task_id, smrt_id, smrt_name, sq_id, squad.player_id)
|
||
|
|
||
|
for fac,_ in pairs(faction_def) do
|
||
|
|
||
|
-- if squad is from enemies table
|
||
|
if (is_legit_mutant_squad(squad) and squad.player_id == fac) then
|
||
|
--squad.stay_time = game.get_game_time()
|
||
|
tbl[sq_id] = smrt_id
|
||
|
--printf("- %s | smart (%s) [%s] w/ squad (%s) [%s] = Added", task_id, smrt_id, smrt_name, sq_id, squad.player_id)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function evaluate_squads_smarts(task_id, var, squad, smart)
|
||
|
if squad and simulation_objects.is_on_the_same_level(squad, smart) then
|
||
|
if not ( squad.first_update ) then
|
||
|
--printf("~ %s | not all squads are loaded yet!", task_id)
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
if (var.scripted or (not squad:get_script_target()))
|
||
|
and (squad.current_target_id and squad.current_target_id == smart.id and squad.current_action == 1)
|
||
|
then
|
||
|
--printf("- %s | squad (%s) [%s] is targeting smart (%s)", task_id, squad.id, squad.player_id, smart.id)
|
||
|
for i = 1, #cache_assault_func[task_id] do
|
||
|
local fac = cache_assault_func[task_id][i]
|
||
|
if (is_legit_mutant_squad(squad) and squad.player_id == fac) then
|
||
|
-- updating data
|
||
|
var.squad_id = squad.id
|
||
|
save_var( db.actor, task_id, var )
|
||
|
|
||
|
-- reset gametime so they don't leave
|
||
|
if not _G.WARFARE then
|
||
|
squad.stay_time = game.get_game_time()
|
||
|
end
|
||
|
squad.force_online = true
|
||
|
--printf("- %s | squad (%s) [%s] is saved", task_id, squad.id, squad.player_id)
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
function postpone_for_next_frame(task_id, squad_id)
|
||
|
local is_hostage_task = (task_manager.task_ini:r_string_ex(task_id, "status_functor") == "hostage_task") and true or false
|
||
|
local squad = alife_object(squad_id)
|
||
|
if (squad) then
|
||
|
|
||
|
-- Location
|
||
|
local location = alife():level_name(game_graph():vertex(squad.m_game_vertex_id):level_id())
|
||
|
for k in squad:squad_members() do
|
||
|
local se_obj = k.object or alife_object(k.id)
|
||
|
if se_obj then
|
||
|
location = dynamic_news_helper.GetPointDescription(se_obj)
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
local str_location = game.translate_string("st_location") .. " " .. location
|
||
|
|
||
|
-- Community
|
||
|
local str_comm = ""
|
||
|
local community = squad.player_id
|
||
|
if is_squad_monster[community] then
|
||
|
str_comm = game.translate_string("st_sq_type") .. " " .. game.translate_string(community)
|
||
|
else
|
||
|
str_comm = game.translate_string("st_mm_new_game_faction_2") .. " " .. game.translate_string(community)
|
||
|
end
|
||
|
|
||
|
-- Build News
|
||
|
local news_caption = game.translate_string(task_manager.task_ini:r_string_ex(task_id, "title")) or "error"
|
||
|
|
||
|
local news_text, news_ico
|
||
|
if is_hostage_task then
|
||
|
news_text = str_location
|
||
|
news_ico = task_manager.task_ini:r_string_ex(task_id, "icon")
|
||
|
else
|
||
|
news_text = str_comm .. "\\n " .. str_location
|
||
|
news_ico = news_manager.tips_icons[squad.player_id]
|
||
|
end
|
||
|
if (not news_ico) then
|
||
|
news_ico = task_manager.task_ini:r_string_ex(task_id, "icon") or "ui_iconsTotal_mutant"
|
||
|
end
|
||
|
|
||
|
db.actor:give_talk_message2(news_caption, news_text, news_ico, "iconed_answer_item")
|
||
|
end
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
|
||
|
---------------------------< Target functor >---------------------------
|
||
|
task_functor.assault_task_target_functor = function (task_id,field,p,tsk)
|
||
|
if (field == "target") then
|
||
|
if (tsk and tsk.stage == 1 and tsk.task_giver_id) then
|
||
|
return tsk.task_giver_id
|
||
|
end
|
||
|
|
||
|
local actor = db.actor
|
||
|
local var = actor and load_var(actor, task_id)
|
||
|
if (not var) then
|
||
|
printe("! %s | assault_task_target_functor - var is nil", task_id)
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local smart = var.smart_id and alife_object(var.smart_id)
|
||
|
if (not smart) then
|
||
|
printe("! %s | assault_task_target_functor - smart is nil", task_id)
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local squad = var.squad_id and alife_object(var.squad_id)
|
||
|
|
||
|
-- If enemies are more then 50m from target location, then show red (?) near their location
|
||
|
if (squad) then
|
||
|
if (smart.position:distance_to_sqr(squad.position) > 2500) then
|
||
|
if (squad:clsid() == clsid.online_offline_group_s) then
|
||
|
if (squad.id and level.map_has_object_spot(squad.id,"red_location") == 0) then
|
||
|
level.map_add_object_spot(squad.id, "red_location", "st_ui_pda_task_unknown_enemy")
|
||
|
end
|
||
|
end
|
||
|
else
|
||
|
if (squad:clsid() == clsid.online_offline_group_s) then
|
||
|
if (squad.id and level.map_has_object_spot(squad.id,"red_location") == 1) then
|
||
|
level.map_remove_object_spot(squad.id, "red_location")
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return var.smart_id
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
---------------------------< Status functor >---------------------------
|
||
|
task_status_functor.assault_task_status_functor = function (tsk,task_id)
|
||
|
if not (db.actor and tsk) then return end
|
||
|
|
||
|
if (tsk.stage == 1) then return end -- already completed
|
||
|
|
||
|
local var = load_var(db.actor, task_id) -- check saved data
|
||
|
if (not var) then return "fail" end
|
||
|
|
||
|
local smart_id = var.smart_id -- check smart id
|
||
|
if (not smart_id) then return "fail" end
|
||
|
|
||
|
local smrt = SIMBOARD.smarts[smart_id] -- check smart object (special defines)
|
||
|
if (not smrt) then return "fail" end
|
||
|
|
||
|
local smart = smrt.smrt -- check smart server object
|
||
|
if (not smart) then return "fail" end
|
||
|
|
||
|
-- in case sim_avail is set true during the player's tsk. with simulation_objects.available_by_id[smart.id] nil means unprocess, false is absolutely not avail
|
||
|
if (simulation_objects.available_by_id[smart.id] == nil) then
|
||
|
return
|
||
|
elseif (simulation_objects.available_by_id[smart.id] == false) then
|
||
|
printe("! %s | task failed because smart no longer available", task_id)
|
||
|
return "fail"
|
||
|
end
|
||
|
|
||
|
|
||
|
-- Store factions parameters for the first time to re-use it
|
||
|
if (not cache_assault_func[task_id]) then
|
||
|
cache_assault_func[task_id] = {}
|
||
|
local params = parse_list(task_manager.task_ini,task_id,"status_functor_params")
|
||
|
if var.is_enemy then
|
||
|
for i=1,#params do
|
||
|
if is_squad_monster[params[i]] or factions_list[params[i]] then
|
||
|
cache_assault_func[task_id][i] = params[i]
|
||
|
printf("/ %s | Faction [%s] is re-added to cache_assault_func table", task_id, params[i])
|
||
|
end
|
||
|
end
|
||
|
elseif (not is_squad_monster[params[1]]) then
|
||
|
for fac,_ in pairs(factions_list) do
|
||
|
local cnt = 0
|
||
|
local is_enemy_to_actor = true --game_relations.is_factions_enemies(fac, get_actor_true_community())
|
||
|
for i=1,#params do
|
||
|
if (fac ~= params[i]) and is_enemy_to_actor and game_relations.is_factions_enemies(fac, params[i]) then
|
||
|
cnt = cnt + 1
|
||
|
end
|
||
|
end
|
||
|
if (cnt == #params) then
|
||
|
local idx = #cache_assault_func[task_id] + 1
|
||
|
cache_assault_func[task_id][idx] = fac
|
||
|
printf("/ %s | Faction [%s] is re-added to cache_assault_func table", task_id, fac)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
if (#cache_assault_func[task_id] == 0) then
|
||
|
printe("! %s | no enemy factions found",task_id)
|
||
|
return "fail"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
-- Timer for less pressure
|
||
|
local tg = time_global()
|
||
|
if (tsk.__check_smart_time and tg < tsk.__check_smart_time) then
|
||
|
return
|
||
|
end
|
||
|
tsk.__check_smart_time = tg+3000
|
||
|
|
||
|
-- cleaning data for re-assign next
|
||
|
local squad_id = var.squad_id
|
||
|
var.squad_id = nil
|
||
|
save_var(db.actor, task_id, var)
|
||
|
|
||
|
-- Scan saved squad
|
||
|
local squad = squad_id and alife_object(squad_id)
|
||
|
if squad then
|
||
|
local pass_this = evaluate_squads_smarts(task_id, var, squad, smart)
|
||
|
if pass_this then
|
||
|
return
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Scan all squads
|
||
|
for id,v in pairs( SIMBOARD.squads ) do
|
||
|
local squad = alife_object(id)
|
||
|
if squad then
|
||
|
local pass_this = evaluate_squads_smarts(task_id, var, squad, smart)
|
||
|
if pass_this then
|
||
|
return
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- And who to shoot?
|
||
|
if (smart.position:distance_to(db.actor:position()) > 15) then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
-- If smart is controllable by factions, player dominate it
|
||
|
if (smart.faction_controlled) then
|
||
|
local comm = character_community(db.actor):sub(7)
|
||
|
smart.faction = factions_list[comm] and comm or smart.faction
|
||
|
end
|
||
|
|
||
|
-- Assuming no squads means that target smart got cleared
|
||
|
tsk.stage = 1
|
||
|
end
|
||
|
|
||
|
|
||
|
---------------------------< Precondition >---------------------------
|
||
|
xr_conditions.validate_assault_task = function(actor, npc, p)
|
||
|
if not (p and #p >= 1) then
|
||
|
return false
|
||
|
end
|
||
|
local task_id = p[1]
|
||
|
|
||
|
if (#p < 7) then
|
||
|
printe("! %s | not enough parameters", task_id)
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
--// Return true if a squad is picked already
|
||
|
if cache_assault[task_id] then
|
||
|
local c_squad_id = cache_assault[task_id].squad_id
|
||
|
local c_smart_id = cache_assault[task_id].smart_id
|
||
|
local c_squad = c_squad_id and alife_object(c_squad_id)
|
||
|
local c_smart = c_smart_id and alife_object(c_smart_id)
|
||
|
if c_squad and c_smart and (c_squad.current_target_id == c_smart_id) then
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--// Utilities
|
||
|
local sim = alife()
|
||
|
local gg = game_graph()
|
||
|
local actor_comm = sim:actor():community()
|
||
|
local actor_level = level.name()
|
||
|
local is_avail = simulation_objects.available_by_id
|
||
|
local p_status = parse_list(task_manager.task_ini,task_id,"status_functor_params")
|
||
|
local enemy_faction_list = {}
|
||
|
if (not p_status[1]) then
|
||
|
printe("! %s | status functor parameters are mising!", task_id)
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
--// Defines
|
||
|
local def = {}
|
||
|
def.scan = tonumber(p[2]) or 1
|
||
|
def.num = tonumber(p[3]) or 1
|
||
|
def.stay_time = (p[4] ~= "nil") and tonumber(p[4])
|
||
|
def.scripted = (p[5] == "true") and true or false
|
||
|
def.is_enemy = (p[6] == "true") and true or false
|
||
|
def.smart = (p[7] ~= "nil") and p_status[1]
|
||
|
|
||
|
--// Collect enemy factions
|
||
|
if def.is_enemy then -- if faction parameters are enemies
|
||
|
for i=1,#p_status do
|
||
|
if is_squad_monster[p_status[i]] or factions_list[p_status[i]] then
|
||
|
--printf("/ %s | Faction [%s] is added to enemy_faction_list table", task_id, p_status[i])
|
||
|
enemy_faction_list[p_status[i]] = true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
elseif (not is_squad_monster[p_status[1]]) then -- if faction parameters are matutal factions
|
||
|
for fac,_ in pairs(factions_list) do
|
||
|
local cnt = 0
|
||
|
local is_enemy_to_actor = true --game_relations.is_factions_enemies(fac, get_actor_true_community())
|
||
|
for i=1,#p_status do
|
||
|
if (fac ~= p_status[i]) and is_enemy_to_actor and game_relations.is_factions_enemies(fac, p_status[i]) then
|
||
|
cnt = cnt + 1
|
||
|
end
|
||
|
end
|
||
|
if (cnt == #p_status) then
|
||
|
enemy_faction_list[fac] = true
|
||
|
--printf("/ %s | Faction [%s] is added to enemy_faction_list table", task_id, fac)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if is_empty(enemy_faction_list) then
|
||
|
printe("! %s | no enemy factions found", task_id)
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
|
||
|
--// Search all smarts
|
||
|
local targets = {} -- target[squad_id] = smart_id
|
||
|
if def.smart then -- search in specific smart
|
||
|
local smart = SIMBOARD.smarts_by_names[def.smart]
|
||
|
if smart then
|
||
|
evaluate_smarts_squads(task_id, targets, smart, def, enemy_faction_list)
|
||
|
end
|
||
|
|
||
|
else -- search all smarts
|
||
|
for name,v in pairs(SIMBOARD.smarts_by_names) do
|
||
|
|
||
|
-- if smart is available
|
||
|
if (is_avail[v.id] == true) then
|
||
|
|
||
|
-- if smart is not in blacklisted location
|
||
|
local smart_level = sim:level_name(gg:vertex(v.m_game_vertex_id):level_id())
|
||
|
if (not blacklisted_maps[smart_level]) then
|
||
|
|
||
|
-- if smart location is proper to the parameter
|
||
|
local is_online = v.online
|
||
|
local is_nearby = sfind(simulation_objects.config:r_value(actor_level, "target_maps", 0, ""), smart_level)
|
||
|
if ((def.scan == 1) and is_online) -- same level
|
||
|
or ((def.scan == 2) and (is_online or is_nearby)) -- same + nearby level
|
||
|
or ((def.scan == 3) and is_nearby) -- nearby levels only
|
||
|
or ((def.scan == 4) and (not (is_online or is_nearby))) -- far levels only
|
||
|
or (def.scan == 5) -- anywhere
|
||
|
then
|
||
|
evaluate_smarts_squads(task_id, targets, v, def, enemy_faction_list)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--// Cache results
|
||
|
if is_not_empty(targets) then
|
||
|
local target_squad = random_key_table(targets)
|
||
|
local target_smart = targets[target_squad]
|
||
|
|
||
|
-- local x = alife_object(target_smart)
|
||
|
-- printf('target %s',x and x:name())
|
||
|
|
||
|
cache_assault[task_id] = {
|
||
|
squad_id = target_squad,
|
||
|
smart_id = target_smart,
|
||
|
is_enemy = def.is_enemy,
|
||
|
scripted = def.scripted
|
||
|
}
|
||
|
|
||
|
--printf("- %s | Found %s targets so far", task_id, size_table(targets))
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
--printf("! %s | no targets found", task_id)
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
|
||
|
---------------------------< Effects >---------------------------
|
||
|
xr_effects.setup_assault_task = function(actor, npc, p)
|
||
|
if not (p and p[1]) then
|
||
|
return false
|
||
|
end
|
||
|
local task_id = p[1]
|
||
|
|
||
|
--// Read cache
|
||
|
if cache_assault[task_id] then
|
||
|
local squad_id = cache_assault[task_id].squad_id
|
||
|
local smart_id = cache_assault[task_id].smart_id
|
||
|
local squad = squad_id and alife_object(squad_id)
|
||
|
local smart = smart_id and alife_object(smart_id)
|
||
|
if squad and smart then
|
||
|
squad.stay_time = game.get_game_time()
|
||
|
sim_offline_combat.task_squads[squad_id] = true
|
||
|
local tbl = {
|
||
|
smart_id = smart_id,
|
||
|
squad_id = squad_id,
|
||
|
is_enemy = cache_assault[task_id].is_enemy,
|
||
|
scripted = cache_assault[task_id].scripted,
|
||
|
}
|
||
|
save_var(db.actor, task_id, tbl)
|
||
|
printdbg("- %s | Cached result = squad_id (%s) [%s] - smart_id(%s) - is_enemy: %s - scripted: %s", task_id, squad_id, squad.player_id, smart_id, tbl.is_enemy, tbl.scripted)
|
||
|
|
||
|
CreateTimeEvent(0,"setup_assault_task",0,postpone_for_next_frame,task_id, squad_id)
|
||
|
end
|
||
|
end
|
||
|
end
|