Divergent/mods/iTheon New Tasks/gamedata/scripts/tasks_mirage.script

336 lines
11 KiB
Plaintext
Raw Normal View History

2024-03-17 20:18:03 -04:00
local nta_utils = new_tasks_addon_tasks_utils
local iron_forest_smart = "zat_b100"
local mirage_section = "mirage_stalker"
-- Keep it as local var instead of state var so it can restart after the save reload
local explosion_loop_started = false
local state = {
mirages = {},
sidor_id = nil,
table_id = nil,
sidor_controllers = {},
burer_id = nil,
burer_killed = false,
}
function save_state(mdata)
mdata.mirage_task_data = state
end
function load_state(mdata)
if mdata.mirage_task_data then
state = mdata.mirage_task_data
end
end
task_status_functor.mirage_task_status_functor = function(tsk,task_id)
if not (db.actor and tsk) then return end
local stage = tsk.stage
if stage == 0 then
if
is_zaton()
and db.actor:position():distance_to(mirage_configs.first_spawn_activator) < mirage_configs.first_spawn_activation_distance
then
spawn_mirages(mirage_configs.first_spawn_spots)
tsk.stage = 1
end
if is_zaton() and db.actor:position():distance_to(mirage_configs.second_spawn_activator) < mirage_configs.second_spawn_activation_distance then
teleport()
end
end
if stage == 1 then
if db.actor:position():distance_to(mirage_configs.second_spawn_activator) < mirage_configs.second_spawn_activation_distance then
spawn_mirages(mirage_configs.second_spawn_spots)
tsk.stage = 2
end
end
if stage == 2 then
if db.actor:position():distance_to(mirage_configs.third_spawn_activator) < mirage_configs.third_spawn_activation_distance then
spawn_mirages(mirage_configs.third_spawn_spots)
tsk.stage = 3
end
end
if stage == 3 then
if db.actor:position():distance_to(mirage_configs.sidor_spawn_activator) < mirage_configs.sidor_spawn_activation_distance then
spawn_sidor(tsk)
tsk.stage = 4
end
end
if stage == 5 then
if cotnrollers_dead() then
spawn_burer()
tsk.stage = 6
end
end
if stage == 6 then
if not explosion_loop_started then
explosion_loop_started = true
start_explosions_loop()
end
if state.burer_killed then
tsk.stage = 7
end
end
if stage > 0 and stage < 4 then
update_mirages_visual()
give_mirages_weapon()
end
if stage > 0 and stage < 7 then
if db.actor:position():distance_to(mirage_configs.teleport_activator_center_point) > mirage_configs.teleport_activation_distance then
teleport()
end
end
end
function spawn_mirages(spots)
for _, spot in ipairs(spots) do
local se_id = nta_utils.spawn_helper(spot, mirage_section)
logic_enforcer.assign(se_id,'scripts\\mirage_beh.ltx','logic', 'beh@mirage')
local mirage_se = alife_object(se_id)
local actor_se = alife():actor()
mirage_se:set_rank(actor_se:rank())
mirage_se:set_character_name(actor_se:character_name())
state.mirages[se_id] = true
end
end
function spawn_sidor(tsk)
state.sidor_id = nta_utils.spawn_helper(mirage_configs.sidor_spawn_config)
state.table_id = nta_utils.spawn_helper(mirage_configs.med_table_spawn_config)
local teleport_snd = xr_sound.get_safe_sound_object("new_tasks_addon\\teleport_work_old")
teleport_snd:play(level.object_by_id(state.sidor_id), 0, sound_object.s2d)
local particles_appear = particles_object("anomaly2\\teleport_out_00")
particles_appear:play_at_pos(alife_object(state.sidor_id).position)
local speech_snd = xr_sound.get_safe_sound_object("characters_voice\\scenario\\trader\\trader_bye_6")
speech_snd:play(level.object_by_id(state.sidor_id), 1, sound_object.s2d)
CreateTimeEvent(0,"sidor_despawn", (speech_snd:length() / 1000) + 1.2, function ()
despawn_sidor()
spawn_sidor_controllers()
tsk.stage = 5
return true
end)
end
function despawn_sidor()
local teleport_snd = xr_sound.get_safe_sound_object("new_tasks_addon\\teleport_work_old")
teleport_snd:play(level.object_by_id(state.sidor_id), 0, sound_object.s2d)
local particles_disappear = particles_object("anomaly2\\teleport_tear")
particles_disappear:play_at_pos(alife_object(state.sidor_id).position)
safe_release_manager.release({ id = state.sidor_id })
safe_release_manager.release({ id = state.table_id })
end
function spawn_sidor_controllers()
state.sidor_controllers[nta_utils.spawn_helper(mirage_configs.sidor_controller_1_spawn_config)] = true
state.sidor_controllers[nta_utils.spawn_helper(mirage_configs.sidor_controller_2_spawn_config)] = true
end
function spawn_burer()
state.burer_id = nta_utils.spawn_helper(mirage_configs.burer_spawn_config)
local earthquake_snd = xr_sound.get_safe_sound_object("new_tasks_addon\\earthquake_old")
earthquake_snd:play(db.actor, 0, sound_object.s2d)
local brain_death_snd = xr_sound.get_safe_sound_object("new_tasks_addon\\x16_brain_death_old")
brain_death_snd:play(db.actor, 0, sound_object.s2d)
nta_utils.earthquake_screen_effect_strong(8)
end
function is_zaton()
return level.name() == "zaton"
end
task_functor.mirage_task_target_functor = function(task_id,field,p,tsk)
if not (db.actor and tsk) then return nil end
local stage = tsk.stage
if stage == 7 then
return tsk.task_giver_id
end
return SIMBOARD:get_smart_by_name(iron_forest_smart).id
end
xr_effects.mirage_cleanup = function()
state = {
mirages = {},
sidor_id = nil,
table_id = nil,
sidor_controllers = {},
burer_id = nil,
burer_killed = false
}
end
function teleport ()
nta_utils.teleport_visual()
db.actor:set_actor_position(SIMBOARD:get_smart_by_name(iron_forest_smart).position)
end
function destroy_mirage(id)
local particles = particles_object("monsters\\polter_death_00")
local se_obj = alife_object(id)
particles:play_at_pos(se_obj.position)
local snd = xr_sound.get_safe_sound_object("monsters\\poltergeist\\death_1")
snd:play(level.object_by_id(id), 0, sound_object.s2d)
nta_utils.hide_through_teleport(id)
end
function give_mirages_weapon()
local active_item = db.actor:active_item()
local sec
if not actor_holds_valid_item() then
sec = "wpn_pkp" -- Give mirage a Pecheneg if player has no active weapons or a melee weapon :)
else
sec = active_item:section()
end
for id, alive in pairs(state.mirages) do
local mirage = level.object_by_id(id)
-- Double check required - a mirage can have an entry in the table, but not be spawned yet at this moment (exists as se)
-- It might also happen that an id of already dead mirage is reused and another item will be targeted by selector instead,
-- hence the alive check required
if alive and mirage then
local mirage = level.object_by_id(id)
local has_weapon = false
local function iterate(_, item)
if item:section() == sec then
has_weapon = true
end
end
mirage:iterate_inventory(iterate)
-- Don't give copies of the weapon to avoid rotating between copies on selection
if not has_weapon then
alife_create_item(sec, mirage)
end
end
end
end
function update_mirages_visual()
for id, alive in pairs(state.mirages) do
local mirage = level.object_by_id(id)
if alive and mirage then
mirage:set_visual_name(get_actor_visual())
end
end
end
function actor_holds_valid_item()
local item = db.actor:active_item()
return item and IsWeapon(item) and not nta_utils.is_melee(item)
end
function get_actor_visual()
return str_explode(db.actor:get_visual_name(), "%.")[1] -- Remove the '.ogf' for visuals armor stat mapping to work properly
end
function cotnrollers_dead()
for _, is_alive in pairs(state.sidor_controllers) do
if is_alive then return false end
end
return true
end
local loop_i = 1
local explosion_position = nil
local explosion_lvid = nil
local explosion_gvid = nil
local anomaly_particles = particles_object("anomaly2\\bold_idle")
local anomaly_sound = xr_sound.get_safe_sound_object("anomaly\\gravi_rumble1")
local explosion_planned = false
function start_explosions_loop()
explosion_position = db.actor:position()
explosion_lvid = db.actor:level_vertex_id()
explosion_gvid = db.actor:game_vertex_id()
explosion_planned = true
CreateTimeEvent(0,"mirage_explosion_loop_" .. loop_i,6, function ()
loop_i = loop_i + 1
anomaly_particles:stop()
anomaly_sound:stop()
nta_utils.spawn_at_position(
"immediate_anomaly_explosion",
explosion_position,
explosion_lvid,
explosion_gvid,
false
)
explosion_planned = false
if not state.burer_killed then
start_explosions_loop()
end
return true
end)
end
function on_death_callback(npc)
local id = npc:id()
if state.mirages[id] then
destroy_mirage(id)
state.mirages[id] = false
end
if state.sidor_controllers[id] then
destroy_mirage(id)
state.sidor_controllers[id] = false
end
if id == state.burer_id then
state.burer_killed = true
end
end
function npc_on_choose_weapon(npc, curr_wpn, flags)
if state.mirages[npc:id()] then
local function iterate(_, item)
if not actor_holds_valid_item() then
if item:section() == "wpn_pkp" then
flags.gun_id = item:id()
return
end
end
if (actor_holds_valid_item() and item:section() == db.actor:active_item():section()) then
flags.gun_id = item:id()
return
end
end
npc:iterate_inventory(iterate)
end
end
function actor_on_update()
if explosion_planned then
if not anomaly_particles:playing() then
anomaly_particles:play_at_pos(explosion_position)
end
if not anomaly_sound:playing() then
anomaly_sound:play_at_pos(db.actor, explosion_position)
end
end
end
function on_game_start()
RegisterScriptCallback("save_state",save_state)
RegisterScriptCallback("load_state",load_state)
RegisterScriptCallback("npc_on_death_callback", on_death_callback)
RegisterScriptCallback("monster_on_death_callback", on_death_callback)
RegisterScriptCallback("npc_on_choose_weapon", npc_on_choose_weapon)
RegisterScriptCallback("actor_on_update", actor_on_update)
end