#include maps/mp/zombies/_zm_clone;
#include maps/mp/zombies/_zm_laststand;
#include maps/mp/zombies/_zm_ai_basic;
#include maps/mp/animscripts/shared;
#include maps/mp/zombies/_zm_ai_brutus;
#include maps/mp/zombies/_zm_audio;
#include maps/mp/zm_alcatraz_sq_nixie;
#include maps/mp/zombies/_zm_unitrigger;
#include maps/mp/zombies/_zm_equipment;
#include maps/mp/zm_alcatraz_sq_vo;
#include maps/mp/zm_prison_sq_final;
#include maps/mp/gametypes_zm/_hud;
#include maps/mp/gametypes_zm/_hud_util;
#include maps/mp/zm_alcatraz_utility;
#include maps/mp/zombies/_zm_sidequests;
#include maps/mp/zombies/_zm_craftables;
#include maps/mp/zombies/_zm_utility;
#include maps/mp/zombies/_zm_afterlife;
#include maps/_vehicle;
#include maps/_utility;
#include maps/mp/_utility;
#include common_scripts/utility;

init()
{
	precachemodel( "accessories_gas_canister_1" );
	precachemodel( "p6_zm_al_power_station_panels_03" );
	precacheitem( "falling_hands_zm" );
	precacheitem( "electrocuted_hands_zm" );
	precachestring( &"ZM_PRISON_ELECTRIC_CHAIR_ACTIVATE" );
	precachestring( &"ZM_PRISON_LAUNDRY_MACHINE_ACTIVATE" );
	precachestring( &"ZM_PRISON_PLANE_BEGIN_TAKEOFF" );
	precachestring( &"ZM_PRISON_PLANE_BOARD" );
	precachestring( &"ZM_PRISON_KEY_DOOR_LOCKED" );
	precacherumble( "damage_heavy" );
	precacherumble( "explosion_generic" );
	registerclientfield( "world", "fake_master_key", 9000, 2, "int" );
	flag_init( "map_revealed" );
	flag_init( "key_found" );
	flag_init( "cloth_found" );
	flag_init( "fueltanks_found" );
	flag_init( "engine_found" );
	flag_init( "steering_found" );
	flag_init( "rigging_found" );
	flag_init( "plane_ready" );
	flag_init( "plane_built" );
	flag_init( "plane_boarded" );
	flag_init( "plane_departed" );
	flag_init( "plane_approach_bridge" );
	flag_init( "plane_zapped" );
	flag_init( "plane_crashed" );
	flag_init( "portal_open" );
	flag_init( "spawn_fuel_tanks" );
	flag_init( "plane_is_away" );
	flag_init( "plane_trip_to_nml_successful" );
	flag_init( "story_vo_playing" );
	flag_init( "docks_inner_gate_unlocked" );
	flag_init( "docks_inner_gate_open" );
	flag_init( "docks_outer_gate_open" );
	flag_init( "docks_gates_remain_open" );
	flag_init( "nixie_puzzle_solved" );
	flag_init( "nixie_countdown_started" );
	flag_init( "nixie_countdown_expired" );
	flag_init( "nixie_puzzle_completed" );
	flag_init( "generator_challenge_completed" );
	flag_init( "dryer_cycle_active" );
	flag_init( "quest_completed_thrice" );
	flag_init( "final_quest_ready" );
	flag_init( "final_quest_audio_tour_started" );
	flag_init( "final_quest_audio_tour_finished" );
	flag_init( "final_quest_plane_built" );
	flag_init( "final_quest_plane_boarded" );
	flag_init( "final_quest_plane_departed" );
	flag_init( "final_quest_plane_zapped" );
	flag_init( "final_quest_plane_crashed" );
	flag_init( "final_quest_final_battle_started" );
	flag_init( "final_quest_good_wins" );
	flag_init( "final_quest_evil_wins" );
	flag_init( "nixie_ee_flashing" );
}

start_alcatraz_sidequest()
{
	init();
	onplayerconnect_callback( ::player_disconnect_watcher );
	onplayerconnect_callback( ::player_death_watcher );
	flag_wait( "start_zombie_round_logic" );
/#
	setup_devgui();
#/
	level.n_quest_iteration_count = 1;
	level.n_plane_fuel_count = 5;
	level.n_plane_pieces_found = 0;
	level.final_flight_players = [];
	level.final_flight_activated = 0;
	level.characters_in_nml = [];
	level.someone_has_visited_nml = 0;
	level.custom_game_over_hud_elem = ::maps/mp/zm_prison_sq_final::custom_game_over_hud_elem;
	prevent_theater_mode_spoilers();
	setup_key_doors();
	setup_puzzle_piece_glint();
	setup_puzzles();
	setup_quest_triggers();
	if ( isDefined( level.gamedifficulty ) && level.gamedifficulty != 0 )
	{
		maps/mp/zm_prison_sq_final::final_flight_setup();
	}
	level thread warden_fence_hotjoin_handler();
	if ( isDefined( level.host_migration_listener_custom_func ) )
	{
		level thread [[ level.host_migration_listener_custom_func ]]();
	}
	else
	{
		level thread host_migration_listener();
	}
	if ( isDefined( level.manage_electric_chairs_custom_func ) )
	{
		level thread [[ level.manage_electric_chairs_custom_func ]]();
	}
	else
	{
		level thread manage_electric_chairs();
	}
	if ( isDefined( level.plane_flight_thread_custom_func ) )
	{
		level thread [[ level.plane_flight_thread_custom_func ]]();
	}
	else
	{
		level thread plane_flight_thread();
	}
	if ( isDefined( level.track_quest_status_thread_custom_func ) )
	{
		level thread [[ level.track_quest_status_thread_custom_func ]]();
	}
	else
	{
		level thread track_quest_status_thread();
	}
	maps/mp/zm_alcatraz_sq_vo::opening_vo();
}

host_migration_listener()
{
	level endon( "end_game" );
	level notify( "afterlife_hostmigration" );
	level endon( "afterlife_hostmigration" );
	while ( 1 )
	{
		level waittill( "host_migration_end" );
		m_plane_craftable = getent( "plane_craftable", "targetname" );
		m_plane_about_to_crash = getent( "plane_about_to_crash", "targetname" );
		veh_plane_flyable = getent( "plane_flyable", "targetname" );
		a_players = getplayers();
		if ( flag( "plane_boarded" ) && !flag( "plane_departed" ) )
		{
			_a215 = a_players;
			_k215 = getFirstArrayKey( _a215 );
			while ( isDefined( _k215 ) )
			{
				player = _a215[ _k215 ];
				if ( isDefined( player ) && isDefined( player.character_name ) && isinarray( level.characters_in_nml, player.character_name ) )
				{
					player playerlinktodelta( m_plane_craftable, "tag_player_crouched_" + ( player.n_passenger_index + 1 ) );
				}
				_k215 = getNextArrayKey( _a215, _k215 );
			}
		}
		else if ( flag( "plane_departed" ) && !flag( "plane_approach_bridge" ) )
		{
			_a225 = a_players;
			_k225 = getFirstArrayKey( _a225 );
			while ( isDefined( _k225 ) )
			{
				player = _a225[ _k225 ];
				if ( isDefined( player ) && isDefined( player.character_name ) && isinarray( level.characters_in_nml, player.character_name ) )
				{
					player playerlinktodelta( veh_plane_flyable, "tag_player_crouched_" + ( player.n_passenger_index + 1 ) );
				}
				_k225 = getNextArrayKey( _a225, _k225 );
			}
		}
		else if ( flag( "plane_approach_bridge" ) && !flag( "plane_zapped" ) )
		{
			_a235 = a_players;
			_k235 = getFirstArrayKey( _a235 );
			while ( isDefined( _k235 ) )
			{
				player = _a235[ _k235 ];
				if ( isDefined( player ) && isDefined( player.character_name ) && isinarray( level.characters_in_nml, player.character_name ) )
				{
					player playerlinktoabsolute( veh_plane_flyable, "tag_player_crouched_" + ( player.n_passenger_index + 1 ) );
				}
				_k235 = getNextArrayKey( _a235, _k235 );
			}
		}
		else while ( flag( "plane_zapped" ) && !flag( "plane_crashed" ) )
		{
			_a245 = a_players;
			_k245 = getFirstArrayKey( _a245 );
			while ( isDefined( _k245 ) )
			{
				player = _a245[ _k245 ];
				if ( isDefined( player ) && isDefined( player.character_name ) && isinarray( level.characters_in_nml, player.character_name ) )
				{
					player playerlinktodelta( m_plane_about_to_crash, "tag_player_crouched_" + ( player.n_passenger_index + 1 ), 1, 0, 0, 0, 0, 1 );
				}
				_k245 = getNextArrayKey( _a245, _k245 );
			}
		}
		setup_puzzle_piece_glint();
		setclientfield( "fake_master_key", level.is_master_key_west + 1 );
		if ( !flag( "key_found" ) )
		{
			if ( level.is_master_key_west )
			{
				exploder( 101 );
				break;
			}
			else
			{
				exploder( 100 );
			}
		}
	}
}

prevent_theater_mode_spoilers()
{
	flag_wait( "initial_blackscreen_passed" );
	m_plane_flyable = getent( "plane_flyable", "targetname" );
	m_plane_flyable setinvisibletoall();
	m_plane_hideable_engine = getent( "plane_hideable_engine", "targetname" );
	m_plane_hideable_engine ghost();
	m_plane_hideable_clothes_pile = getent( "plane_hideable_clothes_pile", "targetname" );
	m_plane_hideable_clothes_pile ghost();
	a_str_partnames = [];
	a_str_partnames[ 0 ] = "cloth";
	a_str_partnames[ 1 ] = "steering";
	i = 0;
	while ( i < a_str_partnames.size )
	{
		m_plane_piece = get_craftable_piece_model( "plane", a_str_partnames[ i ] );
		if ( isDefined( m_plane_piece ) )
		{
			m_plane_piece setinvisibletoall();
		}
		i++;
	}
	m_master_key = get_craftable_piece_model( "quest_key1", "p6_zm_al_key" );
	if ( isDefined( m_master_key ) )
	{
		m_master_key setinvisibletoall();
	}
}

