---==================================================================================================================--- --- --- --- Original Author(s) : NLTP_ASHES --- --- Edited : N/A --- --- Date : 17/04/2023 --- --- License : Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) --- --- --- --- Script used to manage the tasks of the second act of the addon's storyline. --- --- --- --- Each task is composed of the following elements : --- --- - A act_2_task_X_start(...) function, used to start the task; --- --- - A act_2_task_X_end(...) function, used to end the task; --- --- - A xr_effects.act_2_task_X_init(...) function, called when the task has started; --- --- - A xr_effects.act_2_task_X_complete(...) function, called when the task was successfully completed; --- --- - A xr_effects.act_2_task_X_fail(...) function, called when the task was failed; --- --- - A task_functor.act_2_task_X_title_f(...) function, called periodically to get the name; --- --- - A task_functor.act_2_task_X_descr_f(...) function, called periodically to get the description; --- --- - A task_functor.act_2_task_X_target_f(...) function, called periodically to get the target (for the PDA); --- --- - A task_status_functor.act_2_task_X_status_f(...) function, called periodically to run the core logic. --- --- --- ---==================================================================================================================--- -- --------------------------------------------------------------------------------------------------------------------- -- Constants, global variables and imported functions -- --------------------------------------------------------------------------------------------------------------------- -- Imported functions local dbg_printf = western_goods_utils.dbg_printf local send_dialog = western_goods_dialogs_manager.send_dialog -- Constants local CONST_SPAWN_THROTTLE = 5000 -- milliseconds local CONST_TASK_2_HARD_DELAY = 1800000 -- milliseconds local CONST_TASK_2_SOFT_DELAY_MIN = 600 -- seconds local CONST_TASK_2_SOFT_DELAY_MAX = 1800 -- seconds local CONST_TASK_2_ECOLOG_TELEPORT_DELAY = 50000 -- milliseconds local CONST_TASK_2_STAGE_6_PROGRESS_DELAY = 30000 -- milliseconds local CONST_TASK_2_STAGE_8_PROGRESS_DELAY = 60000 -- milliseconds local CONST_TASK_2_STAGE_9_PROGRESS_DELAY = 40000 -- milliseconds local CONST_TASK_2_BANDIT_AUTOKILL_DELAY = 40000 -- milliseconds local CONST_TASK_2_CLEAR_SMART_DIST = 2500 -- meters squared local CONST_TASK_2_ZOMBIE_1_DIST = 15625 -- meters squared local CONST_TASK_2_SCHOOL_CLOSE = 400 -- meters squared local CONST_TASK_2_SCHOOL_FAR = 4900 -- meters squared -- Task variables TASK_1_CACHE = {} TASK_2_CACHE = {} -- --------------------------------------------------------------------------------------------------------------------- -- ACT 2 - CONTRACT WORK -- --------------------------------------------------------------------------------------------------------------------- -- TASK 1 - KOLOBOK -- --------------------------------------------------------------------------------------------------------------------- --- Function used to start Act 2, Task 1. --- @param first_speaker cse_alife_object --- @param second_speaker cse_alife_object --- @return nil function act_2_task_1_start(first_speaker, second_speaker) local npc = dialogs.who_is_npc(first_speaker, second_speaker) task_manager.get_task_manager():give_task("western_goods_act_2_task_1", npc:id()) dbg_printf("[WG] Tasks Act 2 | Task 1 - Task started...") end --- Function used to end Act 2, Task 1. --- @return nil function act_2_task_1_end() local kolobok_found = false -- Items in player inventory that have a section among the ones in fallback_items local secondary_items_found = {} -- Iterate player inventory to find if he has kolobok, and gather fallback items in the process western_goods_utils.inventory_iter(db.actor,function(owner, obj) local is_main_item = obj:section() == TASK_1_CACHE.main_item local is_secondary_item = western_goods_utils.table_contains(TASK_1_CACHE.secondary_items, obj:section()) if is_main_item then kolobok_found = true return true elseif is_secondary_item then table.insert(secondary_items_found, obj) end end) -- If the player doesn't have a Kolobok, use one of the fallback items if not kolobok_found then for _,obj in pairs(secondary_items_found) do western_goods_utils.open_container(obj,false) break end end task_manager.get_task_manager():set_task_completed("western_goods_act_2_task_1") TASK_2_CACHE.available_time = time_global() + CONST_TASK_2_HARD_DELAY dbg_printf("[WG] Tasks Act 2 | Task 1 - Task completed...") end --- Function called when the task is initiated. --- @param actor cse_alife_object --- @param npc cse_alife_object --- @return nil function xr_effects.act_2_task_1_init(actor,npc) -- Prepare task cache table TASK_1_CACHE.main_item = "af_fuzz_kolobok" TASK_1_CACHE.secondary_items = { "af_fuzz_kolobok_lead_box", "af_fuzz_kolobok_af_aac", "af_fuzz_kolobok_af_aam", "af_fuzz_kolobok_af_iam" } -- Spawn task target if it doesn't exist in the world local main_item_exists = false western_goods_utils.server_objects_iter(function(se_obj) if se_obj:section_name() == TASK_1_CACHE.main_item then dbg_printf("[WG] Tasks Act 2 | Task 1 - Main target already exist in the world %s", se_obj.id) main_item_exists = true return true end end) if not main_item_exists then dbg_printf("[WG] Tasks Act 2 | Task 1 - Main target does not exist in the world...") local viable_spawn_anomalies = task_1_get_static_chemical_anomalies() or {} local spawn_anomaly_id = random_key_table(viable_spawn_anomalies) local spawn_anomaly = spawn_anomaly_id and alife_object(spawn_anomaly_id) if spawn_anomaly then alife_create(TASK_1_CACHE.main_item, spawn_anomaly.position, spawn_anomaly.m_level_vertex_id, spawn_anomaly.m_game_vertex_id) dbg_printf("[WG] Tasks Act 2 | Task 1 - Spawned main target at pos:%s lvid:%s gvid:%s", spawn_anomaly.position, spawn_anomaly.m_level_vertex_id, spawn_anomaly.m_game_vertex_id) end end -- Process info portions western_goods_utils.give_info("western_goods_act_2_task_1_active") western_goods_utils.give_info("western_goods_act_2_task_1_init") end --- Function called when the task is successfully completed. --- @param actor cse_alife_object --- @param npc cse_alife_object --- @return nil function xr_effects.act_2_task_1_complete(actor,npc) -- Process info portions western_goods_utils.rem_info("western_goods_act_2_task_1_active") western_goods_utils.rem_info("western_goods_act_2_task_1_init") western_goods_utils.give_info("western_goods_act_2_task_1_finished") -- Process quest items xr_effects.remove_item(actor, npc, {"af_fuzz_kolobok"}) -- Process reward xr_effects.reward_random_money(actor,npc,{"10000","15000"}) xr_effects.complete_task_inc_goodwill(actor,npc,{"25","killer"}) end --- Function called when the task is failed. --- @param actor cse_alife_object --- @param npc cse_alife_object --- @return nil function xr_effects.act_2_task_1_fail(actor,npc) -- Process info portions western_goods_utils.rem_info("western_goods_act_2_task_1_active") western_goods_utils.rem_info("western_goods_act_2_task_1_init") -- Process penalty xr_effects.fail_task_dec_goodwill(actor,npc,{"35","killer"}) end --- Function used to retrieve the title of the mission (displayed in the PDA). --- @param task_id number --- @param field string --- @param p any --- @param tsk CGameTask --- @return string function task_functor.act_2_task_1_title_f(task_id,field,p,tsk) if true then -- Cond to force my IDE to fold this fucking function correctly return western_goods_utils.get_translation("st_wg_act_2_task_1_title") end end --- Function used to retrieve the description of the mission (displayed in the PDA). --- Warning : naming contract on the translation string : st_wg_trader_act_2_task_1_stage__descr. --- @param task_id number --- @param field string --- @param p any --- @param tsk CGameTask --- @return string function task_functor.act_2_task_1_descr_f(task_id,field,p,tsk) if true then -- Cond to force my IDE to fold this fucking function correctly return western_goods_utils.get_translation("st_wg_act_2_task_1_stage_" .. tostring(tsk.stage) .. "_descr") end end --- Function used to retrieve the target of the mission (marker displayed (or not) in the PDA). --- @param task_id number --- @param field string --- @param p any --- @param tsk CGameTask --- @return number function task_functor.act_2_task_1_target_f(task_id,field,p,tsk) if western_goods_utils.has_info("western_goods_act_2_task_1_init") then if tsk.stage == 0 then return nil end if tsk.stage == 1 then return tsk.task_giver_id end end end --- Function used to manage the mission logic as a whole. --- @param tsk CGameTask --- @param task_id number --- @return string function task_status_functor.act_2_task_1_status_f(tsk,task_id) if western_goods_utils.has_info("western_goods_act_2_task_1_init") then -- First stage : Find the artefact if tsk.stage == 0 then western_goods_utils.inventory_iter(db.actor,function(owner, obj) local is_main_item = obj:section() == TASK_1_CACHE.main_item local is_secondary_item = western_goods_utils.table_contains(TASK_1_CACHE.secondary_items, obj:section()) if is_main_item or is_secondary_item then dbg_printf("[WG] Tasks Act 2 | Task 1 - Stage 0 - Player found primary (%s) or secondary item (%s)",is_main_item,is_secondary_item) tsk.stage = 1 return true end end) end -- Second stage : Deliver the artefact if tsk.stage == 1 then local has_item = false western_goods_utils.inventory_iter(db.actor,function(owner, obj) local is_main_item = obj:section() == TASK_1_CACHE.main_item local is_secondary_item = western_goods_utils.table_contains(TASK_1_CACHE.secondary_items, obj:section()) if is_main_item or is_secondary_item then has_item = true return true end end) if not has_item then if western_goods_utils.has_info("western_goods_act_2_task_1_ready_finished") then dbg_printf("[WG] Tasks Act 2 | Task 1 - Stage 1 - Task no longer ready to be completed...") western_goods_utils.rem_info("western_goods_act_2_task_1_ready_finished") end tsk.stage = 0 return end if not western_goods_utils.has_info("western_goods_act_2_task_1_ready_finished") then dbg_printf("[WG] Tasks Act 2 | Task 1 - Stage 1 - Task ready to be completed...") western_goods_utils.give_info("western_goods_act_2_task_1_ready_finished") end end end end -- --------------------------------------------------------------------------------------------------------------------- -- TASK 2 - RETRIEVAL MISSION -- --------------------------------------------------------------------------------------------------------------------- --- Function used to start Act 2, Task 2. --- @param task_giver cse_alife_object --- @return nil function act_2_task_2_start(task_giver) task_manager.get_task_manager():give_task("western_goods_act_2_task_2", task_giver.id) dbg_printf("[WG] Tasks Act 2 | Task 2 - Task started...") end --- Function used to end Act 2, Task 2. --- @return nil function act_2_task_2_end() task_manager.get_task_manager():set_task_completed("western_goods_act_2_task_2") dbg_printf("[WG] Tasks Act 2 | Task 2 - Task completed...") end --- Function called when the task is initiated. --- @param actor cse_alife_object --- @param npc cse_alife_object --- @return nil function xr_effects.act_2_task_2_init(actor,npc) TASK_2_CACHE = { main_item = "wg_act_2_task_2_quest_item", marked_enemies = { }, objects = { btr = { sec="veh_btr", x=-32.9, y=-0.5, z=20.9, lvid=155155, gvid=4971 }, sleeping_bag = { sec="itm_sleepbag", x=26.6, y=-4.3, z=129.2, lvid=232806, gvid=4970 } }, squads = { army_spawn_data = { sec="western_goods_act_2_task_2_army_squad", smart="pri_sim_3" }, mono_spawn_data = { sec="western_goods_act_2_task_2_mono_squad", smart="pri_sim_3" }, snork_spawn_data = { sec="western_goods_act_2_task_2_snork_squad", smart="pri_b301" }, ecolog_spawn_data = { sec="western_goods_act_2_task_2_ecolog_squad", smart="pri_a28_school", smart2="pri_b303" }, zombie_1_spawn_data = { sec="western_goods_act_2_task_2_zombie_1_squad", smart="pri_sim_3" }, zombie_2_spawn_data = { sec="western_goods_act_2_task_2_zombie_2_squad", smart="pri_sim_3" }, bandit_1_spawn_data = { sec="western_goods_act_2_task_2_bandit_1_squad", smart="pri_b301" }, bandit_2_spawn_data = { sec="western_goods_act_2_task_2_bandit_2_squad", smart="pri_a28_school" }, controller_spawn_data = { sec="western_goods_act_2_task_2_controller_squad", smart="pri_b303" }, lynn_spawn_data = { sec="stalker_dunn_lynn_squad", smart="ds2_lager_st" }, lynn_guards_spawn_data = { sec="western_goods_act_2_task_2_lynn_guards_squad", smart="ds2_lager_st" } }, restrictors = { zombie = { ax=-97.150344848633, ay=-1.1752129793167, az=-45.473430633545, bx=-81.533813476563, by=5.2640190124512, bz=-64.902114868164 }, snork = { ax=17.24550819397, ay=-4.823212146759, az=129.98883056641, bx=29.521947860718, by=-1.0844190120697, bz=121.89762878418 }, school = { ax=2.7305309772491, ay=-4.823212146759, az=180.73071289063, bx=76.082359313965, by=17.772342681885, bz=112.70443725586 }, overlook = { ax=27.744569778442, ay=7.1062412261963, az=129.99130249023, bx=53.035232543945, by=16.793748855591, bz=121.89153289795 }, kbo = { ax=3.7310662269592, ay=4.2264876365662, az=272.10140991211, bx=14.69687461853, by=8.0234889984131, bz=265.22302246094 } } } -- Process info portions western_goods_utils.give_info("western_goods_act_2_task_2_active") western_goods_utils.give_info("western_goods_act_2_task_2_init") end --- Function called when the task is successfully completed. --- @param actor cse_alife_object --- @param npc cse_alife_object --- @return nil function xr_effects.act_2_task_2_complete(actor,npc) -- Process info portions western_goods_utils.rem_info("western_goods_act_2_task_2_active") western_goods_utils.rem_info("western_goods_act_2_task_2_init") western_goods_utils.give_info("western_goods_act_2_task_2_finished") western_goods_utils.give_info("western_goods_act_2_finished") -- Clean up task_2_clean_squads() -- Process quest items xr_effects.remove_item(actor, npc, {"wg_act_2_task_2_quest_item"}) -- Process reward xr_effects.reward_random_money(actor,npc,{"15000","20000"}) xr_effects.complete_task_inc_goodwill(actor,npc,{"100","isg"}) xr_effects.complete_task_inc_goodwill(actor,npc,{"25","killer"}) end --- Function called when the task is failed. --- @param actor cse_alife_object --- @param npc cse_alife_object --- @return nil function xr_effects.act_2_task_2_fail(actor,npc) -- Process info portions western_goods_utils.rem_info("western_goods_act_2_task_2_active") western_goods_utils.rem_info("western_goods_act_2_task_2_init") -- Process penalty xr_effects.fail_task_dec_goodwill(actor,npc,{"40","killer"}) end --- Function used to retrieve the title of the mission (displayed in the PDA). --- @param task_id number --- @param field string --- @param p any --- @param tsk CGameTask --- @return string function task_functor.act_2_task_2_title_f(task_id,field,p,tsk) if true then -- Cond to force my IDE to fold this fucking function correctly return western_goods_utils.get_translation("st_wg_act_2_task_2_title") end end --- Function used to retrieve the description of the mission (displayed in the PDA). --- Warning : naming contract on the translation string : st_wg_trader_act_2_task_2_stage__descr. --- @param task_id number --- @param field string --- @param p any --- @param tsk CGameTask --- @return string function task_functor.act_2_task_2_descr_f(task_id,field,p,tsk) if true then -- Cond to force my IDE to fold this fucking function correctly return western_goods_utils.get_translation("st_wg_act_2_task_2_stage_" .. tostring(tsk.stage) .. "_descr") end end --- Function used to retrieve the target of the mission (marker displayed (or not) in the PDA). --- @param task_id number --- @param field string --- @param p any --- @param tsk CGameTask --- @return number function task_functor.act_2_task_2_target_f(task_id,field,p,tsk) if western_goods_utils.has_info("western_goods_act_2_task_2_init") then if tsk.stage == 0 then local smart = SIMBOARD:get_smart_by_name("pri_sim_3") return smart and smart.id end if tsk.stage == 1 then local zombie_1_squad_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.squads.zombie_1_spawn_data.sec) return zombie_1_squad_se and zombie_1_squad_se.id end if tsk.stage == 2 then local zombie_2_squad_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.squads.zombie_2_spawn_data.sec) return zombie_2_squad_se and zombie_2_squad_se.id end if tsk.stage == 3 then -- Put a red marker over all the pursuers (only in pripyat, to avoid cheating) if level.name() == "pripyat" then task_2_mark_current_enemies(TASK_2_CACHE.marked_enemies) end -- Return nil as to not place an actual task marker return nil end if tsk.stage == 4 then -- Remove left-over markers (just in case) if level.name() == "pripyat" then task_2_mark_current_enemies(TASK_2_CACHE.marked_enemies) end local restrictor = db.zone_by_name["pri_surge_hide_b301"] return restrictor and restrictor:id() end if tsk.stage == 5 then local snork_squad_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.squads.snork_spawn_data.sec) local restrictor = db.zone_by_name["pri_surge_hide_b301"] return (snork_squad_se and snork_squad_se.id) or (restrictor and restrictor:id()) end if tsk.stage == 6 then local restrictor = db.zone_by_name["pri_surge_hide_b301"] return restrictor and restrictor:id() end if tsk.stage == 7 then local smart = SIMBOARD:get_smart_by_name("pri_b301") return smart and smart.id end if tsk.stage == 8 then local smart = SIMBOARD:get_smart_by_name("pri_b301") return smart and smart.id end if tsk.stage == 9 then local smart = SIMBOARD:get_smart_by_name("pri_b301") return smart and smart.id end if tsk.stage == 10 then local restrictor = db.zone_by_name["pri_surge_hide_b301"] return restrictor and restrictor:id() end if tsk.stage == 11 then local smart = SIMBOARD:get_smart_by_name("pri_b303") return smart and smart.id end if tsk.stage == 12 then local supplies_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.main_item) return supplies_se and supplies_se.id end if tsk.stage == 13 then local lynn_squad_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.squads.lynn_spawn_data.sec) return lynn_squad_se and lynn_squad_se.id end end end --- Function used to manage the mission logic as a whole. --- @param tsk CGameTask --- @param task_id number --- @return string function task_status_functor.act_2_task_2_status_f(tsk,task_id) if western_goods_utils.has_info("western_goods_act_2_task_2_init") then -- Throughout the whole task : clear the two smarts so nothing interfere with the task task_2_clear_smart_terrain("pri_a28_school") task_2_clear_smart_terrain("pri_b301") task_2_clear_smart_terrain("pri_b303") -- First stage : Get to the Outskirts, somewhere close to the zombies if tsk.stage == 0 then local zombie_1_squad_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.squads.zombie_1_spawn_data.sec) -- Spawn Zombie squad if not TASK_2_CACHE.zombie_1_spawned and not zombie_1_squad_se then local spawn_data = TASK_2_CACHE.squads.zombie_1_spawn_data zombie_1_squad_se = western_goods_utils.spawn_squad_smart(spawn_data.sec, spawn_data.smart) if zombie_1_squad_se then TASK_2_CACHE.zombie_1_spawned = true return else printf("![WG] ERROR | Tasks Act 2 | Failed to spawn %s smart:%s",spawn_data.sec, spawn_data.smart) return "fail" end end if level.name() ~= "pripyat" then return end local dist_to_zombies = zombie_1_squad_se and western_goods_utils.get_distance_sqr(alife():actor().position,zombie_1_squad_se.position) if (not dist_to_zombies) or dist_to_zombies and dist_to_zombies > CONST_TASK_2_ZOMBIE_1_DIST then return end dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 0 - Player close to Zombied squad %s", dist_to_zombies) tsk.stage = 1 end -- Second stage : Kill the zombies and get close if tsk.stage == 1 then local zombie_1_squad_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.squads.zombie_1_spawn_data.sec) -- If the zombie squad is dead, and the player is near them if not zombie_1_squad_se and western_goods_restrictors.in_bounds(db.actor, TASK_2_CACHE.restrictors.zombie) then dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 1 - First Zombied squad is dead...") local zombie_2_squad_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.squads.zombie_2_spawn_data.sec) -- Spawn Zombie squad for next stage if not TASK_2_CACHE.zombie_2_spawned and not zombie_2_squad_se then local spawn_data = TASK_2_CACHE.squads.zombie_2_spawn_data zombie_2_squad_se = western_goods_utils.spawn_squad_smart(spawn_data.sec, spawn_data.smart) if zombie_2_squad_se then TASK_2_CACHE.spawn_throttle = time_global() + CONST_SPAWN_THROTTLE TASK_2_CACHE.zombie_2_spawned = true return else printf("![WG] ERROR | Tasks Act 2 | Failed to spawn %s smart:%s",spawn_data.sec, spawn_data.smart) return "fail" end end tsk.stage = 2 end end -- Third stage : Wait for zombies to die or take part in the fight if tsk.stage == 2 then local zombie_2_squad_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.squads.zombie_2_spawn_data.sec) local army_squad_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.squads.army_spawn_data.sec) -- Spawn Army squad if TASK_2_CACHE.spawn_throttle < time_global() and not TASK_2_CACHE.army_squad_spawned and not army_squad_se then local spawn_data = TASK_2_CACHE.squads.army_spawn_data army_squad_se = western_goods_utils.spawn_squad_smart(spawn_data.sec, spawn_data.smart) if army_squad_se then TASK_2_CACHE.spawn_throttle = time_global() + CONST_SPAWN_THROTTLE TASK_2_CACHE.army_squad_spawned = true return else printf("![WG] ERROR | Tasks Act 2 | Failed to spawn %s smart:%s",spawn_data.sec, spawn_data.smart) return "fail" end end -- Spawn Army BTR if TASK_2_CACHE.spawn_throttle < time_global() and not TASK_2_CACHE.army_btr_spawned then local spawn_data = TASK_2_CACHE.objects.btr local btr_se = alife_create(spawn_data.sec, vector():set(spawn_data.x, spawn_data.y, spawn_data.z), spawn_data.lvid, spawn_data.gvid) if btr_se then btr_se.angle = vector():set(0,-1.5708,0) logic_enforcer.assign(btr_se.id,'scripts\\tasks\\veh_idle.ltx','logic','ph_car@idle') TASK_2_CACHE.spawn_throttle = time_global() + CONST_SPAWN_THROTTLE TASK_2_CACHE.army_btr_spawned = true return else printf("![WG] ERROR | Tasks Act 2 | Failed to spawn %s x:%s y:%s z:%s lvid:%s gvid:%s",spawn_data.sec, spawn_data.x, spawn_data.y, spawn_data.z, spawn_data.lvid, spawn_data.gvid) return "fail" end end -- When zombies are dead, progress task if TASK_2_CACHE.zombie_2_spawned and not zombie_2_squad_se then dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 2 - Second Zombied squad is dead...") send_dialog({ {sender="Anonymous", icon="ui_inGame2_no_data", message=western_goods_utils.get_translation("st_wg_trader_act_2_task_2_stage_2_message_1")} }) if western_goods_utils.is_player_fighting() then dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 2 - Player is fighting :\n%s", utils_data.print_table(xr_combat_ignore.fighting_with_actor_npcs, false, true)) tsk.stage = 3 else dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 2 - Player is not fighting...") tsk.stage = 4 end end end -- Fourth stage : Get to the basement of the school but kill pursuers if tsk.stage == 3 then if western_goods_utils.is_player_fighting() then local restrictor = db.zone_by_name["pri_surge_hide_b301"] local dist_to_basement = restrictor and western_goods_utils.get_distance_sqr(alife():actor().position,restrictor:position()) dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 3 - Player is fighting :\n%s", utils_data.print_table(xr_combat_ignore.fighting_with_actor_npcs, false, true)) if dist_to_basement and dist_to_basement <= CONST_TASK_2_SCHOOL_FAR and dist_to_basement >= CONST_TASK_2_SCHOOL_CLOSE then if not TASK_2_CACHE.message_school_pursuers_far_sent then send_dialog({ {sender="Anonymous", icon="ui_inGame2_no_data", message=western_goods_utils.get_translation("st_wg_trader_act_2_task_2_message_school_pursuers_far")} },true) TASK_2_CACHE.message_school_pursuers_far_sent = true end return elseif dist_to_basement and dist_to_basement < CONST_TASK_2_SCHOOL_CLOSE then send_dialog({ {sender="Anonymous", icon="ui_inGame2_no_data", message=western_goods_utils.get_translation("st_wg_trader_act_2_task_2_message_school_pursuers_close")} },true) return "fail" else TASK_2_CACHE.message_school_pursuers_far_sent = false return end else dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 3 - Player is not fighting...") TASK_2_CACHE.message_school_pursuers_far_sent = false tsk.stage = 4 end end -- Fifth stage : Get to the basement of the school without being seen if tsk.stage == 4 then local restrictor = db.zone_by_name["pri_surge_hide_b301"] if not western_goods_utils.is_player_fighting() then local dist_to_basement = restrictor and western_goods_utils.get_distance_sqr(alife():actor().position,restrictor:position()) if dist_to_basement and dist_to_basement < CONST_TASK_2_SCHOOL_CLOSE then dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 4 - Player arrived at the school dist(sqr):%s",dist_to_basement) local snork_squad_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.squads.snork_spawn_data.sec) -- Spawn snork squad if not TASK_2_CACHE.snork_spawned and not snork_squad_se then local spawn_data = TASK_2_CACHE.squads.snork_spawn_data snork_squad_se = western_goods_utils.spawn_squad_smart(spawn_data.sec, spawn_data.smart) if snork_squad_se then TASK_2_CACHE.snork_spawned = true tsk.stage = 5 return else printf("![WG] ERROR | Tasks Act 2 | Failed to spawn %s smart:%s",spawn_data.sec, spawn_data.smart) return "fail" end end end else dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 4 - Player is fighting :\n%s", utils_data.print_table(xr_combat_ignore.fighting_with_actor_npcs, false, true)) tsk.stage = 3 end end -- Sixth stage : Kill the snorks if tsk.stage == 5 then local snork_squad_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.squads.snork_spawn_data.sec) -- Dialog when player reaches school if not TASK_2_CACHE.stage_5_dialog_sent then local dialog_sender_1 = { name=alife():actor():character_name(), icon=alife():actor():character_icon()} local dialog_sender_2 = { name="Anonymous", icon="ui_inGame2_no_data"} send_dialog({ {sender=dialog_sender_1.name, icon=dialog_sender_1.icon, message=western_goods_utils.get_translation("st_wg_trader_act_2_task_2_stage_5_message_1")}, {sender=dialog_sender_2.name, icon=dialog_sender_2.icon, message=western_goods_utils.get_translation("st_wg_trader_act_2_task_2_stage_5_message_2")} }) TASK_2_CACHE.stage_5_dialog_sent = true end -- Spawn sleeping bag if not TASK_2_CACHE.sleeping_bag_spawned then local spawn_data = TASK_2_CACHE.objects.sleeping_bag local sleep_bag_se = alife_create(spawn_data.sec, vector():set(spawn_data.x, spawn_data.y, spawn_data.z), spawn_data.lvid, spawn_data.gvid) if sleep_bag_se then TASK_2_CACHE.sleeping_bag_spawned = true return else printf("![WG] ERROR | Tasks Act 2 | Failed to spawn %s x:%s y:%s z:%s lvid:%s gvid:%s",spawn_data.sec, spawn_data.x, spawn_data.y, spawn_data.z, spawn_data.lvid, spawn_data.gvid) return "fail" end end -- Fail task if player gets out of school if not western_goods_restrictors.in_bounds(db.actor, TASK_2_CACHE.restrictors.school) then dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 5 - Player left restrictor bounds...") send_dialog({ {sender="Anonymous", icon="ui_inGame2_no_data", message=western_goods_utils.get_translation("st_wg_trader_act_2_task_2_message_left_position")} },true) return "fail" end -- If the zombie squad is dead, and the player is near them if TASK_2_CACHE.snork_spawned and not snork_squad_se and western_goods_restrictors.in_bounds(db.actor, TASK_2_CACHE.restrictors.snork) then dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 5 - Snork squad is dead...") local bandit_1_squad_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.squads.bandit_1_spawn_data.sec) -- Spawn bandit squad for next stage if not TASK_2_CACHE.bandit_1_spawned and not bandit_1_squad_se then local spawn_data = TASK_2_CACHE.squads.bandit_1_spawn_data bandit_1_squad_se = western_goods_utils.spawn_squad_smart(spawn_data.sec, spawn_data.smart) if bandit_1_squad_se then TASK_2_CACHE.bandit_1_spawned = true return else printf("![WG] ERROR | Tasks Act 2 | Failed to spawn %s smart:%s",spawn_data.sec, spawn_data.smart) return "fail" end end tsk.stage = 6 end end -- Seventh stage : Kill the bandits if tsk.stage == 6 then local bandit_1_squad_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.squads.bandit_1_spawn_data.sec) -- Fail task if player gets out of school if not western_goods_restrictors.in_bounds(db.actor, TASK_2_CACHE.restrictors.school) then dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 6 - Player left restrictor bounds...") send_dialog({ {sender="Anonymous", icon="ui_inGame2_no_data", message=western_goods_utils.get_translation("st_wg_trader_act_2_task_2_message_left_position")} },true) return "fail" end -- If bandits are dead, or if they are not enemy with the player if TASK_2_CACHE.bandit_1_spawned and not bandit_1_squad_se then if not TASK_2_CACHE.stage_6_dialog_sent then dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 6 - First bandit squad is dead...") local dialog_sender_1 = { name="Anonymous", icon="ui_inGame2_no_data"} local dialog_sender_2 = { name=alife():actor():character_name(), icon=alife():actor():character_icon()} send_dialog({ {sender=dialog_sender_1.name, icon=dialog_sender_1.icon, message=western_goods_utils.get_translation("st_wg_trader_act_2_task_2_stage_6_message_1")}, {sender=dialog_sender_2.name, icon=dialog_sender_2.icon, message=western_goods_utils.get_translation("st_wg_trader_act_2_task_2_stage_6_message_2")}, {sender=dialog_sender_1.name, icon=dialog_sender_1.icon, message=western_goods_utils.get_translation("st_wg_trader_act_2_task_2_stage_6_message_3")}, {sender=dialog_sender_2.name, icon=dialog_sender_2.icon, message=western_goods_utils.get_translation("st_wg_trader_act_2_task_2_stage_6_message_4")} }) TASK_2_CACHE.progress_timer = time_global() + CONST_TASK_2_STAGE_6_PROGRESS_DELAY TASK_2_CACHE.stage_6_dialog_sent = true end if TASK_2_CACHE.progress_timer < time_global() then tsk.stage = 7 end end end -- Eighth stage : Get to the 3rd floor of the school if tsk.stage == 7 then -- When the player gets in position, progress task if western_goods_restrictors.in_bounds(db.actor, TASK_2_CACHE.restrictors.overlook) then dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 7 - Player reached third floor of school...") tsk.stage = 8 return end end -- Ninth stage : Wait for ecologs to arrive if tsk.stage == 8 then local ecolog_squad_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.squads.ecolog_spawn_data.sec) -- Spawn Ecolog squad if not TASK_2_CACHE.ecolog_spawned and not ecolog_squad_se then local spawn_data = TASK_2_CACHE.squads.ecolog_spawn_data ecolog_squad_se = western_goods_utils.spawn_squad_smart(spawn_data.sec, spawn_data.smart) if ecolog_squad_se then TASK_2_CACHE.progress_timer = time_global() + CONST_TASK_2_STAGE_8_PROGRESS_DELAY TASK_2_CACHE.ecolog_spawned = true return else printf("![WG] ERROR | Tasks Act 2 | Failed to spawn %s smart:%s",spawn_data.sec, spawn_data.smart) return "fail" end end -- If player leaves his position, fail task if not western_goods_restrictors.in_bounds(db.actor, TASK_2_CACHE.restrictors.overlook) then dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 8 - Player left restrictor bounds...") send_dialog({ {sender="Anonymous", icon="ui_inGame2_no_data", message=western_goods_utils.get_translation("st_wg_trader_act_2_task_2_message_left_position")} },true) return "fail" end -- After 45s, progress task if TASK_2_CACHE.progress_timer < time_global() then tsk.stage = 9 end end -- Tenth stage : Wait for the bandits to die if tsk.stage == 9 then local bandit_1_squad_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.squads.bandit_1_spawn_data.sec) local bandit_2_squad_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.squads.bandit_2_spawn_data.sec) -- Spawn Bandit squad if not TASK_2_CACHE.bandit_2_squad_spawned and not bandit_2_squad_se then local spawn_data = TASK_2_CACHE.squads.bandit_2_spawn_data bandit_2_squad_se = western_goods_utils.spawn_squad_smart(spawn_data.sec, spawn_data.smart) if bandit_2_squad_se then -- Dialog when bandit spawn if not TASK_2_CACHE.stage_9_dialog_sent then local dialog_sender = { name="Anonymous", icon="ui_inGame2_no_data"} send_dialog({ {sender=dialog_sender.name, icon=dialog_sender.icon, message=western_goods_utils.get_translation("st_wg_trader_act_2_task_2_stage_9_message")} }) TASK_2_CACHE.stage_9_dialog_sent = true end TASK_2_CACHE.bandit_2_autokill_timer = time_global() + CONST_TASK_2_BANDIT_AUTOKILL_DELAY TASK_2_CACHE.bandit_2_squad_spawned = true return else printf("![WG] ERROR | Tasks Act 2 | Failed to spawn %s smart:%s",spawn_data.sec, spawn_data.smart) return "fail" end end -- If player leaves his position, fail task if not western_goods_restrictors.in_bounds(db.actor, TASK_2_CACHE.restrictors.overlook) then dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 9 - Player left restrictor bounds...") send_dialog({ {sender="Anonymous", icon="ui_inGame2_no_data", message=western_goods_utils.get_translation("st_wg_trader_act_2_task_2_message_left_position")} },true) return "fail" end -- After 20s, start killing the bandits (both bandit_1 and bandit_2 squads) automatically if not TASK_2_CACHE.bandit_2_autokilled and TASK_2_CACHE.bandit_2_autokill_timer < time_global() then if bandit_1_squad_se then for npc in bandit_1_squad_se:squad_members() do local npc_obj = db.storage[npc.id] and db.storage[npc.id].object if (npc_obj and npc_obj:alive()) then western_goods_utils.next_tick(surge_manager.make_dead,npc_obj:id()) end end end if bandit_2_squad_se then for npc in bandit_2_squad_se:squad_members() do local npc_obj = db.storage[npc.id] and db.storage[npc.id].object if (npc_obj and npc_obj:alive()) then western_goods_utils.next_tick(surge_manager.make_dead,npc_obj:id()) end end dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 9 - Bandit squads auto-killed...") end TASK_2_CACHE.progress_timer = time_global() + CONST_TASK_2_STAGE_9_PROGRESS_DELAY TASK_2_CACHE.bandit_2_autokilled = true end -- When all bandits are dead, progress task if not bandit_1_squad_se and not bandit_2_squad_se and TASK_2_CACHE.progress_timer < time_global() then dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 9 - Both bandit squads are dead...") tsk.stage = 10 end end -- Eleventh stage : Take cover from the emission if tsk.stage == 10 then local ecolog_squad_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.squads.ecolog_spawn_data.sec) -- Start emission and make ecologs flee if not TASK_2_CACHE.surge_started then surge_manager.start_surge() -- Give info to link script with logic scheme western_goods_utils.give_info("western_goods_act_2_task_2_ecolog_flee") TASK_2_CACHE.ecolog_teleport_timer = time_global() + CONST_TASK_2_ECOLOG_TELEPORT_DELAY dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 10 - Emission started...") TASK_2_CACHE.surge_started = true end -- Teleport ecolog squad because stalker and pathfinding definitely don't make one... if TASK_2_CACHE.ecolog_teleport_timer < time_global() and not TASK_2_CACHE.ecolog_teleported or not ecolog_squad_se then local cond_no_rem_no_exist = not TASK_2_CACHE.old_ecolog_squad_removed and not ecolog_squad_se local cond_no_rem_exist = not TASK_2_CACHE.old_ecolog_squad_removed and ecolog_squad_se local cond_rem_no_exist = TASK_2_CACHE.old_ecolog_squad_removed and not ecolog_squad_se -- Release 'old' squad if cond_no_rem_exist then SIMBOARD:remove_squad(ecolog_squad_se) dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 10 - Ecolog squad removed...") TASK_2_CACHE.old_ecolog_squad_removed = true return end -- Create 'new' squad at new location if cond_rem_no_exist or cond_no_rem_no_exist then local spawn_data = TASK_2_CACHE.squads.ecolog_spawn_data ecolog_squad_se = western_goods_utils.spawn_squad_smart(spawn_data.sec, spawn_data.smart2) dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 10 - Ecolog squad re-added...") TASK_2_CACHE.ecolog_teleported = true return end end if TASK_2_CACHE.surge_started and surge_manager.is_finished() and TASK_2_CACHE.ecolog_teleported and ecolog_squad_se then dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 10 - Emission finished...") for member in ecolog_squad_se:squad_members() do local member_se = alife_object(member.id) if member_se:section_name() ~= "stalker_ecolog_convoy_yellow" then CreateTimeEvent("western_goods_delay_kill_squad",member.id,0,surge_manager.make_dead,member.id) end dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 10 - Trimmed Ecolog squad...") end local dialog_sender_1 = { name="Anonymous", icon="ui_inGame2_no_data"} local dialog_sender_2 = { name=alife():actor():character_name(), icon=alife():actor():character_icon()} send_dialog({ {sender=dialog_sender_1.name, icon=dialog_sender_1.icon, message=western_goods_utils.get_translation("st_wg_trader_act_2_task_2_stage_10_message_1")}, {sender=dialog_sender_2.name, icon=dialog_sender_2.icon, message=western_goods_utils.get_translation("st_wg_trader_act_2_task_2_stage_10_message_2")}, {sender=dialog_sender_1.name, icon=dialog_sender_1.icon, message=western_goods_utils.get_translation("st_wg_trader_act_2_task_2_stage_10_message_3")} }) tsk.stage = 11 end end -- Twelfth stage : Get to the yellow crewmate & kill the controller if tsk.stage == 11 then local controller_squad_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.squads.controller_spawn_data.sec) local supplies_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.main_item) -- Wait for the player to reach the KBO if not TASK_2_CACHE.kbo_room_reached then if not western_goods_restrictors.in_bounds(db.actor, TASK_2_CACHE.restrictors.kbo) then return end TASK_2_CACHE.kbo_room_reached = true western_goods_utils.give_info("western_goods_act_2_task_2_ecolog_calm") dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 11 - Calmed Yellow crewmate...") end -- Send signal to logic scheme western_goods_utils.give_info("western_goods_act_2_task_2_ecolog_calm") -- Wait for player to finish dialog with crewmate if not western_goods_utils.has_info("western_goods_ecolog_convoy_yellow_meet_over") then return end -- Spawn Controller squad if not TASK_2_CACHE.controller_squad_spawned and not controller_squad_se then local spawn_data = TASK_2_CACHE.squads.controller_spawn_data controller_squad_se = western_goods_utils.spawn_squad_smart(spawn_data.sec, spawn_data.smart) if controller_squad_se then western_goods_utils.give_info("western_goods_act_2_task_2_controller_spawned") TASK_2_CACHE.controller_squad_spawned = true return else printf("![WG] ERROR | Tasks Act 2 | Failed to spawn %s smart:%s",spawn_data.sec, spawn_data.smart) return "fail" end end -- Wait for controller to be dead if not TASK_2_CACHE.controller_squad_killed then if TASK_2_CACHE.controller_squad_spawned and controller_squad_se then return end TASK_2_CACHE.controller_squad_killed = true western_goods_utils.give_info("western_goods_act_2_task_2_controller_killed") dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 11 - Controller is dead...") end -- Send signal to logic scheme western_goods_utils.give_info("western_goods_act_2_task_2_controller_killed") -- Wait for player to finish second dialog if not western_goods_utils.has_info("western_goods_ecolog_convoy_yellow_second_meet_over") then return end local lynn_squad_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.squads.lynn_spawn_data.sec) local lynn_guards_squad_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.squads.lynn_guards_spawn_data.sec) -- Spawn Dunn Lynn squad if not TASK_2_CACHE.lynn_squad_spawned and not lynn_squad_se then local spawn_data = TASK_2_CACHE.squads.lynn_spawn_data lynn_squad_se = western_goods_utils.spawn_squad_smart(spawn_data.sec, spawn_data.smart) if lynn_squad_se then local dialog_sender_1 = { name=alife():actor():character_name(), icon=alife():actor():character_icon()} local dialog_sender_2 = { name="Anonymous", icon="ui_inGame2_no_data"} send_dialog({ {sender=dialog_sender_1.name, icon=dialog_sender_1.icon, message=western_goods_utils.get_translation("st_wg_trader_act_2_task_2_stage_11_message_1")}, {sender=dialog_sender_2.name, icon=dialog_sender_2.icon, message=western_goods_utils.get_translation("st_wg_trader_act_2_task_2_stage_11_message_2")} }) TASK_2_CACHE.lynn_squad_spawned = true return else printf("![WG] ERROR | Tasks Act 2 | Failed to spawn %s smart:%s",spawn_data.sec, spawn_data.smart) return "fail" end end -- Spawn Dunn Lynn guards squad if not TASK_2_CACHE.lynn_guards_squad_spawned and not lynn_guards_squad_se then local spawn_data = TASK_2_CACHE.squads.lynn_guards_spawn_data lynn_guards_squad_se = western_goods_utils.spawn_squad_smart(spawn_data.sec, spawn_data.smart) if lynn_guards_squad_se then TASK_2_CACHE.lynn_guards_squad_spawned = true return else printf("![WG] ERROR | Tasks Act 2 | Failed to spawn %s smart:%s",spawn_data.sec, spawn_data.smart) return "fail" end end if not (supplies_se and (supplies_se.parent_id ~= AC_ID)) then dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 11 - Player has the main item %s",supplies_se.id) tsk.stage = 13 else dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 11 - Player does not have the main item...") tsk.stage = 12 end end -- Thirteenth stage : Get the case if tsk.stage == 12 then local supplies_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.main_item) -- If the supplies cannot be found, respawn them if not supplies_se then -- Spawn the supplies, objective the player has to retrieve supplies_se = alife_create(TASK_2_CACHE.main_item, vector():set(4.8,4.6,269.3), 204172, 4926) dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 12 - Main item respawned %s", supplies_se.id) return end -- If the player picked up the supplies, progress to stage 3 if not (supplies_se and (supplies_se.parent_id ~= AC_ID)) then dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 12 - Player picked up the main item %s", supplies_se.id) tsk.stage = 13 return end end -- Thirteenth stage : Return the case if tsk.stage == 13 then local supplies_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.main_item) -- If the supplies cannot be found (which is odd), roll back to stage 12 if not supplies_se then dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 13 - Main item disappeared, rolling back to stage 12...") tsk.stage = 12 return end western_goods_utils.give_info("western_goods_act_2_task_2_ready_finished") -- If the player dropped the supplies, rollback to stage 2 if supplies_se and (supplies_se.parent_id ~= AC_ID) then dbg_printf("[WG] Tasks Act 2 | Task 2 - Stage 13 - Player dropped the main item %s", supplies_se.id) western_goods_utils.rem_info("western_goods_act_2_task_2_ready_finished") tsk.stage = 12 return end end end end -- --------------------------------------------------------------------------------------------------------------------- -- Callbacks registration -- --------------------------------------------------------------------------------------------------------------------- --- Function used to register callbacks. --- @return nil function on_game_start() RegisterScriptCallback("save_state", save_state) RegisterScriptCallback("load_state", load_state) RegisterScriptCallback("on_game_load", task_2_prefetch_models) RegisterScriptCallback("actor_on_first_update", task_2_setup) RegisterScriptCallback("actor_on_first_update", task_2_remove_dunn_lynn_after_task) end -- --------------------------------------------------------------------------------------------------------------------- -- Data persistence -- --------------------------------------------------------------------------------------------------------------------- --- Function used to store information in the save file. --- @param m_data table --- @return nil function save_state(m_data) -- Prepare save tables local TASK_1_SAVE = {} local TASK_2_SAVE = {} -- Make copies of task caches copy_table(TASK_1_SAVE, TASK_1_CACHE) copy_table(TASK_2_SAVE, TASK_2_CACHE) -- Pre-process tables TASK_2_SAVE.available_time = (TASK_2_SAVE.available_time or 0) - time_global() TASK_2_SAVE.spawn_throttle = (TASK_2_SAVE.spawn_throttle or 0) - time_global() TASK_2_SAVE.progress_timer = (TASK_2_SAVE.progress_timer or 0) - time_global() TASK_2_SAVE.bandit_2_autokill_timer = (TASK_2_SAVE.bandit_2_autokill_timer or 0) - time_global() -- Save tables m_data.wg_act_2_task_1_cache = TASK_1_SAVE m_data.wg_act_2_task_2_cache = TASK_2_SAVE -- Debug prints dbg_printf("[WG] Tasks Act 2 | Task 1 - Saved variables...\n%s",utils_data.print_table(TASK_1_SAVE, false, true)) dbg_printf("[WG] Tasks Act 2 | Task 2 - Saved variables...\n%s",utils_data.print_table(TASK_2_SAVE, false, true)) end --- Function used to load information stored in the save file. --- @param m_data table --- @return nil function load_state(m_data) -- Retrieve save tables local TASK_1_SAVE = m_data.wg_act_2_task_1_cache or {} local TASK_2_SAVE = m_data.wg_act_2_task_2_cache or {} -- Post-process tables TASK_2_SAVE.available_time = (TASK_2_SAVE.available_time or 0) + time_global() TASK_2_SAVE.spawn_throttle = (TASK_2_SAVE.spawn_throttle or 0) + time_global() TASK_2_SAVE.progress_timer = (TASK_2_SAVE.progress_timer or 0) + time_global() TASK_2_SAVE.bandit_2_autokill_timer = (TASK_2_SAVE.bandit_2_autokill_timer or 0) + time_global() -- Restore task caches copy_table(TASK_1_CACHE, TASK_1_SAVE) copy_table(TASK_2_CACHE, TASK_2_SAVE) -- Debug prints dbg_printf("[WG] Tasks Act 2 | Task 1 - Loaded variables...\n%s",utils_data.print_table(TASK_1_CACHE, false, true)) dbg_printf("[WG] Tasks Act 2 | Task 2 - Loaded variables...\n%s",utils_data.print_table(TASK_2_CACHE, false, true)) end -- --------------------------------------------------------------------------------------------------------------------- -- General functions -- --------------------------------------------------------------------------------------------------------------------- --- Function used to get a table where keys are IDs of chemical anomalies and values are boolean true. --- @return table function task_1_get_static_chemical_anomalies() local viable_anomalies = {} -- Set to false all dynamic anomalies for _,v in pairs(bind_anomaly_field.dyn_anomalies) do for id, _ in pairs(v) do viable_anomalies[id] = false end end -- Go through all IDs western_goods_utils.server_objects_iter(function(se_obj) if IsAnomaly(se_obj) then if viable_anomalies[se_obj.id] == nil then -- If the ID is unknown, then check for correct type of anomalies if string.find(se_obj:section_name(), "chemical") or string.find(se_obj:section_name(), "acidic") then viable_anomalies[se_obj.id] = true end else -- If the ID isn't nil, then it's a dynamic anomaly, we set to nil viable_anomalies[se_obj.id] = nil end end end) dbg_printf("[WG] Tasks Act 2 | Task 1 - Number of matching anomalies : %s", size_table(viable_anomalies)) return viable_anomalies end --- Function used to setup the automatic start of Act 2, Task 2. --- @return nil function task_2_setup() -- Conditions local task_1_done = western_goods_utils.has_info("western_goods_act_2_task_1_finished") local task_2_started = western_goods_utils.has_info("western_goods_act_2_task_2_active") local task_2_done = western_goods_utils.has_info("western_goods_act_2_task_2_finished") local task_opted_in = western_goods_utils.has_info("western_goods_act_2_task_2_opted_in") local timeout_ready = time_global() > (TASK_2_CACHE.available_time or 0) -- If the task is available if not task_1_done or task_2_started or task_2_done then return end -- If the task should be set up if task_opted_in and timeout_ready then local delay = math.random(CONST_TASK_2_SOFT_DELAY_MIN, CONST_TASK_2_SOFT_DELAY_MAX) CreateTimeEvent("western_goods_setup_task", "act_2_task_2", delay, function () dbg_printf("[WG] Tasks Act 2 | Task 2 - Task set up, time before start : %s",delay) -- Give the task some time after loading the save act_2_task_2_start(western_goods_utils.server_object_by_sid("cit_killers_merc_trader_stalker")) -- Send a message to the player send_dialog({ {sender="Anonymous", icon="ui_inGame2_no_data", message=western_goods_utils.get_translation("st_wg_act_2_task_2_job_descr")} }, true) return true end) else dbg_printf("[WG] Tasks Act 2 | Task 2 - What's missing? task_opted_in:%s - timeout_ready:%s (%s/%s)", task_opted_in, timeout_ready, time_global(), TASK_2_CACHE.available_time) end end --- Function used to clean up the squads after Act 2, Task 2. --- @return nil function task_2_clean_squads() western_goods_utils.server_objects_iter(function(se_obj) for _,spawn_data in pairs(TASK_2_CACHE.squads) do local is_from_task_2 = se_obj:section_name() == spawn_data.sec local is_not_dunn_lynn = se_obj:section_name() ~= TASK_2_CACHE.squads.lynn_spawn_data.sec local is_not_dunn_guards = se_obj:section_name() ~= TASK_2_CACHE.squads.lynn_guards_spawn_data.sec if is_from_task_2 and is_not_dunn_lynn and is_not_dunn_guards then dbg_printf("[WG] Tasks Act 2 | Task 2 - Removing '%s'",se_obj:name()) SIMBOARD:remove_squad(se_obj) end end end) dbg_printf("[WG] Tasks Act 2 | Task 2 - Removed squads ...") end --- Function used to mark on the PDA the enemies currently fighting with the player. --- @param marked_enemies table --- @return nil function task_2_mark_current_enemies(marked_enemies) for _,id in pairs(marked_enemies) do level.map_remove_object_spot(id, "anomaly_thermal") end empty_table(marked_enemies) for id,_ in pairs(xr_combat_ignore.fighting_with_actor_npcs) do if not western_goods_utils.table_contains(marked_enemies, id) then table.insert(marked_enemies, id) level.map_add_object_spot_ser(id, "anomaly_thermal", "Enemy") end end end --- Function called from dialog to give the player the quest item during Act 2, Task 2. --- @param first_speaker game_object --- @param second_speaker game_object --- @return nil function task_2_give_quest_item(first_speaker, second_speaker) dialogs.relocate_item_section_to_actor(first_speaker, second_speaker, "wg_act_2_task_2_quest_item") end --- Function used to desspawn Dunn Lynn and his guards after Act 2, Task 2 is done and the player is on another level. --- @return nil function task_2_remove_dunn_lynn_after_task() if western_goods_utils.has_info("western_goods_act_2_task_2_finished") and not (TASK_2_CACHE.lynn_squad_removed and TASK_2_CACHE.lynn_guards_squad_removed) then local lynn_squad_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.squads.lynn_spawn_data.sec) local lynn_guards_squad_se = western_goods_utils.server_object_by_sid(TASK_2_CACHE.squads.lynn_guards_spawn_data.sec) dbg_printf("[WG] Tasks Act 2 | Task 2 - Trying to remove Lynn and/or his guards") -- Remove Dunn Lynn squad if it wasn't removed yet if not TASK_2_CACHE.lynn_squad_removed and lynn_squad_se and not simulation_objects.is_on_the_same_level(alife():actor(), lynn_squad_se) then SIMBOARD:remove_squad(lynn_squad_se) dbg_printf("[WG] Tasks Act 2 | Task 2 - Removed '%s'", lynn_squad_se:section_name()) end -- Remove Dunn Lynn guards squad if it wasn't removed yet if not TASK_2_CACHE.lynn_guards_squad_removed and lynn_guards_squad_se and not simulation_objects.is_on_the_same_level(alife():actor(), lynn_guards_squad_se)then SIMBOARD:remove_squad(lynn_guards_squad_se) dbg_printf("[WG] Tasks Act 2 | Task 2 - Removed '%s'", lynn_guards_squad_se:section_name()) end end end --- Function used to clear all the squads at a given smart terrain, except the squads defined in TASK_2_CACHE. --- @param smart_name string --- @return nil function task_2_clear_smart_terrain(smart_name) local smart_clear = SIMBOARD:get_smart_by_name(smart_name) for id,bool in pairs(SIMBOARD.smarts[smart_clear.id].squads) do local can_stay = false local squad = alife_object(id) if squad then for _,spawn_data in pairs(TASK_2_CACHE.squads) do if squad:section_name() == spawn_data.sec then can_stay = true end end local is_another_map = not simulation_objects.is_on_the_same_level(alife():actor(), squad) local distance = western_goods_utils.get_distance_sqr(alife():actor().position,squad.position) if not can_stay and (is_another_map or distance > CONST_TASK_2_CLEAR_SMART_DIST) then SIMBOARD:remove_squad(squad) dbg_printf("[WG] Tasks Act 2 | Task 2 - Released random squad '%s' from smart '%s' (dist(sqr):%s)",squad:name(),smart_clear:name(),distance) end end end end --- Function used to front load some models if Act 2, Task 2 is active. --- @return nil function task_2_prefetch_models() local tsk = task_manager.get_task_manager().task_info["western_goods_act_2_task_2"] if tsk and level.name() == "pripyat" then game.prefetch_model([[dynamics\vehicles\veh_btr\veh_btr_u_01.ogf]]) end end