fix_broken_emissions = false --[[ If your emissions are broken from the vanilla bug, follow these steps: 1. Edit the first line of this file from false to true like so: fix_broken_emissions = true 2. Load your save with this mod active 3. Make a separate save 4. Edit true back to false at the top of this script --]] -- ======================================================================================= -- Created by tdef -- Last modification: 2023/06/23 -- Modified by Catspaw: fix emissions no longer occurring after failing barrier task -- Defend the barrier from monolith/zombies invasion -- ======================================================================================= local ti2ta = utils_data.CTime_to_table local ta2ti = utils_data.CTime_from_table local send_tip = dynamic_news_helper.send_tip local MONO_SQUADS = 6 --10 local ZOMB_SQUADS = 8 --15 local STARTING_SQUADS = 3 --5 local CHAT_DEL = 5 -- seconds local DEBUG = false function printl(frmt, ...) if DEBUG then printf(strformat('!tasks_defense: ' .. frmt, ...)) end end ------------------------- --2d distance between 2 vectors --a,b : vectors, either object.position or vector():set(x,y,z) ------------------------- local function dist2d_sqr( a, b ) return (b.x-a.x)^2 + (b.z-a.z)^2 end ------------------------- --2d distance between 2 objects --a,b : id of objects ------------------------- local function dist2d_id_sqr(a,b) local sim = alife() local ao = sim:object(a) local bo = sim:object(b) -- printf("ao %s bo %s",ao,bo) -- printf("ao.pos %s bo.pos %s",ao.position,bo.position) if ao and bo then return dist2d_sqr(ao.position,bo.position) else return nil end end if DEBUG then add_console_command('start_barrier_monolith', function() db.actor:give_info_portion("barrier_defense_monolith_announced") end) add_console_command('start_barrier_zombie', function() db.actor:give_info_portion("barrier_defense_zombie_announced") end) end --============================================< Callbacks >============================================-- local MDATA function save_state(mdata) mdata.defense_task_data = MDATA end function load_state(mdata) if mdata.defense_task_data and not fix_broken_emissions then printf('loading mdata') MDATA = mdata.defense_task_data --utils_data.print_table(MDATA) else printf('new mdata') MDATA = {} MDATA.barrier_defense_monolith = {} MDATA.barrier_defense_zombie = {} MDATA.allow_surge = true end end function f_chatter(opponent, stage) printl('f_chatter(%s, %s)',opponent,stage) local name, icon = get_random_stalker_chatter() if name and icon then send_tip(game.translate_string("barrier_defense_"..opponent.."_chatter_"..math.random(1,7)),name,nil,nil, icon, nil, 'npc') else printf('error: get_random_stalker_chatter returned nil') end if stage < 3 then stage = stage + 1 CreateTimeEvent(math.random(999),"chatter",CHAT_DEL,f_chatter,opponent,stage) end return true end function f_expire(x) xr_effects.barrier_defense_news(nil,nil,{'end',x}) return true end function actor_on_first_update() -- needs to be delayed because it runs before loading actually finishes so players dont see first message local delay = 20 -- seconds printl('actor_on_first_update') if barrier_defense_available('barrier_defense_monolith') then -- if DEBUG then db.actor:give_info_portion("TRIGGERED_M") end CreateTimeEvent(0,"give_info",delay,function() db.actor:give_info_portion("barrier_defense_monolith_announced") send_tip(game.translate_string("barrier_defense_monolith_announce"),game.translate_string("mil_freedom_leader_name"),nil,nil, 'ui_inGame2_lukash', nil, 'npc') MDATA.barrier_defense_monolith.last_msg_time = ti2ta(game.get_game_time()) CreateTimeEvent(math.random(999),"chatter",CHAT_DEL,f_chatter,'monolith',0) return true end) elseif barrier_defense_available('barrier_defense_zombie') then -- if DEBUG then db.actor:give_info_portion("TRIGGERED_Z") end CreateTimeEvent(0,"give_info",delay,function() db.actor:give_info_portion("barrier_defense_zombie_announced") send_tip(game.translate_string("barrier_defense_zombie_announce"),game.translate_string("mil_freedom_leader_name"),nil,nil, 'ui_inGame2_lukash', nil, 'npc') MDATA.barrier_defense_zombie.last_msg_time = ti2ta(game.get_game_time()) CreateTimeEvent(math.random(999),"chatter",CHAT_DEL,f_chatter,'zombie',0) return true end) end if db.actor:has_info('barrier_defense_monolith_announced') then local last1 = MDATA.barrier_defense_monolith.last_msg_time and ta2ti(MDATA.barrier_defense_monolith.last_msg_time) local delta1 = last1 and game.get_game_time():diffSec(last1) / (3600 * 24) or 100 if delta1 > 1 then db.actor:disable_info_portion('barrier_defense_monolith_announced') CreateTimeEvent(math.random(999),"chatter",delay,f_expire,'monolith') end end if db.actor:has_info('barrier_defense_zombie_announced') then local last2 = MDATA.barrier_defense_zombie.last_msg_time and ta2ti(MDATA.barrier_defense_zombie.last_msg_time) local delta2 = last2 and game.get_game_time():diffSec(last2) / (3600 * 24) or 100 if delta2 > 1 then db.actor:disable_info_portion('barrier_defense_zombie_announced') CreateTimeEvent(math.random(999),"chatter",delay,f_expire,'zombie') end end end function skip_surge(t) if MDATA and MDATA.allow_surge == false then t.allow = false end end function on_game_start() RegisterScriptCallback("actor_on_first_update",actor_on_first_update) RegisterScriptCallback("save_state",save_state) RegisterScriptCallback("load_state",load_state) RegisterScriptCallback("on_before_surge",skip_surge) RegisterScriptCallback("on_before_psi_storm",skip_surge) if DEBUG then RegisterScriptCallback("npc_on_update",npc_on_update) end end function npc_on_update(npc) local be = npc:best_enemy() if be and be:id() == 0 then npc:kill(npc) end end --============================================< Logic >============================================-- local RAND_S function get_random_stalker_chatter() if not RAND_S then RAND_S = {} for i=1,65534 do local se_obj = alife_object(i) if se_obj and IsStalker(nil, se_obj:clsid()) and se_obj:alive() and not game_relations.is_factions_enemies(se_obj:community(),"freedom") and string.find(se_obj:name(),'default') and not (se_obj:community() == 'bandit') and not (se_obj:community() == 'trader') then table.insert(RAND_S,{game.translate_string(se_obj:character_name()),se_obj:character_icon()}) if #RAND_S > 10 then break end end end end if #RAND_S < 1 then return nil, nil end local i = math.random(#RAND_S) local x = table.remove(RAND_S, i) return x[1],x[2] end function get_closest_freedom() local mind = 9999999 local ret = nil for i,v in ipairs(db.OnlineStalkers) do local se_obj = alife_object(v) if se_obj and IsStalker(nil, se_obj:clsid()) and se_obj:alive() and se_obj:community() == 'freedom' and string.find(se_obj:name(),'default') then local dist = dist2d_id_sqr(0,v) if dist < mind then mind = dist ret = v end end end if ret then local se_obj = alife_object(ret) return game.translate_string(se_obj:character_name()),se_obj:character_icon() end return nil, nil end function mid_news(str) local name, icon = get_closest_freedom() if name and icon then send_tip(game.translate_string("barrier_defense_"..str.."_midchatter"),name,nil,nil, icon, nil, 'npc') else printf('get_closest_freedom returned nil') end end function barrier_defense_available(tid) printl('barrier_defense_available(%s)',tid) -- dont crash if not db.actor then return false end -- dont trigger if already had message if db.actor:has_info('barrier_defense_monolith_announced') then return false end if db.actor:has_info('barrier_defense_zombie_announced') then return false end -- dont trigger if quest already started if axr_task_manager.ongoing_tasks['barrier_defense_monolith'] then return false end if axr_task_manager.ongoing_tasks['barrier_defense_zombie'] then return false end -- printl('barrier_defense_available(%s) AAA',tid) -- dont trigger if hostile to freedom -- should check vs default_comm in gameplay_disguise but what if disguise is disabled? it it nil? or what? if game_relations.is_factions_enemies(character_community(db.actor),"freedom") then return false end if not MDATA then return false end -- printl('barrier_defense_available(%s) BBB',tid) -- dont trigger if game started less than a week ago if game.get_game_time():diffSec(level.get_start_time()) < 3600 * 24 * 7 and not DEBUG then return false end -- printl('barrier_defense_available(%s) CCC',tid) -- dont trigger if less than a week passed since last defense, either joined or skipped local last1 = MDATA.barrier_defense_monolith.last_msg_time and ta2ti(MDATA.barrier_defense_monolith.last_msg_time) -- printl('barrier_defense_available(%s) CCC1',tid) local last2 = MDATA.barrier_defense_zombie.last_msg_time and ta2ti(MDATA.barrier_defense_zombie.last_msg_time) -- printl('barrier_defense_available(%s) CCC2',tid) local delta1 = last1 and game.get_game_time():diffSec(last1) / (3600 * 24) or 100 -- printl('barrier_defense_available(%s) CCC3',tid) local delta2 = last2 and game.get_game_time():diffSec(last2) / (3600 * 24) or 100 -- printl('barrier_defense_available(%s) CCC4',tid) local delta = math.min(delta1, delta2) -- printf('delta - %s', delta) if delta < 7 then printl('not enough time since last mission') return false end -- printl('barrier_defense_available(%s) DDD',tid) -- dont trigger if last check was done less than a day ago to prevent changing level until triggered -- printf('MDATA[tid].last_check_time -> %s',MDATA[tid].last_check_time) if MDATA[tid].last_check_time then -- printf('checking MDATA[tid].last_check_time') local lct = ta2ti(MDATA[tid].last_check_time) local delta = game.get_game_time():diffSec(lct) / (3600 * 24) -- printf('delta2 - %s', delta2) if delta < 1 then printf('not enough time since last check') return false end end MDATA[tid].last_check_time = ti2ta(game.get_game_time()) -- printl('barrier_defense_available(%s) EEE',tid) -- local function pret(x) -- -- printf('chance: %s',x) -- return x -- end local chance = DEBUG and -1 or ((tid == 'barrier_defense_monolith') and 0.75 or 0.5) -- local ret = pret(math.random()) > chance local ret = math.random() > chance -- printf('ret %s', ret) return ret end function barrier_defense_monolith_start(a,b) -- dialog -- printf('barrier_defense_monolith_start') local gid = a:id()> 0 and a:id() or b:id() MDATA.barrier_defense_monolith.giver = gid MDATA.barrier_defense_monolith.defenders = {} MDATA.barrier_defense_monolith.attackers = {} MDATA.barrier_defense_monolith.squads_left = MONO_SQUADS local sq sq = SIMBOARD:create_squad(SIMBOARD.smarts_by_names['mil_smart_terrain_7_8'],"barrier_defense_squad") MDATA.barrier_defense_monolith.defenders[sq.id] = true -- printf('def: %s',sq.id) sq = SIMBOARD:create_squad(SIMBOARD.smarts_by_names['mil_smart_terrain_7_8'],"barrier_defense_squad") MDATA.barrier_defense_monolith.defenders[sq.id] = true -- printf('def: %s',sq.id) -- printf('init done, start task') MDATA.allow_surge = false task_manager.get_task_manager():give_task('barrier_defense_monolith') end function barrier_defense_zombie_start(a,b) -- dialog -- printf('barrier_defense_zombie_start') local gid = a:id()> 0 and a:id() or b:id() MDATA.barrier_defense_zombie.giver = gid MDATA.barrier_defense_zombie.defenders = {} MDATA.barrier_defense_zombie.attackers = {} MDATA.barrier_defense_zombie.squads_left = ZOMB_SQUADS local sq sq = SIMBOARD:create_squad(SIMBOARD.smarts_by_names['mil_smart_terrain_7_8'],"barrier_defense_squad") MDATA.barrier_defense_zombie.defenders[sq.id] = true -- printf('def: %s',sq.id) local spawn_l = 'barrier_spawn_'..math.random(1,9) sq = SIMBOARD:create_squad_at_named_location(spawn_l,'barrier_zombie_squad') local spawn_l = 'barrier_spawn_'..math.random(1,9) sq = SIMBOARD:create_squad_at_named_location(spawn_l,'barrier_zombie_squad') -- printf('init done, start task') MDATA.allow_surge = false task_manager.get_task_manager():give_task('barrier_defense_zombie') end function barrier_defense_monolith_complete(a,b) MDATA.allow_surge = true task_manager.get_task_manager():set_task_completed('barrier_defense_monolith') xr_effects.barrier_defense_clean_flags(nil,nil,{'monolith'}) local r = {} r.joint = 1 local w1, w2 = db.actor:item_in_slot(2), db.actor:item_in_slot(3) if w1 then local s = ini_sys:r_string_ex(w1:section(), "ammo_class") if (s) then local ammos = str_explode(s,",") r[ammos[1]] = math.random(4,6) end end if w2 then local s = ini_sys:r_string_ex(w2:section(), "ammo_class") if (s) then local ammos = str_explode(s,",") r[ammos[1]] = math.random(4,6) end end local sim = alife() for k,v in pairs(r) do news_manager.relocate_item(db.actor, 'in', k, v) for i=1,v do alife_create_item(k, db.actor) end end local amt = 40000 news_manager.relocate_money(db.actor, 'in', amt) db.actor:give_money(amt) end function barrier_defense_zombie_complete(a,b) MDATA.allow_surge = true task_manager.get_task_manager():set_task_completed('barrier_defense_zombie') xr_effects.barrier_defense_clean_flags(nil,nil,{'zombie'}) local r = {} r.vodka = 1 r.drug_psy_blockade = 2 local w1, w2 = db.actor:item_in_slot(2), db.actor:item_in_slot(3) if w1 then local s = ini_sys:r_string_ex(w1:section(), "ammo_class") if (s) then local ammos = str_explode(s,",") r[ammos[1]] = math.random(2,4) end end if w2 then local s = ini_sys:r_string_ex(w2:section(), "ammo_class") if (s) then local ammos = str_explode(s,",") r[ammos[1]] = math.random(2,4) end end local sim = alife() for k,v in pairs(r) do news_manager.relocate_item(db.actor, 'in', k, v) for i=1,v do alife_create_item(k, db.actor) end end local amt = 25000 news_manager.relocate_money(db.actor, 'in', amt) db.actor:give_money(amt) end function task_functor.barrier_defense_target_functor(task_id) if db.actor and MDATA[task_id] and MDATA[task_id].giver and (db.actor:has_info('barrier_defense_monolith_finished') or db.actor:has_info('barrier_defense_zombie_finished')) then return MDATA[task_id].giver end local dist = 999999 local sm = SIMBOARD.smarts_by_names['mil_smart_terrain_3_8'] local ret = sm.id if MDATA[task_id] and MDATA[task_id].attackers then for k,v in pairs(MDATA[task_id].attackers) do if alife_object(k) then local newd = dist2d_id_sqr(0,k) if newd < dist then ret = k dist = newd end end end end return ret end --============================================< Conditions >============================================-- function xr_conditions.all_attackers_dead(a,b,c) -- printf('all_attackers_dead start') local tid = c and c[1] if not tid then printf('all_attackers_dead: called without arguments') return end if not MDATA[tid] then return false end for k,v in pairs(MDATA[tid].attackers) do local so = alife_object(k) if so then -- printf('all_attackers_dead end false') return false end end -- printf('all_attackers_dead end true') return true end function xr_conditions.distance_to_barrier(a,b,c) -- mil_smart_terrain_3_8 -- printf('distance_to_barrier %s, %s', c[1], c[2]) if not (c and c[1] and c[2] and tonumber(c[2])) then printf('distance_to_barrier: missing or malformed arguments, must be ("le" or "ge":number)') return end local sm = SIMBOARD.smarts_by_names['mil_smart_terrain_3_8'] local dist = dist2d_id_sqr(0,sm.id) local d = tonumber(c[2]) ^ 2 if c[1] == 'le' then return dist <= d end if c[1] == 'ge' then return dist >= d end printf('distance_to_barrier: unknown comparison requested, got %s expected "ge" or "le"', c[1]) end --============================================< Effects >============================================-- function xr_effects.barrier_defense_first_spawn(a,b,c) -- barrier_monolith_squad -- barrier_spawn -- barrier_defense_monolith -- printf('barrier_defense_monolith_first_spawn') if not (c and c[1] and (c[1]=='zombie' or c[1]=='monolith')) then printf('barrier_defense_first_spawn: missing or unexpected arguments, must be "zombie" or "monolith"') return end local squad_s = 'barrier_'..c[1]..'_squad' for i=1,STARTING_SQUADS do local spawn_l = 'barrier_spawn_'..math.random(1,9) local sq = SIMBOARD:create_squad_at_named_location(spawn_l,squad_s) -- sq.rush_to_target = true MDATA['barrier_defense_'..c[1]].attackers[sq.id] = true end -- printf('barrier_defense_monolith_first_spawn end') end function xr_effects.on_barrier_defense_squad_death(a,b,c) local tid = c[1] if c[1] == 'barrier_defense_freedom' then sq = SIMBOARD:create_squad(SIMBOARD.smarts_by_names['mil_smart_terrain_4_7'],"barrier_defense_squad") if sq and sq:commander_id() and alife_object(sq:commander_id()) then local se_obj = alife_object(sq:commander_id()) local name, icon = game.translate_string(se_obj:character_name()), se_obj:character_icon() send_tip(game.translate_string('barrier_defense_reinforcements_'..math.random(1,4)),name,nil,nil, icon, nil, 'npc') else printf('squad commander still doesnt exist?') end -- MDATA.barrier_defense_zombie.defenders[sq.id] = true else local squad_s = tid == "barrier_defense_monolith" and 'barrier_monolith_squad' or 'barrier_zombie_squad' -- printf('on_death_sq triggered') local count = MDATA[tid].squads_left if count > 0 then if DEBUG then printf('left %s', count) end local spawn = 'barrier_spawn_'..math.random(1,9) local rpg = (count < 3 and tid == "barrier_defense_monolith") and '_rpg' or '' local sq = SIMBOARD:create_squad_at_named_location(spawn, squad_s..rpg) MDATA[tid].attackers[sq.id] = true if count == 2 and tid=='barrier_defense_monolith' then mid_news('monolith') end if count == 5 and tid=='barrier_defense_zombie' then mid_news('zombie') end MDATA[tid].squads_left = count - 1 end end end function xr_effects.barrier_defense_clean_flags(a,b,c) -- barrier_defense_ db.actor:disable_info_portion('barrier_defense_'..c[1]..'_first_spawn') db.actor:disable_info_portion('barrier_defense_'..c[1]..'_finished') db.actor:disable_info_portion('barrier_defense_'..c[1]..'_announced') end function xr_effects.barrier_defense_news(a,b,c) if c and c[1] and c[2] and (c[2] == 'monolith' or c[2] == 'zombie') then local x = c[1] if x == 'start' then send_tip(game.translate_string('barrier_defense_'..c[2]..'_start_announce'),game.translate_string("mil_freedom_leader_name"),nil,nil, 'ui_inGame2_lukash', nil, 'npc') return end if x == 'end' then MDATA.allow_surge = true -- Added by Catspaw to fix broken emissions on barrier task fail send_tip(game.translate_string('barrier_defense_'..c[2]..'_end_announce'),game.translate_string("mil_freedom_leader_name"),nil,nil, 'ui_inGame2_lukash', nil, 'npc') return end if x == 'escape' then MDATA.allow_surge = true -- Added by Catspaw to fix broken emissions on barrier task fail send_tip(game.translate_string('barrier_defense_'..c[2]..'_run_announce'),game.translate_string("mil_freedom_leader_name"),nil,nil, 'ui_inGame2_lukash', nil, 'npc') return end printf('barrier_defense_news: first argument must be "start", "end" or "escape"') else printf('barrier_defense_news: missing or wrong arguments, must be ([start,end,escape]:[zombie,monolith])') end end