setup_puzzle_piece_glint()
{
	wait 1;
	a_str_partnames = [];
	a_str_partnames[ 0 ] = "cloth";
	a_str_partnames[ 1 ] = "fueltanks";
	a_str_partnames[ 2 ] = "engine";
	a_str_partnames[ 3 ] = "steering";
	a_str_partnames[ 4 ] = "rigging";
	i = 0;
	while ( i < a_str_partnames.size )
	{
		m_plane_piece = get_craftable_piece_model( "plane", a_str_partnames[ i ] );
		if ( isDefined( m_plane_piece ) )
		{
			playfxontag( level._effect[ "quest_item_glow" ], m_plane_piece, "tag_origin" );
		}
		m_fuel_can = get_craftable_piece_model( "refuelable_plane", "fuel" + ( i + 1 ) );
		if ( isDefined( m_fuel_can ) )
		{
			playfxontag( level._effect[ "quest_item_glow" ], m_fuel_can, "tag_origin" );
		}
		i++;
	}
	m_master_key = get_craftable_piece_model( "quest_key1", "p6_zm_al_key" );
	if ( isDefined( m_master_key ) )
	{
		playfxontag( level._effect[ "key_glint" ], m_master_key, "tag_origin" );
	}
	m_fake_plane_steering = getent( "fake_veh_t6_dlc_zombie_part_control", "targetname" );
	if ( isDefined( m_fake_plane_steering ) )
	{
		playfxontag( level._effect[ "quest_item_glow" ], m_fake_plane_steering, "tag_origin" );
	}
}

setup_devgui()
{
	setdvar( "add_afterlife", "off" );
	setdvar( "build_plane", "off" );
	setdvar( "get_master_key", "off" );
	setdvar( "alcatraz_final_battle", "off" );
	setdvar( "alcatraz_give_shield", "off" );
/#
	adddebugcommand( "devgui_cmd "Zombies/Alcatraz:1/Add Afterlife" "add_afterlife on"\n" );
	adddebugcommand( "devgui_cmd "Zombies/Alcatraz:1/Get Master Key" "get_master_key on"\n" );
	adddebugcommand( "devgui_cmd "Zombies/Alcatraz:1/Alcatraz Final Battle" "alcatraz_final_battle on"\n" );
	adddebugcommand( "devgui_cmd "Zombies/Alcatraz:1/Build Plane:1" "build_plane on"\n" );
	adddebugcommand( "devgui_cmd "Zombies/Alcatraz:1/Give Shield:1" "alcatraz_give_shield on"\n" );
#/
	level thread watch_devgui_alcatraz_final_battle();
	level thread watch_devgui_afterlife();
	level thread watch_devgui_plane();
	level thread watch_devgui_get_key();
	level thread watch_devgui_give_shield();
}

watch_devgui_alcatraz_final_battle()
{
	while ( 1 )
	{
		if ( getDvar( "alcatraz_final_battle" ) == "on" )
		{
			players = getplayers();
			_a379 = players;
			_k379 = getFirstArrayKey( _a379 );
			while ( isDefined( _k379 ) )
			{
				player = _a379[ _k379 ];
/#
				iprintlnbold( "LINK PLAYER TO PLANE, START COUNTDOWN IF NOT YET STARTED" );
#/
				level.final_flight_activated = 1;
				player thread final_flight_player_thread();
				_k379 = getNextArrayKey( _a379, _k379 );
			}
			setdvar( "alcatraz_final_battle", "off" );
		}
		wait 0,1;
	}
}

watch_devgui_get_key()
{
	while ( 1 )
	{
		if ( getDvar( "get_master_key" ) == "on" )
		{
			a_players = [];
			a_players = getplayers();
			m_master_key = get_craftable_piece_model( "quest_key1", "p6_zm_al_key" );
			if ( isDefined( m_master_key ) )
			{
				m_master_key.origin = a_players[ 0 ].origin + vectorScale( ( 0, 0, 0 ), 60 );
				m_master_key setvisibletoall();
			}
			setdvar( "get_master_key", "off" );
		}
		wait 0,1;
	}
}

watch_devgui_afterlife()
{
	while ( 1 )
	{
		if ( getDvar( "add_afterlife" ) == "on" )
		{
			a_players = [];
			a_players = getplayers();
			i = 0;
			while ( i < a_players.size )
			{
				a_players[ i ] afterlife_add();
				i++;
			}
			setdvar( "add_afterlife", "off" );
		}
		wait 0,1;
	}
}

watch_devgui_give_shield()
{
	while ( 1 )
	{
		if ( getDvar( "alcatraz_give_shield" ) == "on" )
		{
			_a438 = getplayers();
			_k438 = getFirstArrayKey( _a438 );
			while ( isDefined( _k438 ) )
			{
				player = _a438[ _k438 ];
				if ( is_equipment_included( "alcatraz_shield_zm" ) )
				{
					player maps/mp/zombies/_zm_equipment::equipment_buy( "alcatraz_shield_zm" );
				}
				_k438 = getNextArrayKey( _a438, _k438 );
			}
			setdvar( "alcatraz_give_shield", "off" );
		}
		wait 0,05;
	}
}

watch_devgui_plane()
{
	is_shortcut_plane_built = 0;
	while ( !is_shortcut_plane_built )
	{
		if ( getDvar( "build_plane" ) == "on" )
		{
/#
			iprintlnbold( "plane built!" );
#/
			is_shortcut_plane_built = 1;
		}
		wait 0,1;
	}
	i = 0;
	while ( i < level.a_uts_craftables.size )
	{
		if ( level.a_uts_craftables[ i ].equipname == "plane" )
		{
			level.a_uts_craftables[ i ].crafted = 1;
		}
		i++;
	}
	level thread maps/mp/zm_alcatraz_sq_vo::escape_flight_vo();
	plane_craftable = getent( "plane_craftable", "targetname" );
	plane_craftable showpart( "tag_support_upper" );
	plane_craftable showpart( "tag_wing_skins_up" );
	plane_craftable showpart( "tag_engines_up" );
	plane_craftable showpart( "tag_feul_tanks" );
	plane_craftable showpart( "tag_control_mechanism" );
	plane_craftable showpart( "tag_fuel_hose" );
	t_plane_fly = getent( "plane_fly_trigger", "targetname" );
	t_plane_fly trigger_on();
	t_plane_fly.require_look_at = 0;
	while ( isDefined( t_plane_fly ) )
	{
		t_plane_fly waittill( "trigger", e_triggerer );
		if ( isplayer( e_triggerer ) )
		{
/#
			iprintlnbold( e_triggerer );
#/
			while ( isDefined( level.custom_plane_validation ) )
			{
				valid = t_plane_fly [[ level.custom_plane_validation ]]( e_triggerer );
				while ( !valid )
				{
					continue;
				}
			}
			if ( level.n_plane_fuel_count == 5 )
			{
				if ( isDefined( level.plane_boarding_thread_custom_func ) )
				{
					e_triggerer thread [[ level.plane_boarding_thread_custom_func ]]();
					break;
				}
				else
				{
/#
					iprintlnbold( "LINK PLAYER TO PLANE, START COUNTDOWN IF NOT YET STARTED" );
#/
					e_triggerer thread plane_boarding_thread();
				}
			}
		}
	}
}

setup_key_doors()
{
	width = 0;
	height = 0;
	length = 0;
	piece_number = 1;
	while ( piece_number < 6 )
	{
		switch( piece_number )
		{
			case 1:
				width = 120;
				height = 112;
				length = 120;
				break;
			case 2:
				width = 120;
				height = 112;
				length = 124;
				break;
			case 3:
				width = 108;
				height = 112;
				length = 90;
				break;
			case 4:
				width = 98;
				height = 112;
				length = 108;
				break;
			case 5:
				width = 60;
				height = 112;
				length = 90;
				break;
		}
		create_key_door_unitrigger( piece_number, width, height, length );
		piece_number++;
	}
}

create_key_door_unitrigger( piece_num, width, height, length )
{
	t_key_door = getstruct( "key_door_" + piece_num + "_trigger", "targetname" );
	t_key_door.unitrigger_stub = spawnstruct();
	t_key_door.unitrigger_stub.origin = t_key_door.origin;
	t_key_door.unitrigger_stub.angles = t_key_door.angles;
	t_key_door.unitrigger_stub.script_unitrigger_type = "unitrigger_box_use";
	t_key_door.unitrigger_stub.hint_string = &"ZM_PRISON_KEY_DOOR_LOCKED";
	t_key_door.unitrigger_stub.cursor_hint = "HINT_NOICON";
	t_key_door.unitrigger_stub.script_width = width;
	t_key_door.unitrigger_stub.script_height = height;
	t_key_door.unitrigger_stub.script_length = length;
	t_key_door.unitrigger_stub.n_door_index = piece_num;
	t_key_door.unitrigger_stub.require_look_at = 0;
	t_key_door.unitrigger_stub.prompt_and_visibility_func = ::key_door_trigger_visibility;
	maps/mp/zombies/_zm_unitrigger::register_static_unitrigger( t_key_door.unitrigger_stub, ::master_key_door_trigger_thread );
}

key_door_trigger_visibility( player )
{
	if ( !player.afterlife && isDefined( self.stub.master_key_door_opened ) && !self.stub.master_key_door_opened )
	{
		if ( self.stub.n_door_index == 2 )
		{
			b_is_invis = !flag( "generator_challenge_completed" );
		}
	}
	self setinvisibletoplayer( player, b_is_invis );
	if ( flag( "key_found" ) )
	{
		self sethintstring( &"ZM_PRISON_KEY_DOOR" );
	}
	else
	{
		self sethintstring( self.stub.hint_string );
	}
	return !b_is_invis;
}

master_key_door_trigger_thread()
{
	self endon( "death" );
	self endon( "kill_trigger" );
	n_door_index = self.stub.n_door_index;
	b_door_open = 0;
	while ( !b_door_open )
	{
		self waittill( "trigger", e_triggerer );
		if ( e_triggerer is_holding_part( "quest_key1", "p6_zm_al_key" ) )
		{
			self.stub.master_key_door_opened = 1;
			self.stub maps/mp/zombies/_zm_unitrigger::run_visibility_function_for_all_triggers();
			level thread open_custom_door_master_key( n_door_index, e_triggerer );
			self playsound( "evt_quest_door_open" );
			b_door_open = 1;
			continue;
		}
		else
		{
			e_triggerer thread do_player_general_vox( "quest", "sidequest_key", undefined, 100 );
/#
			iprintlnbold( "missing key!" );
#/
		}
	}
	level thread maps/mp/zombies/_zm_unitrigger::unregister_unitrigger( self.stub );
}

