336 lines
11 KiB
Plaintext
336 lines
11 KiB
Plaintext
|
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
|