--[[
m15.lua
SR3 Mission Script
DATE: 08-12-2010
AUTHOR: Craig Williams
]]--
-- Debug flags --
-- Tweakable Parameters --
M15_DELAY_BETWEEN_WAVE_SPAWNS = 0.5 -- Time to wait before spawning the next group in the wave
M15_ELEVATOR_OPEN_PROXIMITY = 4.0
M15_LOBBY_LEASH_RADIUS = 25.0
M15_INFINITE_MASS = true -- Set the super computer to have infinite mass?
M15_KNEECAPPER_DAMAGE = 7.0 -- Amount of damage the kneecappers should apply to the tires. 0 for the default behavior.
M15_TIRE_DAMGE_MULTIPLIER = 10.0 -- Modifies how much damage is applied to the tires
M15_SUPER_COMPUTER_SPEED = 65.0 -- Override the super computers speed
M15_VEHICLE_WARN_RADIUS = 150.0 -- Causes the super computer truck to stall (stop) if the player is outside this radius
M15_VEHICLE_MAX_DISTANCE = 250.0 -- Maximum distance the player is allowed to get away from the super computer before the mission fails.
M15_MIN_NOTORIETY = 1
M15_MAX_NOTORIETY = 1 -- Must be <= MAX_NOTORIETY_LEVEL
M15_ROADBLOCKS_ENABLED = false -- Enable notoriety based roadblocks?
M15_WAYPOINT_REMOVE_DIST = 70.0 -- When within this radius, the vehicle's waypoint will be removed.
M15_PED_DENSITY = 0.5
M15_VEHICLE_DENSITY = 0.4
M15_soften_hit_points = 100
RUBBER_BAND_CLOSE_DIST = 5.0
RUBBER_BAND_FAR_DIST = 120.0
RUBBER_BAND_CLOSE_SPEED = 38.0
RUBBER_BAND_FAR_SPEED = 1.0
M15_attack_info = {
decker = {
situation = "M15_computer",
delay = 10.0,
callback = "m15_decker_attack_cb",
timer = INVALID_THREAD_HANDLE
},
stag = {
situation = "M15_attack",
delay = 10.0,
callback = "m15_stag_attack_cb",
timer = INVALID_THREAD_HANDLE
}
}
-- Groups --
NPC_PIERCE = "npc_pierce"
NPC_DRIVER = "npc_chase_driver"
VEHICLE_SUPER_COMPUTER = "vehicle_chase"
M15_groups = {
extra_homies = {name = "group_extra_homies", members = {"npc_extra_homie 001", "npc_extra_homie 002"}},
homies = {name = "group_homies", members = {"npc_pierce"}},
homies_2 = {name = "group_homies 002", members = {"npc_pierce 002"}},
start_vehicle = {name = "group_start_vehicle"},
start_vehicle_2 = {name = "group_start_vehicle 002"},
chase = {name = "group_chase"},
outside_vehicles = {name = "group_outside_vehicles"},
outside_deckers = {name = "group_outside_deckers"},
outside_stag = {name = "group_outside_stag"},
garage = {name = "group_garage_deckers"},
tank_garage = {name = "group_tank_garage", tank = "vehicle_tank_garage"},
tank_outside = {name = "group_tank_outside"},
cars_outside_garage = {name = "group_cars_outside_garage"},
kneecapper_outside={name = "group_kneecapper_outside", vehicles = {"vehicle_kneecapper_outside 002"},},
dead_stag = {
name = "group_garage_dead_stag",
members = {
"npc_garage_dead_stag 001", "npc_garage_dead_stag 002", "npc_garage_dead_stag 003",
"npc_garage_dead_stag 004", "npc_garage_dead_stag 005", "npc_garage_dead_stag 006",
"npc_garage_dead_stag 007", "npc_garage_dead_stag 008",
}
},
deckers_base = {name = "group_deckers_base",},
}
-- kill waves for the group fighting outside
M15_kill_outside = {
arrive = {
vehicle = "vehicle_cover_outside 002",
people = { "npc_stag_outside 002",
"npc_rollerblader_outside 007" },
},
enter = {
vehicle = "vehicle_cover_outside 005",
people = { "npc_stag_outside 009", "npc_stag_outside 003",
"npc_rollerblader_outside 001", "npc_rollerblader_outside 002" },
},
the_rest = {
vehicle = "vehicle_cover_outside 001",
people = { "npc_stag_outside 011", "npc_stag_outside 012", "npc_stag_outside 015",
"npc_deckers_outside 005", "npc_rollerblader_outside 004", "npc_rollerblader_outside 006" },
}
}
-- Tables of waves
M15_LOBBY_LEASH_POINT = "nav_lobby_leash_point"
M15_waves = {
-- Spawned when the player enters the lobby. This runs in parallel with M15_waves.lobby_decker.
lobby_stag = {
groups = {
{
name = "group_lobby_stag_wave 001",
members = {"npc_inside_static_stag 001", "npc_inside_static_stag 003", "npc_inside_static_stag 005", "npc_inside_static_stag 006"}, --, "npc_inside_static_stag 007"
human_shield = "",
run_to = {},
dont_run_trigger = "",
},
{
name = "group_lobby_stag_wave 002",
members = {"npc_inside_stag 001", "npc_inside_stag 002", "npc_inside_stag 003"},
human_shield = "",
run_to = {"nav_middle_level 001", "nav_middle_level 002", "nav_middle_level 003", "nav_middle_level 004"},
dont_run_trigger = "trigger_lobby_upper_level",
},--[[
{
name = "group_lobby_stag_wave 003",
members = {"npc_inside_stag 004", "npc_inside_stag 005", "npc_inside_stag 006"},
human_shield = "",
run_to = {"nav_middle_level 005", "nav_middle_level 006", "nav_middle_level 007", "nav_middle_level 008"},
dont_run_trigger = "trigger_lobby_upper_level",
},]]
},
attack_cb = M15_attack_info.stag.callback,
leash_to = M15_LOBBY_LEASH_POINT,
leash_radius = M15_LOBBY_LEASH_RADIUS,
thread = INVALID_THREAD_HANDLE,
num_remaining = 0
},
-- Spawned when the player enters the lobby. This runs in parallel with M15_waves.lobby_stag.
lobby_decker = {
groups = {
{
name = "group_lobby_deckers_wave 001",
members = {"npc_inside_static_decker 003", "npc_inside_static_decker 007", "npc_inside_static_decker 008", "npc_inside_static_decker 009", "npc_inside_static_decker 010",
"npc_inside_static_decker 011", "npc_inside_static_decker 012", "npc_inside_static_decker 013", "npc_inside_static_decker 015"}, --"npc_inside_static_decker 014", , "npc_inside_static_decker 004"
human_shield = "",
run_to = {},
dont_run_trigger = "",
},
{
name = "group_lobby_deckers_wave 002",
members = {"npc_inside_deckers 001", "npc_inside_deckers 002", "npc_inside_deckers 003", "npc_inside_decker_hs 001"},
human_shield = "npc_inside_decker_hs 001",
run_to = {},
dont_run_trigger = "",
},
{
name = "group_lobby_deckers_wave 003",
members = {"npc_inside_deckers 004", "npc_inside_deckers 005", "npc_inside_decker_hs 002"},
human_shield = "npc_inside_decker_hs 002",
run_to = {},
dont_run_trigger = "",
},
},
attack_cb = M15_attack_info.decker.callback,
leash_to = M15_LOBBY_LEASH_POINT,
leash_radius = M15_LOBBY_LEASH_RADIUS,
thread = INVALID_THREAD_HANDLE,
num_remaining = 0
},
}
M15_outside_garage = {
stag = {
name = "group_stag_rockets",
members = {
{npc = "npc_stag_rocket 003", nav = "nav_stag_rocket 003"}, {npc = "npc_stag_rocket 004", nav = "nav_stag_rocket 004"},
{npc = "npc_stag_rocket 014", nav = "nav_stag_rocket 014"}, {npc = "npc_stag_rocket 015", nav = "nav_stag_rocket 015"},
},
},
stag_leashes = {
"nav_stag_rocket 001",
"nav_stag_rocket 002",
"nav_stag_rocket 003",
"nav_stag_rocket 004",
"nav_stag_rocket 005",
"nav_stag_rocket 006",
"nav_stag_rocket 007",
"nav_stag_rocket 008",
"nav_stag_rocket 009",
"nav_stag_rocket 010",
"nav_stag_rocket 010",
"nav_stag_rocket 012",
"nav_stag_rocket 013",
"nav_stag_rocket 014",
"nav_stag_rocket 015",
"nav_stag_rocket 016",
"nav_stag_rocket 017",
"nav_stag_rocket 018",
},
saints = {
name = "group_kneecappers",
members = {
{npcs = {"npc_kneecapper_driver 001"}, vehicle = "vehicle_kneecapper 001", path = "path_kneecapper 001", thread = INVALID_THREAD_HANDLE},
{npcs = {"npc_kneecapper_driver 003"}, vehicle = "vehicle_kneecapper 002", path = "path_kneecapper 002", thread = INVALID_THREAD_HANDLE},
},
},
}
-- Continuous Spawn Groups --
M15_continuous_spawns = {
deckers = {
groups = {
-- "deckers_spawn_group 001",
"deckers_spawn_group 002",
"deckers_spawn_group 003",
},
regions = {"decker_spawn_region <001>", "decker_spawn_region <002>",},
filter = SRF_1,
respawn_delay = 10.0,
},
stag = {
groups = {
-- "stag_spawn_group 001",
-- "stag_spawn_group 002",
"stag_spawn_group 003",
},
regions = {"stag_spawn_region <001>", "stag_spawn_region <002>",},
filter = SRF_2,
respawn_delay = 10.0,
},
}
M15_pr_front_doors = {"mover_pr_front_door 001", "mover_pr_front_door 004",}
-- Navpoints --
-- Navpoint tables. The following tables map to
-- [1] - Local Player
-- [2] - Remote Player (if M15_in_coop)
-- [3] - Pierce
-- See the helper function m15_teleport_team_to()
M15_NAV_START = {"nav_start_local", "nav_start_remote", "nav_start_pierce"}
M15_NAV_OUTSIDE = {"nav_outside_local", "nav_outside_remote", "nav_outside_pierce"}
M15_NAV_LOBBY = {"nav_lobby_local", "nav_lobby_remote", "nav_lobby_pierce"}
M15_NAV_GARAGE = {"nav_garage_local", "nav_garage_remote", "nav_garage_pierce"}
M15_NAV_GARAGE_OUTSIDE = {"nav_garage_outside_local", "nav_garage_outside_remote", "nav_garage_outside_pierce"}
-- Mission objective nav points
M15_NAV_POWER_PLANT = "nav_power_plant"
M15_NAV_PR_CENTER_ENTERANCE = "nav_pr_center_entrance"
M15_NAV_ELEVATOR_TO_STUDIO = "nav_elevator_to_studio"
M15_NAV_ELEVATOR_TO_GARAGE = "nav_elevator_to_garage"
M15_NAV_GARAGE_DOOR = "nav_garage_door"
-- Super computer route nav points.
M15_SUPER_COMPUTER_START = "nav_to_plant_start"
M15_SUPER_COMPUTER_ROUTE = {{nav = "nav_to_plant 001", delay = "",}, {nav = "nav_to_plant 002", delay = "",},
{nav = "nav_to_plant 003", delay = "",}, {nav = "nav_to_plant 004", delay = "",},
{nav = "nav_to_plant 005", delay = "",}, {nav = "nav_to_plant 006", delay = "",},
{nav = "nav_to_plant 007", delay = "",}, {nav = "nav_to_plant 008", delay = "",},
{nav = "nav_to_plant 009", delay = "",}, {nav = "nav_to_plant 010", delay = "",},
}
-- Triggers --
M15_triggers = {
pr_center_front = "trigger_pr_center_front",
pr_center_enterance = "trigger_pr_center_enterance",
pr_center_radius = "trigger_pr_center_radius",
elevator_to_studio = "trigger_elevator_to_studio", -- Use trigger
elevator_to_garage_1 = "trigger_elevator_to_garage 001",
elevator_to_garage_2 = "trigger_elevator_to_garage 002",
garage_door = "trigger_garage_door",
power_plant = "trigger_power_plant",
pr_center_inside = "trigger_make_npc_critical",
stag_rockets = "trigger_stag_rockets",
}
-- Chase scene triggers
M15_chase_triggers = {
{
name = "Roadblock_group 001", -- Name of the script group to create
prep_trigger = "Roadblock_prep_trigger 001",
trigger_name = "Roadblock_trigger 001", -- Name of the trigger to to enable
on_trigger = "m15_close_roadblock", -- Callback to call when the trigger is triggered
-- List of the vehicles in the group that should follow a path when the trigger is triggered
vehicles = {
{ vehicle = "Roadblock_vehicle 002", members = {"Roadblock_npc 002"}, path = "Roadblock_nav 001", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 003", members = {"Roadblock_npc 003"}, path = "Roadblock_nav 002", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 004", members = {"Roadblock_npc 004"}, path = "Roadblock_nav 003", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 005", members = {"Roadblock_npc 005"}, path = "Roadblock_nav 004", thread = INVALID_THREAD_HANDLE },
},
},
{
name = "Roadblock_group 002",
prep_trigger = "Roadblock_prep_trigger 002",
trigger_name = "Roadblock_trigger 002",
on_trigger = "m15_close_roadblock",
vehicles = {
{ vehicle = "Roadblock_vehicle 007", members = {"Roadblock_npc 007"}, gunner = "Roadblock_npc_gunner 007", path = "Roadblock_nav 007", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 008", members = {"Roadblock_npc 008"}, gunner = "Roadblock_npc_gunner 008", path = "Roadblock_nav 008", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 009", members = {"Roadblock_npc 009"}, gunner = "Roadblock_npc_gunner 009", path = "Roadblock_nav 009", thread = INVALID_THREAD_HANDLE },
},
},
{
name = "Roadblock_group 003",
prep_trigger = "Roadblock_prep_trigger 003",
trigger_name = "Roadblock_trigger 003",
on_trigger = "m15_close_roadblock",
vehicles = {
{ vehicle = "Roadblock_vehicle 010", members = {"Roadblock_npc 010"}, path = "Roadblock_nav 010", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 011", members = {"Roadblock_npc 011"}, path = "Roadblock_nav 011", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 012", members = {"Roadblock_npc 012"}, path = "Roadblock_nav 012", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 013", members = {"Roadblock_npc 013"}, path = "Roadblock_nav 013", thread = INVALID_THREAD_HANDLE },
},
},
{
name = "Roadblock_group 004",
prep_trigger = "Roadblock_prep_trigger 004",
trigger_name = "Roadblock_trigger 004",
on_trigger = "m15_close_roadblock",
vehicles = {
{ vehicle = "Roadblock_vehicle 015", members = {"Roadblock_npc 015"}, path = "Roadblock_nav 015", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 016", members = {"Roadblock_npc 016"}, path = "Roadblock_nav 016", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 017", members = {"Roadblock_npc 017"}, path = "Roadblock_nav 017", thread = INVALID_THREAD_HANDLE },
},
},
{
name = "Roadblock_group 005",
prep_trigger = "Roadblock_prep_trigger 005",
trigger_name = "Roadblock_trigger 005",
on_trigger = "m15_close_roadblock",
vehicles = {
{ vehicle = "Roadblock_vehicle 019", members = {"Roadblock_npc 019"}, path = "Roadblock_nav 019", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 020", members = {"Roadblock_npc 020"}, path = "Roadblock_nav 020", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 021", members = {"Roadblock_npc 021"}, path = "Roadblock_nav 021", thread = INVALID_THREAD_HANDLE },
},
},
{
name = "Roadblock_group 006",
prep_trigger = "Roadblock_prep_trigger 006",
trigger_name = "Roadblock_trigger 006",
on_trigger = "m15_close_roadblock",
vehicles = {
{ vehicle = "Roadblock_vehicle 022", members = {"Roadblock_npc 022"}, path = "Roadblock_nav 022", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 023", members = {"Roadblock_npc 023"}, path = "Roadblock_nav 023", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 024", members = {"Roadblock_npc 024"}, path = "Roadblock_nav 024", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 025", members = {"Roadblock_npc 025"}, path = "Roadblock_nav 025", thread = INVALID_THREAD_HANDLE },
},
},
{
name = "Roadblock_group 007",
prep_trigger = "Roadblock_prep_trigger 007",
trigger_name = "Roadblock_trigger 007",
on_trigger = "m15_close_roadblock",
vehicles = {
{ vehicle = "Roadblock_vehicle 027", members = {"Roadblock_npc 027"}, gunner = "Roadblock_npc_gunner 027", path = "Roadblock_nav 027", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 028", members = {"Roadblock_npc 028"}, gunner = "Roadblock_npc_gunner 028", path = "Roadblock_nav 028", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 029", members = {"Roadblock_npc 029"}, gunner = "Roadblock_npc_gunner 029", path = "Roadblock_nav 029", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 030", members = {"Roadblock_npc 030"}, gunner = "Roadblock_npc_gunner 030", path = "Roadblock_nav 030", thread = INVALID_THREAD_HANDLE },
},
},
{
name = "Roadblock_group 008",
prep_trigger = "Roadblock_prep_trigger 008",
trigger_name = "Roadblock_trigger 008",
on_trigger = "m15_close_roadblock",
vehicles = {
{ vehicle = "Roadblock_vehicle 031", members = {"Roadblock_npc 031"}, path = "Roadblock_nav 031", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 032", members = {"Roadblock_npc 032"}, path = "Roadblock_nav 032", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 033", members = {"Roadblock_npc 033"}, path = "Roadblock_nav 033", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 034", members = {"Roadblock_npc 034"}, path = "Roadblock_nav 034", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 035", members = {"Roadblock_npc 035"}, path = "Roadblock_nav 035", thread = INVALID_THREAD_HANDLE },
},
},
{
name = "Roadblock_group 009",
prep_trigger = "Roadblock_prep_trigger 009",
trigger_name = "Roadblock_trigger 009",
on_trigger = "m15_close_roadblock",
vehicles = {
{ vehicle = "Roadblock_vehicle 036", members = {"Roadblock_npc 036"}, path = "Roadblock_nav 036", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 037", members = {"Roadblock_npc 037"}, path = "Roadblock_nav 037", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 038", members = {"Roadblock_npc 038"}, path = "Roadblock_nav 038", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Roadblock_vehicle 039", members = {"Roadblock_npc 039"}, path = "Roadblock_nav 039", thread = INVALID_THREAD_HANDLE },
},
},
{
name = "Chase_group 001",
prep_trigger = "Chase_prep_trigger 001",
trigger_name = "Chase_trigger 001",
on_trigger = "m15_chase_truck",
vehicles = {
{ vehicle = "Chase_vehicle 001", members = {"Chase_npc 001"}, gunner = "Chase_npc_gunner 001", path = "Chase_nav 001", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Chase_vehicle 002", members = {"Chase_npc 002"}, gunner = "Chase_npc_gunner 002", path = "Chase_nav 002", thread = INVALID_THREAD_HANDLE },
},
},
{
name = "Chase_group 002",
prep_trigger = "Chase_prep_trigger 002",
trigger_name = "Chase_trigger 002",
on_trigger = "m15_chase_truck",
vehicles = {
{ vehicle = "Chase_vehicle 003", members = {"Chase_npc 003"}, gunner = "Chase_npc_gunner 003", path = "Chase_nav 003", thread = INVALID_THREAD_HANDLE },
{ vehicle = "Chase_vehicle 004", members = {"Chase_npc 004"}, gunner = "Chase_npc_gunner 004", path = "Chase_nav 004", thread = INVALID_THREAD_HANDLE },
},
},
{
name = "Ambient_battle 001",
prep_trigger = "",
trigger_name = "Ambient_battle_trigger 001",
on_trigger = "m15_spawn_and_release",
vehicles = { },
},
{
name = "Ambient_battle 002",
prep_trigger = "",
trigger_name = "Ambient_battle_trigger 002",
on_trigger = "m15_spawn_and_release",
vehicles = { },
},
{
name = "",
prep_trigger = "",
trigger_name = "trigger_slow_down_truck",
on_trigger = "m15_reduce_truck_speed",
vehicles = { },
},
{
name = "",
prep_trigger = "",
trigger_name = "trigger_spawn_deckers_base",
on_trigger = "m15_spawn_deckers_base",
vehicles = { },
}
}
-- Trigger Flags --
M15_in_coop = false
M15_trigger_reached = false
M15_chase_notoriety_disabled = false
M15_chase_waypoint_active_local = false
M15_chase_waypoint_active_remote = false
-- Characters --
-- Vehicles --
-- Mesh Movers --
M15_elevators = {
{
nav = "nav_elevator_center 001",
mover = "mover_elevator_doors 001",
},
{
nav = "nav_elevator_center 002",
mover = "mover_elevator_doors 002",
},
{
nav = "nav_elevator_center 003",
mover = "mover_elevator_doors 003",
},
{
nav = "nav_elevator_center 004",
mover = "mover_elevator_doors 004",
},
}
-- Text --
M15_OBJ_GO_TO_PR_CENTER = "m15_obj_go_to_pr_center"
M15_OBJ_GET_TO_THE_GARAGE = "m15_obj_get_to_the_garage"
M15_OBJ_CLEAR_THE_LOBBY = "m15_obj_clear_the_lobby"
M15_OBJ_ESCAPE_THE_GARAGE = "m15_obj_escape_the_garage"
M15_OBJ_GET_IN_THE_TANK = "m15_obj_get_in_the_tank"
M15_OBJ_CATCH_THE_TRUCK = "m15_obj_catch_the_truck"
M15_OBJ_DISABLE_THE_TRUCK = "m15_obj_disable_the_truck"
M15_FAILURE_PIERCE_DIED = "m15_failure_pierce_died"
M15_FAILURE_PIERCE_DISMISSED = "m15_failure_pierce_dismissed"
M15_FAILURE_TRUCK_DECKERS_HQ = "m15_failure_truck_deckers_hq"
M15_FAILURE_TRUCK_ESCAPED = M15_FAILURE_TRUCK_DECKERS_HQ
M15_FAILURE_TRUCK_DESTROYED = "m15_failure_truck_destroyed"
M15_FAILURE_TRUCK_CRUSHED = "m15_failure_truck_crushed"
-- Threads --
-- Elevator manager
M15_THREAD_ELEVATOR_MANAGER = INVALID_THREAD_HANDLE
-- Conversation threads
M15_THREAD_CONVO_TO_PR_CENTER = INVALID_THREAD_HANDLE
M15_THREAD_CONVO_PR_CENTER = INVALID_THREAD_HANDLE
M15_THREAD_CONVO_IN_LOBBY = INVALID_THREAD_HANDLE
M15_THREAD_CONVO_IN_GARAGE = INVALID_THREAD_HANDLE
M15_THREAD_CONVO_TRUCK_FOUND = INVALID_THREAD_HANDLE
M15_THREAD_CONVO_DONT_USE_CANNON = INVALID_THREAD_HANDLE
-- Chase scene threads
M15_THREAD_TIRE_CHECK = INVALID_THREAD_HANDLE
M15_THREAD_CHASE_RUBBER_BAND = INVALID_THREAD_HANDLE
-- Emergency spawn thread
M15_THREAD_SPAWN_ROCKETS = INVALID_THREAD_HANDLE
-- Checkpoints --
M15_checkpoint = {
start = {
name = MISSION_START_CHECKPOINT, -- defined in ug_lib.lua
music = "M15_music_mission_start",
music_chkpnt = "M15_music_start_checkpoint"
},
outside_pr_center = {
name = "m15_checkpoint_outside_pr_center",
music = "M15_music_PR_center_battle",
music_chkpnt = "M15_music_PR_Center_Checkpoint"
},
garage = {
name = "m15_checkpoint_garage",
music = "M15_music_PR_garage",
music_chkpnt = "M15_music_PR_Garage_Checkpoint"
},
chase = {
name = "m15_checkpoint_chase",
music = "M15_music_truck_chase",
music_chkpnt = "M15_music_truck_chase_checkpoint"
},
chase_kneecapper = {
name = "m15_checkpoint_chase_kneecapper",
music = "M15_music_truck_chase",
music_chkpnt = "M15_music_truck_chase_checkpoint"
},
}
-- Cutscenes --
M15_CUTSCENE_MISSION_INTRO = "15_in"
M15_CUTSCENE_MISSION_OUTRO = "15_Out"
-- Conversations --
-- Table of audio conversations to play. Either a actual audio file can be specifed, or a message can be used in its place.
-- Expected Table Members:
-- name - Name of the audio file to play
-- delay - Amount of time (in seconds) to delay after a audio file has played
-- message - Supplementary message to display if no audio file is provided
--
M15_convo = {
-- Starts along with the mission
to_pr_center = {
{name = "", delay = 20.0,},
{name = "m15_mission_start_01", delay = 20.0,},
{name = "m15_drive_01", delay = 20.0,},
{name = "m15_drive_02", delay = 2.0, call_received = true, },
{name = "m15_drive_03", delay = 0.0,},
},
pr_center_outside = {
{name = "m15_arrival", delay = 10.0,},
},
-- Starts when the player enters the lobby
pr_center_lobby = {
{name = "", delay = 10.0,},
{name = "m15_stag_tech", delay = 10.0,},
{name = "M15_PA_Speakers_1", delay = 0.0, direct = true},
},
go_to_garage = {
{name = "m15_get_to_garage", delay = 0.0,},
},
-- Starts when the player enters the garage
pr_center_garage = {
{name = "m15_garage_enter", delay = 1.0,},
},
-- Starts when the player gets within range of the truck
chase_found_truck_in_tank = {
{name = "M15_Truck_Chase", delay = 20.0,},
},
chase_found_truck = {
{name = "", delay = 15.0,},
{name = "M15_Decker_Roadblocks", delay = 3.0,},
{name = "m15_matt_player_convo", delay = 3.0,},
},
}
M15_personas = {
{name = "Kinzie", handle = INVALID_PERSONA_HANDLE},
{name = "Matt", handle = INVALID_PERSONA_HANDLE},
}
-- Other --
-- vehicle_pathfind_check_done() return values defines
M15_PATHFINDING_IN_PROGRESS = 0
M15_PATHFINDING_DONE = 1
M15_PATHFINDING_FAILED = 2
STAG_NOTORIETY = "police"
DECKERS_NOTORIETY = "deckers"
MORNINGSTAR_NOTORIETY = "morningstar"
-- Variables
M15_extra_npcs_in_lobby = {} -- List of NPCs that entered the lobby and are now mission critical
M15_mark_waves = false
M15_radius_shown = false
M15_in_pr_center = false
M15_truck_hit_by_tank_shell = false
M15_phone_call_active = false
-- *************************
--
-- Standard functions
--
-- *************************
-- This is the primary entry point for the mission, and is responsible for starting up the mission
-- at the specified checkpoint.
-- CALLED FROM CODE
--
-- m15_checkpoint: The checkpoint the mission should begin at
-- is_restart: TRUE if the mission is restarting, FALSE otherwise
--
function m15_start(m15_checkpoint, is_restart)
M15_in_coop = coop_is_active()
-- Check if this mission starting from the beginning
if (m15_checkpoint == M15_checkpoint.start.name) then
if (is_restart == false) then
-- First time playing mission
local fade_in_after = false
cutscene_play(M15_CUTSCENE_MISSION_INTRO, nil, nil, fade_in_after)
end
end
-- Handle mission initialization for the current checkpoint
m15_initialize(m15_checkpoint)
-- Run the mission from the current checkpoint
m15_run(m15_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 m15_run(first_checkpoint)
local current_checkpoint = first_checkpoint
-- determine if we need to play fresh audio cues or not
local from_checkpoint = true
-- Run the mission from the beginning
if current_checkpoint == M15_checkpoint.start.name then
m15_setup_start()
M15_THREAD_CONVO_TO_PR_CENTER = thread_new("m15_convo_to_pr_center")
m15_goto_a_trigger(M15_triggers.pr_center_radius, M15_OBJ_GO_TO_PR_CENTER, true)
m15_cleanup_start()
m15_cleanup_audio_threads()
M15_THREAD_CONVO_IN_LOBBY = thread_new("m15_convo_pr_center")
from_checkpoint = false
current_checkpoint = M15_checkpoint.outside_pr_center.name
mission_set_checkpoint(current_checkpoint, true)
end
if current_checkpoint == M15_checkpoint.outside_pr_center.name then
m15_setup_outside_pr_center()
m15_goto_a_trigger(M15_triggers.pr_center_front, M15_OBJ_GO_TO_PR_CENTER, true)
-- drop density to small to try to help framerate
set_ped_density(0.1)
set_traffic_density(0.1)
m15_cleanup_outside_pr_center()
m15_soften_attackers(M15_kill_outside.arrive)
m15_setup_inside_pr_center()
if not from_checkpoint then
m15_music(M15_checkpoint.outside_pr_center.music_chkpnt)
end
M15_in_pr_center = false
M15_waves.lobby_stag.thread = thread_new("m15_run_wave", M15_waves.lobby_stag)
M15_waves.lobby_decker.thread = thread_new("m15_run_wave", M15_waves.lobby_decker)
m15_goto_a_trigger(M15_triggers.pr_center_enterance, M15_OBJ_GO_TO_PR_CENTER, true)
M15_in_pr_center = true
m15_soften_attackers(M15_kill_outside.enter)
--m15_start_continuous_spawns(M15_continuous_spawns.deckers)
--m15_start_continuous_spawns(M15_continuous_spawns.stag)
audio_play_persona_line(NPC_PIERCE, "M15_Inside")
m15_cleanup_audio_threads()
M15_THREAD_CONVO_IN_LOBBY = thread_new("m15_convo_in_lobby")
objective_text(0, M15_OBJ_CLEAR_THE_LOBBY, "", "", SYNC_ALL, OI_ASSET_KILL)
M15_mark_waves = true
m15_mark_wave_npcs(M15_waves.lobby_stag)
m15_mark_wave_npcs(M15_waves.lobby_decker)
m15_mark_extra_npcs()
delay(5.0)
m15_soften_attackers(M15_kill_outside.the_rest)
-- Wait until the waves have been completed
while M15_waves.lobby_stag.thread ~= INVALID_THREAD_HANDLE or M15_waves.lobby_decker.thread ~= INVALID_THREAD_HANDLE do
thread_yield()
end
-- Wait until all the extra npcs are dead
while sizeof_table(M15_extra_npcs_in_lobby) > 0 do
thread_yield()
end
-- Prevent any additional NPCs from being marked as mission critical
on_trigger("", M15_triggers.pr_center_inside)
trigger_enable(M15_triggers.pr_center_inside, false)
m15_goto_a_trigger(M15_triggers.elevator_to_garage_2, M15_OBJ_GET_TO_THE_GARAGE, false)
m15_cleanup_all_waves()
m15_cleanup_inside_pr_center()
m15_setup_garage()
objective_text_clear(0)
m15_teleport_team_to(M15_NAV_GARAGE)
m15_music(M15_checkpoint.garage.music)
current_checkpoint = M15_checkpoint.garage.name
mission_set_checkpoint(current_checkpoint, true)
end
if current_checkpoint == M15_checkpoint.garage.name then
m15_coop_free_pierce()
m15_cleanup_audio_threads()
M15_THREAD_CONVO_IN_GARAGE = thread_new("m15_convo_in_garage")
m15_enter_vehicle(M15_groups.tank_garage.tank, M15_OBJ_GET_IN_THE_TANK)
-- Spawn dudes outside (separate thread) and wait until you come out to attack
M15_trigger_reached = false
M15_THREAD_SPAWN_ROCKETS = thread_new("m15_setup_outside_garage")
m15_goto_a_trigger(M15_triggers.garage_door, M15_OBJ_ESCAPE_THE_GARAGE, false)
m15_outside_garage_battle()
m15_cleanup_garage()
-- Don't cleanup the outside the PR Center until the player is far enough away.
-- This is done inside m15_chase().
if m15_player_is_in_a_tank() then
current_checkpoint = M15_checkpoint.chase.name
m15_music(M15_checkpoint.chase.music)
else
current_checkpoint = M15_checkpoint.chase_kneecapper.name
m15_music(M15_checkpoint.chase_kneecapper.music)
end
mission_set_checkpoint(current_checkpoint, true)
end
if current_checkpoint == M15_checkpoint.chase.name or current_checkpoint == M15_checkpoint.chase_kneecapper.name then
m15_setup_chase()
m15_coop_free_pierce()
m15_chase()
m15_cleanup_chase()
end
m15_music("M15_music_truck_chase_End")
mission_end_success("m15", M15_CUTSCENE_MISSION_OUTRO, {M15_NAV_START[1], M15_NAV_START[2]})
end
-- This is the primary function responsible for cleaning up the entire mission
-- CALLED FROM CODE (+++MUST RETURN IMMEDIATLY+++)
--
function m15_cleanup()
--[[ INSERT ANY MISSION SPECIFIC CLEAN-UP ]]--
m15_cleanup_extra_npcs()
-- The mission may have failed due to a remote player disconnecting. Double check to see if they are connected.
if M15_in_coop then
M15_in_coop = coop_is_active()
end
-- restore ped density
set_ped_density(1.0)
set_traffic_density(1.0)
m15_music("M15_music_STOP")
objective_text_clear(0)
on_death("", NPC_PIERCE)
on_dismiss("", NPC_PIERCE)
waypoint_remove()
m15_unload_all_convos()
if M15_THREAD_TIRE_CHECK ~= INVALID_THREAD_HANDLE then
thread_kill(M15_THREAD_TIRE_CHECK)
end
if M15_THREAD_CHASE_RUBBER_BAND ~= INVALID_THREAD_HANDLE then
thread_kill(M15_THREAD_CHASE_RUBBER_BAND)
end
if M15_THREAD_SPAWN_ROCKETS ~= INVALID_THREAD_HANDLE then
thread_kill(M15_THREAD_SPAWN_ROCKETS)
end
m15_cleanup_start()
m15_cleanup_outside_pr_center()
m15_cleanup_inside_pr_center()
m15_cleanup_garage()
m15_cleanup_outside_garage()
m15_cleanup_chase()
m15_stop_all_continuous_spawns()
m15_cleanup_all_waves()
m15_cleanup_audio_threads()
if group_is_loaded(M15_groups.chase.name) then
on_vehicle_destroyed("", VEHICLE_SUPER_COMPUTER)
on_vehicle_enter_water("", VEHICLE_SUPER_COMPUTER)
on_take_damage("", VEHICLE_SUPER_COMPUTER)
group_destroy(M15_groups.chase.name)
end
if group_is_loaded(M15_groups.homies.name) then
group_destroy(M15_groups.homies.name)
end
if group_is_loaded(M15_groups.homies_2.name) then
group_destroy(M15_groups.homies_2.name)
end
for key, trigger in pairs(M15_triggers) do
m15_cleanup_trigger(trigger)
end
-- Fix any notoriety related changes
notoriety_force_no_spawn(STAG_NOTORIETY, false)
notoriety_force_no_spawn(DECKERS_NOTORIETY, false)
roadblocks_enable(true)
notoriety_set_min(STAG_NOTORIETY, 0)
notoriety_set_max(STAG_NOTORIETY, MAX_NOTORIETY_LEVEL)
notoriety_set_min(DECKERS_NOTORIETY, 0)
notoriety_set_max(DECKERS_NOTORIETY, MAX_NOTORIETY_LEVEL)
notoriety_set_min_and_max(MORNINGSTAR_NOTORIETY, 0, MAX_NOTORIETY_LEVEL)
notoriety_force_no_spawn(MORNINGSTAR_NOTORIETY, false)
--party_set_recruitable(false)
--party_allow_max_followers(false)
if group_is_loaded(M15_groups.tank_outside.name) then
group_destroy(M15_groups.tank_outside.name)
end
if group_is_loaded(M15_groups.cars_outside_garage.name) then
group_destroy(M15_groups.cars_outside_garage.name)
end
if group_is_loaded(M15_groups.kneecapper_outside.name) then
group_destroy(M15_groups.kneecapper_outside.name)
end
if group_is_loaded(M15_groups.tank_garage) then
group_destroy(M15_groups.tank_garage.name)
end
for key, persona in pairs(M15_personas) do
audio_persona_remove_2d(persona.handle)
end
end
-- Called when the mission has ended with success
-- CALLED FROM CODE (+++MUST RETURN IMMEDIATLY+++)
--
function m15_success()
--[[ INSERT ANY MISSION SPECIFIC SUCCESS STUFF ]]--
end
-- *************************
--
-- Local functions
--
-- *************************
-- Initialize the mission for the specified checkpoint
--
-- checkpoint: Checkpoint to initialize the mission to
--
function m15_initialize(checkpoint)
-- Make sure the screen is completly faded out
mission_start_fade_out(0.0)
-- Set the mission author
set_mission_author("Craig Williams")
-- Common initialization
m15_initialize_common()
-- Checkpoint specific initialization
m15_initialize_checkpoint(checkpoint)
-- Start fading in
mission_start_fade_in()
end
-- ***************************************************
-- m15_run Helper Functions
-- ***************************************************
-- Teleports the team to the specified nav points.
--
-- nav_table - Nav points to teleport the group to
-- ignore_vehicle - True to remove the player from any vehicle
function m15_teleport_team_to(nav_table, ignore_vehicle)
if ignore_vehicle == nil then
ignore_vehicle = true
end
fade_out(0.5)
fade_out_block()
teleport_coop(nav_table[1], nav_table[2], ignore_vehicle)
fade_in(0.5)
fade_in_block()
if ignore_vehicle then
teleport(NPC_PIERCE, nav_table[3], ignore_vehicle)
else
if character_is_in_vehicle(LOCAL_PLAYER) then
local vehicle_name = get_char_vehicle_name(LOCAL_PLAYER)
if not character_is_in_vehicle(NPC_PIERCE, vehicle_name) then
-- Pierce is in a different vehicle. Since teleporting him will remove him from his vehicle, just
-- put him in the players vehicle.
vehicle_exit_teleport(NPC_PIERCE)
vehicle_enter_teleport(NPC_PIERCE, vehicle_name, 1, true)
end
else
teleport(NPC_PIERCE, nav_table[3], ignore_vehicle)
end
end
thread_yield()
end
function m15_enter_vehicle(vehicle, obj_text)
if vehicle_is_destroyed(vehicle) then
return
end
objective_text(0, obj_text, "", "", SYNC_ALL, OI_ASSET_USE)
marker_add(vehicle, MINIMAP_ICON_USE, OI_ASSET_USE, OI_FLAGS_DEFAULT, SYNC_ALL)
while not vehicle_is_destroyed(vehicle) do
if character_is_in_vehicle(LOCAL_PLAYER, vehicle) then
break
end
if M15_in_coop and character_is_in_vehicle(REMOTE_PLAYER, vehicle) then
break
end
thread_yield()
end
marker_remove(vehicle)
end
-- General task. Instructs the player to go to the specific trigger.
--
-- trigger - Name of the trigger to activate and go to.
-- obj_text - Objective text to display
-- gps - Should a gps waypoint be added?
function m15_goto_a_trigger(trigger, obj_text, gps)
if gps == nil then
gps = false
end
m15_goto_a_trigger_setup(trigger, obj_text, gps)
while not M15_trigger_reached do
thread_yield()
end
m15_goto_a_trigger_cleanup(trigger, gps)
-- To be on the safe side, give pathfinding a similar a full frame to remove the nav points and such
thread_yield()
end
function m15_goto_a_trigger_setup(trigger, obj_text, gps)
if type(trigger) == "table" then
for key, element in pairs(trigger) do
m15_goto_a_trigger_setup(element, obj_text, gps)
end
return
end
if obj_text ~= nil and obj_text ~= "" then
objective_text(0, obj_text, "", "", SYNC_ALL, OI_ASSET_LOCATION)
end
marker_add_trigger(trigger, MINIMAP_ICON_LOCATION, INGAME_EFFECT_CHECKPOINT, OI_ASSET_LOCATION, OI_FLAGS_LOCATION, SYNC_ALL)
if gps then
waypoint_add(trigger)
end
M15_trigger_reached = false
m15_setup_trigger(trigger, "m15_trigger_cback")
end
function m15_goto_a_trigger_cleanup(trigger, gps)
if type(trigger) == "table" then
for key, element in pairs(trigger) do
m15_goto_a_trigger_cleanup(element, gps)
end
return
end
-- Cleanup the task.
m15_cleanup_trigger(trigger)
marker_remove_trigger(trigger)
if gps then
waypoint_remove()
end
end
function m15_start_continuous_spawns(spawns)
for key, region in pairs(spawns.regions) do
spawn_region_toggle_filter(region, spawns.filter)
spawn_region_enable(region, true)
end
for key, group in pairs(spawns.groups) do
continuous_spawn_start(group, 0, spawns.respawn_delay, "", spawns.filter)
end
spawns.loaded = true
end
function m15_stop_continuous_spawns(spawns)
if spawns.loaded ~= true then
return
end
for key, group in pairs(spawns.groups) do
local destroy = true
continuous_spawn_stop(group, destroy)
end
continuous_spawn_regions_enable(spawns.regions, false)
spawns.loaded = nil
end
function m15_stop_all_continuous_spawns()
for key, spawns in pairs(M15_continuous_spawns) do
m15_stop_continuous_spawns(spawns)
end
end
-- ***************************************************
-- m15_initialize Helper Functions
-- ***************************************************
-- Handle any common initialization
--
function m15_initialize_common()
--[[ INSERT ANY COMMON INITIALIZATION CODE HERE ]]--
set_ped_density(M15_PED_DENSITY)
set_traffic_density(M15_VEHICLE_DENSITY)
for key, persona in pairs(M15_personas) do
persona.handle = audio_persona_load_2d(persona.name)
end
-- This is a Deckers mission in Morningstar territory. Always have a 1 star Deckers notoriety and 0 Morningstar
notoriety_set_min(DECKERS_NOTORIETY, 1)
notoriety_set_min_and_max(MORNINGSTAR_NOTORIETY, 0, 0)
notoriety_force_no_spawn(MORNINGSTAR_NOTORIETY, true)
end
-- Moves the player to an area that should be by Pierce, spawns Pierce, and then moves
-- Pierce to the correct location.
--
-- pierce_group: Group that pierce belongs to Assumes that pierce_group.members[1] == Pierce
-- nav_table: List of nav points. Same format as m15_teleport_team_to()
function m15_setup_pierce_and_move_to(pierce_group, nav_table)
teleport_coop(nav_table[1], nav_table[2], true)
group_create(pierce_group.name, true)
NPC_PIERCE = pierce_group.members[1]
teleport(NPC_PIERCE, nav_table[3], true)
party_add(NPC_PIERCE, LOCAL_PLAYER)
follower_set_can_abandon(NPC_PIERCE, true)
on_death("m15_on_death", NPC_PIERCE)
on_dismiss("m15_party_member_dismissed", NPC_PIERCE)
end
-- Checkpoint specific initialization
--
-- checkpoint: The checkpoint to be initialized
function m15_initialize_checkpoint(checkpoint)
if checkpoint == M15_checkpoint.start.name then
group_create(M15_groups.start_vehicle.name, true)
group_create(M15_groups.extra_homies.name, true)
m15_setup_pierce_and_move_to(M15_groups.homies, M15_NAV_START)
release_to_world(M15_groups.extra_homies.name)
group_destroy(M15_groups.extra_homies.name)
-- Intro animations
-- Player(s) look cool GO!
local anim_name = "M10 Player Mission Start"
local morph_name = "M10 Player Mission Start"
local force_play = false
local stand_still = false
local zero_movement = false
action_play_non_blocking(LOCAL_PLAYER, anim_name, morph_name, force_play, stand_still, zero_movement)
player_script_controlled_clear(LOCAL_PLAYER) -- let player escape this animation
if (coop_is_active()) then
action_play_non_blocking(REMOTE_PLAYER, anim_name, morph_name, force_play, stand_still, zero_movement)
player_script_controlled_clear(REMOTE_PLAYER) -- let player escape this animation
end
elseif checkpoint == M15_checkpoint.outside_pr_center.name then
group_create(M15_groups.start_vehicle_2.name, true)
m15_setup_pierce_and_move_to(M15_groups.homies_2, M15_NAV_OUTSIDE)
m15_music(M15_checkpoint.outside_pr_center.music_chkpnt)
elseif checkpoint == M15_checkpoint.garage.name then
m15_setup_garage()
-- give a moment for the dead stag to hit the floor.
delay(2.0)
m15_setup_pierce_and_move_to(M15_groups.homies_2, M15_NAV_GARAGE)
m15_music(M15_checkpoint.garage.music_chkpnt)
elseif checkpoint == M15_checkpoint.chase.name or checkpoint == M15_checkpoint.chase_kneecapper.name then
if checkpoint == M15_checkpoint.chase.name then
group_create(M15_groups.cars_outside_garage.name, true)
group_create(M15_groups.tank_outside.name, true)
m15_setup_pierce_and_move_to(M15_groups.homies_2, M15_NAV_GARAGE_OUTSIDE)
vehicle_enter_teleport(LOCAL_PLAYER, "vehicle_tank_outside", 0, true)
vehicle_enter_teleport(NPC_PIERCE, "vehicle_tank_outside", 1, true)
m15_music(M15_checkpoint.chase.music_chkpnt)
else
group_create(M15_groups.kneecapper_outside.name, true)
for key, vehicle in pairs(M15_groups.kneecapper_outside.vehicles) do
vehicle_set_kneecappers(vehicle, true)
end
m15_setup_pierce_and_move_to(M15_groups.homies_2, M15_NAV_GARAGE_OUTSIDE)
m15_music(M15_checkpoint.chase_kneecapper.music_chkpnt)
end
notoriety_force_no_spawn(STAG_NOTORIETY, true)
--notoriety_force_no_spawn(DECKERS_NOTORIETY, true)
roadblocks_enable(M15_ROADBLOCKS_ENABLED)
-- Point the player in the direction of the truck
marker_add(M15_SUPER_COMPUTER_START, MINIMAP_ICON_LOCATION, OI_ASSET_LOCATION, OI_FLAGS_LOCATION, SYNC_ALL)
waypoint_add(M15_SUPER_COMPUTER_START)
objective_text(0, M15_OBJ_CATCH_THE_TRUCK, "", "", SYNC_ALL, OI_ASSET_LOCATION)
end
end
-- Checkpoint specific setup/cleanup code
--
function m15_setup_start()
end
function m15_cleanup_start()
end
function m15_setup_pr_center_npc(np)
end
function m15_setup_outside_pr_center()
group_create(M15_groups.outside_stag.name, true)
group_create(M15_groups.outside_deckers.name, true)
group_create(M15_groups.outside_vehicles.name, true)
group_foreach_npc(M15_groups.outside_stag, m15_setup_pr_center_npc)
m15_disable_notoriety()
on_trigger("m15_disable_notoriety", M15_triggers.pr_center_radius)
on_trigger_exit("m15_enable_notoriety", M15_triggers.pr_center_radius)
trigger_enable(M15_triggers.pr_center_radius, true)
m15_set_notoriety_range()
on_trigger("m15_make_npc_mission_critical", M15_triggers.pr_center_inside)
trigger_enable(M15_triggers.pr_center_inside, true)
end
function m15_cleanup_outside_pr_center()
-- Don't cleanup the outside groups yet since the player still see the battle from the lobby
end
function m15_setup_inside_pr_center()
-- close these doors. hide the people. get performance. profit!
for key, mover in pairs(M15_pr_front_doors) do
if door_is_open(mover) then
door_close(mover)
end
end
M15_THREAD_ELEVATOR_MANAGER = thread_new("m15_thread_elevator_manager")
end
function m15_cleanup_inside_pr_center()
-- Groups from m15_initialize_checkpoint(M15_checkpoint.start.name)
if group_is_loaded(M15_groups.start_vehicle) then
group_destroy(M15_groups.start_vehicle)
end
-- Groups from m15_initialize_checkpoint(M15_checkpoint.outside_pr_center.name)
if group_is_loaded(M15_groups.start_vehicle_2) then
group_destroy(M15_groups.start_vehicle_2)
end
-- Groups from m15_setup_outside_pr_center()
if group_is_loaded(M15_groups.outside_stag.name) then
group_destroy(M15_groups.outside_stag.name)
end
if group_is_loaded(M15_groups.outside_deckers.name) then
group_destroy(M15_groups.outside_deckers.name)
end
if group_is_loaded(M15_groups.outside_vehicles.name) then
group_destroy(M15_groups.outside_vehicles.name)
end
m15_stop_continuous_spawns(M15_continuous_spawns.deckers)
m15_stop_continuous_spawns(M15_continuous_spawns.stag)
m15_enable_notoriety()
trigger_enable(M15_triggers.pr_center_radius, false)
on_trigger("", M15_triggers.pr_center_radius)
on_trigger_exit("", M15_triggers.pr_center_radius)
if M15_THREAD_ELEVATOR_MANAGER ~= INVALID_THREAD_HANDLE then
thread_kill(M15_THREAD_ELEVATOR_MANAGER)
M15_THREAD_ELEVATOR_MANAGER = INVALID_THREAD_HANDLE
end
on_trigger("", M15_triggers.pr_center_inside)
trigger_enable(M15_triggers.pr_center_inside, false)
end
function m15_setup_garage()
m15_set_notoriety_range()
group_create(M15_groups.dead_stag.name, true)
for key, npc in pairs(M15_groups.dead_stag.members) do
character_kill(npc, true)
end
group_create(M15_groups.cars_outside_garage.name, true)
group_create(M15_groups.garage.name, true)
group_create(M15_groups.tank_garage.name, true)
notoriety_force_no_spawn(STAG_NOTORIETY, true)
--notoriety_force_no_spawn(DECKERS_NOTORIETY, true)
roadblocks_enable(M15_ROADBLOCKS_ENABLED)
end
function m15_cleanup_garage()
-- Don't cleanup M15_groups.kneecapper.name since the player will drive this vehicle till the end of
-- the mission. Destroyed in m15_cleanup().
if group_is_loaded(M15_groups.garage.name) then
release_to_world(M15_groups.garage.name)
group_destroy(M15_groups.garage.name)
end
end
function m15_outside_garage_setup_stag_member(npc)
on_death("m15_release_npc_to_world", npc)
end
function m15_setup_outside_garage()
-- create the group and assign the "on death" callback.
group_create(M15_outside_garage.stag.name, true)
group_foreach_npc(M15_outside_garage.stag.name, m15_outside_garage_setup_stag_member)
group_create(M15_outside_garage.saints.name, true)
-- Prevent anything bad from happening to the NPCs while they wait around
for key, vehicle_group in pairs(M15_outside_garage.saints.members) do
for npc_key, npc in pairs(vehicle_group.npcs) do
set_ignore_ai_flag(npc, true)
set_dont_attack_me_on_sight_flag(npc, true)
character_prevent_explosion_fling(npc, true)
character_prevent_flinching(npc, true)
end
end
-- wait until the trigger is hit
while not M15_trigger_reached do
thread_yield()
end
delay(1.0)
-- Have the STAG members move to attack the player
group_foreach_npc(M15_outside_garage.stag, m15_leash_stag_outside_garage)
-- Have some Saints plow through the group of STAG
for key, vehicle in pairs(M15_outside_garage.saints.members) do
vehicle.thread = thread_new("m15_saints_kneecapper_thread", vehicle)
end
end
function m15_npc_clear_death_callback(npc)
on_death("", npc)
end
function m15_cleanup_outside_garage()
if group_is_loaded(M15_groups.dead_stag.name) then
group_destroy(M15_groups.dead_stag.name)
end
if group_is_loaded(M15_outside_garage.stag.name) then
group_destroy(M15_outside_garage.stag.name)
-- clear all the death callbacks.
group_foreach_npc(M15_outside_garage.stag, m15_npc_clear_death_callback)
end
if group_is_loaded(M15_outside_garage.saints.name) then
for key, vehicle in pairs(M15_outside_garage.saints.members) do
if vehicle.thread ~= INVALID_THREAD_HANDLE then
thread_kill(vehicle.thread)
vehicle.thread = INVALID_THREAD_HANDLE
end
end
group_destroy(M15_outside_garage.saints.name)
end
end
function m15_setup_chase()
m15_setup_all_chase_triggers()
set_ped_density(0.3)
set_traffic_density(0.8)
end
function m15_cleanup_chase()
m15_cleanup_all_chase_triggers()
if group_is_loaded(M15_groups.deckers_base) then
group_destroy(M15_groups.deckers_base)
end
end
-- In coop, Pierce doesn't need to stay with the player for the last part of the mission
function m15_coop_free_pierce()
if coop_is_active() then
-- remove callbacks
on_death("", NPC_PIERCE)
on_dismiss("", NPC_PIERCE)
-- make invulnerable
turn_invulnerable(NPC_PIERCE)
-- dismiss from party
party_dismiss(NPC_PIERCE)
end
end
-- ***************************************************
-- Miscellaneous m15 Helper Functions
-- ***************************************************
-- play music on the local player
function m15_music(disco)
audio_object_post_event(disco, nil, nil, LOCAL_PLAYER)
end
-- Go through the list and make people really weak
--
-- kill_table: (table) information on people and vehicle to weaken
--
function m15_soften_attackers(kill_table)
-- check the people first to see if anyone is left
local people_alive = false
for i,person in pairs(kill_table.people) do
if not character_is_dead(person) then
set_current_hit_points(person, M15_soften_hit_points)
people_alive = true
end
end
if people_alive then
set_current_hit_points(kill_table.vehicle, M15_soften_hit_points)
vehicle_set_smoke_and_fire_state(kill_table.vehicle, true, true)
delay(3.0)
vehicle_detonate(kill_table.vehicle)
end
end
-- Enables the specified trigger
--
-- trigger - Name of the trigger to enable
-- call_back - call back function to call when the trigger is triggered.
function m15_setup_trigger(trigger, call_back)
trigger_enable(trigger, true)
on_trigger(call_back, trigger)
end
-- Disables the specified trigger
--
-- trigger - Name of the trigger to disable
function m15_cleanup_trigger(trigger)
on_trigger("", trigger)
trigger_enable(trigger, false)
end
-- Starts loading an audio conversation
--
-- convo: Conversation to load
function m15_preload_convo(convo)
if convo.name == nil or convo.name == "" then
return
end
if convo.call_received == true or convo.call_sent == true then
-- phone convos get autoloaded
return
end
if convo.handle == nil then
if convo.direct == true then
convo.handle = audio_conversation_load_direct(convo.name)
else
convo.handle = audio_conversation_load(convo.name)
end
end
end
-- Plays a conversation loaded with m15_preload_convo()
--
-- convo: Conversation to play
function m15_play_convo(convo)
if convo.name ~= nil and convo.name ~= "" then
if convo.call_received == true or convo.call_sent == true then
local auto_answer = true
local receiving_call = false
if convo.call_received == true then
receiving_call = true
end
-- phone call
M15_phone_call_active = true
audio_play_for_mission_cellphone(convo.name, receiving_call, auto_answer, "", "m15_call_ended_cb")
while M15_phone_call_active == true do
thread_yield()
end
else
if convo.handle == nil then
convo.handle = audio_conversation_load(convo.name)
end
--message("Playing Convo: " .. convo.name, 2.0)
audio_conversation_play(convo.handle)
audio_conversation_wait_for_end(convo.handle)
convo.handle = nil
end
end
if convo.delay ~= nil and convo.delay > 0.01 then
delay(convo.delay)
end
end
-- Unloads a conversation loaded with m15_preload_convo()
--
-- convo: Conversation to unload
function m15_unload_convo(convo)
if convo.handle == nil then
if convo.call_received == true or convo.call_sent == true then
audio_remove_mission_cellphone(convo.name)
end
return
end
audio_conversation_end(convo.handle)
convo.handle = nil
end
-- Unloads all conversations from the convo table
--
function m15_unload_all_convos()
for convo in pairs(M15_convo) do
if #M15_convo[convo] ~= 0 then
for i = 1, #M15_convo[convo] do
m15_unload_convo(M15_convo[convo][i])
end
else
m15_unload_convo(M15_convo[convo])
end
end
end
-- Iterates through a table of conversations and plays them.
--
-- convo: Table (array) of conversations
-- start_delay: time to delay the conversation. Minimum/default is 2.0 sec.
--
function m15_play_audio_set(convo, start_delay)
if #convo <= 0 then
-- Empty table array. Each element should not be named.
return
end
if (start_delay == nil or start_delay < 2.0) then
start_delay = 2.0
end
-- Load the intial conversation
m15_preload_convo(convo[1])
-- General delay to give the intial audio clip some time to load
delay(start_delay)
-- Play all the intermediate conversations
if #convo > 1 then
for i = 1, #convo - 1 do
-- Preload the next conversation
m15_preload_convo(convo[i + 1])
-- Play and unload the current conversation
m15_play_convo(convo[i])
end
end
-- Play the final conversation
m15_play_convo(convo[#convo])
end
-- Unloads a convo table (array)
--
-- convo: Table of conversations to unload
--
function m15_unload_convo_set(convo)
for i = 1, #convo do
m15_unload_convo(convo[i])
end
end
-- Checks to see if a player is close to the chase start point
--
function m15_player_is_close_to_chase_start()
if get_dist(LOCAL_PLAYER, M15_SUPER_COMPUTER_START) <= 100.0 then
return true
end
if M15_in_coop then
if get_dist(REMOTE_PLAYER, M15_SUPER_COMPUTER_START) <= 100.0 then
return true
end
end
return false
end
function m15_player_in_fast_vehicle()
if not character_is_in_vehicle(LOCAL_PLAYER) then
return false
end
local vehicle = get_char_vehicle_name(LOCAL_PLAYER)
-- Assume all non-scripted vehicles are fast
if vehicle == "" or vehicle == LOCAL_PLAYER then
return true
end
-- Assume anything is faster then a tank
if vehicle_is_tank(vehicle) then
return false
end
return true
end
function m15_set_notoriety_range()
notoriety_set_min(STAG_NOTORIETY, M15_MIN_NOTORIETY)
notoriety_set_max(STAG_NOTORIETY, M15_MAX_NOTORIETY)
notoriety_set_min(DECKERS_NOTORIETY, M15_MIN_NOTORIETY)
notoriety_set_max(DECKERS_NOTORIETY, M15_MAX_NOTORIETY)
end
function m15_leash_stag_outside_garage(npc)
local leash_nav = get_closest_object(npc, M15_outside_garage.stag_leashes)
npc_leash_to_nav(npc, leash_nav, 3.0)
ai_add_enemy_target(npc, LOCAL_PLAYER, ATTACK_NOW)
end
function m15_outside_garage_battle()
-- These triggers are right on top of each other. Just consider this hit with a minimal delay
delay(1.0)
--M15_trigger_reached = false
--on_trigger("m15_trigger_outside_battle", M15_triggers.stag_rockets)
--trigger_enable(M15_triggers.stag_rockets, true)
-- Point the player in the direction of the truck
marker_add(M15_SUPER_COMPUTER_START, MINIMAP_ICON_LOCATION, OI_ASSET_LOCATION, OI_FLAGS_LOCATION, SYNC_ALL)
objective_text(0, M15_OBJ_CATCH_THE_TRUCK, "", "", SYNC_ALL, OI_ASSET_LOCATION)
--while not M15_trigger_reached do
-- thread_yield()
--end
-- Establish GPS to truck (we wait till you're far enough out of the garage for it to WORK)
waypoint_add(M15_SUPER_COMPUTER_START)
end
function m15_prep_kneecapper()
m15_prep_kneecapper_vehicle(LOCAL_PLAYER)
if M15_in_coop then
m15_prep_kneecapper_vehicle(REMOTE_PLAYER)
end
end
function m15_prep_kneecapper_vehicle(player)
local vehicle = get_char_vehicle_name(player)
if vehicle == "" or vehicle == player then
-- The player is in an invalid or non-scripted vehicle
return
end
if not character_is_in_a_driver_seat(player) then
-- They are not driving. Don't change a vehicle the player doesn't have explicit control over
return
end
if not vehicle_is_tank(vehicle) then
vehicle_suppress_flipping(vehicle, true)
vehicle_set_kneecappers_damage(vehicle, M15_KNEECAPPER_DAMAGE)
release_to_world(vehicle)
end
end
-- Handles the logic for the chase sequence
---
function m15_chase()
-- Spawn the super computer truck and it's driver (create early to nuke any custom vehicles while we can)
group_create_hidden(M15_groups.chase.name, true)
m15_set_notoriety_range()
delay(5.0)
-- Remove any ongoing conversations and start up the new sections convos
m15_cleanup_audio_threads()
M15_THREAD_CONVO_TRUCK_FOUND = thread_new("m15_convo_truck_found")
-- Wait until the vehilce is streamed in before we do anything with it
while not m15_player_is_close_to_chase_start() do
thread_yield()
end
group_show(M15_groups.chase.name)
thread_yield()
-- Setup any necessary flags on the player's choosen vehicle. This will release the vehicle to the world as well
m15_prep_kneecapper()
-- The player is far enough from the PR Center. Clean up any groups.
m15_cleanup_outside_garage()
-- Prevent the driver for attempting to attack and from being killed
npc_combat_enable(NPC_DRIVER, false)
turn_invulnerable(NPC_DRIVER, false)
-- Prevent Pierce from attacking the truck
set_dont_attack_me_on_sight_flag(NPC_DRIVER, true)
-- Force the NPC to enter the vehicle and stop him for exiting
vehicle_enter_teleport(NPC_DRIVER, VEHICLE_SUPER_COMPUTER, 0, true)
vehicle_suppress_npc_exit(VEHICLE_SUPER_COMPUTER, true)
audio_object_post_event("m15_sfx_supercomputer_hum", nil, nil, VEHICLE_SUPER_COMPUTER)
vehicle_speed_override(VEHICLE_SUPER_COMPUTER, M15_SUPER_COMPUTER_SPEED)
delay(4.0)
-- Cleanup our directions to the truck since it now exists
marker_remove(M15_SUPER_COMPUTER_START)
waypoint_remove()
-- Setup the HUD to point to the actual vehicle
m15_vehicle_add_marker(VEHICLE_SUPER_COMPUTER)
m15_vehicle_add_waypoint(VEHICLE_SUPER_COMPUTER)
-- Detect vehicle destruction (mission failure)
on_vehicle_destroyed("m15_vehicle_destroyed", VEHICLE_SUPER_COMPUTER)
on_vehicle_enter_water("m15_vehicle_destroyed", VEHICLE_SUPER_COMPUTER)
on_take_damage("m15_vehicle_damaged", VEHICLE_SUPER_COMPUTER)
-- Record the health of all the tires in case they stream out
M15_THREAD_TIRE_CHECK = thread_new("m15_thread_tire_check")
M15_THREAD_CHASE_RUBBER_BAND = thread_new("m15_thread_chase_rubber_band")
-- destroy the group outside the garage so as to free up vehicle instances.
if group_is_loaded(M15_groups.cars_outside_garage.name) then
group_destroy(M15_groups.cars_outside_garage.name)
end
-- Update the objective and start the vehicle on its actual path
objective_text(0, M15_OBJ_DISABLE_THE_TRUCK, "", "", SYNC_ALL, OI_ASSET_KILL)
m15_vehicle_follow_path(VEHICLE_SUPER_COMPUTER, NPC_DRIVER, M15_SUPER_COMPUTER_ROUTE)
-- If m15_vehicle_follow_path returned, then we reached the destination which is a failure
-- or the truck got stuck somewhere.
if get_dist(VEHICLE_SUPER_COMPUTER, M15_SUPER_COMPUTER_ROUTE[#M15_SUPER_COMPUTER_ROUTE].nav) > 100.0 then
m15_music("M15_music_truck_chase_End")
-- The truck is stuck somewhere. Consider this a success
mission_end_success("m15", M15_CUTSCENE_MISSION_OUTRO, {M15_NAV_START[1], M15_NAV_START[2]})
else
mission_end_failure("m15", M15_FAILURE_TRUCK_DECKERS_HQ)
end
end
function m15_vehicle_add_marker(vehicle)
marker_add(vehicle, MINIMAP_ICON_KILL, OI_ASSET_KILL_TIRES, OI_FLAGS_DEFAULT, SYNC_ALL)
end
function m15_vehicle_remove_marker(vehicle)
marker_remove(vehicle, SYNC_ALL)
end
function m15_vehicle_remove_waypoint(vehicle)
if M15_chase_waypoint_active_local then
waypoint_remove(SYNC_LOCAL)
M15_chase_waypoint_active_local = false
end
if M15_in_coop and M15_chase_waypoint_active_remote then
waypoint_remove(SYNC_REMOTE)
M15_chase_waypoint_active_remote = false
end
end
function m15_vehicle_add_waypoint(vehicle)
if not M15_chase_waypoint_active_local then
waypoint_add(vehicle, SYNC_LOCAL)
M15_chase_waypoint_active_local = true
end
if M15_in_coop and not M15_chase_waypoint_active_remote then
waypoint_add(vehicle, SYNC_REMOTE)
M15_chase_waypoint_active_remote = true
end
end
-- Adds/removes a waypoint attached to a vehicle when the player is inside/outside a radius. This is
-- done since the waypoint isn't updated each frame and it is very distracting when the player is
-- close to the truck. This will be invidually for the local and remote player, since they can be
-- in seperate cars.
--
-- vehicle: Name of the vehicle theplayer is chasing
function m15_update_chase_waypoint(vehicle)
local dist_to_local = get_dist(LOCAL_PLAYER, vehicle)
if M15_chase_waypoint_active_local then
if dist_to_local < M15_WAYPOINT_REMOVE_DIST then
waypoint_remove(SYNC_LOCAL)
M15_chase_waypoint_active_local = false
end
else
if dist_to_local > M15_WAYPOINT_REMOVE_DIST then
waypoint_add(vehicle, SYNC_LOCAL)
M15_chase_waypoint_active_local = true
end
end
if M15_in_coop then
local dist_to_remote = get_dist(REMOTE_PLAYER, vehicle)
if M15_chase_waypoint_active_remote then
if dist_to_remote < M15_WAYPOINT_REMOVE_DIST then
waypoint_remove(SYNC_REMOTE)
M15_chase_waypoint_active_remote = false
end
else
if dist_to_remote > M15_WAYPOINT_REMOVE_DIST then
waypoint_add(vehicle, SYNC_REMOTE)
M15_chase_waypoint_active_remote = true
end
end
end
end
-- Gets the distance to the specific vehicle and sorts the output
--
-- vehicle: Name of the vehicle the player(s) are chasing
--
-- Returns: min_dist_to_vehicle, max_dist_to_vehicle
function m15_get_chase_distance(vehicle)
local dist_to_local = get_dist(LOCAL_PLAYER, vehicle)
local dist_to_remote = dist_to_local
if M15_in_coop then
dist_to_remote = get_dist(REMOTE_PLAYER, vehicle)
end
if dist_to_local < dist_to_remote then
return dist_to_local, dist_to_remote
else
return dist_to_remote, dist_to_local
end
end
-- Checks to see if a player is in range of the vehicle. If so, this function does nothing.
-- If no player is within range, this function will block until they are within range again.
--
-- vehicle: Name of the vehicle the player(s) are chasing
function m15_update_chase_radius(vehicle)
local dist_min, dist_max = m15_get_chase_distance(vehicle)
-- Check to see if the player is outside the failure radius
if dist_min > M15_VEHICLE_MAX_DISTANCE then
mission_end_failure("m15", M15_FAILURE_TRUCK_ESCAPED)
elseif dist_min <= M15_VEHICLE_WARN_RADIUS then
if M15_radius_shown == true then
minimap_icon_remove_radius(vehicle, SYNC_ALL)
M15_radius_shown = false
end
else
if M15_radius_shown == false then
minimap_icon_add_radius(vehicle, M15_VEHICLE_MAX_DISTANCE * 0.95, SYNC_ALL)
M15_radius_shown = true
end
end
end
-- Iterates through a table of nav points and tells the specific vehicle to follow it while calling the
-- helper functions:
-- m15_update_chase_waypoint() - Add/remove a waypoint to the vehicle when necessary
-- m15_update_chase_radius() - Stop the vehicle if the player is out of range.
--
-- vehicle: Name of the vehicle the player is chasing
-- driver: Name of the driver of the vehicle
-- path: Table of nav points to pathfind through
--
function m15_vehicle_follow_path(vehicle, driver, path)
-- Setup some paramters/flags to force the vehicle to follow the nav points.
vehicle_set_script_hard_goto(vehicle, true)
vehicle_set_crazy(vehicle, true)
vehicle_set_tire_damage_multiplier(vehicle, M15_TIRE_DAMGE_MULTIPLIER)
-- The tires won't go flat if the durability is at the default value.
vehicle_set_tire_durability(vehicle, 1.01)
vehicle_ignore_repulsors(vehicle, true)
vehicle_set_allow_ram_ped(vehicle, true)
vehicle_set_ignore_rail_obstacles(vehicle, true)
vehicle_prevent_transition_to_ambient(vehicle, true)
vehicle_infinite_mass(vehicle, M15_INFINITE_MASS)
-- Give AI some time to apply all the set flags
thread_yield()
local i = 1
while i <= #path do
local attempts = 0
local in_progress = M15_PATHFINDING_IN_PROGRESS
thread_yield()
-- Start pathfinding
local pathfind_started = vehicle_pathfind_to_do(vehicle, path[i].nav, false, true)
if pathfind_started ~= false and pathfind_started ~= 0 then
-- No issues starting the path. Loop until we error out or reach our destination
in_progress = vehicle_pathfind_check_done(vehicle)
while in_progress == M15_PATHFINDING_IN_PROGRESS do
thread_yield()
in_progress = vehicle_pathfind_check_done(vehicle)
m15_update_chase_radius(vehicle)
-- Add a waypoint if needed
m15_update_chase_waypoint(vehicle)
end
-- If the driver has died or is not in the vehicle (still alive), then the player
-- has effectivly disabled the truck since it can no longer move.
if (character_is_dead(driver) or not character_is_in_vehicle(driver)) and not vehicle_is_destroyed(vehicle) then
m15_music("M15_music_truck_chase_End")
mission_end_success("m15", M15_CUTSCENE_MISSION_OUTRO, {M15_NAV_START[1], M15_NAV_START[2]})
end
if in_progress == M15_PATHFINDING_FAILED then
-- Failed to get to the nav point.
if attempts < 5 then
-- We have not hit our attempt limit. Go ahead and try again.
thread_yield()
attempts = attempts + 1
delay(5.0)
message("Failed to pathfind to: " .. path[i].nav, 4.0)
else
delay(0.5)
-- Failed to many times. Attempt to pathfind to the next point
i = i + 1
end
elseif in_progress == M15_PATHFINDING_DONE then
if path[i].delay ~= "" then
local dist = get_dist(vehicle, path[i].delay)
while dist > 11.0 do
thread_yield()
dist = get_dist(vehicle, path[i].delay)
end
end
-- We reached the specified point. Move to the next one.
i = i + 1
end
end
thread_yield()
end
end
-- Finds a chase trigger by name
--
-- trigger_name: Name of the trigger to find
--
-- Returns the trigger table or nil if it is not found
function m15_find_trigger(trigger_name)
for key, trigger in pairs(M15_chase_triggers) do
if trigger.trigger_name == trigger_name then
return trigger
end
end
return nil
end
function m15_find_prep_trigger(trigger_name)
for key, trigger in pairs(M15_chase_triggers) do
if trigger.prep_trigger == trigger_name then
return trigger
end
end
return nil
end
-- Sets up a chase trigger
--
-- trigger: M15_chase_triggers table to setup
--
function m15_setup_chase_trigger(trigger)
group_create(trigger.name, true)
for key, vehicle in pairs(trigger.vehicles) do
vehicle_enter_group_teleport(vehicle.members, vehicle.vehicle)
-- Tell the NPCs to ignore AI so they don't try to chase the player before they are triggered
for npc_key, npc in pairs(vehicle.members) do
set_ignore_ai_flag(npc, true)
end
if vehicle.gunner ~= nil and vehicle.gunner ~= "" then
vehicle_enter_teleport(vehicle.gunner, vehicle.vehicle, 4, true)
set_ignore_ai_flag(vehicle.gunner, true)
end
end
on_trigger(trigger.on_trigger, trigger.trigger_name)
trigger_enable(trigger.trigger_name, true)
end
-- Sets up all chase triggers
--
function m15_setup_all_chase_triggers()
for key, trigger in pairs(M15_chase_triggers) do
if trigger.prep_trigger ~= "" then
on_trigger("m15_prep_chase_trigger", trigger.prep_trigger)
trigger_enable(trigger.prep_trigger, true)
else
on_trigger(trigger.on_trigger, trigger.trigger_name)
trigger_enable(trigger.trigger_name, true)
end
end
end
-- Cleans up a trigger setup by m15_setup_chase_trigger()
--
-- trigger: M15_chase_triggers table to cleanup
--
function m15_cleanup_chase_trigger(trigger)
if trigger.prep_trigger ~= "" then
on_trigger("", trigger.prep_trigger)
trigger_enable(trigger.prep_trigger, false)
end
if trigger.trigger_name ~= "" then
on_trigger("", trigger.trigger_name)
trigger_enable(trigger.trigger_name, false)
end
for key, vehicle in pairs(trigger.vehicles) do
if vehicle.thread ~= INVALID_THREAD_HANDLE then
thread_kill(vehicle.thread)
vehicle.thread = INVALID_THREAD_HANDLE
end
end
if trigger.name ~= "" and group_is_loaded(trigger.name) then
release_to_world(trigger.name)
group_destroy(trigger.name)
end
end
-- Cleanup a trigger if all the pathfinding threads are complete
--
-- trigger: M15_chase_triggers table to cleanup
--
function m15_maybe_cleanup_chase_trigger(trigger)
for key, vehicle in pairs(trigger.vehicles) do
if vehicle.thread ~= INVALID_THREAD_HANDLE then
-- At least one pathfinding thread is active. Cannot cleanup the group yet.
return
end
end
m15_cleanup_chase_trigger(trigger)
end
-- Cleans up all chase triggers
--
function m15_cleanup_all_chase_triggers()
for key, trigger in pairs(M15_chase_triggers) do
m15_cleanup_chase_trigger(trigger)
end
end
function m15_player_is_in_a_tank()
if character_is_in_a_driver_seat(LOCAL_PLAYER) then
local veh_name = get_char_vehicle_name(LOCAL_PLAYER)
if veh_name ~= "" and veh_name ~= LOCAL_PLAYER and vehicle_is_tank(veh_name) then
return true
end
end
if M15_in_coop and character_is_in_a_driver_seat(REMOTE_PLAYER) then
local veh_name = get_char_vehicle_name(REMOTE_PLAYER)
if veh_name ~= "" and veh_name ~= REMOTE_PLAYER and vehicle_is_tank(veh_name) then
return true
end
end
return false
end
-- *************************
--
-- Callback functions
--
-- *************************
function m15_release_npc_to_world(victim, killer)
on_death("", victim)
release_to_world(victim)
end
-- Generic trigger callback. Whenever the trigger is triggered by the player, the
-- M15_trigger_reached flag is set.
--
-- human - Who triggered the trigger
-- trigger - Name of the triggered trigger.
function m15_trigger_cback(human, trigger)
if human == LOCAL_PLAYER or human == REMOTE_PLAYER then
M15_trigger_reached = true
end
end
-- Ends the mission if an important human is killed.
--
-- victim - Who was killed
-- killer - The killer...
function m15_on_death(victim, killer)
if victim == NPC_PIERCE then
mission_end_failure("m15", M15_FAILURE_PIERCE_DIED)
end
end
function m15_party_member_dismissed(npc, leader)
if npc == NPC_PIERCE then
mission_end_failure("m15", M15_FAILURE_PIERCE_DISMISSED)
end
end
-- Fail if the super computer truck is destroyed
--
function m15_vehicle_destroyed(vehicle)
mission_end_failure("m15", M15_FAILURE_TRUCK_DESTROYED)
end
function m15_vehicle_damaged(truck, attacker, hp_percent, from_explosion)
-- We only care about what the player did to the truck
if attacker ~= LOCAL_PLAYER and attacker ~= REMOTE_PLAYER then
return
end
-- Only consider explosion harmful to the computer
if from_explosion ~= true then
return
end
-- Check to see if the player was in a tank
local vehicle = get_char_vehicle_name(attacker)
if vehicle == "" or not vehicle_is_tank(vehicle) then
return
end
-- Give them a warning if they haven't shot the truck with the cannon yet
if not M15_truck_hit_by_tank_shell then
m15_cleanup_audio_threads()
M15_THREAD_CONVO_DONT_USE_CANNON = thread_new("m15_convo_player_used_cannon_on_truck")
M15_truck_hit_by_tank_shell = true
return
end
-- The truck was shot at least twice with the cannon. Destroy it.
vehicle_detonate(truck)
end
-- Prepares a chase trigger
--
-- human: Name of the player that triggered the callback
-- prep_trigger_name: Name of the trigger that was activated
--
function m15_prep_chase_trigger(human, prep_trigger_name)
local trigger = m15_find_prep_trigger(prep_trigger_name)
m15_setup_chase_trigger(trigger)
trigger_enable(prep_trigger_name, false)
on_trigger("", prep_trigger_name)
end
-- Closes a roadblock
--
-- human: Name of the player that triggered the callback
-- trigger_name: Name of the trigger that was activated
--
function m15_close_roadblock(human, trigger_name)
local trigger = m15_find_trigger(trigger_name)
for key, vehicle in pairs(trigger.vehicles) do
vehicle.thread = thread_new("m15_roadblock_pathfind_to_thread", trigger, vehicle)
end
trigger_enable(trigger_name, false)
on_trigger("", trigger_name)
end
function m15_chase_truck(human, trigger_name)
local trigger = m15_find_trigger(trigger_name)
for key, vehicle in pairs(trigger.vehicles) do
vehicle.thread = thread_new("m15_chase_pathfind_to_thread", trigger, vehicle)
end
trigger_enable(trigger_name, false)
on_trigger("", trigger_name)
end
function m15_spawn_and_release(human, trigger_name)
local trigger = m15_find_trigger(trigger_name)
trigger_enable(trigger_name, false)
on_trigger("", trigger_name)
group_create(trigger.name)
release_to_world(trigger.name)
group_destroy(trigger.name)
end
function m15_reduce_truck_speed(human, trigger_name)
if human ~= NPC_DRIVER then
return
end
vehicle_speed_override(VEHICLE_SUPER_COMPUTER, M15_SUPER_COMPUTER_SPEED * 0.60)
trigger_enable(trigger_name, false)
on_trigger("", trigger_name)
end
function m15_spawn_deckers_base(human, trigger_name)
group_create(M15_groups.deckers_base)
trigger_enable(trigger_name, false)
on_trigger("", trigger_name)
end
function m15_disable_notoriety()
notoriety_force_no_spawn(STAG_NOTORIETY, true)
notoriety_force_no_spawn(DECKERS_NOTORIETY, true)
end
function m15_enable_notoriety()
notoriety_force_no_spawn(STAG_NOTORIETY, false)
notoriety_force_no_spawn(DECKERS_NOTORIETY, false)
end
function m15_make_npc_mission_critical(npc)
if npc == nil or npc == "" or npc == LOCAL_PLAYER or npc == REMOTE_PLAYER then
return
end
-- Only mark a decker, police member, or stag member as mission critical
local team = get_team(npc)
if team ~= "Deckers" and team ~= "Police" and team ~= "STAG" then
return
end
-- Ignore already marked NPCs
if M15_extra_npcs_in_lobby[npc] == true then
return
end
on_death("m15_enemy_killed", npc)
if M15_mark_waves then
marker_add(npc, MINIMAP_ICON_KILL, OI_ASSET_KILL, OI_FLAGS_DEFAULT, SYNC_ALL)
end
npc_leash_to_nav(npc, M15_LOBBY_LEASH_POINT, M15_LOBBY_LEASH_RADIUS)
M15_extra_npcs_in_lobby[npc] = true
end
function m15_trigger_outside_battle(human, trigger_name)
trigger_enable(trigger_name, false)
on_trigger("", trigger_name)
M15_trigger_reached = true
end
-- Generic callback when a phone call conversation has ended
function m15_call_ended_cb()
M15_phone_call_active = false
end
-- Handle actually playing the attack lines
--
-- name: (string) name of character
-- attack_info: (table) information about the attack situation to play
--
function m15_process_attack(name, attack_info)
-- don't play this if we have done this recently
if (not thread_check_done(attack_info.timer)) then
return
end
-- start timer
attack_info.timer = thread_new("delay", attack_info.delay)
-- play situation
if not audio_play_persona_line(name, attack_info.situation) then
-- line didn't play, kill our timer
thread_kill(attack_info.timer)
attack_info.timer = INVALID_THREAD_HANDLE
end
end
-- play attack lines for deckers
--
-- name: (string) name of character
-- weapon: (string) name of weapon
-- attack: (string) type of attack performed
--
function m15_decker_attack_cb(name, weapon, attack)
m15_process_attack(name, M15_attack_info.decker)
end
-- play attack lines for stag
--
-- name: (string) name of character
-- weapon: (string) name of weapon
-- attack: (string) type of attack performed
--
function m15_stag_attack_cb(name, weapon, attack)
m15_process_attack(name, M15_attack_info.stag)
end
-- *************************
--
-- Thread functions
--
-- *************************
function m15_saints_kneecapper_thread(vehicle)
local extra_delay = rand_int(0, 5) / 10
delay(1.0 + extra_delay)
vehicle_enter_group_teleport(vehicle.npcs, vehicle.vehicle)
vehicle_suppress_npc_exit(vehicle.vehicle, true)
vehicle_set_kneecappers(vehicle.vehicle, true)
for key, npc_name in pairs(vehicle.npcs) do
set_ignore_ai_flag(npc_name, false)
set_dont_attack_me_on_sight_flag(npc_name, false)
character_prevent_explosion_fling(npc_name, false)
character_prevent_flinching(npc_name, false)
end
vehicle_pathfind_to(vehicle.vehicle, vehicle.path, true, true)
vehicle_suppress_npc_exit(vehicle.vehicle, false)
vehicle_exit_group(vehicle.npcs)
vehicle.thread = INVALID_THREAD_HANDLE
end
function m15_roadblock_pathfind_to_thread(trigger, vehicle_table)
vehicle_pathfind_to(vehicle_table.vehicle, vehicle_table.path, true)
for key, npc in pairs(vehicle_table.members) do
set_ignore_ai_flag(npc, false)
end
if vehicle_table.gunner ~= nil and vehicle_table.gunner ~= "" then
set_ignore_ai_flag(vehicle_table.gunner, false)
end
vehicle_exit_group(vehicle_table.members)
vehicle_table.thread = INVALID_THREAD_HANDLE
m15_maybe_cleanup_chase_trigger(trigger)
end
function m15_chase_pathfind_to_thread(trigger, vehicle_table)
vehicle_pathfind_to(vehicle_table.vehicle, vehicle_table.path, true)
for key, npc in pairs(vehicle_table.members) do
set_ignore_ai_flag(npc, false)
end
if vehicle_table.gunner ~= nil and vehicle_table.gunner ~= "" then
set_ignore_ai_flag(vehicle_table.gunner, false)
end
local exit_at_stop = false
vehicle_chase(vehicle_table.vehicle, NPC_DRIVER, exit_at_stop)
vehicle_table.thread = INVALID_THREAD_HANDLE
end
function m15_cleanup_all_waves()
for key, wave in pairs(M15_waves) do
-- Kill the wave thread if it is running
if wave.thread ~= INVALID_THREAD_HANDLE then
thread_kill(wave.thread)
wave.thread = INVALID_THREAD_HANDLE
end
-- Cleanup any active groups
for group_key, group in pairs(wave.groups) do
if group_is_loaded(group.name) then
m15_cleanup_enemy_set(group)
release_to_world(group.name)
group_destroy(group.name)
end
end
end
end
function m15_process_enemy_set(group, leash_to, leash_radius, attack_cb)
m15_setup_enemy_set(group, attack_cb)
-- Tell the group to run to a random nav point
m15_enemy_set_run_to_nav(group)
-- Flag the NPCs as mission critical
m15_enemy_set_flag(group)
-- Leash the NPCs so they don't attempt to leave their area
m15_enemey_set_leash(group, leash_to, leash_radius)
local members_alive = 2
while members_alive > 0 do
members_alive = 0
for key, npc in pairs(group.members) do
-- we only care that the value is not zero, so move on once we know that
if (members_alive == 0) and not character_is_dead(npc) then
members_alive = members_alive + 1
end
end
thread_yield()
end
m15_cleanup_enemy_set(group)
end
function m15_setup_enemy_set(group, attack_cb)
for key, npc in pairs(group.members) do
if not character_is_dead(npc) then
-- set a callback to know when the enemy is killed
on_death("m15_enemy_killed", npc)
on_attack_performed(attack_cb, npc)
end
end
if group.human_shield ~= "" then
character_take_human_shield(group.members[1], group.human_shield, true)
end
end
function m15_cleanup_enemy_set(group)
for key, npc in pairs(group.members) do
marker_remove(npc)
on_death("", npc)
on_attack_performed("", npc)
end
end
function m15_enemy_set_run_to_nav(group)
if sizeof_table(group.run_to) == 0 then
return
end
-- Check to see if a player is to close to the NPCs for them to run to cover
if group.dont_run_trigger ~= "" then
local in_trigger = object_is_in_trigger(group.dont_run_trigger, LOCAL_PLAYER)
if M15_in_coop then
in_trigger = in_trigger or object_is_in_trigger(group.dont_run_trigger, REMOTE_PLAYER)
end
if in_trigger then
return
end
end
-- Tell all NPCS to run to a random nav point
for key, npc in pairs(group.members) do
local nav = get_random_table_entry(group.run_to)
ai_do_scripted_move(npc, nav, true, true)
end
end
function m15_enemy_set_flag(group)
if M15_mark_waves == false then
return
end
for key, npc in pairs(group.members) do
if not character_is_dead(npc) then
marker_add(npc, MINIMAP_ICON_KILL, OI_ASSET_KILL, OI_FLAGS_DEFAULT, SYNC_ALL)
end
end
end
function m15_enemey_set_leash(group, leash_to, leash_radius)
if leash_to == "" then
return
end
for key, npc in pairs(group.members) do
if not character_is_dead(npc) then
npc_leash_to_nav(npc, leash_to, leash_radius)
end
end
end
function m15_enemy_killed(enemy)
marker_remove(enemy)
on_death("", enemy)
M15_extra_npcs_in_lobby[enemy] = nil
end
-- wave: the waves of enemies to spawn.
--
function m15_run_wave(wave)
-- set up the wave counter
wave.num_remaining = #wave.groups
for key, group in pairs(wave.groups) do
if not M15_in_pr_center then
-- if we are not inside yet, just wait until we are
group_create_hidden(group.name, true)
while not M15_in_pr_center do
thread_yield()
end
group_show(group.name)
else
group_create(group.name, true)
end
m15_process_enemy_set(group, wave.leash_to, wave.leash_radius, wave.attack_cb)
-- decrement the wave counter
wave.num_remaining = wave.num_remaining - 1
release_to_world(group.name)
group_destroy(group.name)
delay(M15_DELAY_BETWEEN_WAVE_SPAWNS)
end
wave.thread = INVALID_THREAD_HANDLE
end
function m15_mark_wave_npcs(wave)
if wave.thread == INVALID_THREAD_HANDLE then
return
end
for key, group in pairs(wave.groups) do
if group_is_loaded(group.name) then
m15_enemy_set_flag(group)
end
end
end
function m15_mark_extra_npcs()
if M15_mark_waves == false then
return
end
-- NPC names are used as the key for the table. val is a bool that is set to true
for npc, val in pairs(M15_extra_npcs_in_lobby) do
marker_add(npc, MINIMAP_ICON_KILL, OI_ASSET_KILL, OI_FLAGS_DEFAULT, SYNC_ALL)
end
end
function m15_cleanup_extra_npcs()
for npc, val in pairs(M15_extra_npcs_in_lobby) do
m15_enemy_killed(npc)
end
end
-- Kills any active audio threads and stops any audio they were playing
--
function m15_cleanup_audio_threads()
if M15_THREAD_CONVO_TO_PR_CENTER ~= INVALID_THREAD_HANDLE then
thread_kill(M15_THREAD_CONVO_TO_PR_CENTER)
M15_THREAD_CONVO_TO_PR_CENTER = INVALID_THREAD_HANDLE
m15_unload_convo_set(M15_convo.to_pr_center)
end
if M15_THREAD_CONVO_PR_CENTER ~= INVALID_THREAD_HANDLE then
thread_kill(M15_THREAD_CONVO_PR_CENTER)
M15_THREAD_CONVO_PR_CENTER = INVALID_THREAD_HANDLE
m15_unload_convo_set(M15_convo.pr_center_outside)
end
if M15_THREAD_CONVO_IN_LOBBY ~= INVALID_THREAD_HANDLE then
thread_kill(M15_THREAD_CONVO_IN_LOBBY)
M15_THREAD_CONVO_IN_LOBBY = INVALID_THREAD_HANDLE
m15_unload_convo_set(M15_convo.pr_center_lobby)
end
if M15_THREAD_CONVO_IN_GARAGE ~= INVALID_THREAD_HANDLE then
thread_kill(M15_THREAD_CONVO_IN_GARAGE)
M15_THREAD_CONVO_IN_GARAGE = INVALID_THREAD_HANDLE
m15_unload_convo_set(M15_convo.pr_center_garage)
end
if M15_THREAD_CONVO_TRUCK_FOUND ~= INVALID_THREAD_HANDLE then
thread_kill(M15_THREAD_CONVO_TRUCK_FOUND)
M15_THREAD_CONVO_TRUCK_FOUND = INVALID_THREAD_HANDLE
m15_unload_convo_set(M15_convo.chase_found_truck_in_tank)
m15_unload_convo_set(M15_convo.chase_found_truck)
end
if M15_THREAD_CONVO_DONT_USE_CANNON ~= INVALID_THREAD_HANDLE then
thread_kill(M15_THREAD_CONVO_DONT_USE_CANNON)
M15_THREAD_CONVO_DONT_USE_CANNON = INVALID_THREAD_HANDLE
end
end
function m15_convo_to_pr_center()
m15_play_audio_set(M15_convo.to_pr_center)
M15_THREAD_CONVO_TO_PR_CENTER = INVALID_THREAD_HANDLE
end
function m15_convo_pr_center()
m15_play_audio_set(M15_convo.pr_center_outside)
audio_play_persona_line(NPC_PIERCE, "M15_Get_In_Building")
M15_THREAD_CONVO_PR_CENTER = INVALID_THREAD_HANDLE
end
function m15_convo_in_lobby()
-- Play the conversation that happens when you first enter the lobby.
m15_play_audio_set(M15_convo.pr_center_lobby)
-- wait for the very last wave.
while (M15_waves.lobby_decker.num_remaining + M15_waves.lobby_stag.num_remaining) > 1 do
thread_yield()
end
-- Play the conversation to prompt you to leave the lobby.
m15_play_audio_set(M15_convo.go_to_garage)
m15_cleanup_audio_threads()
M15_THREAD_CONVO_IN_LOBBY = INVALID_THREAD_HANDLE
end
function m15_convo_in_garage()
m15_play_audio_set(M15_convo.pr_center_garage)
M15_THREAD_CONVO_IN_GARAGE = INVALID_THREAD_HANDLE
end
function m15_convo_truck_found()
if m15_player_is_in_a_tank() then
m15_play_audio_set(M15_convo.chase_found_truck_in_tank)
end
m15_play_audio_set(M15_convo.chase_found_truck)
M15_THREAD_CONVO_TRUCK_FOUND = INVALID_THREAD_HANDLE
end
function m15_convo_player_used_cannon_on_truck()
--message("Kinzie: I said don't use the cannon. Use your minigun!", 4.0)
-- Give at brief grace period to allow the player to hear Kinzie's warning
on_take_damage("", VEHICLE_SUPER_COMPUTER)
delay(2.0)
on_take_damage("m15_vehicle_damaged", VEHICLE_SUPER_COMPUTER)
message("M15_MSG_USE_ALT_FIRE", 8.0)
M15_THREAD_CONVO_DONT_USE_CANNON = INVALID_THREAD_HANDLE
end
-- Check to see if all the tires have been destroyed
--
function m15_thread_tire_check()
while true do
local n_tires_left = vehicle_tire_indicators_alive(VEHICLE_SUPER_COMPUTER)
if n_tires_left == 0 then
m15_music("M15_music_truck_chase_End")
-- Forcefully stop the vehicle in its tracks
vehicle_speed_cancel(VEHICLE_SUPER_COMPUTER)
vehicle_stop(VEHICLE_SUPER_COMPUTER, true)
m15_vehicle_remove_marker()
mission_end_success("m15", M15_CUTSCENE_MISSION_OUTRO, {M15_NAV_START[1], M15_NAV_START[2]})
end
thread_yield()
end
end
function m15_thread_chase_rubber_band()
while true do
local dist = get_dist_closest_player_to_object(VEHICLE_SUPER_COMPUTER) - RUBBER_BAND_CLOSE_DIST
local dist_diff = (RUBBER_BAND_FAR_DIST - RUBBER_BAND_CLOSE_DIST)
local dist_pct = dist / dist_diff
if (dist_pct < 0.0) then
dist_pct = 0.0
elseif (dist_pct > 1.0) then
dist_pct = 1.0
end
local speed_diff = RUBBER_BAND_FAR_SPEED - RUBBER_BAND_CLOSE_SPEED
local adjusted_speed = RUBBER_BAND_CLOSE_SPEED + (dist_pct * speed_diff)
vehicle_speed_override(VEHICLE_SUPER_COMPUTER, adjusted_speed)
delay(3.0)
end
end
function m15_thread_elevator_manager()
while true do
for key, elevator in pairs(M15_elevators) do
local dist_to_npc, npc = get_closest_npc_to_object(elevator.nav, M15_ELEVATOR_OPEN_PROXIMITY)
local should_open = false
if npc ~= "" then
-- A script NPC is in range. Open the doors.
should_open = true
else
-- A script NPC is not in range. Check to see if a player is nearby.
local dist = get_dist(LOCAL_PLAYER, elevator.nav)
if dist < M15_ELEVATOR_OPEN_PROXIMITY then
should_open = true
end
if M15_in_coop == true then
dist = get_dist(REMOTE_PLAYER, elevator.nav)
if dist < M15_ELEVATOR_OPEN_PROXIMITY then
should_open = true
end
end
end
if should_open then
door_open(elevator.mover, false)
else
door_close(elevator.mover, true)
end
end
thread_yield()
end
end