open_custom_door_master_key( n_door_index, e_triggerer )
{
	m_lock = getent( "masterkey_lock_" + n_door_index, "targetname" );
	m_lock playsound( "zmb_quest_key_unlock" );
	playfxontag( level._effect[ "fx_alcatraz_unlock_door" ], m_lock, "tag_origin" );
	wait 0,5;
	m_lock delete();
	switch( n_door_index )
	{
		case 1:
			shower_key_door = getent( "shower_key_door", "targetname" );
			shower_key_door moveto( shower_key_door.origin + vectorScale( ( 0, 0, 0 ), 80 ), 0,25 );
			shower_key_door connectpaths();
			if ( isDefined( e_triggerer ) )
			{
				e_triggerer door_rumble_on_open();
			}
			shower_key_door playsound( "zmb_chainlink_open" );
			break;
		case 2:
			admin_powerhouse_puzzle_door_clip = getent( "admin_powerhouse_puzzle_door_clip", "targetname" );
			admin_powerhouse_puzzle_door_clip delete();
			admin_powerhouse_puzzle_door = getent( "admin_powerhouse_puzzle_door", "targetname" );
			admin_powerhouse_puzzle_door rotateyaw( 90, 0,5 );
			admin_powerhouse_puzzle_door playsound( "zmb_chainlink_open" );
			break;
		case 3:
			m_nixie_door_left = getent( "nixie_door_left", "targetname" );
			m_nixie_door_right = getent( "nixie_door_right", "targetname" );
			m_nixie_door_left rotateyaw( -165, 0,5 );
			m_nixie_door_right rotateyaw( 165, 0,5 );
			m_nixie_tube_weaponclip = getent( "nixie_tube_weaponclip", "targetname" );
			m_nixie_tube_weaponclip delete();
			if ( isDefined( e_triggerer ) )
			{
				e_triggerer door_rumble_on_open();
			}
			break;
		case 4:
			m_gate_01 = getent( "cable_puzzle_gate_01", "targetname" );
			m_gate_01 moveto( m_gate_01.origin + ( -16, 80, 0 ), 0,5 );
			m_gate_01 connectpaths();
			gate_1_monsterclip = getent( "docks_gate_1_monsterclip", "targetname" );
			gate_1_monsterclip.origin += vectorScale( ( 0, 0, 0 ), 256 );
			gate_1_monsterclip disconnectpaths();
			gate_1_monsterclip.origin -= vectorScale( ( 0, 0, 0 ), 256 );
			if ( isDefined( e_triggerer ) )
			{
				e_triggerer door_rumble_on_open();
			}
			m_gate_01 playsound( "zmb_chainlink_open" );
			flag_set( "docks_inner_gate_unlocked" );
			flag_set( "docks_inner_gate_open" );
			break;
		case 5:
			m_infirmary_case_door_left = getent( "infirmary_case_door_left", "targetname" );
			m_infirmary_case_door_right = getent( "infirmary_case_door_right", "targetname" );
			m_infirmary_case_door_left rotateyaw( -165, 0,5 );
			m_infirmary_case_door_right rotateyaw( 165, 0,5 );
			m_fake_plane_steering = getent( "fake_veh_t6_dlc_zombie_part_control", "targetname" );
			m_plane_steering = get_craftable_piece_model( "plane", "steering" );
			m_plane_steering moveto( m_plane_steering.origin + vectorScale( ( 0, 0, 0 ), 512 ), 0,05 );
			m_plane_steering setvisibletoall();
			m_fake_plane_steering hide();
			if ( isDefined( e_triggerer ) )
			{
				e_triggerer door_rumble_on_open();
			}
			m_infirmary_case_door_right playsound( "zmb_cabinet_door" );
			break;
	}
}

door_rumble_on_open()
{
	self endon( "disconnect" );
	level endon( "end_game" );
	self setclientfieldtoplayer( "rumble_door_open", 1 );
	wait_network_frame();
	self setclientfieldtoplayer( "rumble_door_open", 0 );
}

setup_puzzles()
{
	level thread setup_master_key();
	level thread setup_dryer_challenge();
	level thread setup_generator_challenge();
	level thread maps/mp/zm_alcatraz_sq_nixie::setup_nixie_tubes_puzzle();
	level thread setup_gate_puzzle();
}

setup_quest_triggers()
{
	t_plane_fuelable = getent( "plane_fuelable_trigger", "targetname" );
	t_plane_fuelable trigger_off();
	t_plane_fly = getent( "plane_fly_trigger", "targetname" );
	t_plane_fly setcursorhint( "HINT_NOICON" );
	t_plane_fly sethintstring( &"ZM_PRISON_PLANE_BOARD" );
	t_plane_fly.require_look_at = 0;
	t_plane_fly thread plane_fly_trigger_thread();
}

setup_master_key()
{
	level.is_master_key_west = randomintrange( 0, 2 );
	setclientfield( "fake_master_key", level.is_master_key_west + 1 );
	if ( level.is_master_key_west )
	{
		level thread key_pulley( "west" );
		exploder( 101 );
		array_delete( getentarray( "wires_pulley_east", "script_noteworthy" ) );
	}
	else
	{
		level thread key_pulley( "east" );
		exploder( 100 );
		array_delete( getentarray( "wires_pulley_west", "script_noteworthy" ) );
	}
}

key_pulley( str_master_key_location )
{
	if ( level.is_master_key_west )
	{
		t_other_hurt_trigger = getent( "pulley_hurt_trigger_east", "targetname" );
		t_other_panel = getent( "master_key_pulley_east", "targetname" );
	}
	else
	{
		t_other_hurt_trigger = getent( "pulley_hurt_trigger_west", "targetname" );
		t_other_panel = getent( "master_key_pulley_west", "targetname" );
	}
	t_other_hurt_trigger delete();
	t_other_panel setmodel( "p6_zm_al_power_station_panels_03" );
	t_pulley_hurt_trigger = getent( "pulley_hurt_trigger_" + str_master_key_location, "targetname" );
	t_pulley_hurt_trigger thread maps/mp/zm_alcatraz_sq_vo::sndhitelectrifiedpulley( str_master_key_location );
	m_master_key_pulley = getent( "master_key_pulley_" + str_master_key_location, "targetname" );
	m_master_key_pulley play_fx( "fx_alcatraz_panel_on_2", m_master_key_pulley.origin, m_master_key_pulley.angles, "power_down", 1, undefined, undefined );
	m_master_key_pulley thread afterlife_interact_object_think();
	level waittill( "master_key_pulley_" + str_master_key_location );
	m_master_key_pulley playsound( "zmb_quest_generator_panel_spark" );
	m_master_key_pulley notify( "power_down" );
	m_master_key_pulley setmodel( "p6_zm_al_power_station_panels_03" );
	playfxontag( level._effect[ "fx_alcatraz_panel_ol" ], m_master_key_pulley, "tag_origin" );
	m_master_key_pulley play_fx( "fx_alcatraz_panel_off_2", m_master_key_pulley.origin, m_master_key_pulley.angles, "power_down", 1, undefined, undefined );
	if ( level.is_master_key_west )
	{
		stop_exploder( 101 );
		array_delete( getentarray( "wires_pulley_west", "script_noteworthy" ) );
	}
	else
	{
		stop_exploder( 100 );
		array_delete( getentarray( "wires_pulley_east", "script_noteworthy" ) );
	}
	t_hurt_trigger = getent( "pulley_hurt_trigger_" + str_master_key_location, "targetname" );
	t_hurt_trigger delete();
	if ( str_master_key_location == "west" )
	{
		level setclientfield( "fxanim_pulley_down_start", 1 );
	}
	else
	{
		if ( str_master_key_location == "east" )
		{
			level setclientfield( "fxanim_pulley_down_start", 2 );
		}
	}
	wait 3;
	level setclientfield( "master_key_is_lowered", 1 );
	m_master_key = get_craftable_piece_model( "quest_key1", "p6_zm_al_key" );
	if ( isDefined( m_master_key ) )
	{
		e_master_key_target = getstruct( "master_key_" + str_master_key_location + "_origin", "targetname" );
		m_master_key.origin = e_master_key_target.origin;
		m_master_key setvisibletoall();
	}
}

setup_dryer_challenge()
{
	t_dryer = getent( "dryer_trigger", "targetname" );
	t_dryer setcursorhint( "HINT_NOICON" );
	t_dryer sethintstring( &"ZM_PRISON_LAUNDRY_MACHINE_ACTIVATE" );
	t_dryer thread dryer_trigger_thread();
	t_dryer thread dryer_zombies_thread();
	t_dryer trigger_off();
	level waittill( "laundry_power_switch_afterlife" );
	level setclientfield( "dryer_stage", 1 );
/#
	iprintlnbold( "dryer can now be activated" );
#/
	t_dryer trigger_on();
	t_dryer playsound( "evt_dryer_rdy_bell" );
	wait 1;
	players = getplayers();
	_a900 = players;
	_k900 = getFirstArrayKey( _a900 );
	while ( isDefined( _k900 ) )
	{
		player = _a900[ _k900 ];
		if ( !player.afterlife && distance( player.origin, t_dryer.origin ) < 1500 )
		{
			player thread do_player_general_vox( "general", "power_on", undefined, 100 );
			return;
		}
		_k900 = getNextArrayKey( _a900, _k900 );
	}
}

dryer_trigger_thread()
{
	self endon( "death" );
	n_dryer_cycle_duration = 30;
	a_dryer_spawns = [];
	sndent = spawn( "script_origin", ( 1613, 10599, 1203 ) );
	self waittill( "trigger" );
	self trigger_off();
	level setclientfield( "dryer_stage", 2 );
	dryer_playerclip = getent( "dryer_playerclip", "targetname" );
	dryer_playerclip moveto( dryer_playerclip.origin + vectorScale( ( 0, 0, 0 ), 104 ), 0,05 );
	level clientnotify( "sndFF" );
	if ( isDefined( level.music_override ) && !level.music_override )
	{
		level notify( "sndStopBrutusLoop" );
		level thread maps/mp/zombies/_zm_audio::sndmusicstingerevent( "laundry_defend" );
	}
	exploder( 1000 );
	sndent thread snddryercountdown( n_dryer_cycle_duration );
	sndent playsound( "evt_dryer_start" );
	sndent playloopsound( "evt_dryer_lp" );
	level clientnotify( "fxanim_dryer_start" );
	flag_set( "dryer_cycle_active" );
	wait 1;
	sndset = sndmusicvariable();
	level clientnotify( "fxanim_dryer_idle_start" );
	i = 3;
	while ( i > 0 )
	{
/#
		iprintlnbold( ( ( i / 3 ) * n_dryer_cycle_duration ) + " seconds left!" );
#/
		wait ( n_dryer_cycle_duration / 3 );
		i--;

	}
	level clientnotify( "fxanim_dryer_end_start" );
	wait 2;
	flag_clear( "dryer_cycle_active" );
	dryer_playerclip = getent( "dryer_playerclip", "targetname" );
	dryer_playerclip delete();
	sndent stoploopsound();
	sndent playsound( "evt_dryer_stop" );
	if ( isDefined( sndset ) && sndset )
	{
		level.music_override = 0;
	}
	level clientnotify( "sndFF" );
	level setclientfield( "dryer_stage", 3 );
	stop_exploder( 900 );
	stop_exploder( 1000 );
	m_sheets = get_craftable_piece_model( "plane", "cloth" );
	m_sheets.origin = ( 1586,16, 10598,3, 1192 );
	m_sheets setvisibletoall();
	m_sheets ghost();
	self delete();
	sndent thread delaysndenddelete();
}

