--[[
m24.lua
SR3 Mission Script
DATE: 09/02/10
AUTHOR: John Karczewski
]]--
-- Debug flags --
-- Tweakable Parameters --
NUM_TERMINALS_SINGLE = 3
NUM_TERMINALS_COOP = 3 -- Coop uses the full 5
MINIMUM_DISTANCE_FROM_DOOR = 23
TIME_TO_GET_AWAY_FROM_DOOR_MS = 10000 -- milliseconds
MAGMA_CRYSTAL_RESPAWN_TIME = 60 -- seconds
LAIR_NPC_RESPAWN_TIME = 10.0 -- seconds
-- Marsquakes
M24_MARSQUAKE_SHAKE = "m24_marsquake"
M24_MIN_MQ_INTERVAL = 10 -- Minimum time between marsquakes
M24_MAX_MQ_INTERVAL = 25 -- Max time between marsquakes
M24_MIN_MQ_INTENSITY = 0.2
M24_MAX_MQ_INTENSITY = 6.0
-- Marsquakes
M24_MIN_BIG_MQ_INTERVAL = 5 -- Minimum time between marsquakes
M24_MAX_BIG_MQ_INTERVAL = 8 -- Max time between marsquakes
M24_MIN_BIG_MQ_INTENSITY = 0.2
M24_MAX_BIG_MQ_INTENSITY = 6.0
-- Homie "death" nOOOOO!
M24_HOMIE_DEATH_ANIM_DIST = 4.0
M24_HOMIE_DEATH_PLAYER_ANIM = "m24 dramatic no"
M24_HOMIE_DEATH_DELAY = 2.0
M24_HOMIE_DEATH_ANIM_DELAY = 3.0
M24_GAT_CHATTER_MIN_DELAY = 10.0
M24_GAT_CHATTER_MAX_DELAY = 20.0
M24_GAT_CHATTER_INITIAL_DELAY = 25.0
M24_deaths = {
{
pos = "Homie_Wounded_Nav_01",
},
{
pos = "Homie_Wounded_Nav_02",
},
{
pos = "Homie_Wounded_Nav_03",
},
}
-- Groups --
M24_groups = {
homies = {
name = "Group_Homies",
members = { "Homie_Pierce", "Homie_Shaundi", "Homie_Gat" },
spawned = false,
gat = "Homie_Gat",
pierce = "Homie_Pierce",
shaundi = "Homie_Shaundi",
},
luchadores = {
name = "Group_Forcefield_01",
-- two member lists for chatter
members_luchadore = { "Lucha_NPC 002", "Lucha_NPC 015", "Lucha_NPC 016", "Lucha_NPC 017", "Lucha_NPC 025", "Lucha_NPC 026", "Lucha_NPC 024", "Lucha_NPC 027", "Lucha_NPC 028", },
members_saints = { "Saint_NPC 002", "Saint_NPC 005", "Saint_NPC 006", "Saint_NPC 004" },
spawned = false,
},
-- groups spawned after each terminal
terminal = {
{
name = "Group_Forcefield_02",
members = { "Lucha_NPC 001", "Lucha_NPC 005", "Lucha_NPC 006", "Lucha_NPC 018", "Lucha_NPC 019", "Lucha_NPC 020", "Lucha_NPC 021", "Lucha_NPC 022", "Lucha_NPC 019<001>", "Lucha_NPC 019<002>", "Lucha_NPC 019<003>", "Lucha_NPC 019<004>", "Lucha_NPC 001<001>",
"Lucha_NPC 007<001>", "Lucha_NPC 007<002>", "Lucha_NPC 007<003>", "Lucha_NPC 007<004>", "Lucha_NPC 007<005>", "Lucha_NPC 007<006>", "Lucha_NPC 007<007>", "Lucha_NPC 007<008>", "Lucha_NPC 007<009>", "Lucha_NPC 007<010>", "Lucha_NPC 006<001>", "Lucha_NPC 006<002>", "Lucha_NPC 001<004>",
"Lucha_NPC 001<005>", "Lucha_NPC 001<006>", "Lucha_NPC 001<007>", "Lucha_NPC 022<002>", },
spawned = false,
},
{
name = "Group_Forcefield_03",
members = { "Lucha_NPC 004", "Lucha_NPC 023<001>", "Lucha_NPC 023<002>", "Lucha_NPC 023<003>", "Lucha_NPC 023<004>", "Lucha_NPC 023<005>", "Lucha_NPC 023<007>", "Lucha_NPC 023<008>", "Lucha_NPC 023<010>", "Lucha_NPC 023<011>", "Lucha_NPC 023<012>", "Lucha_NPC 004<001>", "Lucha_NPC 023<013>", },
spawned = false,
},
{
name = "Group_Gate",
members = { "Lucha_NPC 010", "Lucha_NPC 011", "Lucha_NPC 012", "Lucha_NPC 013", "Lucha_NPC 014", "Lucha_NPC 009<002>", "Lucha_NPC 009<003>", "Lucha_NPC 009<004>", "Lucha_NPC 009<005>", "Lucha_NPC 011<001>", "Lucha_NPC 012<001>", },
spawned = false,
},
},
saints = {
name = "Group_Saints",
members = { "Saint_NPC 001", "Saint_NPC 002", "Saint_NPC 003", "Saint_NPC 004", "Saint_NPC 005",
"Saint_NPC 006", "Saint_NPC 007", "Saint_NPC 008"},
spawned = false,
},
bridge = {
name = "Group_Bridge",
members = { "Mars_Bridge_NPC 001", "Mars_Bridge_NPC 002", "Mars_Bridge_NPC 003", "Mars_Bridge_NPC 004", "Mars_Bridge_NPC 005",
"Mars_Bridge_NPC 006", "Mars_Bridge_NPC 007", "Mars_Bridge_NPC 008", "Mars_Bridge_NPC 009", "Mars_Bridge_NPC 010",
"Mars_Bridge_NPC 011", "Mars_Bridge_NPC 012", "Mars_Bridge_NPC 013", "Mars_Bridge_NPC 014", "Mars_Bridge_NPC 015",
"Mars_Bridge_NPC 016", "Mars_Bridge_NPC 017", "Mars_Bridge_NPC 018", "Mars_Bridge_NPC 019", "Mars_Bridge_NPC 020" },
spawned = false,
},
killbane = {
name = "Group_Killbane",
members = { "Killbane" },
spawned = false,
killbane = "Killbane",
},
cte_npcs = {
name = "Group_CTE_NPC",
},
cte_killbane = {
name = "Group_CTE_Killbane",
},
}
-- Navpoints --
M24_navpoints = {
local_start = "Local_Player_Start",
remote_start = "Remote_Player_Start",
local_exit = "M24_Local_Player_Exit",
remote_exit = "M24_Remote_Player_Exit",
bomb_door = "Bomb_Door_Navpoint",
bridge_start = "Bridge_Start_Navpoint",
bridge_start_coop = "Bridge_Start_Navpoint_a",
killbane_fight_start = "Killbane_Fight_Start_Navpoint",
killbane_fight_start_coop = "Killbane_Fight_Start_Navpoint_a",
terminals = {"Terminal_Navpoint_01", "Terminal_Navpoint_02", "Terminal_Navpoint_03"},
terminals_coop = {"Terminal_Navpoint_01a", "Terminal_Navpoint_02a", "Terminal_Navpoint_03a"},
homie_start_navs = {
[ "m24_terminal2" ] = { nil, "Nav Shaundi Terminal 1", "Nav Gat Terminal 1" },
[ "m24_terminal3" ] = { nil, nil, "Nav Gat Terminal 2" },
},
lava_height = "Lava_Height_Navpoint",
bombs = {
-- "bomb_nav_01",
"bomb_nav_02",
"bomb_nav_03",
-- "bomb_nav_04"
},
stumbles = { "nav stumble dir <001>", "nav stumble dir <002>", "nav stumble dir <003>", "nav stumble dir <004>" },
}
-- Triggers --
M24_triggers = {
terminal_triggers = { "Terminal_Trigger 001", "Terminal_Trigger 002", "Terminal_Trigger 003", "Terminal_Trigger 004", "Terminal_Trigger 005" },
junction_triggers = {
-- "Junction_Trigger 001",
"Junction_Trigger 002",
"Junction_Trigger 003",
-- "Junction_Trigger 004",
},
end_of_bridge = "End_of_bridge_trigger",
lava_death = "Lava_Death_Trigger",
jet_convo = "Trigger_Jet_Wreckage_Convo",
active_breadcrumb = "",
}
M24_forcefields = {
{
door = "m24_forcefield_door_01",
emitter = "force_field_0",
},
{
door = "m24_forcefield_door_02",
emitter = "force_field_1",
},
{
door = "m24_forcefield_door_03",
emitter = "force_field_2",
},
}
M24_terminal_breadcrumbs = {
{nil},
{"breadcrumb_trigger_01", "breadcrumb_trigger_02"},
{"breadcrumb_trigger_03"},
}
-- Characters --
M24_characters = {
killbane = M24_groups.killbane.killbane
}
-- Vehicles --
M24_vehicles = {
"Mars_Rover_01",
"Mars_Rover_02",
"Mars_Rover_06",
"Mars_Rover_07",
"Mars_Rover_09",
"Mars_Rover_10",
"Mars_Rover_11",
}
-- Mesh Movers --
M24_movers = {
magma_crystals = { "magma_crystal 001", "magma_crystal 002", "magma_crystal 003", "magma_crystal 004",
"magma_crystal 005", "magma_crystal 006", "magma_crystal 007", "magma_crystal 008",
"magma_crystal 009", "magma_crystal 010", "magma_crystal 011", "magma_crystal 012",
"magma_crystal 013", "magma_crystal 014", "magma_crystal 015", "magma_crystal 016",
"magma_crystal 017", "magma_crystal 018", "magma_crystal 019", "magma_crystal 020",
"magma_crystal 021", "magma_crystal 022", "magma_crystal 023", "magma_crystal 024",
"magma_crystal 025", "magma_crystal 026", "magma_crystal 027", "magma_crystal 028",
"magma_crystal 029" }
}
-- Text --
M24_text = {
failure_death = {
[M24_groups.homies.pierce] = "m07_failure_pierce_died",
[M24_groups.homies.shaundi] = "m07_failure_shaundi_died",
[M24_groups.homies.gat] = "m01_failure_gat_died",
},
}
-- Threads --
M24_threads = {
convo_handle = INVALID_THREAD_HANDLE,
mover_respawn_handles = { INVALID_THREAD_HANDLE, INVALID_THREAD_HANDLE, INVALID_THREAD_HANDLE, INVALID_THREAD_HANDLE,
INVALID_THREAD_HANDLE, INVALID_THREAD_HANDLE, INVALID_THREAD_HANDLE, INVALID_THREAD_HANDLE,
INVALID_THREAD_HANDLE, INVALID_THREAD_HANDLE, INVALID_THREAD_HANDLE, INVALID_THREAD_HANDLE,
INVALID_THREAD_HANDLE, INVALID_THREAD_HANDLE, INVALID_THREAD_HANDLE, INVALID_THREAD_HANDLE,
INVALID_THREAD_HANDLE, INVALID_THREAD_HANDLE, INVALID_THREAD_HANDLE, INVALID_THREAD_HANDLE,
INVALID_THREAD_HANDLE, INVALID_THREAD_HANDLE, INVALID_THREAD_HANDLE, INVALID_THREAD_HANDLE,
INVALID_THREAD_HANDLE, INVALID_THREAD_HANDLE, INVALID_THREAD_HANDLE, INVALID_THREAD_HANDLE,
INVALID_THREAD_HANDLE },
marsquake_handle = INVALID_THREAD_HANDLE,
homie_death_thread_handle = INVALID_THREAD_HANDLE,
vehicle_lava_handle = INVALID_THREAD_HANDLE,
gat_chatter_handle = INVALID_THREAD_HANDLE,
breadcrumb_thread = INVALID_THREAD_HANDLE,
}
-- Checkpoints --
M24_checkpoints = {
start = {
name = MISSION_START_CHECKPOINT
},
terminal1 = {
name = "m24_terminal2"
},
terminal2 = {
name = "m24_terminal3"
},
junction = {
name = "m24_bombs"
},
cross_bridge = {
name = "m24_bridge"
},
kill_killbane = {
name = "m24_killbane"
}
}
-- Continuous Spawn Groups --
M24_spawn_groups = {
mars = {
saints = { "Group_CS_Saint 001", "Group_CS_Saint 002", "Group_CS_Saint 003", "Group_CS_Saint 004" },
luchadores = { "Group_CS_Lucha 001", "Group_CS_Lucha 002", "Group_CS_Lucha 003", "Group_CS_Lucha 004" }
},
lair = { "Group_Lair_Reinforcement 001", "Group_Lair_Reinforcement 002",
"Group_Lair_Reinforcement 003", "Group_Lair_Reinforcement 004" }
}
-- Spawn Regions --
M24_spawn_regions = {
mars = { "Script_Spawn_Region 001", "Script_Spawn_Region 002", "Script_Spawn_Region 003", "Script_Spawn_Region 004",
"Script_Spawn_Region 005", "Script_Spawn_Region 006", "Script_Spawn_Region 007", "Script_Spawn_Region 008",
"Script_Spawn_Region 009", "Script_Spawn_Region 010", "Script_Spawn_Region 011" },
lair = { "Lair_Spawn_Region 001", "Lair_Spawn_Region 002", "Lair_Spawn_Region 003", "Lair_Spawn_Region 004" }
}
-- Cutscenes --
CUTSCENE_MISSION_INTRO = "22_out2"
CUTSCENE_MISSION_CTE_BRIDGE_SETUP = "m24_cte_01"
CUTSCENE_MISSION_CTE_LAIR_SETUP = "m24_cte_02"
CUTSCENE_MISSION_OUTRO = "24_Out"
-- Conversations --
M24_conversations = {
mission_start = {
name = "M24_Mission_Start",
handle = INVALID_CONVERSATION_HANDLE,
played = false,
direct = false,
},
all_terminals_down = {
name = "M24_All_Terminals_Down",
handle = INVALID_CONVERSATION_HANDLE,
played = false,
direct = false,
},
cross_bridge = {
name = "M24_Gate_Down",
handle = INVALID_CONVERSATION_HANDLE,
played = false,
direct = false,
},
enter_lair = {
name = "M24_Enter_Lair",
handle = INVALID_CONVERSATION_HANDLE,
played = false,
direct = false,
},
final_battle_begins = {
name = "M24_Final_Battle_Begins",
handle = INVALID_CONVERSATION_HANDLE,
played = false,
direct = false,
},
final_battle_1 = {
name = "M24_Killbane_Battle_1",
handle = INVALID_CONVERSATION_HANDLE,
played = false,
direct = false,
},
final_battle_2 = {
name = "M24_Killbane_Battle_2",
handle = INVALID_CONVERSATION_HANDLE,
played = false,
direct = false,
},
final_battle_3 = {
name = "M24_Final_Battle",
handle = INVALID_CONVERSATION_HANDLE,
played = false,
direct = false,
},
}
M24_dialog_lines = {
gat_chatter = {
"M24_Movie_Lines_01",
"M24_Movie_Lines_02",
"M24_Movie_Lines_03",
"M24_Movie_Lines_04",
"M24_Movie_Lines_06",
"M24_Movie_Lines_07",
"M24_Movie_Lines_08",
"M24_Movie_Lines_09",
"M24_Movie_Lines_10",
"M24_Movie_Lines_13",
"M24_Movie_Lines_14",
"M24_Movie_Lines_15",
"M24_Movie_Lines_16",
"M24_Movie_Lines_17",
"M24_Movie_Lines_18",
"M24_Movie_Lines_19",
},
gat_death_lines = {
"M24_Movie_Lines_18",
"M24_Movie_Lines_11",
nil,
},
homie_death_lines = {
"M24_Pierce_Dies_01",
"M24_Shaundi_Dies_01",
"M24_Movie_Lines_05",
},
homie_hurt_lines = {
"M24_Pierce_Hit",
"M24_Shaundi_Hit",
"M24_Movie_Lines_04",
},
terminal_down = {
"M24_First_Terminal_Down",
"M24_Second_Terminal_Down",
nil,
},
wrecked_jets = "M24_Crashed_Jets",
escape_blast_area = "M24_Escaping_Blast_Area",
}
M24_personas = {
kinzie = {
name = "Kinzie",
persona_id = INVALID_PERSONA_HANDLE,
},
}
-- Other --
in_coop = false
objective_reached = false
door_exploded = false
junctions_remaining = #M24_triggers.junction_triggers
-- Todo: get the extra terminals for co-op working again.
terminals_remaining = NUM_TERMINALS_SINGLE
terminals_completed = 0
M24_killbane_reacting = false
M24_killbane_dead = false
M24_PLAYER_PISTOL = "Pistol-Gang"
M24_PLAYER_SHOTGUN = "Shotgun-Gang"
M24_PLAYER_RIFLE = "Rifle-STAG"
M24_PLAYER_SMG = "SMG-Gang"
PLAYER_LOADOUT = { M24_PLAYER_PISTOL, M24_PLAYER_SHOTGUN, M24_PLAYER_SMG }
M24_active_conversation = {
convo_table = nil
}
-- Killbane fight parameters
MAGMA_CRYSTAL_PERCENT_DAMAGE = 10 -- will inflict 10% of killbane's health; player/npc dmg will be different
HP_PERCENT_FOR_FIRST_DOWN = 70
HP_PERCENT_FOR_SECOND_DOWN = 40
HP_PERCENT_FOR_FINAL_DOWN = 12
M24_KILLBANE_EXPLOSION_STUN_TIME_MS = 3000
M24_status = {
homie_is_dying = { },
}
M24_general_chatterers = { }
-- *************************
--
-- Standard functions
--
-- *************************
function m24_clear_breadcrumb()
if (M24_triggers.active_breadcrumb ~= "") then
on_trigger("", M24_triggers.active_breadcrumb)
trigger_enable(M24_triggers.active_breadcrumb, false)
marker_remove_trigger(M24_triggers.active_breadcrumb, SYNC_ALL)
end
M24_triggers.active_breadcrumb = ""
end
function m24_on_breadcrumb_reached()
m24_clear_breadcrumb()
end
function m24_set_breadcrumb(trig_name)
m24_clear_breadcrumb()
trigger_enable(trig_name, true)
marker_add_trigger(trig_name, MINIMAP_ICON_LOCATION, INGAME_EFFECT_CHECKPOINT, OI_ASSET_LOCATION, OI_FLAGS_LOCATION, SYNC_ALL)
on_trigger("m24_on_breadcrumb_reached", trig_name)
M24_triggers.active_breadcrumb = trig_name
end
-- This is the primary entry point for the mission, and is responsible for starting up the mission
-- at the specified checkpoint.
-- CALLED FROM CODE
--
-- m24_checkpoint: The checkpoint the mission should begin at
-- is_restart: TRUE if the mission is restarting, FALSE otherwise
--
function m24_start(m24_checkpoint, is_restart)
-- Make sure the screen is completly faded out
mission_start_fade_out(0.0)
in_coop = coop_is_active()
city_zone_swap( "Mars", true )
local terminal_checkpoint = false
if (m24_checkpoint == M24_checkpoints.start.name) then
if (is_restart == false) then
-- First time playing mission
cutscene_play(CUTSCENE_MISSION_INTRO, nil, { M24_navpoints.local_start, M24_navpoints.remote_start } )
else
teleport_coop( M24_navpoints.local_start, M24_navpoints.remote_start )
end
end
-- Handle mission initialization for the current checkpoint
m24_initialize(m24_checkpoint)
-- post voice event to mute non-modulated voice
audio_object_post_event("Mission_VO_Override")
m24_setup_mission_surface(m24_checkpoint)
if m24_checkpoint == M24_checkpoints.kill_killbane.name then
m24_cutscene_killbane()
end
-- Start fading in
mission_start_fade_in()
-- Run the mission from the current checkpoint
m24_run(m24_checkpoint)
end
-- This is the primary function responsible for running the entire mission from start to finish.
--
-- first_checkpoint: The first checkpoint to begin running the mission at
--
function m24_run(first_checkpoint)
local current_checkpoint = first_checkpoint
-- Run the mission from the beginning
if current_checkpoint == M24_checkpoints.start.name then
character_ragdoll_set_last_resort_position(M24_navpoints.local_start)
-- All terminals down, play convo.
m24_play_conversation(M24_conversations.mission_start, 1.0)
m24_hack_terminal(1)
current_checkpoint = M24_checkpoints.terminal1.name
mission_set_checkpoint(M24_checkpoints.terminal1.name)
end
-- First terminal complete
if current_checkpoint == M24_checkpoints.terminal1.name then
character_ragdoll_set_last_resort_position(M24_navpoints.terminals[1])
m24_hack_terminal(2)
current_checkpoint = M24_checkpoints.terminal2.name
mission_set_checkpoint(M24_checkpoints.terminal2.name)
end
-- Second terminal complete
if current_checkpoint == M24_checkpoints.terminal2.name then
character_ragdoll_set_last_resort_position(M24_navpoints.terminals[2])
m24_hack_terminal(3)
m24_hack_terminal(4)
m24_hack_terminal(5)
-- Spawn bridge forces
group_create(M24_groups.bridge.name, true)
M24_groups.bridge.spawned = true
m24_add_chatterers( M24_groups.bridge.members, { "M24_Attack" } )
-- All terminals down, play convo.
m24_play_conversation(M24_conversations.all_terminals_down, 1.0)
current_checkpoint = M24_checkpoints.junction.name
mission_set_checkpoint(M24_checkpoints.junction.name)
end
-- Third terminal complete
if current_checkpoint == M24_checkpoints.junction.name then
character_ragdoll_set_last_resort_position(M24_navpoints.terminals[3])
m24_remove_forcefields()
m24_process_junction_boxes()
m24_door_blown_cutscene()
--destroy all the terminal groups. we won't see them anymore
m24_cleanup_terminal_groups()
current_checkpoint = M24_checkpoints.cross_bridge.name
mission_set_checkpoint(M24_checkpoints.cross_bridge.name)
end
if current_checkpoint == M24_checkpoints.cross_bridge.name then
character_ragdoll_set_last_resort_position(M24_navpoints.bridge_start)
m24_remove_forcefields()
m24_bridge_sequence()
current_checkpoint = M24_checkpoints.kill_killbane.name
mission_set_checkpoint(M24_checkpoints.kill_killbane.name)
m24_cutscene_killbane()
fade_in(0.5)
fade_in_block()
end
if current_checkpoint == M24_checkpoints.kill_killbane.name then
character_ragdoll_set_last_resort_position(M24_navpoints.killbane_fight_start)
m24_remove_forcefields()
m24_kill_killbane_sequence()
end
mission_end_success("m24", CUTSCENE_MISSION_OUTRO, {M24_navpoints.local_exit, M24_navpoints.remote_exit} )
end
function m24_cleanup_terminal_groups()
if (group_is_loaded(M24_groups.luchadores.name)) then
group_destroy(M24_groups.luchadores.name)
M24_groups.luchadores.spawned = false
m24_remove_chatterers( M24_groups.luchadores.members_saints )
m24_remove_chatterers( M24_groups.luchadores.members_luchadore )
end
-- destroy the terminal groups
for i, group in pairs(M24_groups.terminal) do
if (group_is_loaded(group.name)) then
group_destroy(group.name)
group.spawned = false
m24_remove_chatterers( group.members )
end
end
end
function m24_release_terminal_groups()
if (group_is_loaded(M24_groups.luchadores.name)) then
release_to_world(M24_groups.luchadores.name)
M24_groups.luchadores.spawned = false
m24_remove_chatterers( M24_groups.luchadores.members_saints )
m24_remove_chatterers( M24_groups.luchadores.members_luchadore )
end
-- release the terminal groups
for i, group in pairs(M24_groups.terminal) do
if (group_is_loaded(group.name)) then
release_to_world(group.name)
group.spawned = false
m24_remove_chatterers( group.members )
end
end
end
-- This is the primary function responsible for cleaning up the entire mission
-- CALLED FROM CODE (+++MUST RETURN IMMEDIATLY+++)
--
function m24_cleanup()
-- stop muting voice
audio_object_post_event("Mission_VO_Override_Stop")
-- unregister the dismissal callbacks.
if group_is_loaded(M24_groups.homies.name) then
for i, homie in pairs(M24_groups.homies.members) do
on_dismiss("", homie)
on_death("", homie)
end
end
-- despawn the groups
if group_is_loaded( M24_groups.saints ) then
group_destroy(M24_groups.saints)
M24_groups.saints.spawned = false
end
if group_is_loaded( M24_groups.luchadores ) then
group_destroy(M24_groups.luchadores)
M24_groups.luchadores.spawned = false
end
if group_is_loaded( M24_groups.homies ) then
group_destroy(M24_groups.homies)
M24_groups.homies.spawned = false
end
m24_cleanup_terminal_groups()
-- Remove costumes
customization_item_revert()
hud_timer_set(1, 0, "")
hud_timer_hide(1, true)
hud_timer_stop(1)
objective_text_clear(0)
on_take_damage("", M24_characters.killbane)
inv_remove_temp_loadout(PLAYER_LOADOUT)
-- Remove the stag rifle.
inv_weapon_remove_temporary(LOCAL_PLAYER, M24_PLAYER_RIFLE)
inv_weapon_remove_temporary(REMOTE_PLAYER, M24_PLAYER_RIFLE)
cleanup_groups(M24_groups)
m24_cleanup_conversations()
m24_cleanup_personas()
m24_cleanup_triggers(M24_triggers)
m24_cleanup_threads(M24_threads)
m24_cleanup_movers(M24_movers)
m24_cleanup_continuous_spawn_groups(M24_spawn_groups)
m24_cleanup_continuous_spawn_regions(M24_spawn_regions)
boss_battle_mars_killbane_active(DISABLE)
qte_cleanup()
party_allow_max_followers(false)
inv_weapon_disable_all_slots(false)
city_zone_swap( "Mars", false )
-- Stop using the mission specified ragdoll get up last resort.
character_ragdoll_clear_last_resort_position()
-- for some reason the player exits the mission wearing a backpack/parachute
-- we'll just put a stop to that right here
player_parachute_wear_backpack( LOCAL_PLAYER, false )
character_set_never_catch_fire( LOCAL_PLAYER, false )
if coop_is_active() then
character_set_never_catch_fire( REMOTE_PLAYER, false )
player_parachute_wear_backpack( REMOTE_PLAYER, false )
end
player_grenades_enable()
end
-- Called when the mission has ended with success
-- CALLED FROM CODE (+++MUST RETURN IMMEDIATLY+++)
--
function m24_success()
--[[ INSERT ANY MISSION SPECIFIC SUCCESS STUFF ]]--
--'Win the Game' World State Changes
local skip_teleport = true
m23_m24_coop_skip(skip_teleport) -- do the stuff we'd do in the skip case
end
-- *************************
--
-- Local functions
--
-- *************************
-- Initialize the mission for the specified checkpoint
--
-- checkpoint: Checkpoint to initialize the mission to
--
function m24_initialize(checkpoint)
-- Dress up players
customization_outfit_wear("Lets Pretend Male A")
-- Set the mission author
set_mission_author("Mike Slone")
-- Common initialization
m24_initialize_common()
if checkpoint ~= M24_checkpoints.terminal2 and checkpoint ~= M24_checkpoints.junction then
trigger_enable(M24_triggers.jet_convo, true)
on_trigger("m24_jet_convo_callback", M24_triggers.jet_convo)
end
m24_checkpoint_teleport(checkpoint, false)
end
function m24_forcefield_hide(ff)
mesh_mover_hide(ff.door)
audio_ambient_emitter_stop(ff.emitter)
end
function m24_remove_forcefields()
for i, forcefield in pairs(M24_forcefields) do
m24_forcefield_hide(forcefield)
end
end
-- Sets up all the npcs, continuous spawns, and homies that are present during the surface portion of the mission
function m24_setup_mission_surface(checkpoint)
-- Spawn ambient homies
if checkpoint == M24_checkpoints.start.name or checkpoint == M24_checkpoints.terminal1.name or checkpoint == M24_checkpoints.terminal2.name then
group_create(M24_groups.homies, true)
M24_groups.homies.spawned = true
end
local start_nav = ""
-- Spawn the appropriate homies
local num_homies = 0
if checkpoint == M24_checkpoints.start.name then
num_homies = 3
group_create(M24_groups.saints, true)
group_create(M24_groups.luchadores, true)
m24_add_chatterers( M24_groups.saints.members, { "M24_Mars_Saints", "M24_Lava" } )
m24_add_chatterers( M24_groups.luchadores.members_saints, { "M24_Mars_Saints", "M24_Lava" } )
m24_add_chatterers( M24_groups.luchadores.members_luchadore, { "M24_Attack" } )
M24_groups.saints.spawned = true
M24_groups.luchadores.spawned = true
end
if checkpoint == M24_checkpoints.terminal1.name then
num_homies = 2
start_nav = M24_navpoints.terminals[1]
group_create(M24_groups.terminal[1].name, true)
M24_groups.terminal[1].spawned = true
m24_add_chatterers( M24_groups.terminal[1].members, { "M24_Attack" } )
end
if checkpoint == M24_checkpoints.terminal2.name then
num_homies = 1
start_nav = M24_navpoints.terminals[2]
group_create(M24_groups.terminal[2].name, true)
M24_groups.terminal[2].spawned = true
m24_add_chatterers( M24_groups.terminal[2].members, { "M24_Attack" } )
end
if checkpoint == M24_checkpoints.junction.name then
start_nav = M24_navpoints.terminals[3]
group_create(M24_groups.terminal[3].name, true)
M24_groups.terminal[3].spawned = true
m24_add_chatterers( M24_groups.terminal[3].members, { "M24_Attack" } )
end
if (checkpoint == M24_checkpoints.junction.name or checkpoint == M24_checkpoints.cross_bridge.name) then
-- Spawn bridge forces
group_create(M24_groups.bridge.name, true)
M24_groups.bridge.spawned = true
m24_add_chatterers( M24_groups.bridge.members, { "M24_Attack" } )
end
-- Either add or kill each of the homies.
if M24_groups.homies.spawned then
for i, homie in pairs(M24_groups.homies.members) do
local local_homie = M24_groups.homies.members[4 - i]
if i <= num_homies then
-- teleport before adding to party
if M24_navpoints.homie_start_navs[ checkpoint ] ~= nil and M24_navpoints.homie_start_navs[ checkpoint ][ 4 - i ] ~= nil then
teleport( local_homie, M24_navpoints.homie_start_navs[ checkpoint ][ 4 - i ] )
end
thread_yield()
party_add_ignore_limits(local_homie)
follower_make_independent(local_homie, false)
character_prevent_explosion_fling(local_homie, true)
turn_invulnerable(local_homie)
follower_set_can_abandon(local_homie, true)
on_dismiss("m24_failure_dismissed_cb", local_homie)
on_death("m24_failure_died_cb", local_homie)
else
-- teleport them to their corresponding trigger and kill them.
teleport(local_homie, M24_deaths[4 - i].pos)
character_kill(local_homie)
end
end
if num_homies >= 1 then
M24_threads.gat_chatter_handle = thread_new("m24_gat_chatter_thread", 3 - num_homies)
end
end
local bridge_or_later = (checkpoint == M24_checkpoints.cross_bridge.name) or (checkpoint == M24_checkpoints.kill_killbane.name)
if not bridge_or_later then
-- Enable the spawn regions and spawn groups
m24_start_continuous_spawn_groups(M24_spawn_groups.mars)
m24_enable_continuous_spawn_regions(M24_spawn_regions.mars)
-- Turn off all the radios
for i, vehicle in pairs(M24_vehicles) do
if (vehicle_exists(vehicle)) then
radio_block(vehicle)
end
end
end
end
-- Sequence where the players must hack terminals before proceeding to the first door
function m24_hack_terminal(terminal_num)
-- Hide all previous terminals
for i, forcefield in pairs(M24_forcefields) do
if i < terminal_num then
m24_forcefield_hide(forcefield)
end
end
objective_text_clear(0)
objective_text( 0, "M24_OBJ_DISABLE_SECURITY_NETWORK", "", "", SYNC_ALL, OI_ASSET_USE)
-- Reduce the number of terminals to activate for single player
local terminals_total = NUM_TERMINALS_SINGLE
if in_coop then
terminals_total = NUM_TERMINALS_COOP
end
-- If we are in single player, we only want to activate a certain number of terminals
if terminal_num <= terminals_total then
-- wait at each breadcrumb.
for i, breadcrumb in pairs(M24_terminal_breadcrumbs[terminal_num]) do
m24_set_breadcrumb(breadcrumb)
while (M24_triggers.active_breadcrumb ~= "") do
thread_yield()
end
end
local trigger = M24_triggers.terminal_triggers[terminal_num]
trigger_enable(trigger, true)
on_trigger("m24_terminal_hack_cb", trigger)
marker_add_trigger(trigger, MINIMAP_ICON_USE, INGAME_EFFECT_CHECKPOINT, OI_ASSET_USE, OI_FLAGS_LOCATION, SYNC_ALL)
-- set up the character who will be killed
if M24_groups.homies.members[terminal_num] ~= nil then
local char_to_kill = M24_groups.homies.members[terminal_num]
M24_threads.homie_death_thread_handle = thread_new("m24_homie_death_thread", char_to_kill)
end
while terminals_completed < terminal_num do
thread_yield()
end
-- Hide the forcefield door, kill a homie, say a line, if there is one.
if (M24_forcefields[terminal_num] ~= nil) then
m24_forcefield_hide(M24_forcefields[terminal_num])
-- play forcefield power down event, 3D. Use emitter as location.
audio_object_post_event("m24_forcefield_pwr_dwn", nil, nil, M24_forcefields[terminal_num].emitter)
-- TODO: does this belong? it might overlap audio from the death thread.
if M24_dialog_lines.terminal_down[terminal_num] ~= nil then
audio_play_persona_line(LOCAL_PLAYER, M24_dialog_lines.terminal_down[terminal_num])
end
end
end
local next_group = M24_groups.terminal[terminal_num]
-- release the previous groups before creating the next one.
m24_release_terminal_groups()
-- create the next group
if (next_group ~= nil and next_group.name ~= "") then
group_create(next_group.name)
next_group.spawned = true
end
-- wait for homie death scene to finish
while M24_threads.homie_death_thread_handle ~= INVALID_THREAD_HANDLE do
thread_yield()
end
end
-- Sequence where the player must goto the 4 junction boxes and plant explosives
function m24_process_junction_boxes()
objective_text_clear(0)
objective_text( 0, "m24_obj_goto_killbane_lair", "", "", SYNC_ALL, OI_ASSET_LOCATION )
-- set the breadcrumb and wait till you get there.
m24_set_breadcrumb("breadcrumb_trigger_04")
while (M24_triggers.active_breadcrumb ~= "") do
thread_yield()
end
objective_text( 0, "m24_obj_plant_charges", "", "", SYNC_ALL, OI_ASSET_USE )
for i, trigger in pairs(M24_triggers.junction_triggers) do
trigger_enable(trigger, true)
on_trigger("m24_junction_bomb_cb", trigger)
marker_add_trigger(trigger, MINIMAP_ICON_USE, INGAME_EFFECT_CHECKPOINT, OI_ASSET_USE, OI_FLAGS_LOCATION, SYNC_ALL)
end
while junctions_remaining > 0 do
thread_yield()
end
--message("The player yells to get away from the door!", 3)
delay(2)
objective_text_clear(0)
objective_text( 0, "m24_obj_run_for_cover", "", "", SYNC_ALL, OI_ASSET_LOCATION )
-- The door will explode
hud_timer_set(1, TIME_TO_GET_AWAY_FROM_DOOR_MS, "m24_door_explosion_cb")
audio_play_persona_line( LOCAL_PLAYER, M24_dialog_lines.escape_blast_area )
-- Wait until the door has exploded or the player has gotten out of the blast radius
local players_are_safe = m24_check_player_distance_to_door()
while (not door_exploded) and (not players_are_safe) do
players_are_safe = m24_check_player_distance_to_door()
thread_yield()
end
hud_timer_stop(1)
-- Clean up all the continuous spawn stuff
m24_cleanup_continuous_spawn_groups(M24_spawn_groups.mars)
m24_cleanup_continuous_spawn_regions(M24_spawn_regions.mars)
-- Clean up all of the stuff from this part of the mission
party_dismiss_all()
if (M24_groups.saints.spawned) then
group_destroy(M24_groups.saints)
M24_groups.saints.spawned = false
m24_remove_chatterers( M24_groups.saints.members )
end
if (M24_groups.luchadores.spawned) then
group_destroy(M24_groups.luchadores)
M24_groups.luchadores.spawned = false
m24_remove_chatterers( M24_groups.luchadores.members_saints )
m24_remove_chatterers( M24_groups.luchadores.members_luchadore )
end
if (M24_groups.homies) then
group_destroy(M24_groups.homies)
M24_groups.homies.spawned = false
end
end
function m24_door_blown_cutscene()
fade_out(0.5)
fade_out_block()
cutscene_in()
group_create(M24_groups.cte_npcs.name, true)
cutscene_play( CUTSCENE_MISSION_CTE_BRIDGE_SETUP, nil, { M24_navpoints.bridge_start, M24_navpoints.bridge_start_coop } )
group_destroy(M24_groups.cte_npcs.name)
cutscene_out()
-- message("The door opens allowing the player to access the bridge.",5)
fade_in(0.5)
fade_in_block()
end
-- Bridge sequence leading up to Killbane's lair
function m24_bridge_sequence()
-- big quakes, restart quake thread so it kicks in sooner
M24_status.big_quakes = true
thread_kill( M24_threads.marsquake_handle )
objective_text_clear(0)
objective_text( 0, "m24_obj_goto_killbane_lair", "", "", SYNC_ALL, OI_ASSET_LOCATION )
marker_add(M24_triggers.end_of_bridge, MINIMAP_ICON_LOCATION, OI_ASSET_LOCATION)
trigger_enable(M24_triggers.end_of_bridge, true)
on_trigger("m24_objective_reached_cb", M24_triggers.end_of_bridge)
objective_reached = false
M24_groups.bridge.spawned = true
delay( 3 )
M24_threads.marsquake_handle = thread_new("m24_marsquake_thread")
delay( 1 )
-- Gate down, play convo.
m24_play_conversation(M24_conversations.cross_bridge, 1.0)
-- blocks until convo is played
local laugh_1_played = false
local laugh_2_played = false
local time_accum = 0
while objective_reached == false do
if laugh_1_played == false and time_accum > 1 then
audio_object_post_event( "m24_bridge_laugh_01" )
laugh_1_played = true
elseif laugh_2_played == false and time_accum > 6 then
audio_object_post_event( "m24_bridge_laugh_02" )
laugh_2_played = true
end
thread_yield()
time_accum = time_accum + get_frame_time()
end
while not objective_reached do
thread_yield()
end
if (group_is_loaded(M24_groups.bridge) ) then
group_destroy(M24_groups.bridge)
m24_remove_chatterers( M24_groups.bridge.members )
M24_groups.bridge.spawned = false
end
marker_remove(M24_triggers.end_of_bridge)
M24_status.big_quakes = false
-- Warp the players into the lair
fade_out(0.5)
fade_out_block()
m24_checkpoint_teleport(M24_checkpoints.kill_killbane.name, false)
end
function m24_cutscene_killbane()
group_create(M24_groups.cte_killbane, true)
cutscene_play(CUTSCENE_MISSION_CTE_LAIR_SETUP, nil, { M24_navpoints.killbane_fight_start, M24_navpoints.killbane_fight_start_coop})
group_destroy(M24_groups.cte_killbane)
end
-- Boss battle with killbane
function m24_kill_killbane_sequence()
group_create(M24_groups.killbane, true)
M24_groups.killbane.spawned = true
turn_invulnerable(M24_characters.killbane) -- Killbane is immune to all damage. Explosions damage him via 'm24_killbane_took_damage_cb'
character_prevent_flinching( M24_characters.killbane, true )
character_set_never_catch_fire( LOCAL_PLAYER, true )
if coop_is_active() then
character_set_never_catch_fire( REMOTE_PLAYER, true )
end
-- Start up continuous spawn of Killbane's thugs
--m24_enable_continuous_spawn_regions(M24_spawn_regions.lair)
--m24_start_continuous_spawn_groups(M24_spawn_groups.lair, 0, LAIR_NPC_RESPAWN_TIME)
boss_battle_mars_killbane_active() -- Start the fight
ai_add_enemy_target(M24_characters.killbane, CLOSEST_PLAYER, ATTACK_ON_SIGHT)
on_take_damage("m24_killbane_took_damage_cb", M24_characters.killbane)
objective_text_clear(0)
objective_text( 0, "m24_obj_defeat_killbane", "", "", SYNC_ALL, OI_ASSET_KILL )
marker_add(M24_characters.killbane, MINIMAP_ICON_KILL, OI_ASSET_KILL_FULL, OI_FLAGS_FULL)
-- Set up all magma crystals
for i, mover in pairs(M24_movers.magma_crystals) do
on_mover_destroyed("m24_magma_crystal_destroyed_cb", mover)
end
--m24_play_conversation( M24_conversations.enter_lair, 0 )
--delay( 5 )
audio_play_persona_line( M24_characters.killbane, "M24_Final_Battle_Begins_01" )
while M24_killbane_dead == false and character_is_dead( M24_characters.killbane ) == false do
thread_yield()
end
end
-- ***************************************************
-- m24_run Helper Functions
-- ***************************************************
-- Starts a table of continuous spawn groups
--
-- cs_group: (string) table of spawn groups
-- max_spawns: (int) number of times to spawn the group (0 is infinite)
-- respawn_delay: (float) time between respawns, in seconds
function m24_start_continuous_spawn_groups(cs_group, max_spawns, respawn_delay)
for i, spawn_group in pairs(cs_group) do
if (type(spawn_group) == "table") then
m24_start_continuous_spawn_groups(spawn_group, max_spawns, respawn_delay)
else
continuous_spawn_start(spawn_group, max_spawns, respawn_delay)
end
end
end
-- Enables a table of active spawn regions
--
-- cs_region: (string) table of spawn regions
function m24_enable_continuous_spawn_regions(cs_region)
for i, region in pairs(cs_region) do
if (type(region) == "table") then
m24_enable_continuous_spawn_region(region)
else
spawn_region_enable(region, ENABLE)
end
end
end
-- Checks if the player(s) are a safe distance from the exploding door
--
-- returns: (bool) true - player(s) is/are outside the blast radius, false - player(s) is/are inside
function m24_check_player_distance_to_door()
local distance_p1 = get_dist(LOCAL_PLAYER, M24_navpoints.bomb_door)
if distance_p1 < MINIMUM_DISTANCE_FROM_DOOR then
return false
end
if in_coop then
local distance_p2 = get_dist(REMOTE_PLAYER, M24_navpoints.bomb_door)
if distance_p2 < MINIMUM_DISTANCE_FROM_DOOR then
return false
end
end
return true
end
-- Loads and plays a conversation
--
-- conversation_table: (table) table containing information about the conversation to played
-- delay_time (int) time to wait before playing the animation
function m24_play_conversation(conversation_table, delay_time)
if M24_active_conversation.convo_table ~= nil then
-- currently playing a conversation, kill it
audio_conversation_end(M24_active_conversation.convo_table.handle)
M24_active_conversation.convo_table.handle = INVALID_CONVERSATION_HANDLE
M24_active_conversation.convo_table = nil
end
-- Load the conversation
if conversation_table.direct then
conversation_table.handle = audio_conversation_load_direct(conversation_table.name)
else
conversation_table.handle = audio_conversation_load(conversation_table.name)
end
-- Keep track of the active conversation
M24_active_conversation.convo_table = conversation_table
delay(delay_time)
conversation_table.played = true
-- Play the conversation and wait for it to end
audio_conversation_play(conversation_table.handle)
audio_conversation_wait_for_end(conversation_table.handle)
audio_conversation_end(conversation_table.handle)
conversation_table.handle = INVALID_CONVERSATION_HANDLE
M24_active_conversation.convo_table = nil
M24_threads.convo_handle = INVALID_THREAD_HANDLE
end
-- ***************************************************
-- m24_initialize Helper Functions
-- ***************************************************
-- Handle any common initialization
--
function m24_initialize_common()
inv_add_temp_loadout(PLAYER_LOADOUT)
-- Add the STAG rifle.
inv_weapon_add_temporary(LOCAL_PLAYER, M24_PLAYER_RIFLE, 1, true, true, false, true, WEAPON_LEVEL4)
inv_weapon_add_temporary(REMOTE_PLAYER, M24_PLAYER_RIFLE, 1, true, true, false, true, WEAPON_LEVEL4)
inv_weapon_disable_all_but_this_slot( WEAPON_SLOT_RIFLE, true, SYNC_ALL )
--set_time_of_day(19, 00)
M24_threads.marsquake_handle = thread_new("m24_marsquake_thread")
M24_threads.vehicle_lava_handle = thread_new("m24_vehicle_lava_thread")
-- Load 2d personas
M24_personas.kinzie.persona_id = audio_persona_load_2d(M24_personas.kinzie.name)
-- Set up the kill trigger
trigger_enable(M24_triggers.lava_death, true)
trigger_allow_dead_guys(M24_triggers.lava_death, true)
on_trigger("m24_lava_death_callback", M24_triggers.lava_death)
party_allow_max_followers(true)
M24_threads.general_chatter_thread = thread_new( "m24_general_chatter_thread" )
-- uncomment to put killbane in downed state every 5 seconds
--M24_threads.killbane_debug = thread_new( "m24_debug_killbane_damage_thread" )
for i, forcefield in pairs( M24_forcefields ) do
mesh_mover_show( forcefield.door )
audio_ambient_emitter_start( forcefield.emitter)
end
player_grenades_disable()
mission_set_cancel_warp_location( M24_navpoints.local_exit, M24_navpoints.remote_exit )
end
-- Checkpoint specific initialization
--
-- checkpoint: The checkpoint to be initialized
function m24_checkpoint_teleport(checkpoint, do_fade)
local warp_local = ""
local warp_remote = ""
local pierce_port, shaundi_port
local terminals_total = NUM_TERMINALS_SINGLE
if in_coop then
terminals_total = NUM_TERMINALS_COOP
end
if (checkpoint == M24_checkpoints.start.name) then
junctions_remaining = #M24_triggers.junction_triggers
terminals_completed = 0
end
if checkpoint == M24_checkpoints.terminal1.name then
warp_local = M24_navpoints.terminals[1]
warp_remote = M24_navpoints.terminals_coop[1]
terminals_completed = 1
pierce_port = M24_navpoints.pierce_terminal1
shaundi_port = M24_navpoints.shaundi_terminal1
end
if checkpoint == M24_checkpoints.terminal2.name then
warp_local = M24_navpoints.terminals[2]
warp_remote = M24_navpoints.terminals_coop[2]
shaundi_port = M24_navpoints.shaundi_terminal2
terminals_completed = 2
end
if checkpoint == M24_checkpoints.junction.name then
warp_local = M24_navpoints.terminals[3]
warp_remote = M24_navpoints.terminals_coop[3]
terminals_completed = 3
end
if checkpoint == M24_checkpoints.cross_bridge.name then
warp_local = M24_navpoints.bridge_start
warp_remote = M24_navpoints.bridge_start_coop
terminals_completed = terminals_total
end
if (checkpoint == M24_checkpoints.kill_killbane.name) then
warp_local = M24_navpoints.killbane_fight_start
warp_remote = M24_navpoints.killbane_fight_start_coop
terminals_completed = terminals_total
end
-- Calculate the number of terminals remaining
terminals_remaining = terminals_total - terminals_completed
-- If there's a warp to do, do it.
if not (warp_local == "" and warp_remote == "") then
if do_fade then
mission_start_fade_out()
end
teleport_coop(warp_local, warp_remote)
if do_fade then
mission_start_fade_in()
end
end
end
-- ***************************************************
-- Miscellaneous m24 Helper Funcrtions
-- ***************************************************
-- Cleans up a table of trigger regions by reseting their on_trigger callback and disabling them
--
-- trigger_group: (string) table of trigger names
function m24_cleanup_triggers(trigger_group)
for i, trigger in pairs(trigger_group) do
if (type(trigger) == "table") then
m24_cleanup_triggers(trigger)
elseif trigger ~= "" then
on_trigger("", trigger)
trigger_enable(trigger, false)
end
end
end
-- Cleans up a table of thread handles by killing them and resetting their thread handle
--
-- thread_group: (string) table of thread names
function m24_cleanup_threads(thread_group)
for i, thread in pairs(thread_group) do
if (type(thread) == "table") then
--recurrrrr
m24_cleanup_threads(thread)
else
if thread ~= INVALID_THREAD_HANDLE then
thread_kill(thread)
thread = INVALID_THREAD_HANDLE
end
end
end
end
-- Cleans up a table of mover names by resetting their on_mover_destroyed callback and removing any HUD markers
--
-- mover_group: (string) table of mover names
function m24_cleanup_movers(mover_group)
for i, mover in pairs(mover_group) do
if (type(mover) == "table") then
m24_cleanup_movers(mover)
else
on_mover_destroyed("", mover)
marker_remove(mover)
end
end
end
-- Cleans up any active continuous spawn groups
--
-- cs_group: (string) table of spawn groups
function m24_cleanup_continuous_spawn_groups(cs_group)
for i, spawn_group in pairs(cs_group) do
if (type(spawn_group) == "table") then
m24_cleanup_continuous_spawn_groups(spawn_group)
else
continuous_spawn_stop(spawn_group, true)
end
end
end
-- Disables any active spawn regions
--
-- cs_region: (string) table of spawn regions
function m24_cleanup_continuous_spawn_regions(cs_region)
for i, region in pairs(cs_region) do
if (type(region) == "table") then
m24_cleanup_continuous_spawn_regions(region)
else
spawn_region_enable(region, DISABLE)
end
end
end
-- Stops any active conversation and restores the handle to invalid
function m24_cleanup_conversations()
-- If we have a conversation thread active, kill it
if M24_threads.convo_handle ~= INVALID_THREAD_HANDLE then
thread_kill(M24_threads.convo_handle)
M24_threads.convo_handle = INVALID_CONVERSATION_HANDLE
end
if M24_active_conversation.convo_table ~= nil then
audio_conversation_end(M24_active_conversation.convo_table.handle)
M24_active_conversation.convo_table.handle = INVALID_CONVERSATION_HANDLE
M24_active_conversation.convo_table = nil
end
end
-- Unloads any 2d personas used in the mission
function m24_cleanup_personas()
for i, persona in pairs(M24_personas) do
if persona.persona_id ~= INVALID_PERSONA_HANDLE then
audio_persona_remove_2d(persona.persona_id)
persona.persona_id = INVALID_PERSONA_HANDLE
end
end
end
-- *************************
--
-- Callback functions
--
-- *************************
-- on_trigger callback for the juction area
--
-- triggerer_name: (string) name of the person who stepped into the trigger
-- trigger_name: (string) name of the trigger
--
function m24_objective_reached_cb(triggerer_name, trigger_name)
objective_reached = true
trigger_enable(trigger_name, false)
on_trigger("", trigger_name)
end
function m24_lava_death_callback(char, trig)
if char == nil then
return
end
character_set_never_catch_fire( char, false )
turn_vulnerable(char)
character_ignite(char)
character_ragdoll(char)
delay(3.0)
-- if this is a homie then fail mission
for i, homie in pairs(M24_groups.homies.members) do
if char == homie then
m24_failure_died_cb( homie )
return
end
end
character_kill(char)
end
function m24_jet_convo_callback(char, trig)
if (char == LOCAL_PLAYER) then
on_trigger("", M24_triggers.jet_convo)
audio_play_persona_line(LOCAL_PLAYER, M24_dialog_lines.wrecked_jets)
end
end
-- hud_timer_set callback. Determines if the player is too close to the explosion.
--
function m24_door_explosion_cb()
hud_timer_hide(1, true)
if not m24_check_player_distance_to_door() then
mission_end_failure("m24", "m24_failure_killed_in_blast")
end
door_exploded = true
end
-- on_take_damage callback for killbane. If he takes explosive damage, it will reduce his hp by MAGMA_CRYSTAL_PERCENT_DAMAGE
--
-- killbane: (string) name of the victim
-- attacker: (string) person that inflicted the damage
-- hp_percent: (int) health
-- was_explosion: (bool) true/false
-- was_melee: (bool) true/false
--
function m24_killbane_took_damage_cb(killbane, attacker, hp_percent, was_explosion, was_melee)
if was_explosion then
if attacker ~= nil and human_is_downed(M24_characters.killbane) == false and M24_killbane_reacting == false and qte_human_is_used( M24_characters.killbane ) == false then
local killbane_max_hp = get_max_hit_points(M24_characters.killbane)
local killbane_hp = get_current_hit_points(M24_characters.killbane)
-- calculate how much damage a blast does and subtract that from killbane's health
local damage_amount = (MAGMA_CRYSTAL_PERCENT_DAMAGE/100) * killbane_max_hp
killbane_hp = killbane_hp - damage_amount
-- set his hit points to that new value
set_current_hit_points(M24_characters.killbane, killbane_hp)
local new_killbane_percent_hp = (killbane_hp/killbane_max_hp) * 100
-- if Killbane is at one of the set qte health %'s, down him for real
if (new_killbane_percent_hp == HP_PERCENT_FOR_FIRST_DOWN or
new_killbane_percent_hp == HP_PERCENT_FOR_SECOND_DOWN or
new_killbane_percent_hp <= HP_PERCENT_FOR_FINAL_DOWN) then
--character_ignite( M24_characters.killbane )
m24_play_lovely_killbane_action( "m24 lava hit" )
human_enter_downed(M24_characters.killbane)
delay(0.5)
-- remove the mission kill marker
marker_remove( M24_characters.killbane )
-- this will, amoung other things, add a finisher marker
human_downed_set_mars_kb_interactable(M24_characters.killbane)
-- otherwise, play a "stunned" animation.
else
m24_play_lovely_killbane_action( "brute melee flashbang" )
-- check to see if the convo has played, and if there's already a convo playing.
if (M24_active_conversation.convo_table == nil) then
if (new_killbane_percent_hp > HP_PERCENT_FOR_FIRST_DOWN) then
if not M24_conversations.final_battle_1.played then
m24_play_conversation(M24_conversations.final_battle_1, 0.0)
end
elseif (new_killbane_percent_hp > HP_PERCENT_FOR_SECOND_DOWN) then
if not M24_conversations.final_battle_2.played then
m24_play_conversation(M24_conversations.final_battle_2, 0.0)
end
else
if not M24_conversations.final_battle_3.played then
m24_play_conversation(M24_conversations.final_battle_3, 0.0)
end
end
end
end
end
end
end
function m24_play_lovely_killbane_action( action )
M24_killbane_reacting = true
set_ignore_ai_flag( M24_characters.killbane, true )
-- wait ? frames to let kb transition out of whatever mode he's in
thread_yield()
thread_yield()
thread_yield()
thread_yield()
action_play(M24_characters.killbane, action, action, true, 0.8, true, true)
while not action_play_is_finished(M24_characters.killbane) do
thread_yield()
end
set_ignore_ai_flag( M24_characters.killbane, false )
M24_killbane_reacting = false
end
-- on_mover_destroyed callback for the magma crystals. Spawns a thread for the crystal which will respawn it
--
-- mover_name: (string) name of the mover that was destroyed
-- attacker_name: (string) name of the attacker
function m24_magma_crystal_destroyed_cb(mover_name, attacker_name)
for i, thread in pairs(M24_threads.mover_respawn_handles) do
if thread == INVALID_THREAD_HANDLE then
M24_threads.mover_respawn_handles[i] = thread_new("m24_respawn_magma_crystal_thread", mover_name, i)
break
end
end
end
-- callback for when you abandon a homie
--
function m24_failure_dismissed_cb()
mission_end_failure("m24", "m06_failure_homie_dismissed")
end
-- Callback for when a homie dies.
--
function m24_failure_died_cb(homie_name)
if M24_status.homie_is_dying[ homie_name ] == true then
return
end
mission_end_failure("m24", M24_text.failure_death[homie_name])
end
-- Callback for when the player uses the trigger region to hack a terminal
--
-- triggerer_name: (string) name of the player that hacked the terminal
-- trigger_name: (string) name of the trigger that was used
--
function m24_terminal_hack_cb(triggerer_name, trigger_name)
trigger_enable(trigger_name, false)
on_trigger("", trigger_name)
marker_remove(trigger_name)
-- play the hacking animation
local anim_name = "m24 computer hack"
local force_play = false
local percentage = 1.0
local stand_still = true
local zero_movement = true
action_play(triggerer_name, anim_name, anim_name, force_play, percentage, stand_still, zero_movement, M24_navpoints.terminals[terminals_completed+1])
delay(3.0)
terminals_remaining = terminals_remaining - 1
terminals_completed = terminals_completed + 1
end
-- Callback for when the player uses the trigger region to plant a charge on a junction box
--
-- triggerer_name: (string) name of the player that set the charge
-- trigger_name: (string) name of the trigger that was used
--
function m24_junction_bomb_cb(triggerer_name, trigger_name)
trigger_enable(trigger_name, false)
on_trigger("", trigger_name)
marker_remove(trigger_name)
local navpoint = nil
for i, bomb_nav in pairs(M24_navpoints.bombs) do
if (M24_triggers.junction_triggers[i] == trigger_name) then
navpoint = bomb_nav
end
end
-- Set up the character for the animation.
character_prevent_explosion_fling(triggerer_name, true)
character_allow_ragdoll(triggerer_name, false)
character_prevent_flinching(triggerer_name, true)
--player_controls_disable(triggerer_name)
local anim_name = "plant explosive enter"
local force_play = false
local percentage = 0.8
local stand_still = false
local zero_movement = false
-- start the action
action_play(triggerer_name, anim_name, anim_name, force_play, percentage, stand_still, zero_movement, navpoint)
-- Set up the middle section of the animation.
set_animation_state(triggerer_name, "plant explosive idle")
if character_is_dead(triggerer_name) == false then
delay(3.0)
end
clear_animation_state(triggerer_name)
-- Play the outro animation.
anim_name = "plant explosive exit"
action_play(triggerer_name, anim_name, anim_name, force_play, percentage, stand_still, zero_movement, navpoint)
-- Clean up any settings we applied for the animation.
player_controls_enable(triggerer_name)
character_prevent_flinching(triggerer_name, false)
character_allow_ragdoll(triggerer_name, true)
character_prevent_explosion_fling(triggerer_name, false)
-- one less junction remains.
junctions_remaining = junctions_remaining - 1
end
-- *************************
--
-- Thread functions
--
-- *************************
-- Thread function that delays and then resets a crystal mover
--
-- name: (string) name of the mover
-- thread_index: (int) index into M24_threads.mover_respawn_handles used to clean up
--
function m24_respawn_magma_crystal_thread(name, thread_index)
delay(MAGMA_CRYSTAL_RESPAWN_TIME)
mesh_mover_reset(name)
M24_threads.mover_respawn_handles[thread_index] = INVALID_THREAD_HANDLE
end
-- Thread function to process Gat's chatter.
--
-- num_dead_homies: the number of dead homies at the beginning of this thread
--
function m24_gat_chatter_thread(num_dead_homies)
local pierce_dead = num_dead_homies >= 1
local shaundi_dead = num_dead_homies >= 2
local pierce_name = M24_groups.homies.pierce
local shaundi_name = M24_groups.homies.shaundi
local gat_name = M24_groups.homies.gat
if num_dead_homies == 0 then
-- Delay for the first line to allow the first convo to play
delay(M24_GAT_CHATTER_INITIAL_DELAY)
else
-- randomly delay
delay(rand_float(M24_GAT_CHATTER_MIN_DELAY, M24_GAT_CHATTER_MAX_DELAY))
end
local num_lines = #(M24_dialog_lines.gat_chatter)
while (num_lines ~= 0 and not character_is_dead(gat_name)) do
local line_name = ""
-- Leave room for Shaundi's death line.
if (not shaundi_dead and character_is_dead(shaundi_name)) then
shaundi_dead = true
-- Leave room for Pierce's death line.
elseif (not pierce_dead and character_is_dead(pierce_name)) then
pierce_dead = true
-- Play any ol line.
else
-- choose a random line number
local line_num = rand_int(1,num_lines)
line_name = M24_dialog_lines.gat_chatter[line_num]
-- remove it from the list
-- This is super-ghetto because we don't load the standard Lua libraries, and so we don't have tremove or table.remove...
M24_dialog_lines.gat_chatter[line_num] = M24_dialog_lines.gat_chatter[num_lines]
M24_dialog_lines.gat_chatter[num_lines] = nil
num_lines = num_lines - 1
end
-- don't talk during the death sequence.
if (M24_threads.homie_death_thread_handle ~= INVALID_THREAD_HANDLE) then
line_name = ""
end
if line_name ~= "" then
-- play that line
audio_play_persona_line(gat_name, line_name)
end
-- randomly delay
delay(rand_float(M24_GAT_CHATTER_MIN_DELAY, M24_GAT_CHATTER_MAX_DELAY))
end
M24_threads.gat_chatter_handle = INVALID_THREAD_HANDLE
end
-- Thread function that plays the "Nooooo!" animation when the player gets near their downed homie.
--
-- homie: (string) Name of the downed homie we're looking for.
--
function m24_homie_death_thread(homie)
local old_terminals_remaining = terminals_remaining
local gat_name = M24_groups.homies.gat
local play_id
M24_status.homie_is_dying[ homie ] = true
local homie_index = 1
if (homie == M24_groups.homies.members[1]) then
homie_index = 1
elseif (homie == M24_groups.homies.members[2]) then
homie_index = 2
elseif (homie == M24_groups.homies.members[3]) then
homie_index = 3
end
-- wait for the player(s) to get near the terminal
while get_dist_closest_player_to_object(M24_deaths[homie_index].pos) > 20.0 do
thread_yield()
end
follower_make_independent(homie, true)
follower_set_can_abandon( homie, false )
ai_do_scripted_move(homie, M24_deaths[homie_index].pos, true, false)
-- He can't be dismissed anymore
on_dismiss("", homie)
while true do
if get_dist( homie, M24_deaths[homie_index].pos ) < 5 then
break
-- if they didn't make it there by the time we activated the terminal, warp them.
elseif (old_terminals_remaining ~= terminals_remaining) then
teleport(homie, M24_deaths[homie_index].pos)
break
else
thread_yield()
end
end
-- now set their leash so they don't go anywheres.
npc_leash_to_nav(homie, M24_deaths[homie_index].pos, 1.0)
-- wait for the terminal to be activated.
while (old_terminals_remaining == terminals_remaining) do
thread_yield()
end
-- Now that the player is close, completely kill your homie.
turn_vulnerable(homie)
effect_play_on_human( "Effect BloodHit", homie, "Head" )
-- play a homie "ouch" line. We're thinking there are custom lines.
if M24_dialog_lines.homie_death_lines[ homie_index ] ~= nil then
play_id = audio_play_persona_line( homie, M24_dialog_lines.homie_hurt_lines[ homie_index ] )
while audio_is_playing( play_id ) do
thread_yield()
end
end
human_enter_downed(homie)
-- must get downed first before we allow death
on_death("", homie)
-- revive homie objective
objective_text( 0, "PLAYER_REVIVE_FOLLOWER", "", "", SYNC_ALL, OI_ASSET_REVIVE )
while not human_being_revived(homie) do
thread_yield()
end
local reviver = human_being_revived_by( homie )
if reviver ~= LOCAL_PLAYER and reviver ~= REMOTE_PLAYER then
reviver = LOCAL_PLAYER
end
-- dramatic music, player screams
audio_object_post_event("m24_homie_death")
local play_id = audio_play_persona_line( reviver, "M24_PC_Scream_In_Agony" )
-- play homie's death line
if M24_dialog_lines.homie_death_lines[homie_index] ~= nil then
play_id = audio_play_persona_line(homie, M24_dialog_lines.homie_death_lines[homie_index])
while audio_is_playing( play_id ) do
thread_yield()
end
end
-- let the stinger audio start, then play the line.
if (homie ~= gat_name) then
delay(4.0)
-- Gat says farewell
play_id = audio_play_persona_line(gat_name, M24_dialog_lines.gat_death_lines[homie_index])
while audio_is_playing( play_id ) do
thread_yield()
end
end
while (human_is_downed(homie)) do
thread_yield()
end
character_kill(homie, true, "", false)
-- Thread done, clear it.
M24_threads.homie_death_thread_handle = INVALID_THREAD_HANDLE
end
-- Simple thread that plays camera shakes periodically to mimic earthquakes
--
function m24_marsquake_thread()
local interval, intensity
while true do
if M24_status.big_quakes == true then
interval = 9
intensity = rand_float( M24_MIN_BIG_MQ_INTENSITY, M24_MAX_BIG_MQ_INTENSITY)
camera_shake_play(M24_MARSQUAKE_SHAKE, intensity)
audio_object_post_event( "m24_rumble_bridge" )
m24_stumble()
else
interval = rand_int (M24_MIN_MQ_INTERVAL,M24_MAX_MQ_INTERVAL)
intensity = rand_float( M24_MIN_MQ_INTENSITY, M24_MAX_MQ_INTENSITY)
audio_object_post_event( "m24_camera_shake" )
camera_shake_play(M24_MARSQUAKE_SHAKE, intensity)
end
delay(interval)
thread_yield()
end
end
function m24_stumble()
local dir = get_random_table_entry( M24_navpoints.stumbles )
m24_character_stumble( LOCAL_PLAYER, dir )
if coop_is_active() then
m24_character_stumble( REMOTE_PLAYER, dir )
end
end
function m24_character_stumble( player, dir )
if character_is_ready( player ) == true and character_is_jumping( player ) == false then
if M24_threads.stumble_threads == nil then
M24_threads.stumble_threads = { }
end
if M24_threads.stumble_threads[ player ] ~= nil then
return
end
M24_threads.stumble_threads[ player ] = thread_new( "m24_character_stumble_thread", player, dir )
end
end
function m24_character_stumble_thread( player, dir )
player_release_human_shield(player, true)
character_clear_combat_move(player)
action_play_directional_stumble(player, dir)
M24_threads.stumble_threads[ player ] = nil
end
-- Checks to see if you drove the rover into the lava, and if so, blows it up.
function m24_vehicle_lava_thread()
-- this is very shady, but other methods were, for some reason, unreliable.
-- I came to this number through trial and error, as vehicle positioning seems inconsistent.
-- Todo: get a navpoint as our basline position.
local y0 = 1080.36
while not (vehicle_exists(M24_vehicles[1]) and vehicle_exists(M24_vehicles[2])) do
thread_yield()
end
while true do
for i, vehicle in pairs(M24_vehicles) do
local x, y, z = get_object_pos(vehicle)
if y < y0 then
vehicle_detonate(vehicle)
end
end
thread_yield()
end
end
function m24_killbane_on_death()
M24_killbane_dead = true
end
function m24_killbane_on_undowned()
marker_add( M24_characters.killbane, MINIMAP_ICON_KILL, OI_ASSET_KILL_FULL, OI_FLAGS_FULL )
end
function m24_general_chatter_thread()
local chatterer, situation, situation_table, index, count, n
while true do
n = sizeof_table( M24_general_chatterers )
if M24_status.no_chatter ~= true and n > 0 then
-- pick someone to say something
index = rand_int( 1, n )
count = 1
for k, v in pairs( M24_general_chatterers ) do
if count == index then
chatterer = k
situation_table = v
break
end
count = count + 1
end
-- pick an appropriate event
situation = situation_table[ rand_int( 1, #situation_table ) ]
-- play it
if character_exists( chatterer ) == true and character_is_dead( chatterer ) == false then
audio_play_persona_line( chatterer, situation )
end
end
delay( rand_float( 5, 15 ) )
end
end
function m24_add_chatterers( chatterers, situations )
for k, v in pairs( chatterers ) do
M24_general_chatterers[ v ] = situations
end
end
function m24_remove_chatterers( chatterers )
for k, v in pairs( chatterers ) do
M24_general_chatterers[ v ] = nil
end
end
function m24_debug_killbane_damage_thread()
MAGMA_CRYSTAL_PERCENT_DAMAGE = 30
M24_status.debug_damage_killbane = true
while true do
delay( 5 )
if character_exists( M24_characters.killbane ) == true and character_is_dead( M24_characters.killbane ) == false then
m24_killbane_took_damage_cb( "", "LOCAL_PLAYER", .1, true, false )
end
end
end