sndmusicvariable()
{
	if ( isDefined( level.music_override ) && !level.music_override )
	{
		level.music_override = 1;
		return 1;
	}
	return 0;
}

dryer_zombies_thread()
{
	n_zombie_count_min = 20;
	e_shower_zone = getent( "cellblock_shower", "targetname" );
	flag_wait( "dryer_cycle_active" );
	if ( level.round_number > 4 || isDefined( level.is_forever_solo_game ) && level.is_forever_solo_game )
	{
		if ( level.zombie_total < n_zombie_count_min )
		{
			level.zombie_total = n_zombie_count_min;
		}
		while ( flag( "dryer_cycle_active" ) )
		{
			a_zombies_in_shower = [];
			a_zombies_in_shower = get_zombies_touching_volume( "axis", "cellblock_shower", undefined );
			if ( a_zombies_in_shower.size < n_zombie_count_min )
			{
				e_zombie = get_farthest_available_zombie( e_shower_zone );
				if ( isDefined( e_zombie ) && !isinarray( a_zombies_in_shower, e_zombie ) )
				{
					e_zombie notify( "zapped" );
					e_zombie thread dryer_teleports_zombie();
				}
			}
			wait 1;
		}
	}
	else maps/mp/zombies/_zm_ai_brutus::brutus_spawn_in_zone( "cellblock_shower" );
}

get_farthest_available_zombie( e_landmark )
{
	if ( !isDefined( e_landmark ) )
	{
		return undefined;
	}
	while ( 1 )
	{
		a_zombies = getaiarray( level.zombie_team );
		if ( isDefined( a_zombies ) )
		{
			zombies = get_array_of_closest( e_landmark.origin, a_zombies );
			x = 0;
			while ( x < zombies.size )
			{
				zombie = zombies[ x ];
				if ( isDefined( zombie ) && isalive( zombie ) && isDefined( zombie.in_the_ground ) && !zombie.in_the_ground && isDefined( zombie.gibbed ) && !zombie.gibbed && isDefined( zombie.head_gibbed ) && !zombie.head_gibbed && isDefined( zombie.is_being_used_as_spawnpoint ) && !zombie.is_being_used_as_spawnpoint && zombie in_playable_area() )
				{
					zombie.is_being_used_as_spawnpoint = 1;
					return zombie;
				}
				x++;
			}
		}
		else return undefined;
		wait 0,05;
	}
}

get_zombies_touching_volume( team, volume_name, volume )
{
	if ( !isDefined( volume ) )
	{
		volume = getent( volume_name, "targetname" );
/#
		assert( isDefined( volume ), volume_name + " does not exist" );
#/
	}
	guys = getaiarray( team );
	guys_touching_volume = [];
	i = 0;
	while ( i < guys.size )
	{
		if ( guys[ i ] istouching( volume ) )
		{
			guys_touching_volume[ guys_touching_volume.size ] = guys[ i ];
		}
		i++;
	}
	return guys_touching_volume;
}

dryer_teleports_zombie()
{
	self endon( "death" );
	self endon( "zapped" );
	if ( self.ai_state == "find_flesh" )
	{
		self.zapped = 1;
		a_nodes = getstructarray( "dryer_zombie_teleports", "targetname" );
		nd_target = random( a_nodes );
		playfx( level._effect[ "afterlife_teleport" ], self.origin );
		self hide();
		linker = spawn( "script_origin", ( 0, 0, 0 ) );
		linker thread linker_delete_watch( self );
		linker.origin = self.origin;
		linker.angles = self.angles;
		self linkto( linker );
		linker moveto( nd_target.origin, 0,05 );
		linker waittill( "movedone" );
		playfx( level._effect[ "afterlife_teleport" ], self.origin );
		linker delete();
		self show();
		self.zapped = undefined;
		self.ignoreall = 1;
		self notify( "stop_find_flesh" );
		self thread afterlife_zapped_fx();
		self animscripted( self.origin, self.angles, "zm_afterlife_stun" );
		self maps/mp/animscripts/shared::donotetracks( "stunned" );
		self.ignoreall = 0;
		self thread maps/mp/zombies/_zm_ai_basic::find_flesh();
	}
}

delaysndenddelete()
{
	wait 5;
	self delete();
}

snddryercountdown( num )
{
	ent = spawn( "script_origin", self.origin );
	i = num;
	while ( i > 0 )
	{
		if ( i <= 10 )
		{
			ent playsound( "zmb_quest_nixie_count_final" );
		}
		else
		{
			ent playsound( "zmb_quest_nixie_count" );
		}
		wait 1;
		i--;

	}
	ent delete();
}

setup_generator_challenge()
{
	level.n_generator_panels_active = 0;
	generator_soundent = spawn( "script_origin", ( -467, 6388, 132 ) );
	i = 1;
	while ( i < 4 )
	{
		level thread generator_panel_trigger_thread( i, generator_soundent );
		i++;
	}
	level thread generator_challenge_main_thread();
}

generator_challenge_main_thread()
{
	exploder( 2000 );
	while ( !flag( "generator_challenge_completed" ) )
	{
		if ( level.n_generator_panels_active == 3 )
		{
/#
			iprintlnbold( "generator overloaded!" );
#/
			flag_set( "generator_challenge_completed" );
		}
		wait 0,1;
	}
	level clientnotify( "sndWard" );
	level thread maps/mp/zombies/_zm_audio::sndmusicstingerevent( "piece_mid" );
	t_warden_fence_damage = getent( "warden_fence_damage", "targetname" );
	t_warden_fence_damage delete();
	level setclientfield( "warden_fence_down", 1 );
	array_delete( getentarray( "generator_wires", "script_noteworthy" ) );
	wait 3;
	stop_exploder( 2000 );
	wait 1;
	players = getplayers();
	player = players[ randomintrange( 0, players.size ) ];
	player do_player_general_vox( "general", "power_off", undefined, 100 );
}

generator_panel_trigger_thread( n_panel_index, generator_soundent )
{
	self endon( "death" );
	m_generator_panel = getent( "generator_panel_" + n_panel_index, "targetname" );
	m_generator_panel thread afterlife_interact_object_think();
	m_generator_panel play_fx( "fx_alcatraz_panel_on_2", m_generator_panel.origin, m_generator_panel.angles, "generator_panel_" + n_panel_index + "_afterlife", 1, undefined, undefined );
	level waittill( "generator_panel_" + n_panel_index + "_afterlife" );
	m_generator_panel notify( "generator_panel_" + n_panel_index + "_afterlife" );
/#
	iprintlnbold( "generator panel " + n_panel_index + " overloaded!" );
#/
	level.n_generator_panels_active += 1;
	m_generator_panel setmodel( "p6_zm_al_power_station_panels_03" );
	playfxontag( level._effect[ "fx_alcatraz_panel_ol" ], m_generator_panel, "tag_origin" );
	m_generator_panel play_fx( "fx_alcatraz_panel_off_2", m_generator_panel.origin, m_generator_panel.angles, undefined, 1, undefined, undefined );
	set_generator_vfx_amount( level.n_generator_panels_active, generator_soundent );
	playsoundatposition( "zmb_quest_generator_panel_spark", m_generator_panel.origin );
}

set_generator_vfx_amount( n_vfx_amount, generator_soundent )
{
	if ( n_vfx_amount == 1 )
	{
		generator_soundent playloopsound( "zmb_quest_generator_loop1" );
	}
	if ( n_vfx_amount == 2 )
	{
		generator_soundent stoploopsound();
		wait 0,05;
		generator_soundent playloopsound( "zmb_quest_generator_loop2" );
	}
	if ( n_vfx_amount == 3 )
	{
		exploder( 3100 );
		exploder( 3200 );
		exploder( 3300 );
		generator_soundent stoploopsound();
		wait 0,05;
		generator_soundent playloopsound( "zmb_quest_generator_loop3" );
	}
}

setup_gate_puzzle()
{
	self endon( "death" );
	is_gate_toggled = 0;
	is_inner_gate_toggleable = 0;
	m_gate_02 = getent( "cable_puzzle_gate_02", "targetname" );
	n_gate_move_duration = 0,5;
	m_docks_shockbox = getent( "docks_panel", "targetname" );
	array_set_visible_to_all( getentarray( "wires_docks_gate_toggle", "script_noteworthy" ), 0 );
	a_players = [];
	a_players = getplayers();
	if ( a_players.size > 1 )
	{
		is_inner_gate_toggleable = 1;
	}
	while ( 1 )
	{
		m_docks_shockbox thread afterlife_interact_object_think();
		level waittill( "cable_puzzle_gate_afterlife" );
		array_set_visible_to_all( getentarray( "wires_docks_gate_toggle", "script_noteworthy" ), 1 );
		if ( is_inner_gate_toggleable && flag( "docks_inner_gate_unlocked" ) )
		{
			level thread toggle_inner_gate( n_gate_move_duration );
		}
		if ( !flag( "docks_outer_gate_open" ) )
		{
			m_gate_02 moveto( m_gate_02.origin + ( -16, 80, 0 ), n_gate_move_duration );
			wait ( n_gate_move_duration + 0,25 );
			m_gate_02 connectpaths();
			gate_2_monsterclip = getent( "docks_gate_2_monsterclip", "targetname" );
			gate_2_monsterclip.origin += vectorScale( ( 0, 0, 0 ), 256 );
			gate_2_monsterclip disconnectpaths();
			gate_2_monsterclip.origin -= vectorScale( ( 0, 0, 0 ), 256 );
			m_gate_02 playsound( "zmb_chainlink_close" );
		}
		else
		{
			if ( !flag( "docks_gates_remain_open" ) )
			{
				m_gate_02 moveto( m_gate_02.origin - ( -16, 80, 0 ), n_gate_move_duration );
				wait ( n_gate_move_duration + 0,25 );
				m_gate_02 disconnectpaths();
				gate_2_monsterclip = getent( "docks_gate_2_monsterclip", "targetname" );
				gate_2_monsterclip connectpaths();
				m_gate_02 playsound( "zmb_chainlink_open" );
			}
		}
		flag_toggle( "docks_outer_gate_open" );
/#
		iprintlnbold( "gate toggled!" );
#/
		wait n_gate_move_duration;
/#
		iprintlnbold( "gate ready to be re-toggled" );
#/
		m_docks_shockbox notify( "afterlife_interact_reset" );
		array_set_visible_to_all( getentarray( "wires_docks_gate_toggle", "script_noteworthy" ), 0 );
	}
}

toggle_inner_gate( n_gate_move_duration )
{
	a_m_gate_01 = getentarray( "cable_puzzle_gate_01", "targetname" );
	if ( flag( "docks_inner_gate_open" ) && !flag( "docks_gates_remain_open" ) )
	{
		i = 0;
		while ( i < a_m_gate_01.size )
		{
			a_m_gate_01[ i ] moveto( a_m_gate_01[ i ].origin - ( -16, 80, 0 ), n_gate_move_duration );
			i++;
		}
		wait ( n_gate_move_duration + 0,25 );
		i = 0;
		while ( i < a_m_gate_01.size )
		{
			a_m_gate_01[ i ] disconnectpaths();
			i++;
		}
		gate_1_monsterclip = getent( "docks_gate_1_monsterclip", "targetname" );
		gate_1_monsterclip connectpaths();
		a_m_gate_01[ 0 ] playsound( "zmb_chainlink_close" );
	}
	else
	{
		i = 0;
		while ( i < a_m_gate_01.size )
		{
			a_m_gate_01[ i ] moveto( a_m_gate_01[ i ].origin + ( -16, 80, 0 ), n_gate_move_duration );
			i++;
		}
		wait ( n_gate_move_duration + 0,25 );
		i = 0;
		while ( i < a_m_gate_01.size )
		{
			a_m_gate_01[ i ] connectpaths();
			i++;
		}
		gate_1_monsterclip = getent( "docks_gate_1_monsterclip", "targetname" );
		gate_1_monsterclip.origin += vectorScale( ( 0, 0, 0 ), 256 );
		gate_1_monsterclip disconnectpaths();
		gate_1_monsterclip.origin -= vectorScale( ( 0, 0, 0 ), 256 );
		a_m_gate_01[ 0 ] playsound( "zmb_chainlink_open" );
	}
	flag_toggle( "docks_inner_gate_open" );
}

plane_fly_trigger_thread()
{
	self setcursorhint( "HINT_NOICON" );
	self sethintstring( &"ZM_PRISON_PLANE_BEGIN_TAKEOFF" );
	flag_wait( "initial_players_connected" );
	flag_wait( "brutus_setup_complete" );
	self trigger_off();
	wait 1;
	m_plane_craftable = getent( "plane_craftable", "targetname" );
	m_plane_craftable show();
	m_plane_craftable hidepart( "tag_support_upper" );
	m_plane_craftable hidepart( "tag_wing_skins_up" );
	m_plane_craftable hidepart( "tag_engines_up" );
	m_plane_craftable hidepart( "tag_feul_tanks" );
	m_plane_craftable hidepart( "tag_control_mechanism" );
	m_plane_craftable hidepart( "tag_engine_ground" );
	m_plane_craftable hidepart( "tag_clothes_ground" );
	m_plane_craftable hidepart( "tag_fuel_hose" );
	waittill_crafted( "plane" );
	maps/mp/zombies/_zm_ai_brutus::transfer_plane_trigger( "build", "fly" );
	self trigger_on();
	while ( isDefined( self ) )
	{
		self waittill( "trigger", e_triggerer );
		if ( isplayer( e_triggerer ) )
		{
			if ( level.n_plane_fuel_count == 5 )
			{
				while ( isDefined( level.custom_plane_validation ) )
				{
					valid = self [[ level.custom_plane_validation ]]( e_triggerer );
					while ( !valid )
					{
						continue;
					}
				}
				self setinvisibletoplayer( e_triggerer );
				if ( isDefined( level.plane_boarding_thread_custom_func ) )
				{
					e_triggerer thread [[ level.plane_boarding_thread_custom_func ]]();
					break;
				}
				else
				{
					e_triggerer thread plane_boarding_thread();
				}
			}
		}
	}
}

plane_boarding_thread()
{
	self endon( "death_or_disconnect" );
	flag_set( "plane_is_away" );
	self thread player_disconnect_watcher();
	self thread player_death_watcher();
/#
	iprintlnbold( "plane boarding thread started" );
#/
	flag_set( "plane_boarded" );
	self setclientfieldtoplayer( "effects_escape_flight", 1 );
	level.brutus_respawn_after_despawn = 0;
	a_nml_teleport_targets = [];
	i = 1;
	while ( i < 6 )
	{
		a_nml_teleport_targets[ i - 1 ] = getstruct( "nml_telepoint_" + i, "targetname" );
		i++;
	}
	level.characters_in_nml[ level.characters_in_nml.size ] = self.character_name;
	self.on_a_plane = 1;
	level.someone_has_visited_nml = 1;
	self.n_passenger_index = level.characters_in_nml.size;
	m_plane_craftable = getent( "plane_craftable", "targetname" );
	m_plane_about_to_crash = getent( "plane_about_to_crash", "targetname" );
	veh_plane_flyable = getent( "plane_flyable", "targetname" );
	t_plane_fly = getent( "plane_fly_trigger", "targetname" );
	t_plane_fly sethintstring( &"ZM_PRISON_PLANE_BOARD" );
	self enableinvulnerability();
	self playerlinktodelta( m_plane_craftable, "tag_player_crouched_" + ( self.n_passenger_index + 1 ) );
	self allowstand( 0 );
	flag_wait( "plane_departed" );
	level notify( "sndStopBrutusLoop" );
	self clientnotify( "sndPS" );
	self playsoundtoplayer( "zmb_plane_takeoff", self );
	level thread maps/mp/zombies/_zm_audio::sndmusicstingerevent( "plane_takeoff", self );
	self playerlinktodelta( veh_plane_flyable, "tag_player_crouched_" + ( self.n_passenger_index + 1 ) );
	self setclientfieldtoplayer( "effects_escape_flight", 2 );
	flag_wait( "plane_approach_bridge" );
	self thread snddelayedimp();
	self setclientfieldtoplayer( "effects_escape_flight", 3 );
	self unlink();
	self playerlinktoabsolute( veh_plane_flyable, "tag_player_crouched_" + ( self.n_passenger_index + 1 ) );
	flag_wait( "plane_zapped" );
	flag_set( "activate_player_zone_bridge" );
	self playsoundtoplayer( "zmb_plane_fall", self );
	self setclientfieldtoplayer( "effects_escape_flight", 4 );
	self.dontspeak = 1;
	self setclientfieldtoplayer( "isspeaking", 1 );
	self playerlinktodelta( m_plane_about_to_crash, "tag_player_crouched_" + ( self.n_passenger_index + 1 ), 1, 0, 0, 0, 0, 1 );
	self forcegrenadethrow();
	str_current_weapon = self getcurrentweapon();
	self giveweapon( "falling_hands_zm" );
	self switchtoweaponimmediate( "falling_hands_zm" );
	self setweaponammoclip( "falling_hands_zm", 0 );
	players = getplayers();
	_a1548 = players;
	_k1548 = getFirstArrayKey( _a1548 );
	while ( isDefined( _k1548 ) )
	{
		player = _a1548[ _k1548 ];
		if ( player != self )
		{
			player setinvisibletoplayer( self );
		}
		_k1548 = getNextArrayKey( _a1548, _k1548 );
	}
	flag_wait( "plane_crashed" );
	self setclientfieldtoplayer( "effects_escape_flight", 5 );
	self takeweapon( "falling_hands_zm" );
	if ( isDefined( str_current_weapon ) && str_current_weapon != "none" )
	{
		self switchtoweaponimmediate( str_current_weapon );
	}
	self thread fadetoblackforxsec( 0, 2, 0, 0,5, "black" );
	self thread snddelayedmusic();
	self unlink();
	self allowstand( 1 );
	self setstance( "stand" );
	players = getplayers();
	_a1571 = players;
	_k1571 = getFirstArrayKey( _a1571 );
	while ( isDefined( _k1571 ) )
	{
		player = _a1571[ _k1571 ];
		if ( player != self )
		{
			player setvisibletoplayer( self );
		}
		_k1571 = getNextArrayKey( _a1571, _k1571 );
	}
	flag_clear( "spawn_zombies" );
	self setorigin( a_nml_teleport_targets[ self.n_passenger_index ].origin );
	e_poi = getstruct( "plane_crash_poi", "targetname" );
	vec_to_target = e_poi.origin - self.origin;
	vec_to_target = vectorToAngle( vec_to_target );
	vec_to_target = ( 0, vec_to_target[ 1 ], 0 );
	self setplayerangles( vec_to_target );
	n_shellshock_duration = 5;
	self shellshock( "explosion", n_shellshock_duration );
	self.dontspeak = 0;
	self setclientfieldtoplayer( "isspeaking", 0 );
	self notify( "player_at_bridge" );
	wait n_shellshock_duration;
	self disableinvulnerability();
	self.on_a_plane = 0;
	if ( level.characters_in_nml.size == 1 )
	{
		self vo_bridge_soliloquy();
	}
	else
	{
		if ( level.characters_in_nml.size == 4 )
		{
			vo_bridge_four_part_convo();
		}
	}
	wait 10;
	self playsoundtoplayer( "zmb_ggb_swarm_start", self );
	flag_set( "spawn_zombies" );
	level.brutus_respawn_after_despawn = 1;
	wait 5;
	character_name = level.characters_in_nml[ randomintrange( 0, level.characters_in_nml.size ) ];
	players = getplayers();
	_a1625 = players;
	_k1625 = getFirstArrayKey( _a1625 );
	while ( isDefined( _k1625 ) )
	{
		player = _a1625[ _k1625 ];
		if ( isDefined( player ) && player.character_name == character_name )
		{
			player thread do_player_general_vox( "quest", "zombie_arrive_gg", undefined, 100 );
		}
		_k1625 = getNextArrayKey( _a1625, _k1625 );
	}
}

snddelayedimp()
{
	self endon( "disconnect" );
	wait 6;
	self playsoundtoplayer( "zmb_plane_explode", self );
}

snddelayedmusic()
{
	self endon( "disconnect" );
	wait 1;
	level thread maps/mp/zombies/_zm_audio::sndmusicstingerevent( "at_golden_gate", self );
	self clientnotify( "sndPE" );
}

track_quest_status_thread()
{
	while ( 1 )
	{
		while ( level.characters_in_nml.size == 0 )
		{
			wait 1;
		}
		while ( level.characters_in_nml.size > 0 )
		{
			wait 1;
		}
		if ( flag( "plane_trip_to_nml_successful" ) )
		{
			bestow_quest_rewards();
			flag_clear( "plane_trip_to_nml_successful" );
		}
		level notify( "bridge_empty" );
		level waittill( "start_of_round" );
		if ( level.n_quest_iteration_count == 2 )
		{
			vo_play_four_part_conversation( level.four_part_convos[ "alcatraz_return_alt" + randomintrange( 0, 2 ) ] );
		}
		prep_for_new_quest();
		waittill_crafted( "refuelable_plane" );
		maps/mp/zombies/_zm_ai_brutus::transfer_plane_trigger( "fuel", "fly" );
		t_plane_fly = getent( "plane_fly_trigger", "targetname" );
		t_plane_fly trigger_on();
	}
}

bestow_quest_rewards()
{
	level.n_quest_iteration_count += 1;
	if ( level.n_quest_iteration_count == 2 )
	{
		level notify( "unlock_all_perk_machines" );
		level notify( "intro_powerup_restored" );
	}
	else
	{
		if ( level.n_quest_iteration_count == 4 )
		{
			flag_set( "quest_completed_thrice" );
		}
	}
}

prep_for_new_quest()
{
	i = 1;
	while ( i < 4 )
	{
		str_trigger_targetname = "trigger_electric_chair_" + i;
		t_electric_chair = getent( str_trigger_targetname, "targetname" );
		t_electric_chair sethintstring( &"ZM_PRISON_ELECTRIC_CHAIR_ACTIVATE" );
		t_electric_chair trigger_on();
		i++;
	}
	flag_set( "spawn_fuel_tanks" );
	wait 0,05;
	flag_clear( "spawn_fuel_tanks" );
	i = 0;
	while ( i < level.a_uts_craftables.size )
	{
		if ( level.a_uts_craftables[ i ].equipname == "refuelable_plane" )
		{
			t_plane_fuelable = level.a_uts_craftables[ i ];
			level.zones[ "zone_roof" ].plane_triggers[ level.zones[ "zone_roof" ].plane_triggers.size ] = t_plane_fuelable;
			break;
		}
		else
		{
			i++;
		}
	}
	t_plane_fly = getent( "plane_fly_trigger", "targetname" );
	t_plane_fly trigger_off();
	players = get_players();
	t_plane_fly setvisibletoall();
	maps/mp/zombies/_zm_ai_brutus::transfer_plane_trigger( "fly", "fuel" );
	i = 1;
	while ( i < 5 )
	{
		m_electric_chair = getent( "electric_chair_" + i, "targetname" );
		m_electric_chair notify( "bridge_empty" );
		i++;
	}
	setup_puzzle_piece_glint();
/#
	iprintlnbold( "plane location reset" );
#/
	m_plane_craftable = getent( "plane_craftable", "targetname" );
	m_plane_craftable show();
	playfxontag( level._effect[ "fx_alcatraz_plane_apear" ], m_plane_craftable, "tag_origin" );
	veh_plane_flyable = getent( "plane_flyable", "targetname" );
	veh_plane_flyable attachpath( getvehiclenode( "zombie_plane_underground", "targetname" ) );
	vo_play_four_part_conversation( level.four_part_convos[ "alcatraz_return_quest_reset" ] );
	flag_clear( "plane_is_away" );
}

plane_flight_thread()
{
	while ( 1 )
	{
		m_plane_about_to_crash = getent( "plane_about_to_crash", "targetname" );
		m_plane_craftable = getent( "plane_craftable", "targetname" );
		t_plane_fly = getent( "plane_fly_trigger", "targetname" );
		veh_plane_flyable = getent( "plane_flyable", "targetname" );
		m_plane_about_to_crash ghost();
		flag_wait( "plane_boarded" );
		level clientnotify( "sndPB" );
		if ( isDefined( level.music_override ) && !level.music_override )
		{
			t_plane_fly playloopsound( "mus_event_plane_countdown_loop", 0,25 );
		}
		i = 10;
		while ( i > 0 )
		{
/#
			iprintlnbold( "TAKE-OFF IN " + i + "..." );
#/
			veh_plane_flyable playsound( "zmb_plane_countdown_tick" );
			wait 1;
			i--;

		}
		t_plane_fly stoploopsound( 2 );
		exploder( 10000 );
		veh_plane_flyable attachpath( getvehiclenode( "zombie_plane_flight_path", "targetname" ) );
		veh_plane_flyable startpath();
		flag_set( "plane_departed" );
		t_plane_fly trigger_off();
		m_plane_craftable ghost();
		veh_plane_flyable setvisibletoall();
		level setclientfield( "fog_stage", 1 );
		playfxontag( level._effect[ "fx_alcatraz_plane_trail" ], veh_plane_flyable, "tag_origin" );
		wait 2;
		playfxontag( level._effect[ "fx_alcatraz_plane_trail_fast" ], veh_plane_flyable, "tag_origin" );
		wait 3;
		exploder( 10001 );
		wait 4;
		playfxontag( level._effect[ "fx_alcatraz_flight_lightning" ], veh_plane_flyable, "tag_origin" );
		level setclientfield( "scripted_lightning_flash", 1 );
		wait 1;
		flag_set( "plane_approach_bridge" );
		stop_exploder( 10001 );
		level setclientfield( "fog_stage", 2 );
		veh_plane_flyable attachpath( getvehiclenode( "zombie_plane_bridge_approach", "targetname" ) );
		veh_plane_flyable startpath();
		wait 6;
		playfxontag( level._effect[ "fx_alcatraz_flight_lightning" ], veh_plane_flyable, "tag_origin" );
		level setclientfield( "scripted_lightning_flash", 1 );
		veh_plane_flyable waittill( "reached_end_node" );
		flag_set( "plane_zapped" );
		level setclientfield( "fog_stage", 3 );
		veh_plane_flyable setinvisibletoall();
		n_crash_duration = 2,25;
		nd_plane_about_to_crash_1 = getstruct( "plane_about_to_crash_point_1", "targetname" );
		m_plane_about_to_crash.origin = nd_plane_about_to_crash_1.origin;
		nd_plane_about_to_crash_2 = getstruct( "plane_about_to_crash_point_2", "targetname" );
		m_plane_about_to_crash moveto( nd_plane_about_to_crash_2.origin, n_crash_duration );
		m_plane_about_to_crash thread spin_while_falling();
		stop_exploder( 10000 );
		m_plane_about_to_crash waittill( "movedone" );
		flag_set( "plane_crashed" );
		wait 2;
		level setclientfield( "scripted_lightning_flash", 1 );
		m_plane_about_to_crash.origin += vectorScale( ( 0, 0, 0 ), 2048 );
		wait 4;
		veh_plane_flyable setvisibletoall();
		veh_plane_flyable play_fx( "fx_alcatraz_plane_fire_trail", veh_plane_flyable.origin, veh_plane_flyable.angles, "reached_end_node", 1, "tag_origin", undefined );
		veh_plane_flyable attachpath( getvehiclenode( "zombie_plane_bridge_flyby", "targetname" ) );
		veh_plane_flyable startpath();
		veh_plane_flyable thread sndpc();
		veh_plane_flyable waittill( "reached_end_node" );
		veh_plane_flyable setinvisibletoall();
		wait 20;
		if ( !level.final_flight_activated )
		{
			if ( isDefined( level.brutus_on_the_bridge_custom_func ) )
			{
				level thread [[ level.brutus_on_the_bridge_custom_func ]]();
				break;
			}
			else
			{
				level thread brutus_on_the_bridge();
			}
		}
		flag_clear( "plane_built" );
		flag_clear( "plane_boarded" );
		flag_clear( "plane_departed" );
		flag_clear( "plane_approach_bridge" );
		flag_clear( "plane_zapped" );
		flag_clear( "plane_crashed" );
		level.n_plane_fuel_count = 0;
	}
}

sndpc()
{
	self playloopsound( "zmb_plane_fire", 4 );
	wait 6;
	self playsound( "zmb_plane_fire_whoosh" );
	wait 1;
	self stoploopsound( 3 );
}

brutus_on_the_bridge()
{
	level endon( "bridge_empty" );
	n_round_on_bridge = 1;
	n_desired_spawn_count = 0;
	n_spawn_cap = 4;
	level.n_bridge_brutuses_killed = 0;
	if ( isDefined( level.last_brutus_on_bridge_custom_func ) )
	{
		level thread [[ level.last_brutus_on_bridge_custom_func ]]();
	}
	else
	{
		level thread last_brutus_on_bridge();
	}
	if ( isDefined( level.brutus_despawn_manager_custom_func ) )
	{
		level thread [[ level.brutus_despawn_manager_custom_func ]]();
	}
	else
	{
		level thread brutus_despawn_manager();
	}
	while ( 1 )
	{
		level.brutus_last_spawn_round = 0;
		n_desired_spawn_count = int( min( n_round_on_bridge, n_spawn_cap ) );
		n_brutuses_on_bridge_count = get_bridge_brutus_count();
		n_spawns_needed = n_desired_spawn_count - n_brutuses_on_bridge_count;
		i = n_spawns_needed;
		while ( i > 0 )
		{
			ai = maps/mp/zombies/_zm_ai_brutus::brutus_spawn_in_zone( "zone_golden_gate_bridge", 1 );
			if ( isDefined( ai ) )
			{
				ai.is_bridge_brutus = 1;
				if ( level.n_bridge_brutuses_killed == 0 )
				{
					ai thread suppress_brutus_bridge_powerups();
				}
			}
			wait randomfloatrange( 1, 4 );
			i--;

		}
		level waittill( "start_of_round" );
		n_round_on_bridge++;
	}
}

last_brutus_on_bridge()
{
	level endon( "bridge_empty" );
	e_gg_zone = getent( "zone_golden_gate_bridge", "targetname" );
	a_bridge_brutuses = [];
	while ( 1 )
	{
		a_bridge_brutuses = get_bridge_brutuses();
		if ( a_bridge_brutuses.size > 1 )
		{
			_a2012 = a_bridge_brutuses;
			_k2012 = getFirstArrayKey( _a2012 );
			while ( isDefined( _k2012 ) )
			{
				brutus = _a2012[ _k2012 ];
				if ( isDefined( brutus ) )
				{
					brutus.suppress_teargas_behavior = 1;
				}
				_k2012 = getNextArrayKey( _a2012, _k2012 );
			}
		}
		else if ( a_bridge_brutuses.size == 1 )
		{
			a_bridge_brutuses[ 0 ].suppress_teargas_behavior = 0;
		}
		wait 0,05;
	}
}

suppress_brutus_bridge_powerups()
{
	self endon( "brutus_teleporting" );
	level endon( "bridge_empty" );
	level endon( "first_bridge_brutus_killed" );
	self waittill( "death" );
	level.n_bridge_brutuses_killed++;
	if ( level.n_bridge_brutuses_killed >= 1 )
	{
		level.global_brutus_powerup_prevention = 1;
		level thread allow_brutus_powerup_spawning();
		level notify( "first_bridge_brutus_killed" );
	}
}

allow_brutus_powerup_spawning()
{
	level notify( "only_one_powerup_thread" );
	level endon( "only_one_powerup_thread" );
	level waittill( "bridge_empty" );
	level.global_brutus_powerup_prevention = 0;
}

get_bridge_brutuses()
{
	e_gg_zone = getent( "zone_golden_gate_bridge", "targetname" );
	a_bridge_brutuses = [];
	zombies = getaispeciesarray( "axis", "all" );
	i = 0;
	while ( i < zombies.size )
	{
		if ( isDefined( zombies[ i ].is_brutus ) && zombies[ i ].is_brutus )
		{
			brutus = zombies[ i ];
			if ( brutus istouching( e_gg_zone ) )
			{
				brutus.is_bridge_brutus = 1;
				a_bridge_brutuses[ a_bridge_brutuses.size ] = brutus;
			}
		}
		i++;
	}
	return a_bridge_brutuses;
}

brutus_despawn_manager()
{
	level notify( "brutus_despawn_manager" );
	level endon( "brutus_despawn_manager" );
	level endon( "bridge_empty" );
	e_gg_zone = getent( "zone_golden_gate_bridge", "targetname" );
	while ( 1 )
	{
		b_is_time_to_despawn = 0;
		while ( !b_is_time_to_despawn )
		{
			b_is_time_to_despawn = 1;
			players = getplayers();
			_a2098 = players;
			_k2098 = getFirstArrayKey( _a2098 );
			while ( isDefined( _k2098 ) )
			{
				player = _a2098[ _k2098 ];
				if ( isDefined( player ) && player istouching( e_gg_zone ) && !player.afterlife && !player maps/mp/zombies/_zm_laststand::player_is_in_laststand() )
				{
					b_is_time_to_despawn = 0;
				}
				_k2098 = getNextArrayKey( _a2098, _k2098 );
			}
			wait 0,1;
		}
		zombies = getaispeciesarray( "axis", "all" );
		i = 0;
		while ( i < zombies.size )
		{
			if ( isDefined( zombies[ i ].is_brutus ) && zombies[ i ].is_brutus && isDefined( zombies[ i ].is_bridge_brutus ) && zombies[ i ].is_bridge_brutus )
			{
				level thread brutus_temp_despawn( zombies[ i ], "bridge_empty", "bring_bridge_brutuses_back" );
			}
			i++;
		}
		b_is_time_to_bring_back = 0;
		while ( !b_is_time_to_bring_back )
		{
			b_is_time_to_bring_back = 0;
			players = getplayers();
			_a2125 = players;
			_k2125 = getFirstArrayKey( _a2125 );
			while ( isDefined( _k2125 ) )
			{
				player = _a2125[ _k2125 ];
				if ( isDefined( player ) && player istouching( e_gg_zone ) && !player.afterlife && !player maps/mp/zombies/_zm_laststand::player_is_in_laststand() )
				{
					b_is_time_to_bring_back = 1;
				}
				_k2125 = getNextArrayKey( _a2125, _k2125 );
			}
			wait 0,1;
		}
		level notify( "bring_bridge_brutuses_back" );
	}
}

get_bridge_brutus_count()
{
	n_touching_count = 0;
	e_gg_zone = getent( "zone_golden_gate_bridge", "targetname" );
	zombies = getaispeciesarray( "axis", "all" );
	i = 0;
	while ( i < zombies.size )
	{
		if ( isDefined( zombies[ i ].is_brutus ) && zombies[ i ].is_brutus )
		{
			brutus = zombies[ i ];
			if ( brutus istouching( e_gg_zone ) )
			{
				n_touching_count++;
			}
		}
		i++;
	}
	return n_touching_count;
}

clean_up_bridge_brutuses()
{
	zombies = getaispeciesarray( "axis", "all" );
	i = 0;
	while ( i < zombies.size )
	{
		if ( isDefined( zombies[ i ].is_brutus ) && zombies[ i ].is_brutus && isDefined( zombies[ i ].is_bridge_brutus ) && zombies[ i ].is_bridge_brutus )
		{
			brutus = zombies[ i ];
			brutus dodamage( 10000, brutus.origin );
		}
		i++;
	}
}

spin_while_falling()
{
	self endon( "movedone" );
	while ( 1 )
	{
		self.angles += vectorScale( ( 0, 0, 0 ), 4 );
		wait 0,05;
	}
}

manage_electric_chairs()
{
	level notify( "manage_electric_chairs" );
	level endon( "manage_electric_chairs" );
	n_chairs_wait = 60;
	while ( 1 )
	{
		flag_wait( "plane_approach_bridge" );
		i = 1;
		while ( i < 5 )
		{
			str_trigger_targetname = "trigger_electric_chair_" + i;
			t_electric_chair = getent( str_trigger_targetname, "targetname" );
			if ( isDefined( level.electric_chair_trigger_thread_custom_func ) )
			{
				t_electric_chair thread [[ level.electric_chair_trigger_thread_custom_func ]]( i );
			}
			else
			{
				t_electric_chair thread electric_chair_trigger_thread( i );
			}
			t_electric_chair setcursorhint( "HINT_NOICON" );
			t_electric_chair sethintstring( &"ZM_PRISON_ELECTRIC_CHAIR_ACTIVATE" );
			t_electric_chair usetriggerrequirelookat();
			i++;
		}
		if ( level.final_flight_activated )
		{
			level.revive_trigger_should_ignore_sight_checks = ::maps/mp/zm_prison_sq_final::revive_trigger_should_ignore_sight_checks;
			j = 0;
			while ( j < level.final_flight_players.size )
			{
				m_electric_chair = getent( "electric_chair_" + ( j + 1 ), "targetname" );
				corpse = level.final_flight_players[ j ].e_afterlife_corpse;
				corpse linkto( m_electric_chair, "tag_origin", ( 0, 0, 0 ), ( 0, 0, 0 ) );
				corpse maps/mp/zombies/_zm_clone::clone_animate( "chair" );
				wait 1;
				corpse.revivetrigger unlink();
				corpse.revivetrigger.origin = m_electric_chair.origin + ( 64, 0, 32 );
/#
				corpse.revivetrigger thread print3d_ent( "revivetrigger" );
#/
				j++;
			}
			j = 1;
			while ( j < 5 )
			{
				str_trigger_targetname = "trigger_electric_chair_" + j;
				t_electric_chair = getent( str_trigger_targetname, "targetname" );
				t_electric_chair trigger_off();
				j++;
			}
			while ( flag( "plane_approach_bridge" ) )
			{
				wait 1;
			}
		}
		else i = 1;
		while ( i < 5 )
		{
			m_electric_chair = getent( "electric_chair_" + i, "targetname" );
			m_electric_chair hide();
			str_trigger_targetname = "trigger_electric_chair_" + i;
			t_electric_chair = getent( str_trigger_targetname, "targetname" );
			t_electric_chair trigger_off();
			i++;
		}
		flag_wait( "plane_crashed" );
		wait n_chairs_wait;
		exploder( 666 );
		i = 1;
		while ( i < 5 )
		{
			m_electric_chair = getent( "electric_chair_" + i, "targetname" );
			m_electric_chair show();
			m_electric_chair thread snddelayedchairaudio( i );
			str_trigger_targetname = "trigger_electric_chair_" + i;
			t_electric_chair = getent( str_trigger_targetname, "targetname" );
			t_electric_chair trigger_on();
			i++;
		}
		wait 3;
		electric_chair_vo();
		wait 6;
	}
}

snddelayedchairaudio( i )
{
	wait ( i / 10 );
	self playsound( "zmb_quest_electricchair_spawn" );
}

electric_chair_trigger_thread( chair_number )
{
	level notify( "electric_chair_trigger_thread_" + chair_number );
	level endon( "electric_chair_trigger_thread_" + chair_number );
	m_electric_chair = getent( "electric_chair_" + chair_number, "targetname" );
	n_effects_wait_1 = 4;
	n_effects_wait_2 = 0,15;
	n_effects_wait_3 = 2;
	n_effects_wait_4 = 2;
	n_effects_duration = n_effects_wait_1 + n_effects_wait_2 + n_effects_wait_3 + n_effects_wait_4;
	while ( 1 )
	{
		self waittill( "trigger", e_triggerer );
		character_name = e_triggerer.character_name;
		if ( isplayer( e_triggerer ) && !e_triggerer.afterlife )
		{
			e_triggerer enableinvulnerability();
			self sethintstring( "" );
			self trigger_off();
			flag_set( "plane_trip_to_nml_successful" );
			if ( level.characters_in_nml.size == 1 )
			{
				clean_up_bridge_brutuses();
			}
			v_origin = m_electric_chair gettagorigin( "seated" ) + ( 10, 0, -40 );
			v_seated_angles = m_electric_chair gettagangles( "seated" );
			m_linkpoint = spawn_model( "tag_origin", v_origin, v_seated_angles );
			if ( isDefined( level.electric_chair_player_thread_custom_func ) )
			{
				e_triggerer thread [[ level.electric_chair_player_thread_custom_func ]]( m_linkpoint, chair_number, n_effects_duration );
			}
			else
			{
				e_triggerer thread electric_chair_player_thread( m_linkpoint, chair_number, n_effects_duration );
			}
			chair_corpse = e_triggerer maps/mp/zombies/_zm_clone::spawn_player_clone( e_triggerer, e_triggerer.origin, undefined );
			chair_corpse linkto( m_electric_chair, "tag_origin", ( 0, 0, 0 ), ( 0, 0, 0 ) );
			chair_corpse.ignoreme = 1;
			chair_corpse show();
			chair_corpse detachall();
			chair_corpse setvisibletoall();
			chair_corpse setinvisibletoplayer( e_triggerer );
			chair_corpse maps/mp/zombies/_zm_clone::clone_animate( "chair" );
			if ( isDefined( e_triggerer ) )
			{
				e_triggerer setclientfieldtoplayer( "rumble_electric_chair", 1 );
			}
			wait n_effects_wait_1;
			m_fx_1 = spawn_model( "tag_origin", ( -516,883, -3912,04, -7494,9 ), vectorScale( ( 0, 0, 0 ), 180 ) );
			m_fx_2 = spawn_model( "tag_origin", ( -517,024, -3252,66, -7496,2 ), ( 0, 0, 0 ) );
			level setclientfield( "scripted_lightning_flash", 1 );
			wait n_effects_wait_2;
			playfxontag( level._effect[ "fx_alcatraz_lightning_finale" ], m_fx_1, "tag_origin" );
			playfxontag( level._effect[ "fx_alcatraz_lightning_finale" ], m_fx_2, "tag_origin" );
			m_fx_3 = spawn_model( "tag_origin", ( -753,495, -3092,62, -8416,6 ), vectorScale( ( 0, 0, 0 ), 270 ) );
			playfxontag( level._effect[ "fx_alcatraz_lightning_wire" ], m_fx_3, "tag_origin" );
			wait n_effects_wait_3;
			m_electric_chair play_fx( "fx_alcatraz_elec_chair", m_electric_chair.origin, m_electric_chair.angles, "bridge_empty" );
			if ( isDefined( e_triggerer ) )
			{
				e_triggerer setclientfieldtoplayer( "rumble_electric_chair", 2 );
			}
			wait n_effects_wait_4;
			playfxontag( level._effect[ "fx_alcatraz_afterlife_zmb_tport" ], m_electric_chair, "tag_origin" );
			if ( isDefined( e_triggerer ) )
			{
				e_triggerer playsoundtoplayer( "zmb_afterlife_death", e_triggerer );
			}
			chair_corpse delete();
			if ( level.characters_in_nml.size == 1 )
			{
				clean_up_bridge_brutuses();
			}
			if ( isinarray( level.characters_in_nml, character_name ) )
			{
				arrayremovevalue( level.characters_in_nml, character_name );
			}
			m_fx_1 delete();
			m_fx_2 delete();
			self sethintstring( &"ZM_PRISON_ELECTRIC_CHAIR_ACTIVATE" );
			self trigger_on();
		}
	}
}

electric_chair_player_thread( m_linkpoint, chair_number, n_effects_duration )
{
	self endon( "death_or_disconnect" );
	e_home_telepoint = getstruct( "home_telepoint_" + chair_number, "targetname" );
	e_corpse_location = getstruct( "corpse_starting_point_" + chair_number, "targetname" );
	self disableweapons();
	self enableinvulnerability();
	self setstance( "stand" );
	self playerlinktodelta( m_linkpoint, "tag_origin", 1, 20, 20, 20, 20 );
	self setplayerangles( m_linkpoint.angles );
	self playsoundtoplayer( "zmb_electric_chair_2d", self );
	self do_player_general_vox( "quest", "chair_electrocution", undefined, 100 );
	self ghost();
	self.ignoreme = 1;
	self.dontspeak = 1;
	self setclientfieldtoplayer( "isspeaking", 1 );
	wait ( n_effects_duration - 2 );
	switch( self.character_name )
	{
		case "Arlington":
			self playsoundontag( "vox_plr_3_arlington_electrocution_0", "J_Head" );
			break;
		case "Sal":
			self playsoundontag( "vox_plr_1_sal_electrocution_0", "J_Head" );
			break;
		case "Billy":
			self playsoundontag( "vox_plr_2_billy_electrocution_0", "J_Head" );
			break;
		case "Finn":
			self playsoundontag( "vox_plr_0_finn_electrocution_0", "J_Head" );
			break;
	}
	wait 2;
	level.zones[ "zone_golden_gate_bridge" ].is_enabled = 1;
	level.zones[ "zone_golden_gate_bridge" ].is_spawning_allowed = 1;
	self.keep_perks = 1;
	self disableinvulnerability();
	self.afterlife = 1;
	self thread afterlife_laststand( 1 );
	self unlink();
	self setstance( "stand" );
	self waittill( "player_fake_corpse_created" );
	self thread track_player_completed_cycle();
	trace_start = e_corpse_location.origin + vectorScale( ( 0, 0, 0 ), 100 );
	trace_end = e_corpse_location.origin + vectorScale( ( 0, 0, 0 ), 100 );
	corpse_trace = bullettrace( trace_start, trace_end, 0, self.e_afterlife_corpse );
	self.e_afterlife_corpse.origin = corpse_trace[ "position" ];
	self setorigin( e_home_telepoint.origin );
	self enableweapons();
	self setclientfieldtoplayer( "rumble_electric_chair", 0 );
	if ( level.n_quest_iteration_count == 2 )
	{
		self waittill( "player_revived" );
		wait 1;
		self do_player_general_vox( "quest", "start_2", undefined, 100 );
	}
}

track_player_completed_cycle()
{
	self endon( "disconnect" );
	self.e_afterlife_corpse waittill( "death" );
	self notify( "player_completed_cycle" );
	level notify( "someone_completed_quest_cycle" );
}

reset_plane_hint_string( player )
{
	if ( isDefined( self.stub ) )
	{
/#
		println( "Error: This should have been handled by the craftables callback" );
#/
	}
	else
	{
		self.fly_trigger sethintstring( &"ZM_PRISON_PLANE_BEGIN_TAKEOFF" );
	}
}

play_fx( str_fx, v_origin, v_angles, time_to_delete_or_notify, b_link_to_self, str_tag, b_no_cull )
{
	if ( isDefined( time_to_delete_or_notify ) && !isstring( time_to_delete_or_notify ) && time_to_delete_or_notify == -1 && isDefined( b_link_to_self ) && b_link_to_self && isDefined( str_tag ) )
	{
		playfxontag( getfx( str_fx ), self, str_tag );
		return self;
	}
	else
	{
		m_fx = spawn_model( "tag_origin", v_origin, v_angles );
		if ( isDefined( b_link_to_self ) && b_link_to_self )
		{
			if ( isDefined( str_tag ) )
			{
				m_fx linkto( self, str_tag, ( 0, 0, 0 ), ( 0, 0, 0 ) );
			}
			else
			{
				m_fx linkto( self );
			}
		}
		if ( isDefined( b_no_cull ) && b_no_cull )
		{
			m_fx setforcenocull();
		}
		playfxontag( getfx( str_fx ), m_fx, "tag_origin" );
		m_fx thread _play_fx_delete( self, time_to_delete_or_notify );
		return m_fx;
	}
}

spawn_model( model_name, origin, angles, n_spawnflags )
{
	if ( !isDefined( n_spawnflags ) )
	{
		n_spawnflags = 0;
	}
	if ( !isDefined( origin ) )
	{
		origin = ( 0, 0, 0 );
	}
	model = spawn( "script_model", origin, n_spawnflags );
	model setmodel( model_name );
	if ( isDefined( angles ) )
	{
		model.angles = angles;
	}
	return model;
}

getfx( fx )
{
/#
	assert( isDefined( level._effect[ fx ] ), "Fx " + fx + " is not defined in level._effect." );
#/
	return level._effect[ fx ];
}

_play_fx_delete( ent, time_to_delete_or_notify )
{
	if ( !isDefined( time_to_delete_or_notify ) )
	{
		time_to_delete_or_notify = -1;
	}
	if ( isstring( time_to_delete_or_notify ) )
	{
		ent waittill_either( "death", time_to_delete_or_notify );
	}
	else if ( time_to_delete_or_notify > 0 )
	{
		ent waittill_notify_or_timeout( "death", time_to_delete_or_notify );
	}
	else
	{
		ent waittill( "death" );
	}
	if ( isDefined( self ) )
	{
		self delete();
	}
}

player_disconnect_watcher()
{
	if ( isDefined( level.player_disconnect_watcher_custom_func ) )
	{
		self thread [[ level.player_disconnect_watcher_custom_func ]]();
		return;
	}
	self notify( "disconnect_watcher" );
	self endon( "disconnect_watcher" );
	level endon( "bridge_empty" );
/#
	iprintlnbold( "player_disconnect_watcher" );
#/
	if ( !isDefined( self.character_name ) )
	{
		wait 0,1;
	}
	character_name = self.character_name;
	self waittill( "disconnect" );
/#
	iprintlnbold( character_name + " disconnected!" );
#/
	if ( isinarray( level.characters_in_nml, character_name ) )
	{
		arrayremovevalue( level.characters_in_nml, character_name );
		flag_set( "spawn_zombies" );
		level.brutus_respawn_after_despawn = 1;
	}
}

player_death_watcher()
{
	if ( isDefined( level.player_death_watcher_custom_func ) )
	{
		self thread [[ level.player_death_watcher_custom_func ]]();
		return;
	}
	self notify( "player_death_watcher" );
	self endon( "player_death_watcher" );
	level endon( "bridge_empty" );
/#
	iprintlnbold( "player_death_watcher" );
#/
	e_gg_zone = getent( "zone_golden_gate_bridge", "targetname" );
	nml_trip_is_over = 0;
	while ( !nml_trip_is_over )
	{
		level waittill( "start_of_round" );
		nml_trip_is_over = 1;
		players = getplayers();
		_a2671 = players;
		_k2671 = getFirstArrayKey( _a2671 );
		while ( isDefined( _k2671 ) )
		{
			player = _a2671[ _k2671 ];
			if ( player istouching( e_gg_zone ) || isDefined( player.on_a_plane ) && player.on_a_plane )
			{
				nml_trip_is_over = 0;
				if ( !isinarray( level.characters_in_nml, player.character_name ) )
				{
					level.characters_in_nml[ level.characters_in_nml.size ] = player.character_name;
				}
			}
			_k2671 = getNextArrayKey( _a2671, _k2671 );
		}
	}
	while ( isDefined( level.characters_in_nml ) )
	{
		i = 0;
		while ( i < level.characters_in_nml.size )
		{
			character_name = level.characters_in_nml[ i ];
			if ( isinarray( level.characters_in_nml, character_name ) )
			{
				arrayremovevalue( level.characters_in_nml, character_name );
			}
			i++;
		}
	}
	flag_set( "spawn_zombies" );
	level.brutus_respawn_after_despawn = 1;
}

array_set_visible_to_all( a_ents, is_visible )
{
	if ( is_visible )
	{
		_a2707 = a_ents;
		_k2707 = getFirstArrayKey( _a2707 );
		while ( isDefined( _k2707 ) )
		{
			ent = _a2707[ _k2707 ];
			ent setvisibletoall();
			_k2707 = getNextArrayKey( _a2707, _k2707 );
		}
	}
	else _a2714 = a_ents;
	_k2714 = getFirstArrayKey( _a2714 );
	while ( isDefined( _k2714 ) )
	{
		ent = _a2714[ _k2714 ];
		ent setinvisibletoall();
		_k2714 = getNextArrayKey( _a2714, _k2714 );
	}
}

warden_fence_hotjoin_handler()
{
	while ( 1 )
	{
		level waittill( "warden_fence_up" );
		stop_exploder( 2000 );
		exploder( 2000 );
		wait 0,05;
	}
}
